disable scraper disabling

This commit is contained in:
Barrett Ruth 2025-10-04 17:45:49 -04:00
parent 0a320945a0
commit aae98a5796
9 changed files with 88 additions and 166 deletions

View file

@ -120,8 +120,8 @@ Here's an example configuration with lazy.nvim: >lua
cmd = 'CP', cmd = 'CP',
build = 'uv sync', build = 'uv sync',
opts = { opts = {
contests = { platforms = {
default = { cses = {
cpp = { cpp = {
compile = { 'g++', '{source}', '-o', '{binary}', compile = { 'g++', '{source}', '-o', '{binary}',
'-std=c++17', '-fdiagnostic-colors=always' }, '-std=c++17', '-fdiagnostic-colors=always' },
@ -138,7 +138,6 @@ Here's an example configuration with lazy.nvim: >lua
}, },
snippets = {}, snippets = {},
debug = false, debug = false,
scrapers = { 'atcoder', 'codeforces', 'cses' },
run_panel = { run_panel = {
ansi = true, ansi = true,
diff_mode = 'vim', diff_mode = 'vim',
@ -156,23 +155,29 @@ Here's an example configuration with lazy.nvim: >lua
< <
By default, all contests are configured to use C++ with the g++ compiler and ISO standard By default, all contests are configured to use C++ with the g++ compiler and ISO standard
17. Python is also configured with the system executable python as a non-default option. Consult lua/cp/config.lua for 17. Python is also configured with the system executable python.
more information.
For example, to run CodeForces contests with Python, only the following config For example, to run CodeForces contests with Python, only the following config
is required: is required:
{ {
contests = { platforms = {
codeforces = { codeforces = {
default_langauge = 'python' default_language = 'python'
} }
} }
}
Any language is supported, provided the proper configuration. For example, to
run CSES problems with Rust:
{
} }
*cp.Config* *cp.Config*
Fields: ~ Fields: ~
{contests} (table<string,ContestConfig>) Contest configurations. {platforms} (table<string,PlatformContestConfig>) Contest configurations.
{hooks} (|cp.Hooks|) Hook functions called at various stages. {hooks} (|cp.Hooks|) Hook functions called at various stages.
{snippets} (table[]) LuaSnip snippet definitions. {snippets} (table[]) LuaSnip snippet definitions.
{debug} (boolean, default: false) Show info messages {debug} (boolean, default: false) Show info messages
@ -189,11 +194,12 @@ is required:
Should return full filename with extension. Should return full filename with extension.
(default: concatenates contest_id and problem_id, lowercased) (default: concatenates contest_id and problem_id, lowercased)
*cp.ContestConfig* *cp.PlatformConfig*
Fields: ~ Fields: ~
{cpp} (|LanguageConfig|) C++ language configuration. {cpp} (|LanguageConfig|) C++ language configuration.
{python} (|LanguageConfig|) Python language configuration. {python} (|LanguageConfig|) Python language configuration.
{default_language} (string, default: "cpp") Default language for contests. {default_language} (string, default: "cpp") Default language for
platform contests.
*cp.LanguageConfig* *cp.LanguageConfig*
Fields: ~ Fields: ~
@ -201,7 +207,6 @@ is required:
{source}, {binary} placeholders. {source}, {binary} placeholders.
{test} (string[]) Test execution command template. {test} (string[]) Test execution command template.
{debug} (string[], optional) Debug compile command template. {debug} (string[], optional) Debug compile command template.
{extension} (string) File extension (e.g. "cc", "py").
{executable} (string, optional) Executable name for interpreted languages. {executable} (string, optional) Executable name for interpreted languages.
*cp.RunPanelConfig* *cp.RunPanelConfig*

View file

@ -1,14 +1,12 @@
---@class LanguageConfig ---@class PlatformLanguageConfig
---@field compile? string[] Compile command template ---@field compile? string[] Compile command template
---@field test string[] Test execution command template ---@field test string[] Test execution command template
---@field debug? string[] Debug command template ---@field debug? string[] Debug command template
---@field executable? string Executable name ---@field executable? string Executable name
---@field version? number Language version
---@field extension? string File extension ---@field extension? string File extension
---@class ContestConfig ---@class PlatformConfig
---@field cpp LanguageConfig ---@field [string] PlatformLanguageConfig
---@field python LanguageConfig
---@field default_language? string ---@field default_language? string
---@class Hooks ---@class Hooks
@ -28,22 +26,20 @@
---@field git DiffGitConfig ---@field git DiffGitConfig
---@class cp.Config ---@class cp.Config
---@field platforms table<string, ContestConfig> ---@field platforms table<string, PlatformConfig>
---@field snippets any[] ---@field snippets any[]
---@field hooks Hooks ---@field hooks Hooks
---@field debug boolean ---@field debug boolean
---@field scrapers string[]
---@field filename? fun(contest: string, contest_id: string, problem_id?: string, config: cp.Config, language?: string): string ---@field filename? fun(contest: string, contest_id: string, problem_id?: string, config: cp.Config, language?: string): string
---@field run_panel RunPanelConfig ---@field run_panel RunPanelConfig
---@field diff DiffConfig ---@field diff DiffConfig
---@field picker string|nil ---@field picker string|nil
---@class cp.PartialConfig ---@class cp.PartialConfig
---@field platforms? table<string, ContestConfig> ---@field platforms? table<string, PlatformConfig>
---@field snippets? any[] ---@field snippets? any[]
---@field hooks? Hooks ---@field hooks? Hooks
---@field debug? boolean ---@field debug? boolean
---@field scrapers? string[]
---@field filename? fun(contest: string, contest_id: string, problem_id?: string, config: cp.Config, language?: string): string ---@field filename? fun(contest: string, contest_id: string, problem_id?: string, config: cp.Config, language?: string): string
---@field run_panel? RunPanelConfig ---@field run_panel? RunPanelConfig
---@field diff? DiffConfig ---@field diff? DiffConfig
@ -64,7 +60,6 @@ local default_contest_config = {
test = { '{source}' }, test = { '{source}' },
debug = { '{source}' }, debug = { '{source}' },
executable = 'python', executable = 'python',
extension = 'py',
}, },
default_language = 'cpp', default_language = 'cpp',
} }
@ -111,7 +106,6 @@ function M.setup(user_config)
snippets = { user_config.snippets, { 'table', 'nil' }, true }, snippets = { user_config.snippets, { 'table', 'nil' }, true },
hooks = { user_config.hooks, { 'table', 'nil' }, true }, hooks = { user_config.hooks, { 'table', 'nil' }, true },
debug = { user_config.debug, { 'boolean', 'nil' }, true }, debug = { user_config.debug, { 'boolean', 'nil' }, true },
scrapers = { user_config.scrapers, { 'table', 'nil' }, true },
filename = { user_config.filename, { 'function', 'nil' }, true }, filename = { user_config.filename, { 'function', 'nil' }, true },
run_panel = { user_config.run_panel, { 'table', 'nil' }, true }, run_panel = { user_config.run_panel, { 'table', 'nil' }, true },
diff = { user_config.diff, { 'table', 'nil' }, true }, diff = { user_config.diff, { 'table', 'nil' }, true },
@ -136,22 +130,6 @@ function M.setup(user_config)
end end
end end
if user_config.scrapers then
for _, platform_name in ipairs(user_config.scrapers) do
if type(platform_name) ~= 'string' then
error(('Invalid scraper value type. Expected string, got %s'):format(type(platform_name)))
end
if not vim.tbl_contains(constants.PLATFORMS, platform_name) then
error(
("Invalid platform '%s' in scrapers config. Valid platforms: %s"):format(
platform_name,
table.concat(constants.PLATFORMS, ', ')
)
)
end
end
end
if user_config.picker then if user_config.picker then
if not vim.tbl_contains({ 'telescope', 'fzf-lua' }, user_config.picker) then if not vim.tbl_contains({ 'telescope', 'fzf-lua' }, user_config.picker) then
error(("Invalid picker '%s'. Must be 'telescope' or 'fzf-lua'"):format(user_config.picker)) error(("Invalid picker '%s'. Must be 'telescope' or 'fzf-lua'"):format(user_config.picker))
@ -206,34 +184,9 @@ function M.setup(user_config)
}) })
for _, platform_config in pairs(config.platforms) do for _, platform_config in pairs(config.platforms) do
for lang_name, lang_config in pairs(platform_config) do
if type(lang_config) == 'table' and not lang_config.extension then
if lang_name == 'cpp' then
lang_config.extension = 'cpp'
elseif lang_name == 'python' then
lang_config.extension = 'py'
end
end
end
vim.print(platform_config)
if not platform_config.default_language then if not platform_config.default_language then
local available_langs = {} -- arbitrarily choose a language
for lang_name, lang_config in pairs(platform_config) do platform_config.default_language = vim.tbl_keys(platform_config)[1]
if type(lang_config) == 'table' and lang_name ~= 'default_language' then
table.insert(available_langs, lang_name)
end
end
if vim.tbl_isemtpy(available_langs) then
error('No language configurations found')
end
vim.print('sorting langs')
--- arbitrarily break ties
table.sort(available_langs)
vim.print(available_langs)
platform_config.default_language = available_langs[1]
end end
end end

View file

@ -24,6 +24,12 @@ M.canonical_filetypes = {
[M.PYTHON] = 'python', [M.PYTHON] = 'python',
} }
---@type table<string, string>
M.canonical_filetype_to_extension = {
[M.CPP] = 'cc',
[M.PYTHON] = 'py',
}
---@type table<number, string> ---@type table<number, string>
M.signal_codes = { M.signal_codes = {
[128] = 'SIGILL', [128] = 'SIGILL',

View file

@ -5,15 +5,11 @@ local logger = require('cp.log')
local state = require('cp.state') local state = require('cp.state')
function M.restore_from_current_file() function M.restore_from_current_file()
local current_file = vim.fn.expand('%:p')
if current_file == '' then
logger.log('No file is currently open.', vim.log.levels.ERROR)
return false
end
cache.load() cache.load()
local current_file = vim.fn.expand('%:p')
local file_state = cache.get_file_state(current_file) local file_state = cache.get_file_state(current_file)
if not file_state then if current_file or not file_state then
logger.log('No cached state found for current file.', vim.log.levels.ERROR) logger.log('No cached state found for current file.', vim.log.levels.ERROR)
return false return false
end end
@ -27,13 +23,9 @@ function M.restore_from_current_file()
) )
local setup = require('cp.setup') local setup = require('cp.setup')
if not setup.set_platform(file_state.platform) then local _ = setup.set_platform(file_state.platform)
return false
end
state.set_contest_id(file_state.contest_id) state.set_contest_id(file_state.contest_id)
state.set_problem_id(file_state.problem_id) state.set_problem_id(file_state.problem_id)
setup.setup_contest(file_state.platform, file_state.contest_id, file_state.problem_id) setup.setup_contest(file_state.platform, file_state.contest_id, file_state.problem_id)
return true return true

