aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/qregisters.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qregisters.cpp')
-rw-r--r--src/qregisters.cpp1680
1 files changed, 0 insertions, 1680 deletions
diff --git a/src/qregisters.cpp b/src/qregisters.cpp
deleted file mode 100644
index c23656c..0000000
--- a/src/qregisters.cpp
+++ /dev/null
@@ -1,1680 +0,0 @@
-/*
- * Copyright (C) 2012-2017 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 <http://www.gnu.org/licenses/>.
- */
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <string.h>
-#include <bsd/sys/queue.h>
-
-#include <glib.h>
-#include <glib/gprintf.h>
-#include <glib/gstdio.h>
-
-#include <Scintilla.h>
-
-#include "sciteco.h"
-#include "string-utils.h"
-#include "interface.h"
-#include "undo.h"
-#include "parser.h"
-#include "expressions.h"
-#include "document.h"
-#include "ring.h"
-#include "ioview.h"
-#include "eol.h"
-#include "error.h"
-#include "qregisters.h"
-
-namespace SciTECO {
-
-namespace States {
- StatePushQReg pushqreg;
- StatePopQReg popqreg;
- StateEQCommand eqcommand;
- StateLoadQReg loadqreg;
- StateEPctCommand epctcommand;
- StateSaveQReg saveqreg;
- StateQueryQReg queryqreg;
- StateCtlUCommand ctlucommand;
- StateEUCommand eucommand;
- StateSetQRegString setqregstring_nobuilding(false);
- StateSetQRegString setqregstring_building(true);
- StateGetQRegString getqregstring;
- StateSetQRegInteger setqreginteger;
- StateIncreaseQReg increaseqreg;
- StateMacro macro;
- StateMacroFile macro_file;
- StateCopyToQReg copytoqreg;
-}
-
-namespace QRegisters {
- QRegisterTable *locals = NULL;
- QRegister *current = NULL;
-
- static QRegisterStack stack;
-}
-
-static QRegister *register_argument = NULL;
-
-void
-QRegisterData::set_string(const gchar *str, gsize len)
-{
- if (QRegisters::current)
- QRegisters::current->string.update(QRegisters::view);
-
- string.reset();
- string.edit(QRegisters::view);
-
- QRegisters::view.ssm(SCI_BEGINUNDOACTION);
- QRegisters::view.ssm(SCI_CLEARALL);
- QRegisters::view.ssm(SCI_APPENDTEXT,
- len, (sptr_t)(str ? : ""));
- QRegisters::view.ssm(SCI_ENDUNDOACTION);
-
- if (QRegisters::current)
- QRegisters::current->string.edit(QRegisters::view);
-}
-
-void
-QRegisterData::undo_set_string(void)
-{
- if (!must_undo)
- return;
-
- /*
- * Necessary, so that upon rubout the
- * string's parameters are restored.
- */
- string.update(QRegisters::view);
-
- if (QRegisters::current && QRegisters::current->must_undo)
- QRegisters::current->string.undo_edit(QRegisters::view);
-
- string.undo_reset();
- QRegisters::view.undo_ssm(SCI_UNDO);
-
- string.undo_edit(QRegisters::view);
-}
-
-void
-QRegisterData::append_string(const gchar *str, gsize len)
-{
- /*
- * NOTE: Will not create undo action
- * if string is empty.
- * Also, appending preserves the string's
- * parameters.
- */
- if (!len)
- return;
-
- if (QRegisters::current)
- QRegisters::current->string.update(QRegisters::view);
-
- string.edit(QRegisters::view);
-
- QRegisters::view.ssm(SCI_BEGINUNDOACTION);
- QRegisters::view.ssm(SCI_APPENDTEXT,
- len, (sptr_t)str);
- QRegisters::view.ssm(SCI_ENDUNDOACTION);
-
- if (QRegisters::current)
- QRegisters::current->string.edit(QRegisters::view);
-}
-
-gchar *
-QRegisterData::get_string(void)
-{
- gint size;
- gchar *str;
-
- if (!string.is_initialized())
- return g_strdup("");
-
- if (QRegisters::current)
- QRegisters::current->string.update(QRegisters::view);
-
- string.edit(QRegisters::view);
-
- size = QRegisters::view.ssm(SCI_GETLENGTH) + 1;
- str = (gchar *)g_malloc(size);
- QRegisters::view.ssm(SCI_GETTEXT, size, (sptr_t)str);
-
- if (QRegisters::current)
- QRegisters::current->string.edit(QRegisters::view);
-
- return str;
-}
-
-gsize
-QRegisterData::get_string_size(void)
-{
- gsize size;
-
- if (!string.is_initialized())
- return 0;
-
- if (QRegisters::current)
- QRegisters::current->string.update(QRegisters::view);
-
- string.edit(QRegisters::view);
-
- size = QRegisters::view.ssm(SCI_GETLENGTH);
-
- if (QRegisters::current)
- QRegisters::current->string.edit(QRegisters::view);
-
- return size;
-}
-
-gint
-QRegisterData::get_character(gint position)
-{
- gint ret = -1;
-
- if (position < 0)
- return -1;
-
- if (QRegisters::current)
- QRegisters::current->string.update(QRegisters::view);
-
- string.edit(QRegisters::view);
-
- if (position < QRegisters::view.ssm(SCI_GETLENGTH))
- ret = QRegisters::view.ssm(SCI_GETCHARAT, position);
-
- if (QRegisters::current)
- QRegisters::current->string.edit(QRegisters::view);
-
- return ret;
-}
-
-void
-QRegisterData::undo_exchange_string(QRegisterData &reg)
-{
- if (must_undo)
- string.undo_exchange();
- if (reg.must_undo)
- reg.string.undo_exchange();
-}
-
-void
-QRegister::edit(void)
-{
- if (QRegisters::current)
- QRegisters::current->string.update(QRegisters::view);
-
- string.edit(QRegisters::view);
- interface.show_view(&QRegisters::view);
- interface.info_update(this);
-}
-
-void
-QRegister::undo_edit(void)
-{
- /*
- * We might be switching the current document
- * to a buffer.
- */
- string.update(QRegisters::view);
-
- if (!must_undo)
- return;
-
- interface.undo_info_update(this);
- string.undo_edit(QRegisters::view);
- interface.undo_show_view(&QRegisters::view);
-}
-
-void
-QRegister::execute(bool locals)
-{
- gchar *str = get_string();
-
- try {
- Execute::macro(str, locals);
- } catch (Error &error) {
- error.add_frame(new Error::QRegFrame(name));
-
- g_free(str);
- throw; /* forward */
- } catch (...) {
- g_free(str);
- throw; /* forward */
- }
-
- g_free(str);
-}
-
-void
-QRegister::undo_set_eol_mode(void)
-{
- if (!must_undo)
- return;
-
- /*
- * Necessary, so that upon rubout the
- * string's parameters are restored.
- */
- string.update(QRegisters::view);
-
- if (QRegisters::current && QRegisters::current->must_undo)
- QRegisters::current->string.undo_edit(QRegisters::view);
-
- QRegisters::view.undo_ssm(SCI_SETEOLMODE,
- QRegisters::view.ssm(SCI_GETEOLMODE));
-
- string.undo_edit(QRegisters::view);
-}
-
-void
-QRegister::set_eol_mode(gint mode)
-{
- if (QRegisters::current)
- QRegisters::current->string.update(QRegisters::view);
-
- string.edit(QRegisters::view);
- QRegisters::view.ssm(SCI_SETEOLMODE, mode);
-
- if (QRegisters::current)
- QRegisters::current->string.edit(QRegisters::view);
-}
-
-void
-QRegister::load(const gchar *filename)
-{
- undo_set_string();
-
- if (QRegisters::current)
- QRegisters::current->string.update(QRegisters::view);
-
- string.edit(QRegisters::view);
- string.reset();
-
- /*
- * IOView::load() might change the EOL style.
- */
- undo_set_eol_mode();
-
- /*
- * undo_set_string() pushes undo tokens that restore
- * the previous document in the view.
- * So if loading fails, QRegisters::current will be
- * made the current document again.
- */
- QRegisters::view.load(filename);
-
- if (QRegisters::current)
- QRegisters::current->string.edit(QRegisters::view);
-}
-
-void
-QRegister::save(const gchar *filename)
-{
- if (QRegisters::current)
- QRegisters::current->string.update(QRegisters::view);
-
- string.edit(QRegisters::view);
-
- try {
- QRegisters::view.save(filename);
- } catch (...) {
- if (QRegisters::current)
- QRegisters::current->string.edit(QRegisters::view);
- throw; /* forward */
- }
-
- if (QRegisters::current)
- QRegisters::current->string.edit(QRegisters::view);
-}
-
-tecoInt
-QRegisterBufferInfo::set_integer(tecoInt v)
-{
- if (!ring.edit(v))
- throw Error("Invalid buffer id %" TECO_INTEGER_FORMAT, v);
-
- return v;
-}
-
-void
-QRegisterBufferInfo::undo_set_integer(void)
-{
- current_doc_undo_edit();
-}
-
-tecoInt
-QRegisterBufferInfo::get_integer(void)
-{
- return ring.get_id();
-}
-
-gchar *
-QRegisterBufferInfo::get_string(void)
-{
- gchar *str = g_strdup(ring.current->filename ? : "");
-
- /*
- * On platforms with a default non-forward-slash directory
- * separator (i.e. Windows), Buffer::filename will have
- * the wrong separator.
- * To make the life of macros that evaluate "*" easier,
- * the directory separators are normalized to "/" here.
- * This does not change the size of the string, so
- * get_string_size() still works.
- */
- return normalize_path(str);
-}
-
-gsize
-QRegisterBufferInfo::get_string_size(void)
-{
- return ring.current->filename ? strlen(ring.current->filename) : 0;
-}
-
-gint
-QRegisterBufferInfo::get_character(gint position)
-{
- if (position < 0 ||
- position >= (gint)QRegisterBufferInfo::get_string_size())
- return -1;
-
- return ring.current->filename[position];
-}
-
-void
-QRegisterBufferInfo::edit(void)
-{
- gchar *str;
-
- QRegister::edit();
-
- QRegisters::view.ssm(SCI_BEGINUNDOACTION);
- str = QRegisterBufferInfo::get_string();
- QRegisters::view.ssm(SCI_SETTEXT, 0, (sptr_t)str);
- g_free(str);
- QRegisters::view.ssm(SCI_ENDUNDOACTION);
-
- QRegisters::view.undo_ssm(SCI_UNDO);
-}
-
-void
-QRegisterWorkingDir::set_string(const gchar *str, gsize len)
-{
- /* str is not null-terminated */
- gchar *dir = g_strndup(str, len);
- int ret = g_chdir(dir);
-
- g_free(dir);
-
- if (ret)
- /* FIXME: Is errno usable on Windows here? */
- throw Error("Cannot change working directory "
- "to \"%.*s\"", (int)len, str);
-}
-
-void
-QRegisterWorkingDir::undo_set_string(void)
-{
- /* pass ownership of current dir string */
- undo.push_own<UndoTokenChangeDir>(g_get_current_dir());
-}
-
-gchar *
-QRegisterWorkingDir::get_string(void)
-{
- /*
- * On platforms with a default non-forward-slash directory
- * separator (i.e. Windows), Buffer::filename will have
- * the wrong separator.
- * To make the life of macros that evaluate "$" easier,
- * the directory separators are normalized to "/" here.
- * This does not change the size of the string, so
- * get_string_size() still works.
- */
- return normalize_path(g_get_current_dir());
-}
-
-gsize
-QRegisterWorkingDir::get_string_size(void)
-{
- gchar *str = g_get_current_dir();
- gsize len = strlen(str);
-
- g_free(str);
- return len;
-}
-
-gint
-QRegisterWorkingDir::get_character(gint position)
-{
- gchar *str = QRegisterWorkingDir::get_string();
- gint ret = -1;
-
- if (position >= 0 &&
- position < (gint)strlen(str))
- ret = str[position];
-
- g_free(str);
- return ret;
-}
-
-void
-QRegisterWorkingDir::edit(void)
-{
- gchar *str;
-
- QRegister::edit();
-
- QRegisters::view.ssm(SCI_BEGINUNDOACTION);
- str = QRegisterWorkingDir::get_string();
- QRegisters::view.ssm(SCI_SETTEXT, 0, (sptr_t)str);
- g_free(str);
- QRegisters::view.ssm(SCI_ENDUNDOACTION);
-
- QRegisters::view.undo_ssm(SCI_UNDO);
-}
-
-void
-QRegisterWorkingDir::exchange_string(QRegisterData &reg)
-{
- gchar *own_str = QRegisterWorkingDir::get_string();
- gchar *other_str = reg.get_string();
-
- QRegisterData::set_string(other_str);
- g_free(other_str);
- reg.set_string(own_str);
- g_free(own_str);
-}
-
-void
-QRegisterWorkingDir::undo_exchange_string(QRegisterData &reg)
-{
- QRegisterWorkingDir::undo_set_string();
- reg.undo_set_string();
-}
-
-void
-QRegisterClipboard::UndoTokenSetClipboard::run(void)
-{
- interface.set_clipboard(name, str, str_len);
-}
-
-void
-QRegisterClipboard::set_string(const gchar *str, gsize len)
-{
- if (Flags::ed & Flags::ED_AUTOEOL) {
- GString *str_converted = g_string_sized_new(len);
- /*
- * This will convert to the Q-Register view's EOL mode.
- */
- EOLWriterMem writer(str_converted,
- QRegisters::view.ssm(SCI_GETEOLMODE));
- gsize bytes_written;
-
- /*
- * NOTE: Shouldn't throw any error, ever.
- */
- bytes_written = writer.convert(str, len);
- g_assert(bytes_written == len);
-
- interface.set_clipboard(get_clipboard_name(),
- str_converted->str, str_converted->len);
-
- g_string_free(str_converted, TRUE);
- } else {
- /*
- * No EOL conversion necessary. The EOLWriter can handle
- * this as well, but will result in unnecessary allocations.
- */
- interface.set_clipboard(get_clipboard_name(), str, len);
- }
-}
-
-void
-QRegisterClipboard::undo_set_string(void)
-{
- gchar *str;
- gsize str_len;
-
- /*
- * Upon rubout, the current contents of the clipboard are
- * restored.
- * We are checking for undo.enabled instead of relying on
- * undo.push_own(), since getting the clipboard
- * is an expensive operation that we want to avoid.
- */
- if (!undo.enabled)
- return;
-
- /*
- * Ownership of str (may be NULL) is passed to
- * the undo token. We do not need undo.push_own() since
- * we checked for undo.enabled before.
- * This avoids any EOL translation as that would be cumbersome
- * and could also modify the clipboard in unexpected ways.
- */
- str = interface.get_clipboard(get_clipboard_name(), &str_len);
- undo.push<UndoTokenSetClipboard>(get_clipboard_name(), str, str_len);
-}
-
-gchar *
-QRegisterClipboard::get_string(gsize *out_len)
-{
- if (!(Flags::ed & Flags::ED_AUTOEOL)) {
- /*
- * No auto-eol conversion - avoid unnecessary copying
- * and allocations.
- * NOTE: get_clipboard() already returns a null-terminated string.
- */
- return interface.get_clipboard(get_clipboard_name(), out_len);
- }
-
- gsize str_len;
- gchar *str = interface.get_clipboard(get_clipboard_name(), &str_len);
- EOLReaderMem reader(str, str_len);
- gchar *str_converted;
-
- try {
- str_converted = reader.convert_all(out_len);
- } catch (...) {
- g_free(str);
- throw; /* forward */
- }
- g_free(str);
-
- return str_converted;
-}
-
-gchar *
-QRegisterClipboard::get_string(void)
-{
- return QRegisterClipboard::get_string(NULL);
-}
-
-gsize
-QRegisterClipboard::get_string_size(void)
-{
- gsize str_len;
- gchar *str = interface.get_clipboard(get_clipboard_name(), &str_len);
- /*
- * Using the EOLReader does not hurt much if AutoEOL is disabled
- * since we use it only for counting the bytes.
- */
- EOLReaderMem reader(str, str_len);
- gsize data_len;
- gsize converted_len = 0;
-
- try {
- while (reader.convert(data_len))
- converted_len += data_len;
- } catch (...) {
- g_free(str);
- throw; /* forward */
- }
- g_free(str);
-
- return converted_len;
-}
-
-gint
-QRegisterClipboard::get_character(gint position)
-{
- gsize str_len;
- gchar *str = QRegisterClipboard::get_string(&str_len);
- gint ret = -1;
-
- /*
- * `str` may be NULL, but only if str_len == 0 as well.
- */
- if (position >= 0 &&
- position < (gint)str_len)
- ret = str[position];
-
- g_free(str);
- return ret;
-}
-
-void
-QRegisterClipboard::edit(void)
-{
- gchar *str;
- gsize str_len;
-
- QRegister::edit();
-
- QRegisters::view.ssm(SCI_BEGINUNDOACTION);
- QRegisters::view.ssm(SCI_CLEARALL);
- str = QRegisterClipboard::get_string(&str_len);
- QRegisters::view.ssm(SCI_APPENDTEXT, str_len, (sptr_t)str);
- g_free(str);
- QRegisters::view.ssm(SCI_ENDUNDOACTION);
-
- QRegisters::view.undo_ssm(SCI_UNDO);
-}
-
-void
-QRegisterClipboard::exchange_string(QRegisterData &reg)
-{
- gchar *own_str;
- gsize own_str_len;
- gchar *other_str = reg.get_string();
- gsize other_str_len = reg.get_string_size();
-
- /*
- * FIXME: What if `reg` is a clipboard and it changes
- * between the two calls?
- * QRegister::get_string() should always return the length as well.
- */
- QRegisterData::set_string(other_str, other_str_len);
- g_free(other_str);
- own_str = QRegisterClipboard::get_string(&own_str_len);
- reg.set_string(own_str, own_str_len);
- g_free(own_str);
-}
-
-void
-QRegisterClipboard::undo_exchange_string(QRegisterData &reg)
-{
- QRegisterClipboard::undo_set_string();
- reg.undo_set_string();
-}
-
-void
-QRegisterTable::UndoTokenRemoveGlobal::run(void)
-{
- delete (QRegister *)QRegisters::globals.remove(reg);
-}
-
-void
-QRegisterTable::UndoTokenRemoveLocal::run(void)
-{
- /*
- * NOTE: QRegisters::locals should point
- * to the correct table when the token is
- * executed.
- */
- delete (QRegister *)QRegisters::locals->remove(reg);
-}
-
-void
-QRegisterTable::undo_remove(QRegister *reg)
-{
- if (!must_undo)
- return;
-
- /*
- * NOTE: Could also be solved using a virtual
- * method and subclasses...
- */
- if (this == &QRegisters::globals)
- undo.push<UndoTokenRemoveGlobal>(reg);
- else
- undo.push<UndoTokenRemoveLocal>(reg);
-}
-
-void
-QRegisterTable::insert_defaults(void)
-{
- /* general purpose registers */
- for (gchar q = 'A'; q <= 'Z'; q++)
- insert(q);
- for (gchar q = '0'; q <= '9'; q++)
- insert(q);
-}
-
-/*
- * NOTE: by not making this inline,
- * we can access QRegisters::current
- */
-void
-QRegisterTable::edit(QRegister *reg)
-{
- reg->edit();
- QRegisters::current = reg;
-}
-
-/**
- * Import process environment into table
- * by setting environment registers for every
- * environment variable.
- * It is assumed that the table does not yet
- * contain any environment register.
- *
- * In general this method is only safe to call
- * at startup.
- */
-void
-QRegisterTable::set_environ(void)
-{
- /*
- * NOTE: Using g_get_environ() would be more efficient,
- * but it appears to be broken, at least on Wine
- * and Windows 2000.
- */
- gchar **env = g_listenv();
-
- for (gchar **key = env; *key; key++) {
- gchar name[1 + strlen(*key) + 1];
- QRegister *reg;
-
- name[0] = '$';
- strcpy(name + 1, *key);
-
- reg = insert(name);
- reg->set_string(g_getenv(*key));
- }
-
- g_strfreev(env);
-}
-
-/**
- * Export environment registers as a list of environment
- * variables compatible with `g_get_environ()`.
- *
- * @return Zero-terminated list of strings in the form
- * `NAME=VALUE`. Should be freed with `g_strfreev()`.
- */
-gchar **
-QRegisterTable::get_environ(void)
-{
- QRegister *first = nfind("$");
-
- gint envp_len = 1;
- gchar **envp, **p;
-
- /*
- * Iterate over all registers beginning with "$" to
- * guess the size required for the environment array.
- * This may waste a few bytes because not __every__
- * register beginning with "$" is an environment
- * register.
- */
- for (QRegister *cur = first;
- cur && cur->name[0] == '$';
- cur = (QRegister *)cur->next())
- envp_len++;
-
- p = envp = (gchar **)g_malloc(sizeof(gchar *)*envp_len);
-
- for (QRegister *cur = first;
- cur && cur->name[0] == '$';
- cur = (QRegister *)cur->next()) {
- gchar *value;
-
- /*
- * Ignore the "$" register (not an environment
- * variable register) and registers whose
- * name contains "=" (not allowed in environment
- * variable names).
- */
- if (!cur->name[1] || strchr(cur->name+1, '='))
- continue;
-
- value = cur->get_string();
- /* more efficient than g_environ_setenv() */
- *p++ = g_strconcat(cur->name+1, "=", value, NIL);
- g_free(value);
- }
-
- *p = NULL;
-
- return envp;
-}
-
-/**
- * Update process environment with environment registers
- * using `g_setenv()`.
- * It does not try to unset environment variables that
- * are no longer in the Q-Register table.
- *
- * This method may be dangerous in a multi-threaded environment
- * but may be necessary for libraries that access important
- * environment variables internally without providing alternative
- * APIs.
- */
-void
-QRegisterTable::update_environ(void)
-{
- for (QRegister *cur = nfind("$");
- cur && cur->name[0] == '$';
- cur = (QRegister *)cur->next()) {
- gchar *value;
-
- /*
- * Ignore the "$" register (not an environment
- * variable register) and registers whose
- * name contains "=" (not allowed in environment
- * variable names).
- */
- if (!cur->name[1] || strchr(cur->name+1, '='))
- continue;
-
- value = cur->get_string();
- g_setenv(cur->name+1, value, TRUE);
- g_free(value);
- }
-}
-
-/**
- * Free resources associated with table.
- *
- * This is similar to the destructor but
- * has the advantage that we can check whether some
- * register is currently edited.
- * Since this is not a destructor, we can throw
- * errors.
- * Therefore this method should be called before
- * a (local) QRegisterTable is deleted.
- */
-void
-QRegisterTable::clear(void)
-{
- QRegister *cur;
-
- while ((cur = (QRegister *)root())) {
- if (cur == QRegisters::current)
- throw Error("Currently edited Q-Register \"%s\" "
- "cannot be discarded", cur->name);
-
- delete (QRegister *)remove(cur);
- }
-}
-
-void
-QRegisterStack::UndoTokenPush::run(void)
-{
- SLIST_INSERT_HEAD(&stack->head, entry, entries);
- entry = NULL;
-}
-
-void
-QRegisterStack::UndoTokenPop::run(void)
-{
- Entry *entry = SLIST_FIRST(&stack->head);
-
- SLIST_REMOVE_HEAD(&stack->head, entries);
- delete entry;
-}
-
-void
-QRegisterStack::push(QRegister &reg)
-{
- Entry *entry = new Entry();
-
- gchar *str = reg.get_string();
- if (*str)
- entry->set_string(str);
- g_free(str);
- entry->string.update(reg.string);
- entry->set_integer(reg.get_integer());
-
- SLIST_INSERT_HEAD(&head, entry, entries);
- undo.push<UndoTokenPop>(this);
-}
-
-bool
-QRegisterStack::pop(QRegister &reg)
-{
- Entry *entry = SLIST_FIRST(&head);
-
- if (!entry)
- return false;
-
- reg.undo_set_integer();
- reg.set_integer(entry->get_integer());
-
- /* exchange document ownership between Stack entry and Q-Register */
- reg.undo_exchange_string(*entry);
- reg.exchange_string(*entry);
-
- SLIST_REMOVE_HEAD(&head, entries);
- /* Pass entry ownership to undo stack. */
- undo.push_own<UndoTokenPush>(this, entry);
-
- return true;
-}
-
-QRegisterStack::~QRegisterStack()
-{
- Entry *entry, *next;
-
- SLIST_FOREACH_SAFE(entry, &head, entries, next)
- delete entry;
-}
-
-void
-QRegisters::hook(Hook type)
-{
- static const gchar *type2name[] = {
- /* [HOOK_ADD-1] = */ "ADD",
- /* [HOOK_EDIT-1] = */ "EDIT",
- /* [HOOK_CLOSE-1] = */ "CLOSE",
- /* [HOOK_QUIT-1] = */ "QUIT",
- };
-
- QRegister *reg;
-
- if (!(Flags::ed & Flags::ED_HOOKS))
- return;
-
- try {
- reg = globals["ED"];
- if (!reg)
- throw Error("Undefined ED-hook register (\"ED\")");
-
- /*
- * ED-hook execution should not see any
- * integer parameters but the hook type.
- * Such parameters could confuse the ED macro
- * and macro authors do not expect side effects
- * of ED macros on the expression stack.
- * Also make sure it does not leave behind
- * additional arguments on the stack.
- *
- * So this effectively executes:
- * (typeM[ED]^[)
- */
- expressions.brace_open();
- expressions.push(type);
- reg->execute();
- expressions.discard_args();
- expressions.brace_close();
- } catch (Error &error) {
- const gchar *type_str = type2name[type-1];
-
- error.add_frame(new Error::EDHookFrame(type_str));
- throw; /* forward */
- }
-}
-
-void
-QRegSpecMachine::reset(void)
-{
- MicroStateMachine<QRegister *>::reset();
- string_machine.reset();
- undo.push_var(is_local) = false;
- undo.push_var(nesting) = 0;
- undo.push_str(name);
- g_free(name);
- name = NULL;
-}
-
-bool
-QRegSpecMachine::input(gchar chr, QRegister *&result)
-{
- gchar *insert;
-
-MICROSTATE_START;
- switch (chr) {
- case '#':
- set(&&StateFirstChar);
- break;
- case '[':
- set(&&StateString);
- undo.push_var(nesting)++;
- break;
- case '.':
- if (!is_local) {
- undo.push_var(is_local) = true;
- break;
- }
- /* fall through */
- default:
- undo.push_str(name) = String::chrdup(String::toupper(chr));
- goto done;
- }
-
- return false;
-
-StateFirstChar:
- undo.push_str(name) = (gchar *)g_malloc(3);
- name[0] = String::toupper(chr);
- name[1] = '\0';
- set(&&StateSecondChar);
- return false;
-
-StateSecondChar:
- name[1] = String::toupper(chr);
- name[2] = '\0';
- goto done;
-
-StateString:
- switch (chr) {
- case '[':
- undo.push_var(nesting)++;
- break;
- case ']':
- undo.push_var(nesting)--;
- if (!nesting)
- goto done;
- break;
- }
-
- if (mode > MODE_NORMAL)
- return false;
-
- if (!string_machine.input(chr, insert))
- return false;
-
- undo.push_str(name);
- String::append(name, insert);
- g_free(insert);
- return false;
-
-done:
- if (mode > MODE_NORMAL) {
- /*
- * StateExpectQRegs with type != OPTIONAL
- * will never see this NULL pointer beyond
- * BEGIN_EXEC()
- */
- result = NULL;
- return true;
- }
-
- QRegisterTable &table = is_local ? *QRegisters::locals
- : QRegisters::globals;
-
- switch (type) {
- case QREG_REQUIRED:
- result = table[name];
- if (!result)
- fail();
- break;
-
- case QREG_OPTIONAL:
- result = table[name];
- break;
-
- case QREG_OPTIONAL_INIT:
- result = table[name];
- if (!result) {
- result = table.insert(name);
- table.undo_remove(result);
- }
- break;
- }
-
- return true;
-}
-
-gchar *
-QRegSpecMachine::auto_complete(void)
-{
- gsize restrict_len = 0;
-
- if (string_machine.qregspec_machine)
- /* nested Q-Reg definition */
- return string_machine.qregspec_machine->auto_complete();
-
- if (state == StateStart)
- /* single-letter Q-Reg */
- restrict_len = 1;
- else if (!nesting)
- /* two-letter Q-Reg */
- restrict_len = 2;
-
- QRegisterTable &table = is_local ? *QRegisters::locals
- : QRegisters::globals;
- return table.auto_complete(name, nesting == 1 ? ']' : '\0',
- restrict_len);
-}
-
-/*
- * Command states
- */
-
-StateExpectQReg::StateExpectQReg(QRegSpecType type) : machine(type)
-{
- transitions['\0'] = this;
-}
-
-State *
-StateExpectQReg::custom(gchar chr)
-{
- QRegister *reg;
-
- if (!machine.input(chr, reg))
- return this;
-
- return got_register(reg);
-}
-
-/*$ "[" "[q" push
- * [q -- Save Q-Register
- *
- * Save Q-Register <q> contents on the global Q-Register push-down
- * stack.
- */
-State *
-StatePushQReg::got_register(QRegister *reg)
-{
- machine.reset();
-
- BEGIN_EXEC(&States::start);
- QRegisters::stack.push(*reg);
- return &States::start;
-}
-
-/*$ "]" "]q" pop
- * ]q -- Restore Q-Register
- *
- * Restore Q-Register <q> by replacing its contents
- * with the contents of the register saved on top of
- * the Q-Register push-down stack.
- * The stack entry is popped.
- *
- * In interactive mode, the original contents of <q>
- * are not immediately reclaimed but are kept in memory
- * to support rubbing out the command.
- * Memory is reclaimed on command-line termination.
- */
-State *
-StatePopQReg::got_register(QRegister *reg)
-{
- machine.reset();
-
- BEGIN_EXEC(&States::start);
-
- if (!QRegisters::stack.pop(*reg))
- throw Error("Q-Register stack is empty");
-
- return &States::start;
-}
-
-/*$ EQ EQq
- * EQq$ -- Edit or load Q-Register
- * EQq[file]$
- *
- * When specified with an empty <file> string argument,
- * EQ makes <q> the currently edited Q-Register.
- * Otherwise, when <file> is specified, it is the
- * name of a file to read into Q-Register <q>.
- * When loading a file, the currently edited
- * buffer/register is not changed and the edit position
- * of register <q> is reset to 0.
- *
- * Undefined Q-Registers will be defined.
- * The command fails if <file> could not be read.
- */
-State *
-StateEQCommand::got_register(QRegister *reg)
-{
- machine.reset();
-
- BEGIN_EXEC(&States::loadqreg);
- undo.push_var(register_argument) = reg;
- return &States::loadqreg;
-}
-
-State *
-StateLoadQReg::got_file(const gchar *filename)
-{
- BEGIN_EXEC(&States::start);
-
- if (*filename) {
- /* Load file into Q-Register */
- register_argument->load(filename);
- } else {
- /* Edit Q-Register */
- current_doc_undo_edit();
- QRegisters::globals.edit(register_argument);
- }
-
- return &States::start;
-}
-
-/*$ E% E%q
- * E%q<file>$ -- Save Q-Register string to file
- *
- * Saves the string contents of Q-Register <q> to
- * <file>.
- * The <file> must always be specified, as Q-Registers
- * have no notion of associated file names.
- *
- * In interactive mode, the E% command may be rubbed out,
- * restoring the previous state of <file>.
- * This follows the same rules as with the \fBEW\fP command.
- *
- * File names may also be tab-completed and string building
- * characters are enabled by default.
- */
-State *
-StateEPctCommand::got_register(QRegister *reg)
-{
- machine.reset();
-
- BEGIN_EXEC(&States::saveqreg);
- undo.push_var(register_argument) = reg;
- return &States::saveqreg;
-}
-
-State *
-StateSaveQReg::got_file(const gchar *filename)
-{
- BEGIN_EXEC(&States::start);
- register_argument->save(filename);
- return &States::start;
-}
-
-/*$ Q Qq query
- * Qq -> n -- Query Q-Register existence, its integer or string characters
- * <position>Qq -> character
- * :Qq -> -1 | size
- *
- * Without any arguments, get and return the integer-part of
- * Q-Register <q>.
- *
- * With one argument, return the <character> code at <position>
- * from the string-part of Q-Register <q>.
- * Positions are handled like buffer positions \(em they
- * begin at 0 up to the length of the string minus 1.
- * An error is thrown for invalid positions.
- * Both non-colon-modified forms of Q require register <q>
- * to be defined and fail otherwise.
- *
- * When colon-modified, Q does not pop any arguments from
- * the expression stack and returns the <size> of the string
- * in Q-Register <q> if register <q> exists (i.e. is defined).
- * Naturally, for empty strings, 0 is returned.
- * When colon-modified and Q-Register <q> is undefined,
- * -1 is returned instead.
- * Therefore checking the return value \fB:Q\fP for values smaller
- * 0 allows checking the existence of a register.
- * Note that if <q> exists, its string part is not initialized,
- * so \fB:Q\fP may be used to handle purely numeric data structures
- * without creating Scintilla documents by accident.
- * These semantics allow the useful idiom \(lq:Q\fIq\fP">\(rq for
- * checking whether a Q-Register exists and has a non-empty string.
- * Note also that the return value of \fB:Q\fP may be interpreted
- * as a condition boolean that represents the non-existence of <q>.
- * If <q> is undefined, it returns \fIsuccess\fP, else a \fIfailure\fP
- * boolean.
- */
-State *
-StateQueryQReg::got_register(QRegister *reg)
-{
- /* like BEGIN_EXEC(&States::start), but resets machine */
- if (mode > MODE_NORMAL)
- goto reset;
-
- expressions.eval();
-
- if (eval_colon()) {
- /* Query Q-Register's existence or string size */
- expressions.push(reg ? reg->get_string_size()
- : (tecoInt)-1);
- goto reset;
- }
-
- /*
- * NOTE: This command is special since the QRegister is required
- * without colon and otherwise optional.
- * While it may be clearer to model this as two States,
- * we cannot currently let parsing depend on the colon-modifier.
- * That's why we have to declare the Q-Reg machine as QREG_OPTIONAL
- * and care about exception throwing on our own.
- */
- if (!reg)
- machine.fail();
-
- if (expressions.args() > 0) {
- /* Query character from Q-Register string */
- gint c = reg->get_character(expressions.pop_num_calc());
- if (c < 0)
- throw RangeError('Q');
- expressions.push(c);
- } else {
- /* Query integer */
- expressions.push(reg->get_integer());
- }
-
-reset:
- machine.reset();
- return &States::start;
-}
-
-/*$ ^Uq
- * [c1,c2,...]^Uq[string]$ -- Set or append to Q-Register string without string building
- * [c1,c2,...]:^Uq[string]$
- *
- * If not colon-modified, it first fills the Q-Register <q>
- * with all the values on the expression stack (interpreted as
- * codepoints).
- * It does so in the order of the arguments, i.e.
- * <c1> will be the first character in <q>, <c2> the second, etc.
- * Eventually the <string> argument is appended to the
- * register.
- * Any existing string value in <q> is overwritten by this operation.
- *
- * In the colon-modified form ^U does not overwrite existing
- * contents of <q> but only appends to it.
- *
- * If <q> is undefined, it will be defined.
- *
- * String-building characters are \fBdisabled\fP for ^U
- * commands.
- * Therefore they are especially well-suited for defining
- * \*(ST macros, since string building characters in the
- * desired Q-Register contents do not have to be escaped.
- * The \fBEU\fP command may be used where string building
- * is desired.
- */
-State *
-StateCtlUCommand::got_register(QRegister *reg)
-{
- machine.reset();
-
- BEGIN_EXEC(&States::setqregstring_nobuilding);
- undo.push_var(register_argument) = reg;
- return &States::setqregstring_nobuilding;
-}
-
-/*$ EU EUq
- * [c1,c2,...]EUq[string]$ -- Set or append to Q-Register string with string building characters
- * [c1,c2,...]:EUq[string]$
- *
- * This command sets or appends to the contents of
- * Q-Register \fIq\fP.
- * It is identical to the \fB^U\fP command, except
- * that this form of the command has string building
- * characters \fBenabled\fP.
- */
-State *
-StateEUCommand::got_register(QRegister *reg)
-{
- machine.reset();
-
- BEGIN_EXEC(&States::setqregstring_building);
- undo.push_var(register_argument) = reg;
- return &States::setqregstring_building;
-}
-
-void
-StateSetQRegString::initial(void)
-{
- int args;
-
- expressions.eval();
- args = expressions.args();
- text_added = args > 0;
- if (!args)
- return;
-
- gchar buffer[args+1];
-
- buffer[args] = '\0';
- while (args--)
- buffer[args] = (gchar)expressions.pop_num_calc();
-
- if (eval_colon()) {
- /* append to register */
- register_argument->undo_append_string();
- register_argument->append_string(buffer);
- } else {
- /* set register */
- register_argument->undo_set_string();
- register_argument->set_string(buffer);
- }
-}
-
-State *
-StateSetQRegString::done(const gchar *str)
-{
- BEGIN_EXEC(&States::start);
-
- if (text_added || eval_colon()) {
- /*
- * Append to register:
- * Note that append_string() does not create an UNDOACTION
- * if str == NULL
- */
- if (str) {
- register_argument->undo_append_string();
- register_argument->append_string(str);
- }
- } else {
- /* set register */
- register_argument->undo_set_string();
- register_argument->set_string(str);
- }
-
- return &States::start;
-}
-
-/*$ G Gq get
- * Gq -- Insert Q-Register string
- *
- * Inserts the string of Q-Register <q> into the buffer
- * at its current position.
- * Specifying an undefined <q> yields an error.
- */
-State *
-StateGetQRegString::got_register(QRegister *reg)
-{
- gchar *str;
-
- machine.reset();
-
- BEGIN_EXEC(&States::start);
-
- str = reg->get_string();
- if (*str) {
- interface.ssm(SCI_BEGINUNDOACTION);
- interface.ssm(SCI_ADDTEXT, strlen(str), (sptr_t)str);
- interface.ssm(SCI_SCROLLCARET);
- interface.ssm(SCI_ENDUNDOACTION);
- ring.dirtify();
-
- interface.undo_ssm(SCI_UNDO);
- }
- g_free(str);
-
- return &States::start;
-}
-
-/*$ U Uq
- * nUq -- Set Q-Register integer
- * -Uq
- * [n]:Uq -> Success|Failure
- *
- * Sets the integer-part of Q-Register <q> to <n>.
- * \(lq-U\(rq is equivalent to \(lq-1U\(rq, otherwise
- * the command fails if <n> is missing.
- *
- * If the command is colon-modified, it returns a success
- * boolean if <n> or \(lq-\(rq is given.
- * Otherwise it returns a failure boolean and does not
- * modify <q>.
- *
- * The register is defined if it does not exist.
- */
-State *
-StateSetQRegInteger::got_register(QRegister *reg)
-{
- machine.reset();
-
- BEGIN_EXEC(&States::start);
-
- expressions.eval();
- if (expressions.args() || expressions.num_sign < 0) {
- reg->undo_set_integer();
- reg->set_integer(expressions.pop_num_calc());
-
- if (eval_colon())
- expressions.push(SUCCESS);
- } else if (eval_colon()) {
- expressions.push(FAILURE);
- } else {
- throw ArgExpectedError('U');
- }
-
- return &States::start;
-}
-
-/*$ % %q increment
- * [n]%q -> q+n -- Increase Q-Register integer
- *
- * Add <n> to the integer part of register <q>, returning
- * its new value.
- * <q> will be defined if it does not exist.
- */
-State *
-StateIncreaseQReg::got_register(QRegister *reg)
-{
- tecoInt res;
-
- machine.reset();
-
- BEGIN_EXEC(&States::start);
-
- reg->undo_set_integer();
- res = reg->get_integer() + expressions.pop_num_calc();
- expressions.push(reg->set_integer(res));
-
- return &States::start;
-}
-
-/*$ M Mq eval
- * Mq -- Execute macro
- * :Mq
- *
- * Execute macro stored in string of Q-Register <q>.
- * The command itself does not push or pop and arguments from the stack
- * but the macro executed might well do so.
- * The new macro invocation level will contain its own go-to label table
- * and local Q-Register table.
- * Except when the command is colon-modified - in this case, local
- * Q-Registers referenced in the macro refer to the parent macro-level's
- * local Q-Register table (or whatever level defined one last).
- *
- * Errors during the macro execution will propagate to the M command.
- * In other words if a command in the macro fails, the M command will fail
- * and this failure propagates until the top-level macro (e.g.
- * the command-line macro).
- *
- * Note that the string of <q> will be copied upon macro execution,
- * so subsequent changes to Q-Register <q> from inside the macro do
- * not modify the executed code.
- */
-State *
-StateMacro::got_register(QRegister *reg)
-{
- machine.reset();
-
- BEGIN_EXEC(&States::start);
- /* don't create new local Q-Registers if colon modifier is given */
- reg->execute(!eval_colon());
- return &States::start;
-}
-
-/*$ EM
- * EMfile$ -- Execute macro from file
- * :EMfile$
- *
- * Read the file with name <file> into memory and execute its contents
- * as a macro.
- * It is otherwise similar to the \(lqM\(rq command.
- *
- * If <file> could not be read, the command yields an error.
- */
-State *
-StateMacroFile::got_file(const gchar *filename)
-{
- BEGIN_EXEC(&States::start);
- /* don't create new local Q-Registers if colon modifier is given */
- Execute::file(filename, !eval_colon());
- return &States::start;
-}
-
-/*$ X Xq
- * [lines]Xq -- Copy into or append to Q-Register
- * -Xq
- * from,toXq
- * [lines]:Xq
- * -:Xq
- * from,to:Xq
- *
- * Copy the next or previous number of <lines> from the buffer
- * into the Q-Register <q> string.
- * If <lines> is omitted, the sign prefix is implied.
- * If two arguments are specified, the characters beginning
- * at position <from> up to the character at position <to>
- * are copied.
- * The semantics of the arguments is analogous to the K
- * command's arguments.
- * If the command is colon-modified, the characters will be
- * appended to the end of register <q> instead.
- *
- * Register <q> will be created if it is undefined.
- */
-State *
-StateCopyToQReg::got_register(QRegister *reg)
-{
- tecoInt from, len;
- Sci_TextRange tr;
-
- machine.reset();
-
- BEGIN_EXEC(&States::start);
- expressions.eval();
-
- if (expressions.args() <= 1) {
- from = interface.ssm(SCI_GETCURRENTPOS);
- sptr_t line = interface.ssm(SCI_LINEFROMPOSITION, from) +
- expressions.pop_num_calc();
-
- if (!Validate::line(line))
- throw RangeError("X");
-
- len = interface.ssm(SCI_POSITIONFROMLINE, line) - from;
-
- if (len < 0) {
- from += len;
- len *= -1;
- }
- } else {
- tecoInt to = expressions.pop_num();
- from = expressions.pop_num();
-
- len = to - from;
-
- if (len < 0 || !Validate::pos(from) || !Validate::pos(to))
- throw RangeError("X");
- }
-
- tr.chrg.cpMin = from;
- tr.chrg.cpMax = from + len;
- tr.lpstrText = (char *)g_malloc(len + 1);
- interface.ssm(SCI_GETTEXTRANGE, 0, (sptr_t)&tr);
-
- if (eval_colon()) {
- reg->undo_append_string();
- reg->append_string(tr.lpstrText);
- } else {
- reg->undo_set_string();
- reg->set_string(tr.lpstrText);
- }
- g_free(tr.lpstrText);
-
- return &States::start;
-}
-
-} /* namespace SciTECO */