/* * Copyright (C) 2012-2024 Robin Haberkorn * * This program 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. * * This program 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, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "sciteco.h" #include "memory.h" #include "string-utils.h" #include "interface.h" #include "undo.h" #include "expressions.h" #include "qreg.h" #include "ring.h" #include "glob.h" #include "error.h" #include "core-commands.h" #include "goto-commands.h" #include "parser.h" //#define DEBUG GArray *teco_loop_stack; static void __attribute__((constructor)) teco_loop_stack_init(void) { teco_loop_stack = g_array_sized_new(FALSE, FALSE, sizeof(teco_loop_context_t), 1024); } TECO_DEFINE_ARRAY_UNDO_INSERT_VAL(teco_loop_stack, teco_loop_context_t); TECO_DEFINE_ARRAY_UNDO_REMOVE_INDEX(teco_loop_stack); static void TECO_DEBUG_CLEANUP teco_loop_stack_cleanup(void) { g_array_free(teco_loop_stack, TRUE); } gboolean teco_machine_input(teco_machine_t *ctx, gunichar chr, GError **error) { teco_state_t *next = ctx->current->input_cb(ctx, chr, error); if (!next) return FALSE; if (next != ctx->current) { if (ctx->must_undo) teco_undo_ptr(ctx->current); ctx->current = next; if (ctx->current->initial_cb && !ctx->current->initial_cb(ctx, error)) return FALSE; } return TRUE; } gboolean teco_state_end_of_macro(teco_machine_t *ctx, GError **error) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Unterminated command"); return FALSE; } /** * Execute macro from current PC to stop position. * * Handles all expected exceptions and preparing them for stack frame insertion. * * @param ctx State machine. * @param macro The macro to execute. * It does not have to be complete. * It must consist only of validated UTF-8 sequences, though. * @param stop_pos Where to stop execution in bytes. * @param error Location to store error. * @return FALSE if an error occurred. */ gboolean teco_machine_main_step(teco_machine_main_t *ctx, const gchar *macro, gsize stop_pos, GError **error) { while (ctx->macro_pc < stop_pos) { if (G_UNLIKELY(teco_interface_is_interrupted())) { teco_error_interrupted_set(error); goto error_attach; } /* * Most allocations are small or of limited size, * so it is (almost) sufficient to check the memory limit regularily. */ if (!teco_memory_check(0, error)) goto error_attach; /* UTF-8 sequences are already validated */ gunichar chr = g_utf8_get_char(macro+ctx->macro_pc); #ifdef DEBUG g_printf("EXEC(%d): input='%C' (U+%04" G_GINT32_MODIFIER "X), state=%p, mode=%d\n", ctx->macro_pc, chr, chr, ctx->parent.current, ctx->mode); #endif if (!teco_machine_input(&ctx->parent, chr, error)) goto error_attach; ctx->macro_pc = g_utf8_next_char(macro+ctx->macro_pc) - macro; } /* * Provide interactive feedback when the * PC is at the end of the command line. * This will actually be called in other situations, * like at the end of macros but that does not hurt. * It should perhaps be in teco_cmdline_insert(), * but doing it here ensures that exceptions get * normalized. */ if (ctx->parent.current->refresh_cb && !ctx->parent.current->refresh_cb(&ctx->parent, error)) goto error_attach; return TRUE; error_attach: g_assert(!error || *error != NULL); /* * FIXME: Maybe this can be avoided altogether by passing in ctx->macro_pc * from the callees? */ teco_error_set_coord(macro, ctx->macro_pc); return FALSE; } gboolean teco_execute_macro(const gchar *macro, gsize macro_len, teco_qreg_table_t *qreg_table_locals, GError **error) { const teco_string_t str = {(gchar *)macro, macro_len}; if (!teco_string_validate_utf8(&str)) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_CODEPOINT, "Invalid UTF-8 byte sequence in macro"); return FALSE; } /* * This is not auto-cleaned up, so it can be initialized * on demand. */ teco_qreg_table_t macro_locals; if (!qreg_table_locals) teco_qreg_table_init(¯o_locals, FALSE); guint parent_brace_level = teco_brace_level; g_auto(teco_machine_main_t) macro_machine; teco_machine_main_init(¯o_machine, qreg_table_locals ? : ¯o_locals, FALSE); GError *tmp_error = NULL; if (!teco_machine_main_step(¯o_machine, macro, macro_len, &tmp_error)) { if (!g_error_matches(tmp_error, TECO_ERROR, TECO_ERROR_RETURN)) { /* passes ownership of tmp_error */ g_propagate_error(error, tmp_error); goto error_cleanup; } g_error_free(tmp_error); /* * Macro returned - handle like regular * end of macro, even though some checks * are unnecessary here. * macro_pc will still point to the return PC. */ g_assert(macro_machine.parent.current == &teco_state_start); /* * Discard all braces, except the current one. */ if (!teco_expressions_brace_return(parent_brace_level, teco_error_return_args, error)) goto error_cleanup; /* * Clean up the loop stack. * We are allowed to return in loops. * NOTE: This does not have to be undone. */ g_array_remove_range(teco_loop_stack, macro_machine.loop_stack_fp, teco_loop_stack->len - macro_machine.loop_stack_fp); } if (G_UNLIKELY(teco_loop_stack->len > macro_machine.loop_stack_fp)) { const teco_loop_context_t *ctx = &g_array_index(teco_loop_stack, teco_loop_context_t, teco_loop_stack->len-1); teco_error_set_coord(macro, ctx->pc); g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Unterminated loop"); goto error_cleanup; } if (G_UNLIKELY(teco_goto_skip_label.len > 0)) { g_autofree gchar *label_printable = teco_string_echo(teco_goto_skip_label.data, teco_goto_skip_label.len); g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Label \"%s\" not found", label_printable); goto error_attach; } /* * Some states (esp. commands involving a * "lookahead") are valid at the end of a macro. */ if (macro_machine.parent.current->end_of_macro_cb && !macro_machine.parent.current->end_of_macro_cb(¯o_machine.parent, error)) goto error_attach; /* * This handles the problem of Q-Registers * local to the macro invocation being edited * when the macro terminates without additional * complexity. * teco_qreg_table_empty() might leave the table * half-empty, but it will eventually be completely * cleared by teco_qreg_table_clear(). * This does not hurt since an error will rub out the * macro invocation itself and macro_locals don't have * to be preserved. */ if (!qreg_table_locals && !teco_qreg_table_empty(¯o_locals, error)) goto error_attach; return TRUE; error_attach: teco_error_set_coord(macro, macro_machine.macro_pc); /* fall through */ error_cleanup: if (!qreg_table_locals) teco_qreg_table_clear(¯o_locals); /* make sure teco_goto_skip_label will be NULL even in batch mode */ teco_string_truncate(&teco_goto_skip_label, 0); return FALSE; } gboolean teco_execute_file(const gchar *filename, teco_qreg_table_t *qreg_table_locals, GError **error) { g_auto(teco_string_t) macro = {NULL, 0}; if (!g_file_get_contents(filename, ¯o.data, ¯o.len, error)) return FALSE; gchar *p; /* only when executing files, ignore Hash-Bang line */ if (*macro.data == '#') { /* * NOTE: We assume that a file starting with Hash does not contain * a null-byte in its first line. */ p = strpbrk(macro.data, "\r\n"); if (G_UNLIKELY(!p)) /* empty script */ return TRUE; p++; } else { p = macro.data; } if (!teco_execute_macro(p, macro.len - (p - macro.data), qreg_table_locals, error)) { /* correct error position for Hash-Bang line */ teco_error_pos += p - macro.data; if (*macro.data == '#') teco_error_line++; teco_error_add_frame_file(filename); return FALSE; } return TRUE; } void teco_machine_main_init(teco_machine_main_t *ctx, teco_qreg_table_t *qreg_table_locals, gboolean must_undo) { memset(ctx, 0, sizeof(*ctx)); teco_machine_init(&ctx->parent, &teco_state_start, must_undo); ctx->loop_stack_fp = teco_loop_stack->len; teco_goto_table_init(&ctx->goto_table, must_undo); ctx->qreg_table_locals = qreg_table_locals; ctx->expectstring.nesting = 1; teco_machine_stringbuilding_init(&ctx->expectstring.machine, '\e', qreg_table_locals, must_undo); } gboolean teco_machine_main_eval_colon(teco_machine_main_t *ctx) { if (!ctx->modifier_colon) return FALSE; if (ctx->parent.must_undo) teco_undo_guint(ctx->__flags); ctx->modifier_colon = FALSE; return TRUE; } teco_state_t * teco_machine_main_transition_input(teco_machine_main_t *ctx, teco_machine_main_transition_t *transitions, guint len, gunichar chr, GError **error) { if (chr >= len || !transitions[chr].next) { teco_error_syntax_set(error, chr); return NULL; } if (ctx->mode == TECO_MODE_NORMAL && transitions[chr].transition_cb) { /* * NOTE: We could also just let transition_cb return a boolean... */ GError *tmp_error = NULL; transitions[chr].transition_cb(ctx, &tmp_error); if (tmp_error) { g_propagate_error(error, tmp_error); return NULL; } } return transitions[chr].next; } void teco_machine_main_clear(teco_machine_main_t *ctx) { teco_goto_table_clear(&ctx->goto_table); teco_machine_stringbuilding_clear(&ctx->expectstring.machine); } /* * FIXME: All teco_state_stringbuilding_* states could be static? */ static teco_state_t *teco_state_stringbuilding_ctl_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error); TECO_DECLARE_STATE(teco_state_stringbuilding_ctl); static teco_state_t *teco_state_stringbuilding_escaped_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error); TECO_DECLARE_STATE(teco_state_stringbuilding_escaped); TECO_DECLARE_STATE(teco_state_stringbuilding_lower); TECO_DECLARE_STATE(teco_state_stringbuilding_upper); TECO_DECLARE_STATE(teco_state_stringbuilding_ctle); TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_num); TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_u); TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_q); TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_quote); TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_n); static teco_state_t * teco_state_stringbuilding_start_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) { if (chr == '^') return &teco_state_stringbuilding_ctl; if (TECO_IS_CTL(chr)) return teco_state_stringbuilding_ctl_input(ctx, TECO_CTL_ECHO(chr), error); return teco_state_stringbuilding_escaped_input(ctx, chr, error); } /* in cmdline.c */ gboolean teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error); TECO_DEFINE_STATE(teco_state_stringbuilding_start, .is_start = TRUE, .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) teco_state_stringbuilding_start_process_edit_cmd ); static teco_state_t * teco_state_stringbuilding_ctl_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) { chr = teco_ascii_toupper(chr); switch (chr) { case '^': break; case 'Q': case 'R': return &teco_state_stringbuilding_escaped; case 'V': return &teco_state_stringbuilding_lower; case 'W': return &teco_state_stringbuilding_upper; case 'E': return &teco_state_stringbuilding_ctle; default: chr = TECO_CTL_KEY(chr); } /* * Source code is always in UTF-8, so it does not * make sense to handle ctx->codepage != SC_CP_UTF8 * separately. */ if (ctx->result) teco_string_append_wc(ctx->result, chr); return &teco_state_stringbuilding_start; } TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_ctl); static teco_state_t * teco_state_stringbuilding_escaped_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) { if (!ctx->result) /* parse-only mode */ return &teco_state_stringbuilding_start; /* * The subtle difference between UTF-8 and single-byte targets * is that we don't try to casefold non-ANSI characters in single-byte mode. */ switch (ctx->mode) { case TECO_STRINGBUILDING_MODE_UPPER: chr = ctx->codepage == SC_CP_UTF8 || chr < 0x80 ? g_unichar_toupper(chr) : chr; break; case TECO_STRINGBUILDING_MODE_LOWER: chr = ctx->codepage == SC_CP_UTF8 || chr < 0x80 ? g_unichar_tolower(chr) : chr; break; default: break; } teco_string_append_wc(ctx->result, chr); return &teco_state_stringbuilding_start; } TECO_DEFINE_STATE(teco_state_stringbuilding_escaped); static teco_state_t * teco_state_stringbuilding_lower_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) { if (!ctx->result) /* parse-only mode */ return &teco_state_stringbuilding_start; /* * FIXME: This does not handle ^V^V typed with up-carets. */ if (chr == TECO_CTL_KEY('V')) { if (ctx->parent.must_undo) teco_undo_guint(ctx->mode); ctx->mode = TECO_STRINGBUILDING_MODE_LOWER; } else { chr = ctx->codepage == SC_CP_UTF8 || chr < 0x80 ? g_unichar_tolower(chr) : chr; teco_string_append_wc(ctx->result, chr); } return &teco_state_stringbuilding_start; } TECO_DEFINE_STATE(teco_state_stringbuilding_lower); static teco_state_t * teco_state_stringbuilding_upper_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) { if (!ctx->result) /* parse-only mode */ return &teco_state_stringbuilding_start; /* * FIXME: This does not handle ^W^W typed with up-carets. */ if (chr == TECO_CTL_KEY('W')) { if (ctx->parent.must_undo) teco_undo_guint(ctx->mode); ctx->mode = TECO_STRINGBUILDING_MODE_UPPER; } else { chr = ctx->codepage == SC_CP_UTF8 || chr < 0x80 ? g_unichar_toupper(chr) : chr; teco_string_append_wc(ctx->result, chr); } return &teco_state_stringbuilding_start; } TECO_DEFINE_STATE(teco_state_stringbuilding_upper); static teco_state_t * teco_state_stringbuilding_ctle_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) { teco_state_t *next; switch (teco_ascii_toupper(chr)) { case '\\': next = &teco_state_stringbuilding_ctle_num; break; case 'U': next = &teco_state_stringbuilding_ctle_u; break; case 'Q': next = &teco_state_stringbuilding_ctle_q; break; case '@': next = &teco_state_stringbuilding_ctle_quote; break; case 'N': next = &teco_state_stringbuilding_ctle_n; break; default: if (ctx->result) { gchar buf[1+6] = {TECO_CTL_KEY('E')}; gsize len = g_unichar_to_utf8(chr, buf+1); teco_string_append(ctx->result, buf, 1+len); } return &teco_state_stringbuilding_start; } if (ctx->machine_qregspec) teco_machine_qregspec_reset(ctx->machine_qregspec); else ctx->machine_qregspec = teco_machine_qregspec_new(TECO_QREG_REQUIRED, ctx->qreg_table_locals, ctx->parent.must_undo); return next; } TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_ctle); /* in cmdline.c */ gboolean teco_state_stringbuilding_qreg_process_edit_cmd(teco_machine_stringbuilding_t *ctx, teco_machine_t *parent_ctx, gunichar chr, GError **error); /** * @interface TECO_DEFINE_STATE_STRINGBUILDING_QREG * @implements TECO_DEFINE_STATE * @ingroup states */ #define TECO_DEFINE_STATE_STRINGBUILDING_QREG(NAME, ...) \ TECO_DEFINE_STATE(NAME, \ .process_edit_cmd_cb = (teco_statHTTP/1.1 200 OK Connection: keep-alive Connection: keep-alive Content-Disposition: inline; filename="parser.c" Content-Disposition: inline; filename="parser.c" Content-Length: 30202 Content-Length: 30202 Content-Security-Policy: default-src 'none' Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Type: text/plain; charset=UTF-8 Date: Wed, 22 Oct 2025 21:46:36 UTC ETag: "45e31cf796f81f84c1e3342ed22a164d64327cc7" ETag: "45e31cf796f81f84c1e3342ed22a164d64327cc7" Expires: Sat, 20 Oct 2035 21:46:36 GMT Expires: Sat, 20 Oct 2035 21:46:36 GMT Last-Modified: Wed, 22 Oct 2025 21:46:36 GMT Last-Modified: Wed, 22 Oct 2025 21:46:36 GMT Server: OpenBSD httpd Server: OpenBSD httpd X-Content-Type-Options: nosniff X-Content-Type-Options: nosniff /* * Copyright (C) 2012-2024 Robin Haberkorn * * This program 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. * * This program 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, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "sciteco.h" #include "memory.h" #include "string-utils.h" #include "interface.h" #include "undo.h" #include "expressions.h" #include "qreg.h" #include "ring.h" #include "glob.h" #include "error.h" #include "core-commands.h" #include "goto-commands.h" #include "parser.h" //#define DEBUG GArray *teco_loop_stack; static void __attribute__((constructor)) teco_loop_stack_init(void) { teco_loop_stack = g_array_sized_new(FALSE, FALSE, sizeof(teco_loop_context_t), 1024); } TECO_DEFINE_ARRAY_UNDO_INSERT_VAL(teco_loop_stack, teco_loop_context_t); TECO_DEFINE_ARRAY_UNDO_REMOVE_INDEX(teco_loop_stack); static void TECO_DEBUG_CLEANUP teco_loop_stack_cleanup(void) { g_array_free(teco_loop_stack, TRUE); } gboolean teco_machine_input(teco_machine_t *ctx, gunichar chr, GError **error) { teco_state_t *next = ctx->current->input_cb(ctx, chr, error); if (!next) return FALSE; if (next != ctx->current) { if (ctx->must_undo) teco_undo_ptr(ctx->current); ctx->current = next; if (ctx->current->initial_cb && !ctx->current->initial_cb(ctx, error)) return FALSE; } return TRUE; } gboolean teco_state_end_of_macro(teco_machine_t *ctx, GError **error) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Unterminated command"); return FALSE; } /** * Execute macro from current PC to stop position. * * Handles all expected exceptions and preparing them for stack frame insertion. * * @param ctx State machine. * @param macro The macro to execute. * It does not have to be complete. * It must consist only of validated UTF-8 sequences, though. * @param stop_pos Where to stop execution in bytes. * @param error Location to store error. * @return FALSE if an error occurred. */ gboolean teco_machine_main_step(teco_machine_main_t *ctx, const gchar *macro, gsize stop_pos, GError **error) { while (ctx->macro_pc < stop_pos) { if (G_UNLIKELY(teco_interface_is_interrupted())) { teco_error_interrupted_set(error); goto error_attach; } /* * Most allocations are small or of limited size, * so it is (almost) sufficient to check the memory limit regularily. */ if (!teco_memory_check(0, error)) goto error_attach; /* UTF-8 sequences are already validated */ gunichar chr = g_utf8_get_char(macro+ctx->macro_pc); #ifdef DEBUG g_printf("EXEC(%d): input='%C' (U+%04" G_GINT32_MODIFIER "X), state=%p, mode=%d\n", ctx->macro_pc, chr, chr, ctx->parent.current, ctx->mode); #endif if (!teco_machine_input(&ctx->parent, chr, error)) goto error_attach; ctx->macro_pc = g_utf8_next_char(macro+ctx->macro_pc) - macro; } /* * Provide interactive feedback when the * PC is at the end of the command line. * This will actually be called in other situations, * like at the end of macros but that does not hurt. * It should perhaps be in teco_cmdline_insert(), * but doing it here ensures that exceptions get * normalized. */ if (ctx->parent.current->refresh_cb && !ctx->parent.current->refresh_cb(&ctx->parent, error)) goto error_attach; return TRUE; error_attach: g_assert(!error || *error != NULL); /* * FIXME: Maybe this can be avoided altogether by passing in ctx->macro_pc * from the callees? */ teco_error_set_coord(macro, ctx->macro_pc); return FALSE; } gboolean teco_execute_macro(const gchar *macro, gsize macro_len, teco_qreg_table_t *qreg_table_locals, GError **error) { const teco_string_t str = {(gchar *)macro, macro_len}; if (!teco_string_validate_utf8(&str)) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_CODEPOINT, "Invalid UTF-8 byte sequence in macro"); return FALSE; } /* * This is not auto-cleaned up, so it can be initialized * on demand. */ teco_qreg_table_t macro_locals; if (!qreg_table_locals) teco_qreg_table_init(¯o_locals, FALSE); guint parent_brace_level = teco_brace_level; g_auto(teco_machine_main_t) macro_machine; teco_machine_main_init(¯o_machine, qreg_table_locals ? : ¯o_locals, FALSE); GError *tmp_error = NULL; if (!teco_machine_main_step(¯o_machine, macro, macro_len, &tmp_error)) { if (!g_error_matches(tmp_error, TECO_ERROR, TECO_ERROR_RETURN)) { /* passes ownership of tmp_error */ g_propagate_error(error, tmp_error); goto error_cleanup; } g_error_free(tmp_error); /* * Macro returned - handle like regular * end of macro, even though some checks * are unnecessary here. * macro_pc will still point to the return PC. */ g_assert(macro_machine.parent.current == &teco_state_start); /* * Discard all braces, except the current one. */ if (!teco_expressions_brace_return(parent_brace_level, teco_error_return_args, error)) goto error_cleanup; /* * Clean up the loop stack. * We are allowed to return in loops. * NOTE: This does not have to be undone. */ g_array_remove_range(teco_loop_stack, macro_machine.loop_stack_fp, teco_loop_stack->len - macro_machine.loop_stack_fp); } if (G_UNLIKELY(teco_loop_stack->len > macro_machine.loop_stack_fp)) { const teco_loop_context_t *ctx = &g_array_index(teco_loop_stack, teco_loop_context_t, teco_loop_stack->len-1); teco_error_set_coord(macro, ctx->pc); g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Unterminated loop"); goto error_cleanup; } if (G_UNLIKELY(teco_goto_skip_label.len > 0)) { g_autofree gchar *label_printable = teco_string_echo(teco_goto_skip_label.data, teco_goto_skip_label.len); g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Label \"%s\" not found", label_printable); goto error_attach; } /* * Some states (esp. commands involving a * "lookahead") are valid at the end of a macro. */ if (macro_machine.parent.current->end_of_macro_cb && !macro_machine.parent.current->end_of_macro_cb(¯o_machine.parent, error)) goto error_attach; /* * This handles the problem of Q-Registers * local to the macro invocation being edited * when the macro terminates without additional * complexity. * teco_qreg_table_empty() might leave the table * half-empty, but it will eventually be completely * cleared by teco_qreg_table_clear(). * This does not hurt since an error will rub out the * macro invocation itself and macro_locals don't have * to be preserved. */ if (!qreg_table_locals && !teco_qreg_table_empty(¯o_locals, error)) goto error_attach; return TRUE; error_attach: teco_error_set_coord(macro, macro_machine.macro_pc); /* fall through */ error_cleanup: if (!qreg_table_locals) teco_qreg_table_clear(¯o_locals); /* make sure teco_goto_skip_label will be NULL even in batch mode */ teco_string_truncate(&teco_goto_skip_label, 0); return FALSE; } gboolean teco_execute_file(const gchar *filename, teco_qreg_table_t *qreg_table_locals, GError **error) { g_auto(teco_string_t) macro = {NULL, 0}; if (!g_file_get_contents(filename, ¯o.data, ¯o.len, error)) return FALSE; gchar *p; /* only when executing files, ignore Hash-Bang line */ if (*macro.data == '#') { /* * NOTE: We assume that a file starting with Hash does not contain * a null-byte in its first line. */ p = strpbrk(macro.data, "\r\n"); if (G_UNLIKELY(!p)) /* empty script */ return TRUE; p++; } else { p = macro.data; } if (!teco_execute_macro(p, macro.len - (p - macro.data), qreg_table_locals, error)) { /* correct error position for Hash-Bang line */ teco_error_pos += p - macro.data; if (*macro.data == '#') teco_error_line++; teco_error_add_frame_file(filename); return FALSE; } return TRUE; } void teco_machine_main_init(teco_machine_main_t *ctx, teco_qreg_table_t *qreg_table_locals, gboolean must_undo) { memset(ctx, 0, sizeof(*ctx)); teco_machine_init(&ctx->parent, &teco_state_start, must_undo); ctx->loop_stack_fp = teco_loop_stack->len; teco_goto_table_init(&ctx->goto_table, must_undo); ctx->qreg_table_locals = qreg_table_locals; ctx->expectstring.nesting = 1; teco_machine_stringbuilding_init(&ctx->expectstring.machine, '\e', qreg_table_locals, must_undo); } gboolean teco_machine_main_eval_colon(teco_machine_main_t *ctx) { if (!ctx->modifier_colon) return FALSE; if (ctx->parent.must_undo) teco_undo_guint(ctx->__flags); ctx->modifier_colon = FALSE; return TRUE; } teco_state_t * teco_machine_main_transition_input(teco_machine_main_t *ctx, teco_machine_main_transition_t *transitions, guint len, gunichar chr, GError **error) { if (chr >= len || !transitions[chr].next) { teco_error_syntax_set(error, chr); return NULL; } if (ctx->mode == TECO_MODE_NORMAL && transitions[chr].transition_cb) { /* * NOTE: We could also just let transition_cb return a boolean... */ GError *tmp_error = NULL; transitions[chr].transition_cb(ctx, &tmp_error); if (tmp_error) { g_propagate_error(error, tmp_error); return NULL; } } return transitions[chr].next; } void teco_machine_main_clear(teco_machine_main_t *ctx) { teco_goto_table_clear(&ctx->goto_table); teco_machine_stringbuilding_clear(&ctx->expectstring.machine); } /* * FIXME: All teco_state_stringbuilding_* states could be static? */ static teco_state_t *teco_state_stringbuilding_ctl_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error); TECO_DECLARE_STATE(teco_state_stringbuilding_ctl); static teco_state_t *teco_state_stringbuilding_escaped_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error); TECO_DECLARE_STATE(teco_state_stringbuilding_escaped); TECO_DECLARE_STATE(teco_state_stringbuilding_lower); TECO_DECLARE_STATE(teco_state_stringbuilding_upper); TECO_DECLARE_STATE(teco_state_stringbuilding_ctle); TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_num); TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_u); TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_q); TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_quote); TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_n); static teco_state_t * teco_state_stringbuilding_start_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) { if (chr == '^') return &teco_state_stringbuilding_ctl; if (TECO_IS_CTL(chr)) return teco_state_stringbuilding_ctl_input(ctx, TECO_CTL_ECHO(chr), error); return teco_state_stringbuilding_escaped_input(ctx, chr, error); } /* in cmdline.c */ gboolean teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error); TECO_DEFINE_STATE(teco_state_stringbuilding_start, .is_start = TRUE, .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) teco_state_stringbuilding_start_process_edit_cmd ); static teco_state_t * teco_state_stringbuilding_ctl_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) { chr = teco_ascii_toupper(chr); switch (chr) { case '^': break; case 'Q': case 'R': return &teco_state_stringbuilding_escaped; case 'V': return &teco_state_stringbuilding_lower; case 'W': return &teco_state_stringbuilding_upper; case 'E': return &teco_state_stringbuilding_ctle; default: chr = TECO_CTL_KEY(chr); } /* * Source code is always in UTF-8, so it does not * make sense to handle ctx->codepage != SC_CP_UTF8 * separately. */ if (ctx->result) teco_string_append_wc(ctx->result, chr); return &teco_state_stringbuilding_start; } TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_ctl); static teco_state_t * teco_state_stringbuilding_escaped_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error) { if (!ctx->result) /* parse-only mode */ return &teco_state_stringbuilding_start; /* * The subtle difference between UTF-8 and single-byte targets * is that we don't try to casefold non-ANSI characters in single