View file

@ -7,26 +7,28 @@
---@field peak_mb number ---@field peak_mb number
---@field signal string|nil ---@field signal string|nil
---@class SubstitutableCommand
---@field source string substituted via '{source}'
---@field binary string substitued via '{binary}'
local M = {} local M = {}
local constants = require('cp.constants') local constants = require('cp.constants')
local logger = require('cp.log') local logger = require('cp.log')
local utils = require('cp.utils') local utils = require('cp.utils')
local filetype_to_language = constants.filetype_to_language ---@param cmd_template string[]
---@param substitutions SubstitutableCommand
local function get_language_from_file(source_file, contest_config) ---@return string[] string normalized with substitutions
local ext = vim.fn.fnamemodify(source_file, ':e')
return filetype_to_language[ext] or contest_config.default_language
end
local function substitute_template(cmd_template, substitutions) local function substitute_template(cmd_template, substitutions)
local out = {} local out = {}
for _, a in ipairs(cmd_template) do for _, arg in ipairs(cmd_template) do
local s = a if arg == '{source}' and substitutions.source then
for k, v in pairs(substitutions) do table.insert(out, substitutions.source)
s = s:gsub('{' .. k .. '}', v) elseif arg == '{binary}' and substitutions.binary then
table.insert(out, substitutions.binary)
else
table.insert(out, arg)
end end
table.insert(out, s)
end end
return out return out
end end
@ -39,12 +41,10 @@ function M.build_command(cmd_template, executable, substitutions)
return cmd return cmd
end end
function M.compile(language_config, substitutions) ---@param compile_cmd string[]
if not language_config.compile then ---@param substitutions SubstitutableCommand
return { code = 0, stdout = '' } function M.compile(compile_cmd, substitutions)
end local cmd = substitute_template(compile_cmd, substitutions)
local cmd = substitute_template(language_config.compile, substitutions)
local sh = table.concat(cmd, ' ') .. ' 2>&1' local sh = table.concat(cmd, ' ') .. ' 2>&1'
local t0 = vim.uv.hrtime() local t0 = vim.uv.hrtime()
@ -164,32 +164,19 @@ function M.run(cmd, stdin, timeout_ms, memory_mb)
} }
end end
function M.compile_problem(contest_config, is_debug) function M.compile_problem()
local state = require('cp.state') local state = require('cp.state')
local source_file = state.get_source_file()
if not source_file then
return { success = false, output = 'No source file found.' }
end
local language = get_language_from_file(source_file, contest_config) local config = require('cp.config').get_config()
local language_config = contest_config[language] local platform = state.get_platform() or ''
if not language_config then local language = config.platforms[platform].default_language
return { success = false, output = ('No configuration for language %s.'):format(language) } local compile_config = config.platforms[platform][language].compile
end if not compile_config then
local binary_file = state.get_binary_file()
local substitutions = { source = source_file, binary = binary_file }
local chosen = (is_debug and language_config.debug) and language_config.debug
or language_config.compile
if not chosen then
return { success = true, output = nil } return { success = true, output = nil }
end end
local saved = language_config.compile local substitutions = { source = state.get_source_file(), binary = state.get_binary_file() }
language_config.compile = chosen local r = M.compile(compile_config, substitutions)
local r = M.compile(language_config, substitutions)
language_config.compile = saved
if r.code ~= 0 then if r.code ~= 0 then
return { success = false, output = r.stdout or 'unknown error' } return { success = false, output = r.stdout or 'unknown error' }

