diff options
Diffstat (limited to 'lua/clever_f/domain.lua')
| -rw-r--r-- | lua/clever_f/domain.lua | 1390 |
1 files changed, 0 insertions, 1390 deletions
diff --git a/lua/clever_f/domain.lua b/lua/clever_f/domain.lua deleted file mode 100644 index 0d802b3..0000000 --- a/lua/clever_f/domain.lua +++ /dev/null @@ -1,1390 +0,0 @@ -local M = {} - -local records = setmetatable({}, { __mode = "k" }) -local record_types = setmetatable({}, { __mode = "k" }) -local methods = {} -local formatters = {} -local equalities = {} -local metatables = {} - -local function fail(message, level) - error(message, (level or 1) + 1) -end - -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 register_type(type_name, type_methods, formatter, equality) - methods[type_name] = type_methods or {} - formatters[type_name] = formatter - equalities[type_name] = equality - - local mt = { - __index = function(value, key) - local field = records[value][key] - if field ~= nil then - return field - end - return methods[type_name][key] - end, - __newindex = function() - fail(type_name .. " values are immutable", 2) - end, - __tostring = function(value) - local format = formatters[type_name] - if format then - return format(records[value]) - end - return type_name - end, - __eq = function(left, right) - if record_types[left] ~= type_name or record_types[right] ~= type_name then - return false - end - local equal = equalities[type_name] - if equal then - return equal(records[left], records[right]) - end - return rawequal(left, right) - end, - __metatable = "clever_f.domain." .. type_name, - } - metatables[type_name] = mt -end - -local function new_record(type_name, fields) - local value = {} - records[value] = fields - record_types[value] = type_name - return setmetatable(value, metatables[type_name]) -end - -local function is_record(value, type_name) - return record_types[value] == type_name -end - -local function require_record(value, type_name, name) - if not is_record(value, type_name) then - fail((name or "value") .. " must be a " .. type_name, 2) - end - return value -end - -local function require_string(value, name, allow_empty) - if type(value) ~= "string" or (not allow_empty and value == "") then - fail(name .. " must be " .. (allow_empty and "a string" or "a nonempty string"), 2) - end - return value -end - -local function require_boolean(value, name) - if type(value) ~= "boolean" then - fail(name .. " must be a Boolean", 2) - end - return value -end - -local function require_nonnegative_integer(value, name) - if not is_integer(value) or value < 0 then - fail(name .. " must be a nonnegative integer", 2) - end - return value -end - -function M.type_of(value) - return record_types[value] -end - -local function define_enum(type_name, entries) - local enum_methods = {} - local namespace = {} - local by_value = {} - - register_type(type_name, enum_methods, function(data) - return data.value - end) - - for constant, serialized in pairs(entries) do - local value = new_record(type_name, { - name = constant, - value = serialized, - }) - namespace[constant] = value - by_value[serialized] = value - end - - function namespace.from_string(value) - if is_record(value, type_name) then - return value - end - local result = by_value[value] - if result == nil then - fail("value must be a valid " .. type_name, 2) - end - return result - end - - function namespace.is(value) - return is_record(value, type_name) - end - - function enum_methods:to_string() - return records[self].value - end - - return namespace -end - -M.Family = define_enum("Family", { - FIND = "FIND", - TILL = "TILL", -}) - -M.Direction = define_enum("Direction", { - FORWARD = "forward", - BACKWARD = "backward", -}) - -M.SelectionKind = define_enum("SelectionKind", { - NONE = "none", - CHARACTER = "character", - LINE = "line", - BLOCK = "block", -}) - -M.SelectionOption = define_enum("SelectionOption", { - INCLUSIVE = "inclusive", - EXCLUSIVE = "exclusive", -}) - -M.TargetKind = define_enum("TargetKind", { - CHARACTER = "character", - SPECIAL_KEY = "special_key", - CODE_FALLBACK = "code_fallback", -}) - -M.CaseMode = define_enum("CaseMode", { - SENSITIVE = "sensitive", - INSENSITIVE = "insensitive", -}) - -M.TargetPlanKind = define_enum("TargetPlanKind", { - EMPTY = "empty", - LITERAL = "literal", - BACKSLASH = "backslash", - SYMBOL = "symbol", - MIGEMO = "migemo", -}) - -M.SearchScope = define_enum("SearchScope", { - BUFFER = "buffer", - CURRENT_LINE = "current_line", -}) - -M.EndpointPolicy = define_enum("EndpointPolicy", { - REGULAR = "regular", - VISUAL_EXCLUSIVE = "visual_exclusive", -}) - -M.SearchStatus = define_enum("SearchStatus", { - COMPLETE = "complete", - BOUNDARY_AFTER_PARTIAL = "boundary_after_partial", - BOUNDARY_BEFORE_ANY = "boundary_before_any", -}) - -M.ActionKind = define_enum("ActionKind", { - MOVEMENT = "movement", - NEUTRAL = "neutral", - ESCAPE = "escape", - FAILED_SEARCH = "failed_search", - ERROR = "error", - EMPTY = "empty", -}) - -local Position = {} -M.Position = Position - -register_type("Position", Position, function(data) - return string.format("(%d,%d)", data.line, data.byte_column) -end, function(left, right) - return left.line == right.line and left.byte_column == right.byte_column -end) - -function Position.new(line, byte_column) - if not is_integer(line) or line < 1 then - fail("line must be a positive one-based integer", 2) - end - if not is_integer(byte_column) or byte_column < 1 then - fail("byte_column must be a positive one-based integer", 2) - end - return new_record("Position", { - line = line, - byte_column = byte_column, - }) -end - -function Position.coerce(value) - if Position.is(value) then - return value - end - if type(value) ~= "table" then - fail("position must be a Position or position table", 2) - end - return Position.new(value.line, value.byte_column) -end - -function Position.is(value) - return is_record(value, "Position") -end - -function Position.compare(left, right) - require_record(left, "Position", "left") - require_record(right, "Position", "right") - if left.line < right.line then - return -1 - end - if left.line > right.line then - return 1 - end - if left.byte_column < right.byte_column then - return -1 - end - if left.byte_column > right.byte_column then - return 1 - end - return 0 -end - -function Position.equal(left, right) - return Position.compare(left, right) == 0 -end - -function Position.stationary(left, right) - return Position.equal(left, right) -end - -function Position.is_forward(candidate, origin) - return Position.compare(candidate, origin) > 0 -end - -function Position.is_backward(candidate, origin) - return Position.compare(candidate, origin) < 0 -end - -function Position:to_table() - return { - line = self.line, - byte_column = self.byte_column, - } -end - -local Descriptor = {} -M.Descriptor = Descriptor - -register_type("Descriptor", Descriptor, function(data) - return data.value -end) - -local descriptors_by_string = {} -local descriptors_by_parts = {} - -local function descriptor_key(family, direction) - return family.value .. ":" .. direction.value -end - -local function define_descriptor(name, serialized, family, direction) - local descriptor = new_record("Descriptor", { - name = name, - value = serialized, - family = family, - direction = direction, - uppercase = serialized:match("%u") ~= nil, - }) - descriptors_by_string[serialized] = descriptor - descriptors_by_parts[descriptor_key(family, direction)] = descriptor - Descriptor[name] = descriptor - Descriptor[serialized] = descriptor - return descriptor -end - -Descriptor.FIND_FORWARD = define_descriptor( - "FIND_FORWARD", - "f", - M.Family.FIND, - M.Direction.FORWARD -) -Descriptor.FIND_BACKWARD = define_descriptor( - "FIND_BACKWARD", - "F", - M.Family.FIND, - M.Direction.BACKWARD -) -Descriptor.TILL_FORWARD = define_descriptor( - "TILL_FORWARD", - "t", - M.Family.TILL, - M.Direction.FORWARD -) -Descriptor.TILL_BACKWARD = define_descriptor( - "TILL_BACKWARD", - "T", - M.Family.TILL, - M.Direction.BACKWARD -) - -function Descriptor.is(value) - return is_record(value, "Descriptor") -end - -function Descriptor.is_valid(value) - return Descriptor.is(value) or descriptors_by_string[value] ~= nil -end - -function Descriptor.from_string(value) - if Descriptor.is(value) then - return value - end - local descriptor = descriptors_by_string[value] - if descriptor == nil then - fail("descriptor must be one of f, F, t, or T", 2) - end - return descriptor -end - -function Descriptor.try_from_string(value) - if Descriptor.is(value) then - return value - end - return descriptors_by_string[value] -end - -function Descriptor.from_parts(family, direction) - family = M.Family.from_string(family) - direction = M.Direction.from_string(direction) - return descriptors_by_parts[descriptor_key(family, direction)] -end - -function Descriptor.to_string(value) - return Descriptor.from_string(value).value -end - -function Descriptor.is_uppercase(value) - return Descriptor.from_string(value).uppercase -end - -function Descriptor.is_lowercase(value) - return not Descriptor.is_uppercase(value) -end - -function Descriptor.swap(value) - local descriptor = Descriptor.from_string(value) - local direction = descriptor.direction == M.Direction.FORWARD - and M.Direction.BACKWARD - or M.Direction.FORWARD - return Descriptor.from_parts(descriptor.family, direction) -end - -function Descriptor.lowercase(value) - local descriptor = Descriptor.from_string(value) - return Descriptor.from_parts(descriptor.family, M.Direction.FORWARD) -end - -function Descriptor.uppercase(value) - local descriptor = Descriptor.from_string(value) - return Descriptor.from_parts(descriptor.family, M.Direction.BACKWARD) -end - -local Count = {} -M.Count = Count - -register_type("Count", Count, function(data) - return tostring(data.value) -end, function(left, right) - return left.value == right.value -end) - -local count_one - -function Count.new(value) - if Count.is(value) then - return value - end - if value == nil then - value = 1 - end - if not is_integer(value) or value < 1 then - fail("count must be a positive integer", 2) - end - if value == 1 and count_one ~= nil then - return count_one - end - local count = new_record("Count", { value = value }) - if value == 1 then - count_one = count - end - return count -end - -function Count.is(value) - return is_record(value, "Count") -end - -function Count.to_number(value) - return Count.new(value).value -end - -Count.ONE = Count.new(1) - -local ModeContext = {} -M.ModeContext = ModeContext - -register_type("ModeContext", ModeContext, function(data) - return data.key -end) - -local mode_contexts = {} -local CTRL_V = string.char(0x16) -local CTRL_S = string.char(0x13) - -local operator_modes = { - no = true, - nov = true, - noV = true, - ["no" .. CTRL_V] = true, -} - -local function mode_traits(full_mode) - local operator = operator_modes[full_mode] == true - local visual_kind - local select_kind - - if not operator then - local lead = full_mode:sub(1, 1) - if lead == "v" then - visual_kind = M.SelectionKind.CHARACTER - elseif lead == "V" then - visual_kind = M.SelectionKind.LINE - elseif lead == CTRL_V then - visual_kind = M.SelectionKind.BLOCK - elseif lead == "s" then - select_kind = M.SelectionKind.CHARACTER - elseif lead == "S" then - select_kind = M.SelectionKind.LINE - elseif lead == CTRL_S then - select_kind = M.SelectionKind.BLOCK - end - end - - return operator, visual_kind, select_kind -end - -function ModeContext.from_full_mode(full_mode) - if ModeContext.is(full_mode) then - return full_mode - end - require_string(full_mode, "full_mode", false) - - local operator, visual_kind, select_kind = mode_traits(full_mode) - local key = operator and "no" or full_mode - local context = mode_contexts[key] - if context ~= nil then - return context - end - - context = new_record("ModeContext", { - key = key, - full_mode = key, - operator = operator, - visual_kind = visual_kind, - select_kind = select_kind, - visual = visual_kind ~= nil, - select = select_kind ~= nil, - command_path = visual_kind == nil, - }) - mode_contexts[key] = context - return context -end - -function ModeContext.is(value) - return is_record(value, "ModeContext") -end - -function ModeContext.equal(left, right) - require_record(left, "ModeContext", "left") - require_record(right, "ModeContext", "right") - return left.key == right.key -end - -function ModeContext:to_key() - return self.key -end - -local Selection = {} -M.Selection = Selection - -register_type("Selection", Selection, function(data) - return data.active and ("selection:" .. data.kind.value) or "selection:none" -end, function(left, right) - return left.active == right.active - and left.kind == right.kind - and left.anchor == right.anchor - and left.focus == right.focus - and left.option == right.option -end) - -function Selection.new(options) - if Selection.is(options) then - return options - end - if type(options) ~= "table" then - fail("selection options must be a table", 2) - end - - local active = require_boolean(options.active, "selection.active") - local kind = M.SelectionKind.from_string(options.kind) - local option = M.SelectionOption.from_string(options.option or "inclusive") - local anchor = options.anchor - local focus = options.focus - - if active then - if kind == M.SelectionKind.NONE then - fail("an active selection must have a selection kind", 2) - end - anchor = Position.coerce(anchor) - focus = Position.coerce(focus) - else - if kind ~= M.SelectionKind.NONE then - fail("an inactive selection must use the none kind", 2) - end - if anchor ~= nil or focus ~= nil then - fail("an inactive selection must have empty endpoints", 2) - end - end - - return new_record("Selection", { - active = active, - kind = kind, - anchor = anchor, - focus = focus, - option = option, - }) -end - -function Selection.inactive(option) - return Selection.new({ - active = false, - kind = M.SelectionKind.NONE, - option = option or M.SelectionOption.INCLUSIVE, - }) -end - -function Selection.active(kind, anchor, focus, option) - return Selection.new({ - active = true, - kind = kind, - anchor = anchor, - focus = focus, - option = option or M.SelectionOption.INCLUSIVE, - }) -end - -function Selection.is(value) - return is_record(value, "Selection") -end - -function Selection:with_focus(focus, kind) - if not self.active then - fail("selection must be active", 2) - end - return Selection.active(kind or self.kind, self.anchor, focus, self.option) -end - -function Selection:to_table() - return { - active = self.active, - kind = self.kind.value, - anchor = self.anchor and self.anchor:to_table() or nil, - focus = self.focus and self.focus:to_table() or nil, - option = self.option.value, - } -end - -local TextSnapshot = {} -M.TextSnapshot = TextSnapshot -local text_lines = setmetatable({}, { __mode = "k" }) - -register_type("TextSnapshot", TextSnapshot, function(data) - return "text:" .. tostring(data.line_count) .. " lines" -end, function(left, right) - if left.line_count ~= right.line_count then - return false - end - local left_lines = text_lines[left.identity] - local right_lines = text_lines[right.identity] - for index = 1, left.line_count do - if left_lines[index] ~= right_lines[index] then - return false - end - end - return true -end) - -function TextSnapshot.new(lines) - if TextSnapshot.is(lines) then - return lines - end - if type(lines) ~= "table" or #lines < 1 then - fail("text lines must be a nonempty list", 2) - end - local copy = {} - for index = 1, #lines do - if type(lines[index]) ~= "string" then - fail("each text line must be a string", 2) - end - copy[index] = lines[index] - end - local identity = {} - text_lines[identity] = copy - return new_record("TextSnapshot", { - identity = identity, - line_count = #copy, - }) -end - -function TextSnapshot.is(value) - return is_record(value, "TextSnapshot") -end - -function TextSnapshot:line(line_number) - if not is_integer(line_number) or line_number < 1 or line_number > self.line_count then - fail("line_number must identify a line in the text snapshot", 2) - end - return text_lines[self.identity][line_number] -end - -function TextSnapshot:lines() - local result = {} - local source = text_lines[self.identity] - for index = 1, self.line_count do - result[index] = source[index] - end - return result -end - -function TextSnapshot:to_table() - return { lines = self:lines() } -end - -local MacroState = {} -M.MacroState = MacroState - -register_type("MacroState", MacroState, function(data) - return data.executing and ("macro:" .. data.register) or "macro:inactive" -end, function(left, right) - return left.register == right.register -end) - -function MacroState.new(register) - if MacroState.is(register) then - return register - end - if register == "" then - register = nil - end - if register ~= nil then - require_string(register, "macro register", false) - end - return new_record("MacroState", { - register = register, - executing = register ~= nil, - }) -end - -function MacroState.is(value) - return is_record(value, "MacroState") -end - -local FoldState = {} -M.FoldState = FoldState -local fold_policies = setmetatable({}, { __mode = "k" }) - -register_type("FoldState", FoldState, function(data) - return "folds:" .. tostring(data.closed_levels) -end) - -function FoldState.new(open_policy, closed_levels) - if FoldState.is(open_policy) and closed_levels == nil then - return open_policy - end - if type(open_policy) ~= "table" then - fail("fold open policy must be a list", 2) - end - require_nonnegative_integer(closed_levels, "closed fold levels") - - local identity = {} - local policies = {} - local seen = {} - for index = 1, #open_policy do - local policy = require_string(open_policy[index], "fold policy item", false) - if not seen[policy] then - seen[policy] = true - policies[#policies + 1] = policy - end - end - fold_policies[identity] = { - list = policies, - set = seen, - } - return new_record("FoldState", { - identity = identity, - closed_levels = closed_levels, - }) -end - -function FoldState.is(value) - return is_record(value, "FoldState") -end - -function FoldState:opens(policy) - require_string(policy, "fold policy", false) - return fold_policies[self.identity].set[policy] == true -end - -function FoldState:policies() - local result = {} - local source = fold_policies[self.identity].list - for index = 1, #source do - result[index] = source[index] - end - return result -end - -local InputPacket = {} -M.InputPacket = InputPacket -local packet_bytes = setmetatable({}, { __mode = "k" }) - -M.InputPacketKind = define_enum("InputPacketKind", { - TEXT = "text", - RAW_BYTES = "raw_bytes", - SPECIAL_KEY = "special_key", - ERROR = "error", -}) - -register_type("InputPacket", InputPacket, function(data) - return "input:" .. data.kind.value -end) - -function InputPacket.text(text) - require_string(text, "input text", false) - return new_record("InputPacket", { - kind = M.InputPacketKind.TEXT, - text = text, - }) -end - -local function validated_packet_bytes(bytes, kind) - if type(bytes) ~= "table" or #bytes < 1 then - fail(kind .. " bytes must be a nonempty list", 3) - end - local copy = {} - for index = 1, #bytes do - local byte = bytes[index] - if not is_integer(byte) or byte < 0 or byte > 255 then - fail(kind .. " bytes must contain byte values", 3) - end - copy[index] = byte - end - return copy -end - -local function bytes_from_string(value) - local bytes = {} - for index = 1, #value do - bytes[index] = string.byte(value, index) - end - return bytes -end - -local function string_from_bytes(bytes) - local characters = {} - for index = 1, #bytes do - characters[index] = string.char(bytes[index]) - end - return table.concat(characters) -end - -function InputPacket.raw_bytes(bytes) - local identity = {} - packet_bytes[identity] = validated_packet_bytes(bytes, "raw input") - return new_record("InputPacket", { - kind = M.InputPacketKind.RAW_BYTES, - identity = identity, - }) -end - -function InputPacket.special_key(name, encoded) - require_string(name, "special key name", false) - local bytes - if type(encoded) == "table" then - bytes = validated_packet_bytes(encoded, "special key") - encoded = string_from_bytes(bytes) - elseif encoded ~= nil then - require_string(encoded, "encoded special key", false) - bytes = bytes_from_string(encoded) - end - local identity - if bytes ~= nil then - identity = {} - packet_bytes[identity] = bytes - end - return new_record("InputPacket", { - kind = M.InputPacketKind.SPECIAL_KEY, - name = name, - encoded = encoded, - identity = identity, - }) -end - -function InputPacket.error(message) - require_string(message, "input error message", false) - return new_record("InputPacket", { - kind = M.InputPacketKind.ERROR, - message = message, - }) -end - -function InputPacket.from_table(packet) - if InputPacket.is(packet) then - return packet - end - if type(packet) ~= "table" then - fail("input packet must be an InputPacket or packet table", 2) - end - local kind = M.InputPacketKind.from_string(packet.kind) - if kind == M.InputPacketKind.TEXT then - return InputPacket.text(packet.text) - end - if kind == M.InputPacketKind.RAW_BYTES then - return InputPacket.raw_bytes(packet.bytes) - end - if kind == M.InputPacketKind.SPECIAL_KEY then - return InputPacket.special_key(packet.name, packet.bytes or packet.encoded) - end - return InputPacket.error(packet.message) -end - -function InputPacket.is(value) - return is_record(value, "InputPacket") -end - -function InputPacket:bytes() - if self.kind ~= M.InputPacketKind.RAW_BYTES - and self.kind ~= M.InputPacketKind.SPECIAL_KEY - then - return nil - end - local source = self.identity and packet_bytes[self.identity] or nil - if source == nil then - return nil - end - local result = {} - for index = 1, #source do - result[index] = source[index] - end - return result -end - -function InputPacket:to_table() - local result = { kind = self.kind.value } - if self.kind == M.InputPacketKind.TEXT then - result.text = self.text - elseif self.kind == M.InputPacketKind.RAW_BYTES then - result.bytes = self:bytes() - elseif self.kind == M.InputPacketKind.SPECIAL_KEY then - result.name = self.name - result.bytes = self:bytes() - else - result.message = self.message - end - return result -end - -local TargetValue = {} -M.TargetValue = TargetValue - -register_type("TargetValue", TargetValue, function(data) - return "target:" .. data.kind.value .. ":" .. tostring(data.first_code) -end, function(left, right) - return left.kind == right.kind - and left.value == right.value - and left.first_code == right.first_code -end) - -function TargetValue.character(value, first_code) - require_string(value, "target character", false) - require_nonnegative_integer(first_code, "target first code") - return new_record("TargetValue", { - kind = M.TargetKind.CHARACTER, - value = value, - first_code = first_code, - }) -end - -function TargetValue.special_key(value, first_code) - require_string(value, "encoded special key", false) - first_code = first_code or string.byte(value, 1) - require_nonnegative_integer(first_code, "target first code") - if first_code ~= 0x80 then - fail("an encoded special key must start with hexadecimal 80", 2) - end - return new_record("TargetValue", { - kind = M.TargetKind.SPECIAL_KEY, - value = value, - first_code = first_code, - }) -end - -function TargetValue.code_fallback(first_code) - first_code = first_code or 0 - require_nonnegative_integer(first_code, "fallback character code") - return new_record("TargetValue", { - kind = M.TargetKind.CODE_FALLBACK, - value = "", - first_code = first_code, - }) -end - -function TargetValue.from_table(target) - if TargetValue.is(target) then - return target - end - if type(target) ~= "table" then - fail("target must be a TargetValue or target table", 2) - end - local kind = M.TargetKind.from_string(target.kind) - if kind == M.TargetKind.CHARACTER then - return TargetValue.character(target.value, target.first_code) - end - if kind == M.TargetKind.SPECIAL_KEY then - return TargetValue.special_key(target.value, target.first_code) - end - return TargetValue.code_fallback(target.first_code) -end - -function TargetValue.is(value) - return is_record(value, "TargetValue") -end - -function TargetValue:to_table() - return { - kind = self.kind.value, - value = self.value, - first_code = self.first_code, - } -end - -local TargetPlan = {} -M.TargetPlan = TargetPlan - -register_type("TargetPlan", TargetPlan, function(data) - return "target-plan:" .. data.kind.value -end) - -function TargetPlan.new(options) - if TargetPlan.is(options) then - return options - end - if type(options) ~= "table" then - fail("target plan options must be a table", 2) - end - local target = require_record(options.target, "TargetValue", "target plan target") - local kind = M.TargetPlanKind.from_string(options.kind) - local case_mode = M.CaseMode.from_string(options.case_mode) - if type(options.matcher) ~= "function" then - fail("target plan matcher must be a function", 2) - end - return new_record("TargetPlan", { - target = target, - kind = kind, - case_mode = case_mode, - matcher = options.matcher, - }) -end - -function TargetPlan.is(value) - return is_record(value, "TargetPlan") -end - -function TargetPlan:matches(...) - return self.matcher(...) -end - -function TargetPlan:matches_at(text_view, position) - if type(text_view) ~= "table" or type(text_view.character_at) ~= "function" then - fail("target plan match requires a text view", 2) - end - position = Position.coerce(position) - return self.matcher(text_view:character_at(position), position, text_view) -end - -function TargetPlan:to_table() - return { - target = self.target:to_table(), - kind = self.kind.value, - case_mode = self.case_mode.value, - } -end - -local ResolvedMotionPlan = {} -M.ResolvedMotionPlan = ResolvedMotionPlan - -register_type("ResolvedMotionPlan", ResolvedMotionPlan, function(data) - return "motion-plan:" .. data.descriptor.value -end) - -function ResolvedMotionPlan.new(options) - if ResolvedMotionPlan.is(options) then - return options - end - if type(options) ~= "table" then - fail("resolved motion plan options must be a table", 2) - end - return new_record("ResolvedMotionPlan", { - target_plan = require_record(options.target_plan, "TargetPlan", "target plan"), - descriptor = Descriptor.from_string(options.descriptor), - search_scope = M.SearchScope.from_string(options.search_scope), - endpoint_policy = M.EndpointPolicy.from_string(options.endpoint_policy), - }) -end - -function ResolvedMotionPlan.is(value) - return is_record(value, "ResolvedMotionPlan") -end - -function ResolvedMotionPlan:to_table() - return { - target_plan = self.target_plan:to_table(), - descriptor = self.descriptor.value, - search_scope = self.search_scope.value, - endpoint_policy = self.endpoint_policy.value, - } -end - -local MotionRequest = {} -M.MotionRequest = MotionRequest - -register_type("MotionRequest", MotionRequest, function(data) - return "motion-request:" .. data.descriptor.value -end) - -function MotionRequest.new(options) - if MotionRequest.is(options) then - return options - end - if type(options) ~= "table" then - fail("motion request options must be a table", 2) - end - if options.policy == nil then - fail("motion request policy is required", 2) - end - return new_record("MotionRequest", { - context = require_record(options.context, "ModeContext", "motion context"), - origin = Position.coerce(options.origin), - descriptor = Descriptor.from_string(options.descriptor), - target = require_record(options.target, "TargetValue", "motion target"), - count = Count.new(options.count), - policy = options.policy, - first_move = require_boolean(options.first_move, "first_move"), - }) -end - -function MotionRequest.is(value) - return is_record(value, "MotionRequest") -end - -local SearchOutcome = {} -M.SearchOutcome = SearchOutcome - -register_type("SearchOutcome", SearchOutcome, function(data) - return "search:" .. data.status.value -end) - -local function new_search_outcome(status, endpoint, successful_steps) - status = M.SearchStatus.from_string(status) - endpoint = Position.coerce(endpoint) - require_nonnegative_integer(successful_steps, "successful_steps") - - if status == M.SearchStatus.COMPLETE and successful_steps < 1 then - fail("a complete search must contain a successful step", 3) - end - if status == M.SearchStatus.BOUNDARY_AFTER_PARTIAL and successful_steps < 1 then - fail("a partial search must contain a successful step", 3) - end - if status == M.SearchStatus.BOUNDARY_BEFORE_ANY and successful_steps ~= 0 then - fail("a boundary-before-any search must contain zero successful steps", 3) - end - - return new_record("SearchOutcome", { - status = status, - endpoint = endpoint, - successful_steps = successful_steps, - complete = status == M.SearchStatus.COMPLETE, - }) -end - -function SearchOutcome.new(options) - if SearchOutcome.is(options) then - return options - end - if type(options) ~= "table" then - fail("search outcome options must be a table", 2) - end - return new_search_outcome(options.status, options.endpoint, options.successful_steps) -end - -function SearchOutcome.complete(endpoint, successful_steps) - return new_search_outcome(M.SearchStatus.COMPLETE, endpoint, successful_steps) -end - -function SearchOutcome.boundary_after_partial(endpoint, successful_steps) - return new_search_outcome( - M.SearchStatus.BOUNDARY_AFTER_PARTIAL, - endpoint, - successful_steps - ) -end - -function SearchOutcome.boundary_before_any(origin) - return new_search_outcome(M.SearchStatus.BOUNDARY_BEFORE_ANY, origin, 0) -end - -function SearchOutcome.is(value) - return is_record(value, "SearchOutcome") -end - -function SearchOutcome:to_table() - return { - status = self.status.value, - endpoint = self.endpoint:to_table(), - successful_steps = self.successful_steps, - complete = self.complete, - } -end - -local DotPayload = {} -M.DotPayload = DotPayload - -register_type("DotPayload", DotPayload, function(data) - return "dot:" .. data.descriptor.value -end, function(left, right) - return left.descriptor == right.descriptor and left.target == right.target -end) - -function DotPayload.new(descriptor, target) - if DotPayload.is(descriptor) and target == nil then - return descriptor - end - return new_record("DotPayload", { - descriptor = Descriptor.from_string(descriptor), - target = require_record(target, "TargetValue", "dot target"), - }) -end - -function DotPayload.is(value) - return is_record(value, "DotPayload") -end - -function DotPayload:to_table() - return { - descriptor = self.descriptor.value, - target = self.target:to_table(), - } -end - -local ExplicitRepeatRequest = {} -M.ExplicitRepeatRequest = ExplicitRepeatRequest - -register_type("ExplicitRepeatRequest", ExplicitRepeatRequest, function(data) - if data.neutral then - return "explicit-repeat:neutral" - end - return "explicit-repeat:" .. data.descriptor.value -end, function(left, right) - return left.neutral == right.neutral - and left.descriptor == right.descriptor - and left.target == right.target -end) - -local neutral_explicit_repeat_request - -function ExplicitRepeatRequest.new(descriptor, target) - if ExplicitRepeatRequest.is(descriptor) and target == nil then - return descriptor - end - descriptor = Descriptor.from_string(descriptor) - target = require_record(target, "TargetValue", "explicit repeat target") - return new_record("ExplicitRepeatRequest", { - descriptor = descriptor, - effective_descriptor = descriptor, - target = target, - neutral = false, - }) -end - -function ExplicitRepeatRequest.neutral() - if neutral_explicit_repeat_request == nil then - neutral_explicit_repeat_request = new_record("ExplicitRepeatRequest", { - descriptor = nil, - effective_descriptor = nil, - target = nil, - neutral = true, - }) - end - return neutral_explicit_repeat_request -end - -function ExplicitRepeatRequest.is(value) - return is_record(value, "ExplicitRepeatRequest") -end - -function ExplicitRepeatRequest:is_neutral() - return self.neutral -end - -function ExplicitRepeatRequest:to_table() - if self.neutral then - return { neutral = true } - end - return { - descriptor = self.descriptor.value, - target = self.target:to_table(), - neutral = false, - } -end - -local ActionOutcome = {} -M.ActionOutcome = ActionOutcome - -register_type("ActionOutcome", ActionOutcome, function(data) - return "action:" .. data.kind.value -end) - -local function new_action_outcome(options) - local kind = M.ActionKind.from_string(options.kind) - local position = Position.coerce(options.position) - local search_outcome = options.search_outcome - local descriptor = options.effective_descriptor - local dot_payload = options.dot_payload - - if search_outcome ~= nil then - require_record(search_outcome, "SearchOutcome", "search outcome") - end - if descriptor ~= nil then - descriptor = Descriptor.from_string(descriptor) - end - if dot_payload ~= nil then - require_record(dot_payload, "DotPayload", "dot payload") - end - if options.diagnostic ~= nil then - require_string(options.diagnostic, "diagnostic", false) - end - - if kind == M.ActionKind.MOVEMENT then - if search_outcome == nil or not search_outcome.complete then - fail("a movement action requires a complete search outcome", 3) - end - elseif kind == M.ActionKind.FAILED_SEARCH then - if search_outcome == nil or search_outcome.complete then - fail("a failed-search action requires an incomplete search outcome", 3) - end - elseif search_outcome ~= nil then - fail("only movement and failed-search actions can contain a search outcome", 3) - end - - if kind == M.ActionKind.ERROR and options.diagnostic == nil then - fail("an error action requires a diagnostic", 3) - end - - local complete - if search_outcome ~= nil then - complete = search_outcome.complete - end - - return new_record("ActionOutcome", { - kind = kind, - position = position, - search_outcome = search_outcome, - complete = complete, - successful_steps = search_outcome and search_outcome.successful_steps or 0, - effective_descriptor = descriptor, - dot_payload = dot_payload, - diagnostic = options.diagnostic, - }) -end - -function ActionOutcome.new(options) - if ActionOutcome.is(options) then - return options - end - if type(options) ~= "table" then - fail("action outcome options must be a table", 2) - end - return new_action_outcome(options) -end - -function ActionOutcome.from_search(search_outcome, descriptor, dot_payload) - require_record(search_outcome, "SearchOutcome", "search outcome") - return new_action_outcome({ - kind = search_outcome.complete and M.ActionKind.MOVEMENT or M.ActionKind.FAILED_SEARCH, - position = search_outcome.endpoint, - search_outcome = search_outcome, - effective_descriptor = descriptor, - dot_payload = dot_payload, - }) -end - -local function simple_action(kind, position, diagnostic) - return new_action_outcome({ - kind = kind, - position = position, - diagnostic = diagnostic, - }) -end - -function ActionOutcome.neutral(position) - return simple_action(M.ActionKind.NEUTRAL, position) -end - -function ActionOutcome.escape(position) - return simple_action(M.ActionKind.ESCAPE, position) -end - -function ActionOutcome.empty(position) - return simple_action(M.ActionKind.EMPTY, position) -end - -function ActionOutcome.error(position, diagnostic) - return simple_action(M.ActionKind.ERROR, position, diagnostic) -end - -function ActionOutcome.is(value) - return is_record(value, "ActionOutcome") -end - -function ActionOutcome:to_table() - return { - kind = self.kind.value, - position = self.position:to_table(), - complete = self.complete, - successful_steps = self.successful_steps, - effective_descriptor = self.effective_descriptor and self.effective_descriptor.value or nil, - dot_payload = self.dot_payload and self.dot_payload:to_table() or nil, - diagnostic = self.diagnostic, - } -end - -return M |
