feat: flesh out language support
This commit is contained in:
parent
e806b23020
commit
4aa16d2858
7 changed files with 254 additions and 99 deletions
|
|
@ -1,13 +1,34 @@
|
|||
---@class LanguageConfig
|
||||
---@field compile? string[] Compile command template
|
||||
---@field run string[] Run command template
|
||||
---@field debug? string[] Debug command template
|
||||
---@field executable? string Executable name
|
||||
---@field version? number Language version
|
||||
---@field extension string File extension
|
||||
|
||||
---@class ContestConfig
|
||||
---@field cpp LanguageConfig
|
||||
---@field python LanguageConfig
|
||||
---@field default_language string
|
||||
---@field timeout_ms number
|
||||
|
||||
---@class cp.Config
|
||||
---@field contests table
|
||||
---@field snippets table
|
||||
---@field contests table<string, ContestConfig>
|
||||
---@field snippets table[]
|
||||
---@field hooks table
|
||||
---@field debug boolean
|
||||
---@field tile? fun(source_buf: number, input_buf: number, output_buf: number)
|
||||
---@field filename? fun(contest: string, problem_id: string, problem_letter?: string): string
|
||||
---@field filename? fun(contest: string, contest_id: string, problem_id?: string, config: cp.Config, language?: string): string
|
||||
|
||||
local M = {}
|
||||
|
||||
local filetype_to_language = {
|
||||
cc = "cpp",
|
||||
c = "cpp",
|
||||
py = "python",
|
||||
py3 = "python",
|
||||
}
|
||||
|
||||
---@type cp.Config
|
||||
M.defaults = {
|
||||
contests = {
|
||||
|
|
@ -37,13 +58,16 @@ M.defaults = {
|
|||
},
|
||||
executable = nil,
|
||||
version = 20,
|
||||
extension = "cc",
|
||||
},
|
||||
python = {
|
||||
compile = nil,
|
||||
run = { "{source}" },
|
||||
debug = { "{source}" },
|
||||
executable = "python3",
|
||||
extension = "py",
|
||||
},
|
||||
default_language = "cpp",
|
||||
timeout_ms = 2000,
|
||||
},
|
||||
atcoder = {
|
||||
|
|
@ -74,8 +98,8 @@ local function extend_contest_config(base_config, contest_config)
|
|||
return result
|
||||
end
|
||||
|
||||
---@param user_config table|nil
|
||||
---@return table
|
||||
---@param user_config cp.Config|nil
|
||||
---@return cp.Config
|
||||
function M.setup(user_config)
|
||||
vim.validate({
|
||||
user_config = { user_config, { "table", "nil" }, true },
|
||||
|
|
@ -97,6 +121,20 @@ function M.setup(user_config)
|
|||
before_debug = { user_config.hooks.before_debug, { "function", "nil" }, true },
|
||||
})
|
||||
end
|
||||
|
||||
if user_config.contests then
|
||||
for contest_name, contest_config in pairs(user_config.contests) do
|
||||
for lang_name, lang_config in pairs(contest_config) do
|
||||
if type(lang_config) == "table" and lang_config.extension then
|
||||
if not vim.tbl_contains(vim.tbl_keys(filetype_to_language), lang_config.extension) then
|
||||
error(("Invalid extension '%s' for language '%s' in contest '%s'. Valid extensions: %s"):format(
|
||||
lang_config.extension, lang_name, contest_name, table.concat(vim.tbl_keys(filetype_to_language), ", ")
|
||||
))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local config = vim.tbl_deep_extend("force", M.defaults, user_config or {})
|
||||
|
|
@ -111,14 +149,32 @@ function M.setup(user_config)
|
|||
return config
|
||||
end
|
||||
|
||||
local function default_filename(contest, contest_id, problem_id)
|
||||
---@param contest string
|
||||
---@param contest_id string
|
||||
---@param problem_id? string
|
||||
---@param config cp.Config
|
||||
---@param language? string
|
||||
---@return string
|
||||
local function default_filename(contest, contest_id, problem_id, config, language)
|
||||
vim.validate({
|
||||
contest = { contest, "string" },
|
||||
contest_id = { contest_id, "string" },
|
||||
problem_id = { problem_id, { "string", "nil" }, true },
|
||||
config = { config, "table" },
|
||||
language = { language, { "string", "nil" }, true },
|
||||
})
|
||||
|
||||
local full_problem_id = contest_id:lower()
|
||||
if contest == "atcoder" or contest == "codeforces" then
|
||||
if problem_id then
|
||||
full_problem_id = full_problem_id .. problem_id:lower()
|
||||
end
|
||||
end
|
||||
return full_problem_id .. ".cc"
|
||||
|
||||
local contest_config = config.contests[contest] or config.contests.default
|
||||
local target_language = language or contest_config.default_language
|
||||
local language_config = contest_config[target_language]
|
||||
return full_problem_id .. "." .. language_config.extension
|
||||
end
|
||||
|
||||
M.default_filename = default_filename
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@ end
|
|||
|
||||
---@param contest_id string
|
||||
---@param problem_id? string
|
||||
local function setup_problem(contest_id, problem_id)
|
||||
---@param language? string
|
||||
local function setup_problem(contest_id, problem_id, language)
|
||||
if not state.platform then
|
||||
logger.log("no platform set. run :CP <platform> <contest> first", vim.log.levels.ERROR)
|
||||
return
|
||||
|
|
@ -88,7 +89,7 @@ local function setup_problem(contest_id, problem_id)
|
|||
state.test_cases = cached_test_cases
|
||||
end
|
||||
|
||||
local ctx = problem.create_context(state.platform, contest_id, problem_id, config)
|
||||
local ctx = problem.create_context(state.platform, contest_id, problem_id, config, language)
|
||||
|
||||
local scrape_result = scrape.scrape_problem(ctx)
|
||||
|
||||
|
|
@ -305,33 +306,52 @@ end
|
|||
|
||||
local function parse_command(args)
|
||||
if #args == 0 then
|
||||
return { type = "error", message = "Usage: :CP <platform> <contest> [problem] | :CP <action> | :CP <problem>" }
|
||||
return { type = "error", message = "Usage: :CP <platform> <contest> [problem] [--lang=<language>] | :CP <action> | :CP <problem>" }
|
||||
end
|
||||
|
||||
local first = args[1]
|
||||
local language = nil
|
||||
|
||||
for i, arg in ipairs(args) do
|
||||
local lang_match = arg:match("^--lang=(.+)$")
|
||||
if lang_match then
|
||||
language = lang_match
|
||||
elseif arg == "--lang" then
|
||||
if i + 1 <= #args then
|
||||
language = args[i + 1]
|
||||
else
|
||||
return { type = "error", message = "--lang requires a value" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local filtered_args = vim.tbl_filter(function(arg)
|
||||
return not (arg:match("^--lang") or arg == language)
|
||||
end, args)
|
||||
|
||||
local first = filtered_args[1]
|
||||
|
||||
if vim.tbl_contains(actions, first) then
|
||||
return { type = "action", action = first }
|
||||
end
|
||||
|
||||
if vim.tbl_contains(platforms, first) then
|
||||
if #args == 1 then
|
||||
return { type = "platform_only", platform = first }
|
||||
elseif #args == 2 then
|
||||
if #filtered_args == 1 then
|
||||
return { type = "platform_only", platform = first, language = language }
|
||||
elseif #filtered_args == 2 then
|
||||
if first == "cses" then
|
||||
return { type = "cses_problem", platform = first, problem = args[2] }
|
||||
return { type = "cses_problem", platform = first, problem = filtered_args[2], language = language }
|
||||
else
|
||||
return { type = "contest_setup", platform = first, contest = args[2] }
|
||||
return { type = "contest_setup", platform = first, contest = filtered_args[2], language = language }
|
||||
end
|
||||
elseif #args == 3 then
|
||||
return { type = "full_setup", platform = first, contest = args[2], problem = args[3] }
|
||||
elseif #filtered_args == 3 then
|
||||
return { type = "full_setup", platform = first, contest = filtered_args[2], problem = filtered_args[3], language = language }
|
||||
else
|
||||
return { type = "error", message = "Too many arguments" }
|
||||
end
|
||||
end
|
||||
|
||||
if state.platform and state.contest_id then
|
||||
return { type = "problem_switch", problem = first }
|
||||
return { type = "problem_switch", problem = first, language = language }
|
||||
end
|
||||
|
||||
return { type = "error", message = "Unknown command or no contest context" }
|
||||
|
|
@ -398,7 +418,7 @@ function M.handle_command(opts)
|
|||
)
|
||||
end
|
||||
|
||||
setup_problem(cmd.contest, cmd.problem)
|
||||
setup_problem(cmd.contest, cmd.problem, cmd.language)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
|
@ -412,16 +432,16 @@ function M.handle_command(opts)
|
|||
vim.log.levels.WARN
|
||||
)
|
||||
end
|
||||
setup_problem(cmd.problem)
|
||||
setup_problem(cmd.problem, nil, cmd.language)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if cmd.type == "problem_switch" then
|
||||
if state.platform == "cses" then
|
||||
setup_problem(cmd.problem)
|
||||
setup_problem(cmd.problem, nil, cmd.language)
|
||||
else
|
||||
setup_problem(state.contest_id, cmd.problem)
|
||||
setup_problem(state.contest_id, cmd.problem, cmd.language)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,10 +15,11 @@ local M = {}
|
|||
---@param contest_id string
|
||||
---@param problem_id? string
|
||||
---@param config cp.Config
|
||||
---@param language? string
|
||||
---@return ProblemContext
|
||||
function M.create_context(contest, contest_id, problem_id, config)
|
||||
function M.create_context(contest, contest_id, problem_id, config, language)
|
||||
local filename_fn = config.filename or require("cp.config").default_filename
|
||||
local source_file = filename_fn(contest, contest_id, problem_id)
|
||||
local source_file = filename_fn(contest, contest_id, problem_id, config, language)
|
||||
local base_name = vim.fn.fnamemodify(source_file, ":t:r")
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -10,102 +10,113 @@ function M.setup(config)
|
|||
|
||||
local s, i, fmt = ls.snippet, ls.insert_node, require("luasnip.extras.fmt").fmt
|
||||
|
||||
local default_snippets = {
|
||||
s(
|
||||
"codeforces",
|
||||
fmt(
|
||||
[[#include <bits/stdc++.h>
|
||||
local filetype_to_language = {
|
||||
cc = "cpp",
|
||||
c = "cpp",
|
||||
py = "python",
|
||||
py3 = "python",
|
||||
}
|
||||
|
||||
local language_to_filetype = {}
|
||||
for ext, lang in pairs(filetype_to_language) do
|
||||
language_to_filetype[lang] = ext
|
||||
end
|
||||
|
||||
local template_definitions = {
|
||||
cpp = {
|
||||
codeforces = [[#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
void solve() {{
|
||||
void solve() {
|
||||
{}
|
||||
}}
|
||||
}
|
||||
|
||||
int main() {{
|
||||
int main() {
|
||||
std::cin.tie(nullptr)->sync_with_stdio(false);
|
||||
|
||||
int tc = 1;
|
||||
std::cin >> tc;
|
||||
|
||||
for (int t = 0; t < tc; ++t) {{
|
||||
for (int t = 0; t < tc; ++t) {
|
||||
solve();
|
||||
}}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}}]],
|
||||
{ i(1) }
|
||||
)
|
||||
),
|
||||
}]],
|
||||
|
||||
s(
|
||||
"atcoder",
|
||||
fmt(
|
||||
[[#include <bits/stdc++.h>
|
||||
atcoder = [[#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
void solve() {{
|
||||
void solve() {
|
||||
{}
|
||||
}}
|
||||
}
|
||||
|
||||
int main() {{
|
||||
int main() {
|
||||
std::cin.tie(nullptr)->sync_with_stdio(false);
|
||||
|
||||
#ifdef LOCAL
|
||||
int tc;
|
||||
std::cin >> tc;
|
||||
|
||||
for (int t = 0; t < tc; ++t) {{
|
||||
for (int t = 0; t < tc; ++t) {
|
||||
solve();
|
||||
}}
|
||||
}
|
||||
#else
|
||||
solve();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}}]],
|
||||
{ i(1) }
|
||||
)
|
||||
),
|
||||
}]],
|
||||
|
||||
s(
|
||||
"cses",
|
||||
fmt(
|
||||
[[#include <bits/stdc++.h>
|
||||
cses = [[#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {{
|
||||
int main() {
|
||||
std::cin.tie(nullptr)->sync_with_stdio(false);
|
||||
|
||||
{}
|
||||
|
||||
return 0;
|
||||
}}]],
|
||||
{ i(1) }
|
||||
)
|
||||
),
|
||||
}]],
|
||||
},
|
||||
|
||||
python = {
|
||||
codeforces = [[def solve():
|
||||
{}
|
||||
|
||||
if __name__ == "__main__":
|
||||
tc = int(input())
|
||||
for _ in range(tc):
|
||||
solve()]],
|
||||
|
||||
atcoder = [[def solve():
|
||||
{}
|
||||
|
||||
if __name__ == "__main__":
|
||||
solve()]],
|
||||
|
||||
cses = [[{}]],
|
||||
},
|
||||
}
|
||||
|
||||
local default_map = {}
|
||||
for _, snippet in pairs(default_snippets) do
|
||||
default_map[snippet.trigger] = snippet
|
||||
for language, filetype in pairs(language_to_filetype) do
|
||||
local snippets = {}
|
||||
|
||||
for contest, template in pairs(template_definitions[language] or {}) do
|
||||
table.insert(snippets, s(contest, fmt(template, { i(1) })))
|
||||
end
|
||||
|
||||
for _, snippet in ipairs(config.snippets or {}) do
|
||||
if snippet.filetype == filetype then
|
||||
table.insert(snippets, snippet)
|
||||
end
|
||||
end
|
||||
|
||||
ls.add_snippets(filetype, snippets)
|
||||
end
|
||||
|
||||
local user_map = {}
|
||||
for _, snippet in pairs(config.snippets or {}) do
|
||||
user_map[snippet.trigger] = snippet
|
||||
end
|
||||
|
||||
local merged_map = vim.tbl_extend("force", default_map, user_map)
|
||||
|
||||
local all_snippets = {}
|
||||
for _, snippet in pairs(merged_map) do
|
||||
table.insert(all_snippets, snippet)
|
||||
end
|
||||
|
||||
ls.add_snippets("cpp", all_snippets)
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue