/**
*
* @file NoteKernel.cpp
* @author Gaspard Kirira
*
* Copyright 2026, Gaspard Kirira.
* All rights reserved.
* https://github.com/vixcpp/note
*
* Use of this source code is governed by a MIT license
* that can be found in the LICENSE file.
*
* Vix Note
*
*/
#include
#include
#include
#include
#include
#include
#include
#include
namespace
{
bool note_dispatch_debug_enabled()
{
const char *value = std::getenv("VIX_NOTE_DEBUG_DISPATCH");
return value != nullptr && *value != '\0' && std::string(value) != "0";
}
std::string dispatch_kind_name(vix::note::NoteCellKind kind)
{
return std::string(vix::note::to_string(kind));
}
}
namespace vix::note
{
bool NoteKernelRunResult::has_failures() const noexcept
{
if (failed > 0)
{
return true;
}
for (const NoteResult &result : results)
{
if (result.failed())
{
return true;
}
}
return false;
}
bool NoteKernelRunResult::has_skipped() const noexcept
{
if (skipped > 0)
{
return true;
}
for (const NoteResult &result : results)
{
if (result.was_skipped())
{
return true;
}
}
return false;
}
bool NoteKernelRunResult::has_results() const noexcept
{
return !results.empty();
}
NoteKernel::NoteKernel()
{
sync_options();
}
NoteKernel::NoteKernel(NoteDocument document)
: session_(std::move(document))
{
sync_options();
}
NoteKernel::NoteKernel(NoteKernelOptions options)
: options_(std::move(options))
{
sync_options();
}
NoteKernel::NoteKernel(NoteDocument document, NoteKernelOptions options)
: options_(std::move(options)),
session_(std::move(document))
{
sync_options();
}
const NoteKernelOptions &NoteKernel::options() const noexcept
{
return options_;
}
void NoteKernel::set_options(NoteKernelOptions options)
{
options_ = std::move(options);
sync_options();
}
const ProjectContext &NoteKernel::project_context() const noexcept
{
return options_.projectContext;
}
const NoteExtensionRegistry &NoteKernel::extension_registry() const noexcept
{
return options_.extensionRegistry ? *options_.extensionRegistry : defaultExtensionManager_.registry();
}
void NoteKernel::set_project_context(ProjectContext context)
{
options_.projectContext = std::move(context);
sync_options();
}
const NoteSession &NoteKernel::session() const noexcept
{
return session_;
}
NoteSession &NoteKernel::session() noexcept
{
return session_;
}
const NoteDocument &NoteKernel::document() const noexcept
{
return session_.document();
}
NoteDocument &NoteKernel::document() noexcept
{
return session_.document();
}
void NoteKernel::set_document(NoteDocument document)
{
session_.set_document(std::move(document));
}
std::size_t NoteKernel::cell_count() const noexcept
{
return session_.cell_count();
}
bool NoteKernel::has_cell(std::size_t index) const noexcept
{
return session_.has_cell(index);
}
bool NoteKernel::can_execute_cell(std::size_t index) const noexcept
{
const NoteCell *cell = session_.cell_at(index);
const NoteExtensionRegistry *registry = options_.extensionRegistry ? options_.extensionRegistry : &defaultExtensionManager_.registry();
const NoteCellTypeDescriptor *cellType = cell == nullptr ? nullptr : registry->find_cell_type(cell->type_id());
return cellType != nullptr && cellType->executable;
}
bool NoteKernel::can_execute_cell(const std::string &id) const noexcept
{
const std::optional index =
session_.cell_index(id);
if (!index)
{
return false;
}
return can_execute_cell(*index);
}
std::optional NoteKernel::cell_index(const std::string &id) const noexcept
{
return session_.cell_index(id);
}
NoteResult NoteKernel::run_cell(std::size_t index)
{
NoteCell *cell = session_.cell_at(index);
if (cell == nullptr)
{
return NoteResult::failure("cell index out of range", 1)
.add_error("cell index out of range");
}
NoteResult result = run_registered_cell(*cell);
if (!result.was_skipped())
{
(void)session_.apply_result(index, result);
}
return result;
}
NoteResult NoteKernel::run_cell(const std::string &id)
{
const std::optional index =
session_.cell_index(id);
if (!index)
{
return NoteResult::failure("cell not found: " + id, 1)
.add_error("cell not found: " + id);
}
return run_cell(*index);
}
NoteResult NoteKernel::stop_cpp_process(std::size_t index)
{
NoteCell *cell = session_.cell_at(index);
if (cell == nullptr || cell->kind() != NoteCellKind::Cpp)
{
return NoteResult::failure("cell is not a C++ cell", 1)
.add_error("cell is not a C++ cell");
}
NoteResult result = cppRunner_.stop();
(void)session_.apply_result(index, result);
return result;
}
NoteKernelRunResult NoteKernel::run_all()
{
NoteKernelRunResult run;
for (std::size_t i = 0; i < session_.cell_count(); ++i)
{
++run.visited;
const NoteCell *cell = session_.cell_at(i);
if (cell == nullptr)
{
continue;
}
const NoteExtensionRegistry *registry = options_.extensionRegistry ? options_.extensionRegistry : &defaultExtensionManager_.registry();
const NoteCellTypeDescriptor *cellType = registry->find_cell_type(cell->type_id());
if (cellType == nullptr || !cellType->executable)
{
if (options_.includeNonExecutableAsSkipped)
{
NoteResult skipped =
NoteResult::skipped("cell is not executable");
run.results.push_back(skipped);
++run.skipped;
}
continue;
}
NoteResult result = run_cell(i);
run.results.push_back(result);
++run.executed;
if (result.failed())
{
++run.failed;
run.ok = false;
if (options_.stopOnFirstFailure)
{
run.stopped = true;
break;
}
}
else if (result.was_skipped())
{
++run.skipped;
}
}
if (run.has_failures())
{
run.ok = false;
}
return run;
}
NoteKernelRunResult NoteKernel::run_executable_cells()
{
NoteKernelRunResult run;
for (std::size_t i = 0; i < session_.cell_count(); ++i)
{
const NoteCell *cell = session_.cell_at(i);
const NoteExtensionRegistry *registry = options_.extensionRegistry ? options_.extensionRegistry : &defaultExtensionManager_.registry();
const NoteCellTypeDescriptor *cellType = cell == nullptr ? nullptr : registry->find_cell_type(cell->type_id());
if (cell == nullptr || cellType == nullptr || !cellType->executable)
{
continue;
}
++run.visited;
NoteResult result = run_cell(i);
run.results.push_back(result);
++run.executed;
if (result.failed())
{
++run.failed;
run.ok = false;
if (options_.stopOnFirstFailure)
{
run.stopped = true;
break;
}
}
else if (result.was_skipped())
{
++run.skipped;
}
}
if (run.has_failures())
{
run.ok = false;
}
return run;
}
void NoteKernel::clear_outputs()
{
session_.clear_outputs();
}
void NoteKernel::reset_execution()
{
session_.reset_execution();
session_.clear_records();
}
void NoteKernel::reset()
{
session_.clear_outputs();
session_.reset_execution();
session_.clear_records();
}
NoteResult NoteKernel::run_reply_cell(const NoteCell &cell)
{
return replyRunner_.run_cell(cell);
}
NoteResult NoteKernel::run_cpp_cell_internal(const NoteCell &cell)
{
return cppRunner_.run_cell(cell);
}
NoteResult NoteKernel::run_registered_cell(NoteCell &cell)
{
const NoteExtensionRegistry *registry = options_.extensionRegistry ? options_.extensionRegistry : &defaultExtensionManager_.registry();
const NoteCellTypeDescriptor *cellType = registry->find_cell_type(cell.type_id());
if (cellType == nullptr)
{
return NoteResult::failure("Cell type '" + cell.type_id() + "' is not available. Install a compatible Vix Note extension.", 1)
.add_error("Cell type '" + cell.type_id() + "' is not available. Install a compatible Vix Note extension.");
}
if (!cellType->executable)
{
return NoteResult::skipped("cell is not executable");
}
// Built-in C++ execution owns persistent child processes in cppRunner_.
// Do not use the registry factory here: it returns a temporary adapter
// whose runner would be destroyed as soon as this call returns.
if (cell.type_id() == "cpp")
{
return cppRunner_.run_cell(cell);
}
auto runner = registry->create_runner_for(cell.type_id());
if (!runner)
{
return NoteResult::failure("missing runner for cell type: " + cell.type_id(), 1)
.add_error("missing runner for cell type: " + cell.type_id());
}
if (note_dispatch_debug_enabled())
{
std::string runnerName = cell.type_id();
std::string extensionId;
if (cell.type_id() == "cpp")
runnerName = "cpp";
else if (cell.type_id() == "reply")
runnerName = "reply";
else if (!cellType->extensionId.empty())
{
runnerName = "extension";
extensionId = cellType->extensionId;
}
std::cerr