summaryrefslogtreecommitdiff
path: root/lua/clever_f/migemo_catalog.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua/clever_f/migemo_catalog.lua')
-rw-r--r--lua/clever_f/migemo_catalog.lua527
1 files changed, 0 insertions, 527 deletions
diff --git a/lua/clever_f/migemo_catalog.lua b/lua/clever_f/migemo_catalog.lua
deleted file mode 100644
index 18d9541..0000000
--- a/lua/clever_f/migemo_catalog.lua
+++ /dev/null
@@ -1,527 +0,0 @@
-local domain = require("clever_f.domain")
-local sequence_state = require("clever_f.sequence_state")
-local state_transitions = require("clever_f.state_transitions")
-local text_topology = require("clever_f.text_topology")
-
-local M = {}
-local MigemoCatalog = {}
-local MigemoDictionary = {}
-M.MigemoCatalog = MigemoCatalog
-M.MigemoDictionary = MigemoDictionary
-
-local catalog_records = setmetatable({}, { __mode = "k" })
-local dictionary_records = setmetatable({}, { __mode = "k" })
-
-local function fail(message, level)
- error(message, (level or 1) + 1)
-end
-
-local function copy_list(values)
- local result = {}
- for index = 1, #values do
- result[index] = values[index]
- end
- return result
-end
-
-local EXPECTED_KEYS = {}
-for code = string.byte("a"), string.byte("z") do
- EXPECTED_KEYS[#EXPECTED_KEYS + 1] = string.char(code)
-end
-for code = string.byte("A"), string.byte("Z") do
- EXPECTED_KEYS[#EXPECTED_KEYS + 1] = string.char(code)
-end
-
-local EXPECTED_KEY_SET = {}
-for _, key in ipairs(EXPECTED_KEYS) do
- EXPECTED_KEY_SET[key] = true
-end
-
-local ASSETS = {
- ["utf-8"] = {
- file = "utf8.vim",
- function_name = "clever_f#migemo#utf8#load_dict",
- },
- cp932 = {
- file = "cp932.vim",
- function_name = "clever_f#migemo#cp932#load_dict",
- },
- ["euc-jp"] = {
- file = "eucjp.vim",
- function_name = "clever_f#migemo#eucjp#load_dict",
- },
-}
-
-local module_source = debug.getinfo(1, "S").source
-local module_file = module_source:sub(1, 1) == "@" and module_source:sub(2) or module_source
-local bundled_root = module_file:match("^(.*)/lua/clever_f/migemo_catalog%.lua$")
-
-local function require_nonempty_string(value, name)
- if type(value) ~= "string" or value == "" then
- fail((name or "value") .. " must be a nonempty string", 2)
- end
- return value
-end
-
-local function asset_path(asset)
- if bundled_root == nil then
- fail("Migemo catalog could not locate its bundled assets", 2)
- end
- return bundled_root .. "/autoload/clever_f/migemo/" .. asset.file
-end
-
-local function read_asset_key_order(path)
- local handle, open_error = io.open(path, "rb")
- if handle == nil then
- fail("Migemo asset could not be opened: " .. tostring(open_error), 2)
- end
-
- local keys = {}
- for line in handle:lines() do
- local key = line:match("^%s*\\%s*'([A-Za-z])'%s*:")
- if key ~= nil then
- keys[#keys + 1] = key
- end
- end
- handle:close()
- return keys
-end
-
-local function assert_key_order(keys, encoding)
- if type(keys) ~= "table" or #keys ~= #EXPECTED_KEYS then
- fail(
- "Migemo " .. encoding .. " asset must contain exactly 52 ordered keys",
- 2
- )
- end
- for index, expected in ipairs(EXPECTED_KEYS) do
- if keys[index] ~= expected then
- fail(
- "Migemo " .. encoding
- .. " asset keys must be ordered a through z, then A through Z",
- 2
- )
- end
- end
-end
-
-local function nvim_runtime()
- local runtime = rawget(_G, "vim")
- if type(runtime) ~= "table"
- or runtime.cmd == nil
- or type(runtime.fn) ~= "table"
- or type(runtime.fn.fnameescape) ~= "function"
- then
- fail("Migemo dictionary loading requires Nvim", 2)
- end
- return runtime
-end
-
-local function default_asset_loader(encoding, asset)
- local runtime = nvim_runtime()
- local path = asset_path(asset)
- local keys = read_asset_key_order(path)
- assert_key_order(keys, encoding)
-
- runtime.cmd("silent source " .. runtime.fn.fnameescape(path))
- local loader = runtime.fn[asset.function_name]
- if type(loader) ~= "function" then
- fail("Migemo " .. encoding .. " asset did not define its dictionary loader", 2)
- end
- local dictionary = loader()
- return dictionary, keys, path
-end
-
-local function explicit_pattern(pattern, case_mode)
- local case_flag = case_mode == domain.CaseMode.INSENSITIVE and "\\c" or "\\C"
- return "\\m" .. case_flag .. "^" .. pattern
-end
-
-local function default_pattern_compiler(pattern, key, encoding)
- local runtime = nvim_runtime()
- if type(runtime.regex) ~= "function" or type(runtime.fn.match) ~= "function" then
- fail("Migemo pattern evaluation requires Nvim regular expressions", 2)
- end
-
- local sensitive = explicit_pattern(pattern, domain.CaseMode.SENSITIVE)
- local insensitive = explicit_pattern(pattern, domain.CaseMode.INSENSITIVE)
- local ok, compile_error = pcall(runtime.regex, sensitive)
- if ok then
- ok, compile_error = pcall(runtime.regex, insensitive)
- end
- if not ok then
- fail(
- "Migemo " .. encoding .. " pattern for '" .. key
- .. "' could not be compiled: " .. tostring(compile_error),
- 2
- )
- end
-
- return function(text, case_mode)
- if type(text) ~= "string" then
- fail("Migemo assertion text must be a string", 2)
- end
- case_mode = domain.CaseMode.from_string(case_mode)
- local selected = case_mode == domain.CaseMode.INSENSITIVE
- and insensitive
- or sensitive
- local matched, start_or_error = pcall(runtime.fn.match, text, selected)
- if not matched then
- fail(
- "Migemo " .. encoding .. " pattern for '" .. key
- .. "' could not be evaluated: " .. tostring(start_or_error),
- 2
- )
- end
- return start_or_error == 0
- end
-end
-
-local dictionary_metatable = {
- __index = function(dictionary, key)
- local method = MigemoDictionary[key]
- if method ~= nil then
- return method
- end
-
- local record = dictionary_records[dictionary]
- if key == "encoding" or key == "effective_encoding" then
- return record.encoding
- end
- if key == "entry_count" then
- return #record.keys
- end
- if key == "asset_path" then
- return record.asset_path
- end
- if EXPECTED_KEY_SET[key] then
- return record.predicates[key]
- end
- return nil
- end,
- __newindex = function()
- fail("MigemoDictionary values are immutable", 2)
- end,
- __tostring = function(dictionary)
- return "migemo-dictionary:" .. dictionary_records[dictionary].encoding
- end,
- __metatable = "clever_f.migemo_catalog.MigemoDictionary",
-}
-
-local function validate_dictionary_data(data, ordered_keys, encoding)
- if type(data) ~= "table" then
- fail("Migemo " .. encoding .. " asset must return a dictionary", 3)
- end
- assert_key_order(ordered_keys, encoding)
-
- local count = 0
- for key, pattern in pairs(data) do
- count = count + 1
- if EXPECTED_KEY_SET[key] ~= true then
- fail("Migemo " .. encoding .. " asset contains an unexpected key", 3)
- end
- if type(pattern) ~= "string" or pattern == "" then
- fail("Migemo " .. encoding .. " patterns must be nonempty strings", 3)
- end
- end
- if count ~= #EXPECTED_KEYS then
- fail("Migemo " .. encoding .. " asset must contain exactly 52 keys", 3)
- end
- for _, key in ipairs(EXPECTED_KEYS) do
- if data[key] == nil then
- fail("Migemo " .. encoding .. " asset is missing key '" .. key .. "'", 3)
- end
- end
-end
-
-local function new_dictionary(encoding, data, ordered_keys, path, compiler)
- validate_dictionary_data(data, ordered_keys, encoding)
-
- local patterns = {}
- local predicates = {}
- for _, key in ipairs(EXPECTED_KEYS) do
- local pattern = data[key]
- patterns[key] = pattern
- local predicate = compiler(pattern, key, encoding)
- if type(predicate) ~= "function" then
- fail("Migemo pattern compiler must return a predicate", 3)
- end
- predicates[key] = predicate
- end
-
- local dictionary = setmetatable({}, dictionary_metatable)
- dictionary_records[dictionary] = {
- encoding = encoding,
- keys = copy_list(ordered_keys),
- patterns = patterns,
- predicates = predicates,
- asset_path = path,
- }
- return dictionary
-end
-
-function MigemoDictionary.is(value)
- return type(value) == "table" and dictionary_records[value] ~= nil
-end
-
-local function dictionary_record(dictionary)
- if not MigemoDictionary.is(dictionary) then
- fail("value must be a MigemoDictionary", 3)
- end
- return dictionary_records[dictionary]
-end
-
-function MigemoDictionary:keys()
- return copy_list(dictionary_record(self).keys)
-end
-
-function MigemoDictionary:has(key)
- return type(key) == "string"
- and dictionary_record(self).predicates[key] ~= nil
-end
-
-function MigemoDictionary:pattern(key)
- require_nonempty_string(key, "Migemo dictionary key")
- local pattern = dictionary_record(self).patterns[key]
- if pattern == nil then
- fail("Migemo dictionary key must be one ASCII alphabetic character", 2)
- end
- return pattern
-end
-
-function MigemoDictionary:predicate(key, case_mode)
- require_nonempty_string(key, "Migemo dictionary key")
- local predicate = dictionary_record(self).predicates[key]
- if predicate == nil then
- fail("Migemo dictionary key must be one ASCII alphabetic character", 2)
- end
- if case_mode == nil then
- return predicate
- end
-
- case_mode = domain.CaseMode.from_string(case_mode)
- return function(text)
- return predicate(text, case_mode)
- end
-end
-
-function MigemoDictionary:matches(key, text, case_mode)
- return self:predicate(key)(text, case_mode)
-end
-
-function MigemoDictionary:to_table()
- local record = dictionary_record(self)
- return {
- encoding = record.encoding,
- entry_count = #record.keys,
- keys = copy_list(record.keys),
- asset_path = record.asset_path,
- }
-end
-
-local function normalize_catalog_options(options)
- if options == nil then
- return {}
- end
- if type(options) ~= "table" then
- fail("MigemoCatalog options must be a table", 3)
- end
- if type(options.disable_migemo_for_unsupported_encoding) == "function"
- and options.policy == nil
- and options.policy_service == nil
- and options.transitions == nil
- and options.state == nil
- and options.asset_loader == nil
- and options.pattern_compiler == nil
- then
- return { policy = options }
- end
- return options
-end
-
-local function require_policy(service)
- if service ~= nil and (type(service) ~= "table"
- or type(service.disable_migemo_for_unsupported_encoding) ~= "function")
- then
- fail(
- "MigemoCatalog policy must provide disable_migemo_for_unsupported_encoding",
- 3
- )
- end
- return service
-end
-
-local function require_transitions(transitions, state)
- transitions = transitions or state_transitions.new(state)
- if type(transitions) ~= "table"
- or type(transitions.CacheMigemo) ~= "function"
- or type(transitions.state) ~= "function"
- or transitions:state() ~= state
- then
- fail("MigemoCatalog transitions must mutate its SequenceState", 3)
- end
- return transitions
-end
-
-local function selected_function(value, fallback, name)
- value = value or fallback
- if type(value) ~= "function" then
- fail("MigemoCatalog " .. name .. " must be a function", 3)
- end
- return value
-end
-
-local catalog_metatable = {
- __index = MigemoCatalog,
- __newindex = function()
- fail("MigemoCatalog values are immutable", 2)
- end,
- __tostring = function()
- return "migemo-catalog"
- end,
- __metatable = "clever_f.migemo_catalog.MigemoCatalog",
-}
-
-function MigemoCatalog.new(options)
- if MigemoCatalog.is(options) then
- return options
- end
- options = normalize_catalog_options(options)
- local state = options.state or sequence_state.get()
- if not sequence_state.is(state) then
- fail("MigemoCatalog requires the plugin-global SequenceState", 2)
- end
-
- local catalog = setmetatable({}, catalog_metatable)
- catalog_records[catalog] = {
- state = state,
- transitions = require_transitions(options.transitions, state),
- policy = require_policy(options.policy or options.policy_service),
- disable_migemo = options.disable_migemo,
- asset_loader = selected_function(
- options.asset_loader,
- default_asset_loader,
- "asset_loader"
- ),
- pattern_compiler = selected_function(
- options.pattern_compiler,
- default_pattern_compiler,
- "pattern_compiler"
- ),
- load_counts = {},
- }
- if catalog_records[catalog].disable_migemo ~= nil
- and type(catalog_records[catalog].disable_migemo) ~= "function"
- then
- fail("MigemoCatalog disable_migemo must be a function", 2)
- end
- return catalog
-end
-
-function MigemoCatalog.is(value)
- return type(value) == "table" and catalog_records[value] ~= nil
-end
-
-function M.new(options)
- return MigemoCatalog.new(options)
-end
-
-setmetatable(M, {
- __call = function(_, options)
- return MigemoCatalog.new(options)
- end,
-})
-
-local function catalog_record(catalog)
- if not MigemoCatalog.is(catalog) then
- fail("value must be a MigemoCatalog", 3)
- end
- return catalog_records[catalog]
-end
-
-local function unsupported(catalog, requested_encoding, policy_override)
- local record = catalog_record(catalog)
- local active_policy = policy_override or record.policy
- if active_policy ~= nil then
- require_policy(active_policy):disable_migemo_for_unsupported_encoding()
- elseif record.disable_migemo ~= nil then
- record.disable_migemo()
- end
- error(
- "clever-f: Encoding '" .. requested_encoding
- .. "' is not supported. Migemo is disabled",
- 0
- )
-end
-
-function MigemoCatalog:get(effective_encoding, policy_override)
- local requested = require_nonempty_string(effective_encoding, "effective encoding")
- local encoding = text_topology.normalize_encoding(requested)
- local asset = ASSETS[encoding]
- if asset == nil then
- return unsupported(self, requested, policy_override)
- end
-
- local record = catalog_record(self)
- local cached = record.state:get_migemo(encoding)
- if cached ~= nil then
- if not MigemoDictionary.is(cached) then
- fail("Migemo cache contains an invalid dictionary", 2)
- end
- return cached
- end
-
- local data, ordered_keys, path = record.asset_loader(encoding, asset)
- local dictionary = new_dictionary(
- encoding,
- data,
- ordered_keys,
- path,
- record.pattern_compiler
- )
- record.transitions:CacheMigemo(encoding, dictionary)
- record.load_counts[encoding] = (record.load_counts[encoding] or 0) + 1
- return dictionary
-end
-
-function MigemoCatalog:load_count(effective_encoding)
- local encoding = text_topology.normalize_encoding(effective_encoding)
- return catalog_record(self).load_counts[encoding] or 0
-end
-
-function MigemoCatalog:cached(effective_encoding)
- local encoding = text_topology.normalize_encoding(effective_encoding)
- local value = catalog_record(self).state:get_migemo(encoding)
- if value ~= nil and not MigemoDictionary.is(value) then
- fail("Migemo cache contains an invalid dictionary", 2)
- end
- return value
-end
-
-MigemoCatalog.load = MigemoCatalog.get
-MigemoCatalog.select = MigemoCatalog.get
-MigemoCatalog.dictionary = MigemoCatalog.get
-
-function M.expected_keys()
- return copy_list(EXPECTED_KEYS)
-end
-
-function M.supported_encodings()
- return { "utf-8", "cp932", "euc-jp" }
-end
-
-function M.bundled_asset_path(effective_encoding)
- local encoding = text_topology.normalize_encoding(effective_encoding)
- local asset = ASSETS[encoding]
- if asset == nil then
- return nil
- end
- return asset_path(asset)
-end
-
-M.load = function(effective_encoding, options)
- return MigemoCatalog.new(options):get(effective_encoding)
-end
-M.EXPECTED_ENTRY_COUNT = #EXPECTED_KEYS
-
-return M