aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/generators/gen_api_dispatch.lua
diff options
context:
space:
mode:
authorbfredl <bjorn.linse@gmail.com>2023-04-03 15:21:24 +0200
committerbfredl <bjorn.linse@gmail.com>2023-04-07 21:30:21 +0200
commitefb0896f21e03f64e3a14e7c09994e81956f47b9 (patch)
treef88cca66495b34631d037bffc3cda6a8db1329fe /src/nvim/generators/gen_api_dispatch.lua
parent04933b1ea968f958d2541dd65fd33ebb503caac3 (diff)
downloadrneovim-efb0896f21e03f64e3a14e7c09994e81956f47b9.tar.gz
rneovim-efb0896f21e03f64e3a14e7c09994e81956f47b9.tar.bz2
rneovim-efb0896f21e03f64e3a14e7c09994e81956f47b9.zip
refactor(api): make typed dicts appear as types in the source code
problem: can we have Serde? solution: we have Serde at home This by itself is just a change of notation, that could be quickly merged to avoid messy merge conflicts, but upcoming changes are planned: - keysets no longer need to be defined in one single file. `keysets.h` is just the initial automatic conversion of the previous `keysets.lua`. keysets just used in a single api/{scope}.h can be moved to that file, later on. - Typed dicts will have more specific types than Object. this will enable most of the existing manual typechecking boilerplate to be eliminated. We will need some annotation for missing value, i e a boolean will need to be represented as a TriState (none/false/true) in some cases. - Eventually: optional parameters in form of a `Dict opts` final parameter will get added in some form to metadata. this will require a discussion/desicion about type forward compatibility.
Diffstat (limited to 'src/nvim/generators/gen_api_dispatch.lua')
-rw-r--r--src/nvim/generators/gen_api_dispatch.lua140
1 files changed, 106 insertions, 34 deletions
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua
index 8aa1829364..f292c265ec 100644
--- a/src/nvim/generators/gen_api_dispatch.lua
+++ b/src/nvim/generators/gen_api_dispatch.lua
@@ -8,6 +8,7 @@ if arg[1] == '--help' then
print(' 3: functions metadata output file (funcs_metadata.generated.h)')
print(' 4: API metadata output file (api_metadata.mpack)')
print(' 5: lua C bindings output file (lua_api_c_bindings.generated.c)')
+ print(' 6: keyset definitions output file (keysets_defs.generated.h)')
print(' rest: C files where API functions are defined')
end
assert(#arg >= 4)
@@ -32,6 +33,7 @@ local funcs_metadata_outputf = arg[3]
-- output metadata mpack file, for use by other build scripts
local mpack_outputf = arg[4]
local lua_c_bindings_outputf = arg[5]
+local keysets_outputf = arg[6]
-- set of function names, used to detect duplicates
local function_names = {}
@@ -42,8 +44,57 @@ local function startswith(String,Start)
return string.sub(String,1,string.len(Start))==Start
end
+local function add_function(fn)
+ local public = startswith(fn.name, "nvim_") or fn.deprecated_since
+ if public and not fn.noexport then
+ functions[#functions + 1] = fn
+ function_names[fn.name] = true
+ if #fn.parameters >= 2 and fn.parameters[2][1] == 'Array' and fn.parameters[2][2] == 'uidata' then
+ -- function receives the "args" as a parameter
+ fn.receives_array_args = true
+ -- remove the args parameter
+ table.remove(fn.parameters, 2)
+ end
+ if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then
+ -- this function should receive the channel id
+ fn.receives_channel_id = true
+ -- remove the parameter since it won't be passed by the api client
+ table.remove(fn.parameters, 1)
+ end
+ if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'error' then
+ -- function can fail if the last parameter type is 'Error'
+ fn.can_fail = true
+ -- remove the error parameter, msgpack has it's own special field
+ -- for specifying errors
+ fn.parameters[#fn.parameters] = nil
+ end
+ if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'arena' then
+ -- return value is allocated in an arena
+ fn.arena_return = true
+ fn.parameters[#fn.parameters] = nil
+ end
+ if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'lstate' then
+ fn.has_lua_imp = true
+ fn.parameters[#fn.parameters] = nil
+ end
+ end
+end
+
+local keysets = {}
+
+local function add_keyset(val)
+ local keys = {}
+ for _,field in ipairs(val.fields) do
+ if field.type ~= 'Object' then
+ error 'not yet implemented: types other than Object'
+ end
+ table.insert(keys, field.name)
+ end
+ table.insert(keysets, {val.keyset_name, keys})
+end
+
-- read each input file, parse and append to the api metadata
-for i = 6, #arg do
+for i = 7, #arg do
local full_path = arg[i]
local parts = {}
for part in string.gmatch(full_path, '[^/]+') do
@@ -55,39 +106,11 @@ for i = 6, #arg do
local tmp = c_grammar.grammar:match(input:read('*all'))
for j = 1, #tmp do
- local fn = tmp[j]
- local public = startswith(fn.name, "nvim_") or fn.deprecated_since
- if public and not fn.noexport then
- functions[#functions + 1] = tmp[j]
- function_names[fn.name] = true
- if #fn.parameters >= 2 and fn.parameters[2][1] == 'Array' and fn.parameters[2][2] == 'uidata' then
- -- function receives the "args" as a parameter
- fn.receives_array_args = true
- -- remove the args parameter
- table.remove(fn.parameters, 2)
- end
- if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then
- -- this function should receive the channel id
- fn.receives_channel_id = true
- -- remove the parameter since it won't be passed by the api client
- table.remove(fn.parameters, 1)
- end
- if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'error' then
- -- function can fail if the last parameter type is 'Error'
- fn.can_fail = true
- -- remove the error parameter, msgpack has it's own special field
- -- for specifying errors
- fn.parameters[#fn.parameters] = nil
- end
- if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'arena' then
- -- return value is allocated in an arena
- fn.arena_return = true
- fn.parameters[#fn.parameters] = nil
- end
- if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'lstate' then
- fn.has_lua_imp = true
- fn.parameters[#fn.parameters] = nil
- end
+ local val = tmp[j]
+ if val.keyset_name then
+ add_keyset(val)
+ else
+ add_function(val)
end
end
input:close()
@@ -195,6 +218,7 @@ funcs_metadata_output:close()
-- start building the dispatch wrapper output
local output = io.open(dispatch_outputf, 'wb')
+local keysets_defs = io.open(keysets_outputf, 'wb')
-- ===========================================================================
-- NEW API FILES MUST GO HERE.
@@ -224,6 +248,52 @@ output:write([[
]])
+for _,keyset in ipairs(keysets) do
+ local name, keys = unpack(keyset)
+ local special = {}
+ local function sanitize(key)
+ if special[key] then
+ return key .. "_"
+ end
+ return key
+ end
+
+ for i = 1,#keys do
+ if vim.endswith(keys[i], "_") then
+ keys[i] = string.sub(keys[i],1, #(keys[i]) - 1)
+ special[keys[i]] = true
+ end
+ end
+ local neworder, hashfun = hashy.hashy_hash(name, keys, function (idx)
+ return name.."_table["..idx.."].str"
+ end)
+
+ keysets_defs:write("extern KeySetLink "..name.."_table[];\n")
+
+ output:write("KeySetLink "..name.."_table[] = {\n")
+ for _, key in ipairs(neworder) do
+ output:write(' {"'..key..'", offsetof(KeyDict_'..name..", "..sanitize(key)..")},\n")
+ end
+ output:write(' {NULL, 0},\n')
+ output:write("};\n\n")
+
+ output:write(hashfun)
+
+ output:write([[
+Object *KeyDict_]]..name..[[_get_field(void *retval, const char *str, size_t len)
+{
+ int hash = ]]..name..[[_hash(str, len);
+ if (hash == -1) {
+ return NULL;
+ }
+
+ return (Object *)((char *)retval + ]]..name..[[_table[hash].ptr_off);
+}
+
+]])
+ keysets_defs:write("#define api_free_keydict_"..name.."(x) api_free_keydict(x, "..name.."_table)\n")
+end
+
local function real_type(type)
local rv = type
local rmatch = string.match(type, "Dict%(([_%w]+)%)")
@@ -475,6 +545,7 @@ output:write([[
#include "nvim/func_attr.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/lua/converter.h"
#include "nvim/lua/executor.h"
#include "nvim/memory.h"
@@ -670,3 +741,4 @@ output:write([[
]])
output:close()
+keysets_defs:close()