diff options
Diffstat (limited to 'lua/clever_f/testing/memory_host.lua')
| -rw-r--r-- | lua/clever_f/testing/memory_host.lua | 1075 |
1 files changed, 0 insertions, 1075 deletions
diff --git a/lua/clever_f/testing/memory_host.lua b/lua/clever_f/testing/memory_host.lua deleted file mode 100644 index 6147d2c..0000000 --- a/lua/clever_f/testing/memory_host.lua +++ /dev/null @@ -1,1075 +0,0 @@ -local capabilities = require("clever_f.capabilities") -local domain = require("clever_f.domain") -local text_topology = require("clever_f.text_topology") - -local M = {} -local MemoryHost = {} -MemoryHost.__index = MemoryHost -M.MemoryHost = MemoryHost -local unpack_values = table.unpack or unpack - -local function is_integer(value) - return type(value) == "number" - and value > -math.huge - and value < math.huge - and value == math.floor(value) -end - -local function copy(value, seen) - if type(value) ~= "table" or domain.type_of(value) ~= nil then - return value - end - seen = seen or {} - if seen[value] ~= nil then - return seen[value] - end - local result = {} - seen[value] = result - for key, item in pairs(value) do - result[copy(key, seen)] = copy(item, seen) - end - return result -end - -local function list_copy(values) - local result = {} - for index = 1, #values do - result[index] = values[index] - end - return result -end - -local function text_snapshot(value) - if domain.TextSnapshot.is(value) then - return value - end - if type(value) == "table" and value.lines ~= nil then - value = value.lines - end - return domain.TextSnapshot.new(value) -end - -local function selection_value(value) - if value == nil then - return domain.Selection.inactive() - end - return domain.Selection.new(value) -end - -local function macro_state(value) - if type(value) == "table" and not domain.MacroState.is(value) then - value = value.register - end - return domain.MacroState.new(value) -end - -local function fold_state(options) - if domain.FoldState.is(options.fold_state) then - return options.fold_state - end - return domain.FoldState.new( - options.fold_open_policy or {}, - options.closed_fold_levels or 0 - ) -end - -local function input_packet(value) - return domain.InputPacket.from_table(value) -end - -local function normalize_event_names(event_names) - if type(event_names) == "string" then - event_names = { event_names } - end - if type(event_names) ~= "table" or #event_names < 1 then - error("event names must be a nonempty list", 3) - end - local result = {} - local seen = {} - for index = 1, #event_names do - local name = event_names[index] - if type(name) ~= "string" or name == "" then - error("event name must be a nonempty string", 3) - end - if not seen[name] then - seen[name] = true - result[#result + 1] = name - end - end - return result, seen -end - -local function normalize_modes(modes) - if type(modes) == "string" then - modes = { modes } - end - if type(modes) ~= "table" or #modes < 1 then - error("mapping modes must be a nonempty list", 3) - end - local result = {} - for index = 1, #modes do - if type(modes[index]) ~= "string" or modes[index] == "" then - error("mapping mode must be a nonempty string", 3) - end - result[index] = modes[index] - end - return result -end - -function MemoryHost.new(options) - options = options or {} - if type(options) ~= "table" then - error("memory host options must be a table", 2) - end - - local cursor_presentation_support = options.cursor_presentation_support - if cursor_presentation_support == nil then - cursor_presentation_support = options.cmdline_cursor_support - end - if cursor_presentation_support == nil then - cursor_presentation_support = true - end - - local raw_mode = options.mode or "n" - if domain.ModeContext.is(raw_mode) then - raw_mode = raw_mode.full_mode - end - domain.ModeContext.from_full_mode(raw_mode) - - local self = setmetatable({ - _text = text_snapshot(options.text or options.buffer_lines or { "" }), - _buffer = options.buffer or "buffer-1", - _cursor = domain.Position.coerce(options.cursor or { line = 1, byte_column = 1 }), - _window = options.window or "window-1", - _mode = raw_mode, - _selection = selection_value(options.selection), - _count = domain.Count.new(options.count), - _configuration = copy(options.configuration or {}), - _encoding = options.encoding or options.effective_encoding or "utf-8", - _lowercase = options.lowercase or vim.fn.tolower, - _macro_state = macro_state(options.macro_state or options.macro_register), - _fold_state = fold_state(options), - _pending_operator = options.pending_operator, - _time_values = list_copy(options.time_values_ms or {}), - _time_index = 1, - _current_time = options.time_ms or 0, - _input_packets = {}, - _input_index = 1, - _timer_support = options.timer_support ~= false, - _cursor_presentation_support = cursor_presentation_support, - _cursor_presentation = copy(options.cursor_presentation or { - hidden = false, - }), - _emit_movement_events = options.emit_movement_events ~= false, - _operator_inclusive = false, - _operations = {}, - _prompts = {}, - _redraws = {}, - _diagnostics = {}, - _highlight_groups = copy(options.highlight_groups or {}), - _highlights = {}, - _timers = {}, - _event_registrations = {}, - _event_registration_order = {}, - _actions = {}, - _mappings = {}, - _cursor_leases = {}, - _dot_repeat = nil, - _identity_counters = {}, - }, MemoryHost) - - for index, packet in ipairs(options.input_packets or {}) do - self._input_packets[index] = input_packet(packet) - end - - self._event_queue = capabilities.EventQueue.new(function(name, payload) - self:_deliver_event_now(name, payload) - end) - - return capabilities.assert_implements(self) -end - -function M.new(options) - return MemoryHost.new(options) -end - -setmetatable(M, { - __call = function(_, options) - return MemoryHost.new(options) - end, -}) - -function MemoryHost:_next_identity(prefix) - local next_value = (self._identity_counters[prefix] or 0) + 1 - self._identity_counters[prefix] = next_value - return prefix .. "-" .. tostring(next_value) -end - -function MemoryHost:_record(operation, details) - local entry = { operation = operation } - for key, value in pairs(details or {}) do - entry[key] = copy(value) - end - self._operations[#self._operations + 1] = entry -end - -function MemoryHost:operations() - return copy(self._operations) -end - -function MemoryHost:clear_operations() - self._operations = {} -end - -function MemoryHost:read_text() - self:_record("read_text") - return self._text -end - -function MemoryHost:read_cursor() - self:_record("read_cursor") - return self._cursor -end - -function MemoryHost:read_buffer() - self:_record("read_buffer", { buffer = self._buffer }) - return self._buffer -end - -function MemoryHost:read_window() - self:_record("read_window", { window = self._window }) - return self._window -end - -function MemoryHost:read_mode() - self:_record("read_mode", { mode = self._mode }) - return self._mode -end - -function MemoryHost:read_mode_context() - return domain.ModeContext.from_full_mode(self:read_mode()) -end - -function MemoryHost:read_pending_operator() - self:_record("read_pending_operator", { operator = self._pending_operator }) - return self._pending_operator -end - -function MemoryHost:read_selection() - self:_record("read_selection") - return self._selection -end - -function MemoryHost:read_count() - self:_record("read_count", { count = self._count.value }) - return self._count -end - -function MemoryHost:configuration_present(name) - if type(name) ~= "string" or name == "" then - error("configuration name must be a nonempty string", 2) - end - local present = self._configuration[name] ~= nil - self:_record("configuration_present", { name = name, present = present }) - return present -end - -function MemoryHost:read_configuration(name) - if type(name) ~= "string" or name == "" then - error("configuration name must be a nonempty string", 2) - end - local value = copy(self._configuration[name]) - self:_record("read_configuration", { name = name, value = value }) - return value -end - -function MemoryHost:write_configuration(name, value) - if type(name) ~= "string" or name == "" then - error("configuration name must be a nonempty string", 2) - end - self._configuration[name] = copy(value) - self:_record("write_configuration", { name = name, value = value }) -end - -function MemoryHost:read_encoding() - self:_record("read_encoding", { encoding = self._encoding }) - return self._encoding -end - -function MemoryHost:lowercase(value) - if type(value) ~= "string" then - error("case conversion value must be a string", 2) - end - local result = self._lowercase(value) - if type(result) ~= "string" then - error("case converter must return a string", 2) - end - self:_record("lowercase", { value = value, result = result }) - return result -end - -function MemoryHost:read_macro_state() - self:_record("read_macro_state", { executing = self._macro_state.executing }) - return self._macro_state -end - -function MemoryHost:read_fold_state() - self:_record("read_fold_state", { closed_levels = self._fold_state.closed_levels }) - return self._fold_state -end - -function MemoryHost:read_time_ms() - local value = self._time_values[self._time_index] - if value ~= nil then - self._time_index = self._time_index + 1 - self._current_time = value - else - value = self._current_time - end - if type(value) ~= "number" then - error("time value must be a number", 2) - end - self:_record("read_time_ms", { value = value }) - return value -end - -function MemoryHost:set_text(value) - self._text = text_snapshot(value) -end - -function MemoryHost:set_cursor(position) - self._cursor = domain.Position.coerce(position) -end - -function MemoryHost:set_buffer(buffer) - if buffer == nil then - error("buffer identity must be present", 2) - end - self._buffer = buffer -end - -function MemoryHost:set_window(window) - if window == nil then - error("window identity must be present", 2) - end - self._window = window -end - -function MemoryHost:set_mode(full_mode) - if domain.ModeContext.is(full_mode) then - full_mode = full_mode.full_mode - end - domain.ModeContext.from_full_mode(full_mode) - self._mode = full_mode -end - -function MemoryHost:set_selection(selection) - self._selection = selection_value(selection) -end - -function MemoryHost:set_count(count) - self._count = domain.Count.new(count) -end - -function MemoryHost:set_configuration(name, value) - if type(name) ~= "string" or name == "" then - error("configuration name must be a nonempty string", 2) - end - self._configuration[name] = copy(value) -end - -function MemoryHost:unset_configuration(name) - self._configuration[name] = nil -end - -function MemoryHost:set_encoding(encoding) - if type(encoding) ~= "string" or encoding == "" then - error("encoding must be a nonempty string", 2) - end - self._encoding = encoding -end - -function MemoryHost:set_macro_state(state) - self._macro_state = macro_state(state) -end - -function MemoryHost:set_fold_state(state, closed_levels) - if domain.FoldState.is(state) then - self._fold_state = state - else - self._fold_state = domain.FoldState.new(state, closed_levels) - end -end - -function MemoryHost:set_pending_operator(operator) - self._pending_operator = operator -end - -function MemoryHost:push_time_ms(value) - if type(value) ~= "number" then - error("time value must be a number", 2) - end - self._time_values[#self._time_values + 1] = value -end - -function MemoryHost:push_input(packet) - self._input_packets[#self._input_packets + 1] = input_packet(packet) -end - -function MemoryHost:_emit_movement_event(previous) - if self._emit_movement_events and not domain.Position.equal(previous, self._cursor) then - self:deliver_event("CursorMoved", { - cursor = self._cursor, - }) - end -end - -local function motion_descriptor(motion) - if domain.Descriptor.is(motion) then - return motion - end - if type(motion) == "table" and motion.descriptor ~= nil then - return domain.Descriptor.from_string(motion.descriptor) - end - return nil -end - -local function character_boundary(view, position) - if view:line_is_empty(position.line) then - return 1 - end - return view:character_index_for_position(position) -end - -local function character_lines(snapshot) - local result = {} - for line_number, line in ipairs(snapshot:lines()) do - result[line_number] = text_topology.split_editor_characters(line) - end - return result -end - -local function joined_range(characters, first, last) - local result = {} - for index = first, last do - result[#result + 1] = characters[index] - end - return table.concat(result) -end - -local function delete_character_range( - snapshot, - start_line, - start_index, - finish_line, - finish_index -) - local source = character_lines(snapshot) - local lines = snapshot:lines() - local result = {} - - for line_number = 1, start_line - 1 do - result[#result + 1] = lines[line_number] - end - - local prefix = joined_range(source[start_line], 1, start_index - 1) - if start_line == finish_line then - result[#result + 1] = prefix - .. joined_range( - source[start_line], - finish_index, - #source[start_line] - ) - else - result[#result + 1] = prefix - .. joined_range( - source[finish_line], - finish_index, - #source[finish_line] - ) - end - - for line_number = finish_line + 1, #lines do - result[#result + 1] = lines[line_number] - end - return domain.TextSnapshot.new(result) -end - -local function normalized_cursor(snapshot, encoding, position) - local line_number = math.min(position.line, snapshot.line_count) - local view = text_topology.new(snapshot, encoding) - return view:normalize_endpoint(line_number, position.byte_column) -end - -function MemoryHost:_apply_pending_delete(origin, destination, descriptor) - if self._pending_operator ~= "delete" and self._pending_operator ~= "d" then - return false - end - - local view = text_topology.new(self._text, self._encoding) - local origin_index = character_boundary(view, origin) - local destination_index = character_boundary(view, destination) - local start_line - local start_index - local finish_line - local finish_index - local final_cursor - - if descriptor.direction == domain.Direction.FORWARD then - start_line = origin.line - start_index = origin_index - finish_line = destination.line - finish_index = destination_index + (self._operator_inclusive and 1 or 0) - final_cursor = origin - elseif descriptor.family == domain.Family.FIND then - start_line = destination.line - start_index = destination_index + 1 - finish_line = origin.line - finish_index = origin_index + 1 - final_cursor = destination - else - start_line = destination.line - start_index = destination_index - finish_line = origin.line - finish_index = origin_index - final_cursor = view:predecessor(destination) or destination - end - - self._text = delete_character_range( - self._text, - start_line, - start_index, - finish_line, - finish_index - ) - self._cursor = normalized_cursor(self._text, self._encoding, final_cursor) - self:_record("apply_operator", { - operator = self._pending_operator, - descriptor = descriptor.value, - origin = origin, - endpoint = destination, - position = self._cursor, - }) - return true -end - -function MemoryHost:apply_cursor(position, motion) - position = domain.Position.coerce(position) - local previous = self._cursor - local descriptor = motion_descriptor(motion) - self._cursor = position - self:_record("apply_cursor", { - position = position, - descriptor = descriptor and descriptor.value or nil, - }) - if descriptor ~= nil then - local origin = type(motion) == "table" and motion.origin or previous - self:_apply_pending_delete(domain.Position.coerce(origin), position, descriptor) - end - self:_emit_movement_event(previous) -end - -function MemoryHost:apply_selection(position, kind) - local previous = self._cursor - local next_selection - if domain.Selection.is(position) then - next_selection = position - position = next_selection.focus - else - position = domain.Position.coerce(position) - if kind == nil then - kind = self._selection.kind - end - kind = domain.SelectionKind.from_string(kind) - if kind == domain.SelectionKind.NONE then - error("selection movement requires a Visual selection kind", 2) - end - local anchor = self._selection.active and self._selection.anchor or previous - next_selection = domain.Selection.active( - kind, - anchor, - position, - self._selection.option - ) - end - - self._selection = next_selection - self._cursor = position - self:_record("apply_selection", { - position = position, - kind = next_selection.kind.value, - }) - self:_emit_movement_event(previous) -end - -function MemoryHost:set_operator_inclusive(enabled) - if type(enabled) ~= "boolean" then - error("operator inclusivity must be a Boolean", 2) - end - self._operator_inclusive = enabled - self:_record("set_operator_inclusive", { enabled = enabled }) -end - -function MemoryHost:operator_inclusive() - return self._operator_inclusive -end - -function MemoryHost:read_input() - local packet = self._input_packets[self._input_index] - if packet == nil then - error("in-memory input queue is empty", 2) - end - self._input_index = self._input_index + 1 - self:_record("read_input", { packet = packet:to_table() }) - if packet.kind == domain.InputPacketKind.ERROR then - error(packet.message, 0) - end - return packet -end - -function MemoryHost:open_fold(position) - position = position and domain.Position.coerce(position) or self._cursor - local closed_levels = self._fold_state.closed_levels - if closed_levels == 0 then - self:_record("open_fold", { position = position, opened = false }) - return false - end - self:_record("open_fold", { - position = position, - fold_level = closed_levels, - opened = true, - }) - self._fold_state = domain.FoldState.new( - self._fold_state:policies(), - closed_levels - 1 - ) - return true -end - -function MemoryHost:show_prompt(text) - if type(text) ~= "string" then - error("prompt must be a string", 2) - end - self._prompts[#self._prompts + 1] = text - self:_record("show_prompt", { text = text }) -end - -function MemoryHost:prompts() - return list_copy(self._prompts) -end - -function MemoryHost:redraw(kind) - if kind ~= "screen" and kind ~= "full" and kind ~= "suppressed" then - error("redraw kind must be screen, full, or suppressed", 2) - end - self._redraws[#self._redraws + 1] = kind - self:_record("redraw", { kind = kind }) -end - -function MemoryHost:redraws() - return list_copy(self._redraws) -end - -function MemoryHost:emit_diagnostic(level, text) - if level ~= "error" and level ~= "warning" and level ~= "info" then - error("diagnostic level must be error, warning, or info", 2) - end - if type(text) ~= "string" or text == "" then - error("diagnostic text must be a nonempty string", 2) - end - local diagnostic = { level = level, text = text } - self._diagnostics[#self._diagnostics + 1] = diagnostic - self:_record("emit_diagnostic", diagnostic) -end - -function MemoryHost:diagnostics() - return copy(self._diagnostics) -end - -function MemoryHost:read_highlight_group(name) - if type(name) ~= "string" or name == "" then - error("highlight group name must be a nonempty string", 2) - end - local definition = self._highlight_groups[name] - self:_record("read_highlight_group", { - name = name, - defined = definition ~= nil, - }) - return copy(definition) -end - -function MemoryHost:highlight_groups() - return copy(self._highlight_groups) -end - -function MemoryHost:define_highlight_group(name, definition, options) - if type(name) ~= "string" or name == "" then - error("highlight group name must be a nonempty string", 2) - end - if type(definition) ~= "table" then - error("highlight group definition must be a table", 2) - end - options = options or {} - if type(options) ~= "table" then - error("highlight group options must be a table", 2) - end - if options.default ~= nil and type(options.default) ~= "boolean" then - error("highlight group default option must be a Boolean", 2) - end - if options.force ~= nil and type(options.force) ~= "boolean" then - error("highlight group force option must be a Boolean", 2) - end - - local exists = self._highlight_groups[name] ~= nil - local applied = not (exists and options.default) - if applied then - self._highlight_groups[name] = copy(definition) - end - self:_record("define_highlight_group", { - name = name, - definition = definition, - options = options, - applied = applied, - }) - return applied -end - -function MemoryHost:create_highlight(specification) - if type(specification) ~= "table" then - error("highlight specification must be a table", 2) - end - if type(specification.group) ~= "string" or specification.group == "" then - error("highlight group must be a nonempty string", 2) - end - local identity = specification.identity or self:_next_identity("highlight") - if self._highlights[identity] ~= nil then - error("highlight identity is already active", 2) - end - local stored = copy(specification) - stored.identity = identity - self._highlights[identity] = stored - self:_record("create_highlight", stored) - return identity -end - -function MemoryHost:remove_highlight(identity) - if type(identity) ~= "string" or identity == "" then - error("highlight identity must be a nonempty string", 2) - end - local removed = self._highlights[identity] ~= nil - self._highlights[identity] = nil - self:_record("remove_highlight", { identity = identity, removed = removed }) - return removed -end - -function MemoryHost:highlights() - return copy(self._highlights) -end - -function MemoryHost:supports_cursor_presentation() - self:_record("supports_cursor_presentation", { - supported = self._cursor_presentation_support, - }) - return self._cursor_presentation_support -end - -function MemoryHost:suppress_cursor_presentation() - if not self._cursor_presentation_support then - self:_record("suppress_cursor_presentation", { supported = false }) - return nil - end - local identity = self:_next_identity("cursor-presentation") - self._cursor_leases[identity] = copy(self._cursor_presentation) - local suppressed = copy(self._cursor_presentation) - suppressed.hidden = true - self._cursor_presentation = suppressed - self:_record("suppress_cursor_presentation", { - identity = identity, - supported = true, - }) - return identity -end - -function MemoryHost:restore_cursor_presentation(identity) - if identity == nil then - self:_record("restore_cursor_presentation", { restored = false }) - return false - end - local saved = self._cursor_leases[identity] - if saved == nil then - error("cursor presentation lease is inactive", 2) - end - self._cursor_presentation = saved - self._cursor_leases[identity] = nil - self:_record("restore_cursor_presentation", { - identity = identity, - restored = true, - }) - return true -end - -function MemoryHost:cursor_presentation() - return copy(self._cursor_presentation) -end - -function MemoryHost:supports_timers() - self:_record("supports_timers", { supported = self._timer_support }) - return self._timer_support -end - -function MemoryHost:start_timer(delay_ms, callback) - if not is_integer(delay_ms) or delay_ms < 0 then - error("timer delay must be a nonnegative integer", 2) - end - if type(callback) ~= "function" then - error("timer callback must be a function", 2) - end - if not self._timer_support then - self:_record("start_timer", { delay_ms = delay_ms, supported = false }) - return nil - end - local identity = self:_next_identity("timer") - self._timers[identity] = { - identity = identity, - delay_ms = delay_ms, - callback = callback, - active = true, - } - self:_record("start_timer", { - identity = identity, - delay_ms = delay_ms, - supported = true, - }) - return identity -end - -function MemoryHost:stop_timer(identity) - if type(identity) ~= "string" or identity == "" then - error("timer identity must be a nonempty string", 2) - end - local timer = self._timers[identity] - local stopped = timer ~= nil and timer.active - if timer ~= nil then - timer.active = false - end - self:_record("stop_timer", { identity = identity, stopped = stopped }) - return stopped -end - -function MemoryHost:fire_timer(identity) - local timer = self._timers[identity] - if timer == nil then - error("timer identity is unknown", 2) - end - if not timer.active then - self:_record("ignore_timer", { identity = identity }) - return false - end - timer.active = false - self:_record("fire_timer", { identity = identity }) - timer.callback(identity) - return true -end - -function MemoryHost:timers() - local result = {} - for identity, timer in pairs(self._timers) do - result[identity] = { - identity = identity, - delay_ms = timer.delay_ms, - active = timer.active, - } - end - return result -end - -function MemoryHost:register_events(event_names, callback, options) - local names, name_set = normalize_event_names(event_names) - if type(callback) ~= "function" then - error("event callback must be a function", 2) - end - local identity = self:_next_identity("event-registration") - self._event_registrations[identity] = { - identity = identity, - names = names, - name_set = name_set, - callback = callback, - options = copy(options or {}), - active = true, - } - self._event_registration_order[#self._event_registration_order + 1] = identity - self:_record("register_events", { - identity = identity, - names = names, - options = options or {}, - }) - return identity -end - -function MemoryHost:remove_event_registration(identity) - local registration = self._event_registrations[identity] - local removed = registration ~= nil and registration.active - if registration ~= nil then - registration.active = false - end - self:_record("remove_event_registration", { - identity = identity, - removed = removed, - }) - return removed -end - -function MemoryHost:_deliver_event_now(name, payload) - self:_record("event", { name = name, payload = payload }) - local order = list_copy(self._event_registration_order) - local event_buffer = payload.buffer or self._buffer - for _, identity in ipairs(order) do - local registration = self._event_registrations[identity] - local registration_buffer = registration.options.buffer - if registration.active - and registration.name_set[name] - and (registration_buffer == nil or registration_buffer == event_buffer) - then - registration.callback(name, payload) - end - end -end - -function MemoryHost:deliver_event(name, payload) - if type(name) ~= "string" or name == "" then - error("event name must be a nonempty string", 2) - end - payload = copy(payload or {}) - local queued = self._event_queue:is_transition_active() - self:_record(queued and "queue_event" or "deliver_event", { - name = name, - payload = payload, - }) - return self._event_queue:emit(name, payload) -end - -function MemoryHost:begin_action_transition() - local token = self._event_queue:begin_transition() - self:_record("begin_action_transition", { identity = token }) - return token -end - -function MemoryHost:commit_action_transition(token) - self:_record("commit_action_transition", { identity = token }) - self._event_queue:commit_transition(token) -end - -function MemoryHost:pending_event_count() - return self._event_queue:pending_count() -end - -function MemoryHost:event_registrations() - local result = {} - for identity, registration in pairs(self._event_registrations) do - result[identity] = { - identity = identity, - names = list_copy(registration.names), - options = copy(registration.options), - active = registration.active, - } - end - return result -end - -function MemoryHost:register_action(name, callback) - if type(name) ~= "string" or name == "" then - error("action name must be a nonempty string", 2) - end - if type(callback) ~= "function" then - error("action callback must be a function", 2) - end - if self._actions[name] ~= nil then - error("action is already registered", 2) - end - self._actions[name] = callback - self:_record("register_action", { name = name }) - return name -end - -function MemoryHost:invoke_action(name, ...) - local callback = self._actions[name] - if callback == nil then - error("action is not registered", 2) - end - local arguments = { ... } - local argument_count = select("#", ...) - local token = self:begin_action_transition() - local results = { - pcall(function() - return callback(unpack_values(arguments, 1, argument_count)) - end), - } - self:commit_action_transition(token) - local succeeded = table.remove(results, 1) - if not succeeded then - error(results[1], 0) - end - return unpack_values(results) -end - -function MemoryHost:register_mapping(modes, lhs, action, options) - modes = normalize_modes(modes) - if type(lhs) ~= "string" or lhs == "" then - error("mapping lhs must be a nonempty string", 2) - end - if type(action) ~= "string" and type(action) ~= "function" then - error("mapping action must be an action name or function", 2) - end - local identity = self:_next_identity("mapping") - self._mappings[identity] = { - identity = identity, - modes = modes, - lhs = lhs, - action = action, - options = copy(options or {}), - } - self:_record("register_mapping", { - identity = identity, - modes = modes, - lhs = lhs, - action = type(action) == "string" and action or "<function>", - options = options or {}, - }) - return identity -end - -function MemoryHost:mappings() - return copy(self._mappings) -end - -function MemoryHost:register_dot_repeat(payload, callback) - if not domain.DotPayload.is(payload) then - error("dot-repeat payload must be a DotPayload", 2) - end - if callback ~= nil and type(callback) ~= "function" then - error("dot-repeat callback must be a function", 2) - end - self._dot_repeat = { - payload = payload, - callback = callback, - operator = self._pending_operator, - mode = self._mode, - } - self:_record("register_dot_repeat", { payload = payload:to_table() }) - return payload -end - -function MemoryHost:dot_repeat_payload() - return self._dot_repeat and self._dot_repeat.payload or nil -end - -function MemoryHost:replay_dot(count) - if self._dot_repeat == nil or self._dot_repeat.callback == nil then - error("dot repeat is not executable", 2) - end - self._pending_operator = self._dot_repeat.operator - self._mode = self._dot_repeat.mode - return self._dot_repeat.callback(self._dot_repeat.payload, domain.Count.new(count)) -end - -return M |