View file

@ -31,8 +31,11 @@
local M = {} local M = {}
local cache = require('cp.cache') local cache = require('cp.cache')
local config = require('cp.config').get_config()
local constants = require('cp.constants') local constants = require('cp.constants')
local execute = require('cp.runner.execute')
local logger = require('cp.log') local logger = require('cp.log')
local state = require('cp.state')
---@type RunPanelState ---@type RunPanelState
local run_panel_state = { local run_panel_state = {
@ -90,42 +93,35 @@ local function create_sentinal_panel_data(test_cases)
return out return out
end end
---@param language_config LanguageConfig ---@param cmd string[]
---@param substitutions table<string, string> ---@param executable string
---@return string[] ---@return string[]
local function build_command(language_config, substitutions) local function build_command(cmd, executable, substitutions)
local execute = require('cp.runner.execute') return execute.build_command(cmd, executable, substitutions)
return execute.build_command(language_config.test, language_config.executable, substitutions)
end end
---@param contest_config ContestConfig
---@param cp_config cp.Config
---@param test_case RanTestCase ---@param test_case RanTestCase
---@return { status: "pass"|"fail"|"tle"|"mle", actual: string, actual_highlights: Highlight[], error: string, stderr: string, time_ms: number, code: integer, ok: boolean, signal: string, tled: boolean, mled: boolean, rss_mb: number } ---@return { status: "pass"|"fail"|"tle"|"mle", actual: string, actual_highlights: Highlight[], error: string, stderr: string, time_ms: number, code: integer, ok: boolean, signal: string, tled: boolean, mled: boolean, rss_mb: number }
local function run_single_test_case(contest_config, cp_config, test_case) local function run_single_test_case(test_case)
local state = require('cp.state')
local exec = require('cp.runner.execute')
local source_file = state.get_source_file() local source_file = state.get_source_file()
local ext = vim.fn.fnamemodify(source_file or '', ':e')
local lang_name = constants.filetype_to_language[ext] or contest_config.default_language
local language_config = contest_config[lang_name]
local binary_file = state.get_binary_file() local binary_file = state.get_binary_file()
local substitutions = { source = source_file, binary = binary_file } local substitutions = { source = source_file, binary = binary_file }
local cmd = build_command(language_config, substitutions) local platform_config = config.platforms[state.get_platform() or '']
local language_config = platform_config[platform_config.default_language]
local cmd = build_command(language_config.test, language_config.executable, substitutions)
local stdin_content = (test_case.input or '') .. '\n' local stdin_content = (test_case.input or '') .. '\n'
local timeout_ms = (run_panel_state.constraints and run_panel_state.constraints.timeout_ms) or 0 local timeout_ms = (run_panel_state.constraints and run_panel_state.constraints.timeout_ms) or 0
local memory_mb = run_panel_state.constraints and run_panel_state.constraints.memory_mb or 0 local memory_mb = run_panel_state.constraints and run_panel_state.constraints.memory_mb or 0
local r = exec.run(cmd, stdin_content, timeout_ms, memory_mb) local r = execute.run(cmd, stdin_content, timeout_ms, memory_mb)
local ansi = require('cp.ui.ansi') local ansi = require('cp.ui.ansi')
local out = r.stdout or '' local out = r.stdout or ''
local highlights = {} local highlights = {}
if out ~= '' then if out ~= '' then
if cp_config.run_panel.ansi then if config.run_panel.ansi then
local parsed = ansi.parse_ansi_text(out) local parsed = ansi.parse_ansi_text(out)
out = table.concat(parsed.lines, '\n') out = table.concat(parsed.lines, '\n')
highlights = parsed.highlights highlights = parsed.highlights
@ -134,7 +130,7 @@ local function run_single_test_case(contest_config, cp_config, test_case)
end end
end end
local max_lines = cp_config.run_panel.max_output_lines local max_lines = config.run_panel.max_output_lines
local lines = vim.split(out, '\n') local lines = vim.split(out, '\n')
if #lines > max_lines then if #lines > max_lines then
local trimmed = {} local trimmed = {}
@ -180,9 +176,8 @@ local function run_single_test_case(contest_config, cp_config, test_case)
} }
end end
---@param state table
---@return boolean ---@return boolean
function M.load_test_cases(state) function M.load_test_cases()
local tcs = cache.get_test_cases( local tcs = cache.get_test_cases(
state.get_platform() or '', state.get_platform() or '',
state.get_contest_id() or '', state.get_contest_id() or '',
@ -201,18 +196,16 @@ function M.load_test_cases(state)
return #tcs > 0 return #tcs > 0
end end
---@param contest_config ContestConfig
---@param cp_config cp.Config
---@param index number ---@param index number
---@return boolean ---@return boolean
function M.run_test_case(contest_config, cp_config, index) function M.run_test_case(index)
local tc = run_panel_state.test_cases[index] local tc = run_panel_state.test_cases[index]
if not tc then if not tc then
return false return false
end end
tc.status = 'running' tc.status = 'running'
local r = run_single_test_case(contest_config, cp_config, tc) local r = run_single_test_case(tc)
tc.status = r.status tc.status = r.status
tc.actual = r.actual tc.actual = r.actual
@ -230,13 +223,11 @@ function M.run_test_case(contest_config, cp_config, index)
return true return true
end end
---@param contest_config ContestConfig
---@param cp_config cp.Config
---@return RanTestCase[] ---@return RanTestCase[]
function M.run_all_test_cases(contest_config, cp_config) function M.run_all_test_cases()
local results = {} local results = {}
for i = 1, #run_panel_state.test_cases do for i = 1, #run_panel_state.test_cases do
M.run_test_case(contest_config, cp_config, i) M.run_test_case(i)
results[i] = run_panel_state.test_cases[i] results[i] = run_panel_state.test_cases[i]
end end
return results return results

View file

@ -4,7 +4,6 @@ local utils = require('cp.utils')
local function syshandle(result) local function syshandle(result)
if result.code ~= 0 then if result.code ~= 0 then
vim.print(('<%s>'):format(result.stderr))
local msg = 'Scraper failed: ' .. (result.stderr or 'Unknown error') local msg = 'Scraper failed: ' .. (result.stderr or 'Unknown error')
return { success = false, error = msg } return { success = false, error = msg }
end end

View file

@ -42,19 +42,13 @@ end
---@param contest_id string ---@param contest_id string
---@param problem_id string|nil ---@param problem_id string|nil
function M.setup_contest(platform, contest_id, problem_id) function M.setup_contest(platform, contest_id, problem_id)
local config = config_module.get_config()
if not vim.tbl_contains(config.scrapers, platform) then
logger.log(('Scraping disabled for %s.'):format(platform), vim.log.levels.WARN)
return
end
state.set_contest_id(contest_id) state.set_contest_id(contest_id)
cache.load() cache.load()
local function proceed(contest_data) local function proceed(contest_data)
local problems = contest_data.problems local problems = contest_data.problems
local pid = problems[(problem_id and contest_data.index_map[problem_id] or 1)].id local pid = problems[(problem_id and contest_data.index_map[problem_id] or 1)].id
M.setup_problem(pid, language) M.setup_problem(pid)
local cached_len = #vim.tbl_filter(function(p) local cached_len = #vim.tbl_filter(function(p)
return not vim.tbl_isempty(cache.get_test_cases(platform, contest_id, p.id)) return not vim.tbl_isempty(cache.get_test_cases(platform, contest_id, p.id))
@ -96,8 +90,7 @@ function M.setup_contest(platform, contest_id, problem_id)
end end
---@param problem_id string ---@param problem_id string
---@param language? string function M.setup_problem(problem_id)
function M.setup_problem(problem_id, language)
local platform = state.get_platform() local platform = state.get_platform()
if not platform then if not platform then
logger.log('No platform set.', vim.log.levels.ERROR) logger.log('No platform set.', vim.log.levels.ERROR)
@ -111,10 +104,8 @@ function M.setup_problem(problem_id, language)
vim.schedule(function() vim.schedule(function()
vim.cmd.only({ mods = { silent = true } }) vim.cmd.only({ mods = { silent = true } })
local language = config.platforms[platform].default_language
local source_file = state.get_source_file(language) local source_file = state.get_source_file(language)
if not source_file then
return
end
vim.cmd.e(source_file) vim.cmd.e(source_file)
local source_buf = vim.api.nvim_get_current_buf() local source_buf = vim.api.nvim_get_current_buf()

View file

@ -90,10 +90,8 @@ function M.toggle_interactive()
vim.cmd(('mksession! %s'):format(state.saved_interactive_session)) vim.cmd(('mksession! %s'):format(state.saved_interactive_session))
vim.cmd('silent only') vim.cmd('silent only')
local config = config_module.get_config()
local contest_config = config.platforms[state.get_platform() or '']
local execute = require('cp.runner.execute') local execute = require('cp.runner.execute')
local compile_result = execute.compile_problem(contest_config, false) local compile_result = execute.compile_problem()
if not compile_result.success then if not compile_result.success then
require('cp.runner.run').handle_compilation_failure(compile_result.output) require('cp.runner.run').handle_compilation_failure(compile_result.output)
return return
@ -120,7 +118,8 @@ function M.toggle_interactive()
state.set_active_panel('interactive') state.set_active_panel('interactive')
end end
function M.toggle_run_panel(is_debug) ---@param debug? boolean
function M.toggle_run_panel(debug)
if state.get_active_panel() == 'run' then if state.get_active_panel() == 'run' then
if current_diff_layout then if current_diff_layout then
current_diff_layout.cleanup() current_diff_layout.cleanup()
@ -191,7 +190,7 @@ function M.toggle_run_panel(is_debug)
if config.hooks and config.hooks.before_run then if config.hooks and config.hooks.before_run then
config.hooks.before_run(state) config.hooks.before_run(state)
end end
if is_debug and config.hooks and config.hooks.before_debug then if debug and config.hooks and config.hooks.before_debug then
config.hooks.before_debug(state) config.hooks.before_debug(state)
end end
@ -199,7 +198,7 @@ function M.toggle_run_panel(is_debug)
local input_file = state.get_input_file() local input_file = state.get_input_file()
logger.log(('run panel: checking test cases for %s'):format(input_file or 'none')) logger.log(('run panel: checking test cases for %s'):format(input_file or 'none'))
if not run.load_test_cases(state) then if not run.load_test_cases() then
logger.log('no test cases found', vim.log.levels.WARN) logger.log('no test cases found', vim.log.levels.WARN)
return return
end end
@ -284,10 +283,9 @@ function M.toggle_run_panel(is_debug)
setup_keybindings_for_buffer(test_buffers.tab_buf) setup_keybindings_for_buffer(test_buffers.tab_buf)
local execute = require('cp.runner.execute') local execute = require('cp.runner.execute')
local contest_config = config.platforms[state.get_platform() or ''] local compile_result = execute.compile_problem()
local compile_result = execute.compile_problem(contest_config, is_debug)
if compile_result.success then if compile_result.success then
run.run_all_test_cases(contest_config, config) run.run_all_test_cases()
else else
run.handle_compilation_failure(compile_result.output) run.handle_compilation_failure(compile_result.output)
end end