2026-07-18 18:48:23 +02:00
|
|
|
#include "util.h"
|
|
|
|
|
#include "eval.h"
|
|
|
|
|
#include "eval_python.h"
|
|
|
|
|
#include "eval_cpp.h"
|
|
|
|
|
#include "log.h"
|
|
|
|
|
#include "show.h"
|
|
|
|
|
#include "katom.h"
|
|
|
|
|
#include "file.h"
|
2026-07-24 21:37:58 +02:00
|
|
|
#include <algorithm>
|
2026-07-18 18:48:23 +02:00
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
std::string shell(State state, std::string command, Locator loc)
|
|
|
|
|
{
|
|
|
|
|
command = state.subst(command);
|
|
|
|
|
FILE* pipe = popen(command.c_str(), "r");
|
|
|
|
|
if (!pipe) {
|
|
|
|
|
throw Parsing_error(
|
|
|
|
|
"Could not run command:\n" + command, loc, false);
|
|
|
|
|
}
|
|
|
|
|
char buffer[128];
|
|
|
|
|
std::string result = "";
|
|
|
|
|
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
|
|
|
result += buffer;
|
|
|
|
|
}
|
|
|
|
|
pclose(pipe);
|
|
|
|
|
// std::cout << "Command output:\n" << result << "\n";
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool is_haskell_file(const std::string& text)
|
|
|
|
|
{
|
|
|
|
|
std::string trimmed = trim(text);
|
|
|
|
|
if (trimmed.size() < 4) return false;
|
|
|
|
|
if (trimmed.find(' ') != std::string::npos) return false;
|
|
|
|
|
if (trimmed.find('\n') != std::string::npos) return false;
|
|
|
|
|
return trimmed.substr(trimmed.size() - 3) == ".hs";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string run_haskell(const std::string& hsfile, Locator loc)
|
|
|
|
|
{
|
|
|
|
|
std::string command = "runghc " + hsfile + " 2>&1";
|
|
|
|
|
FILE* pipe = popen(command.c_str(), "r");
|
|
|
|
|
if (!pipe) {
|
|
|
|
|
throw Parsing_error(
|
|
|
|
|
"Could not run runghc.", loc, false);
|
|
|
|
|
}
|
|
|
|
|
char buffer[128];
|
|
|
|
|
std::string result = "";
|
|
|
|
|
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
|
|
|
result += buffer;
|
|
|
|
|
}
|
|
|
|
|
int status = pclose(pipe);
|
|
|
|
|
|
|
|
|
|
if (status != 0) {
|
|
|
|
|
throw Parsing_error(
|
|
|
|
|
"Haskell evaluation failed:\n" + result, loc, false);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string haskell(State state, std::string code, Locator loc)
|
|
|
|
|
{
|
|
|
|
|
if (system("which runghc > /dev/null 2>&1") != 0) {
|
|
|
|
|
throw Parsing_error(
|
|
|
|
|
"@eval with the :haskell argument requires runghc, which was not found in PATH.\n"
|
|
|
|
|
"Install it using GHCup; see https://www.haskell.org/ghcup/install/.",
|
|
|
|
|
loc, false);
|
|
|
|
|
}
|
|
|
|
|
code = state.subst(code);
|
|
|
|
|
|
|
|
|
|
if (is_haskell_file(code)) {
|
|
|
|
|
return run_haskell(trim(code), loc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char* tmpdir = std::getenv("TMPDIR");
|
|
|
|
|
if (!tmpdir) tmpdir = "/tmp";
|
|
|
|
|
std::string tmpl = std::string(tmpdir) + "/klammertext_haskell_XXXXXX";
|
|
|
|
|
std::vector<char> tmppath(tmpl.begin(), tmpl.end());
|
|
|
|
|
tmppath.push_back('\0');
|
|
|
|
|
int fd = mkstemp(tmppath.data());
|
|
|
|
|
if (fd < 0) {
|
|
|
|
|
throw Parsing_error(
|
|
|
|
|
"Could not create temporary file for Haskell evaluation.", loc, false);
|
|
|
|
|
}
|
|
|
|
|
std::string hsfile = std::string(tmppath.data()) + ".hs";
|
|
|
|
|
close(fd);
|
|
|
|
|
rename(tmppath.data(), hsfile.c_str());
|
|
|
|
|
|
|
|
|
|
FILE* f = fopen(hsfile.c_str(), "w");
|
|
|
|
|
if (!f) {
|
|
|
|
|
unlink(hsfile.c_str());
|
|
|
|
|
throw Parsing_error(
|
|
|
|
|
"Could not write temporary Haskell file.", loc, false);
|
|
|
|
|
}
|
|
|
|
|
fprintf(f, "%s\n", code.c_str());
|
|
|
|
|
fclose(f);
|
|
|
|
|
|
|
|
|
|
std::string result = run_haskell(hsfile, loc);
|
|
|
|
|
unlink(hsfile.c_str());
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void check_cpp_arguments(katom_list args, Locator loc)
|
|
|
|
|
{
|
|
|
|
|
if (args.size() != 4 && args.size() != 5) {
|
|
|
|
|
std::stringstream ss {};
|
|
|
|
|
ss << "Incorrect @eval format for a C++ function. Either:\n"
|
|
|
|
|
<< " @eval :cpp <library-basename> @\n"
|
|
|
|
|
<< "or\n"
|
|
|
|
|
<< " @eval :cpp <library-basename> <function-name> @\n"
|
|
|
|
|
<< "In the first case, the library basename is used for the function name.";
|
|
|
|
|
throw Argument_error(ss.str(), loc, false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-07-25 21:16:21 +02:00
|
|
|
std::string Eval::eval_command(katom_iter begin, katom_iter end)
|
2026-07-18 18:48:23 +02:00
|
|
|
{
|
|
|
|
|
(void)K::log(3, *begin, *(end - 1));
|
|
|
|
|
//msg() << "in Eval::eval:\n" << ktype << kall << kindex << std::pair(begin, end) << "\n";
|
|
|
|
|
katom_iter first = after_whitespace(begin + 1);
|
|
|
|
|
std::string eval_result = "[unevaluated]";
|
|
|
|
|
std::string first_word = first->m_text;
|
|
|
|
|
int offset = first_word[0] == ':' ? 1 : 0;
|
|
|
|
|
std::string command = as_string(first + offset, end - 1, true);
|
|
|
|
|
|
|
|
|
|
if (offset == 0 || first_word == ":python") { // Default is Python
|
|
|
|
|
Eval_python E_python(m_machine, begin->m_loc);
|
|
|
|
|
eval_result = E_python.eval(command);
|
|
|
|
|
} else if (first_word == ":shell") {
|
|
|
|
|
eval_result = shell(m_machine.m_state, command, begin->m_loc);
|
|
|
|
|
} else if (first_word == ":haskell") {
|
|
|
|
|
eval_result = haskell(m_machine.m_state, command, begin->m_loc);
|
|
|
|
|
} else if (first_word == ":cpp") {
|
|
|
|
|
//msg() << ":cpp: " << first_word << *(begin + 4) << "\n";
|
|
|
|
|
for (auto ki = begin; ki < end; ki++) {
|
|
|
|
|
//msg() << " " << kall << kindex << *ki << "\n";
|
|
|
|
|
}
|
|
|
|
|
katom_list args = text_katoms(begin, end);
|
|
|
|
|
check_cpp_arguments(args, begin->m_loc);
|
|
|
|
|
//msg() << "args: |" << args << "|\n";
|
|
|
|
|
std::string lib_text = args[2].m_text;
|
|
|
|
|
std::string khome = m_machine.m_state.value("KLAMMERTEXT_HOME", false);
|
|
|
|
|
if (!khome.empty()) {
|
|
|
|
|
lib_text = string_replace(lib_text, "*KLAMMERTEXT_HOME*", khome);
|
|
|
|
|
}
|
|
|
|
|
fs::path libpath(lib_text + ".so");
|
|
|
|
|
libpath = fs::absolute(libpath);
|
|
|
|
|
std::string funcname = args.size() == 4 ? libpath.stem().string() : args[3].m_text;
|
|
|
|
|
Eval_cpp E_cpp(m_machine, begin->m_loc);
|
|
|
|
|
eval_result = E_cpp.eval(libpath, funcname);
|
|
|
|
|
}
|
2026-07-25 21:16:21 +02:00
|
|
|
return eval_result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
katom_list Eval::eval(katom_iter begin, katom_iter end)
|
|
|
|
|
{
|
|
|
|
|
std::string eval_result = eval_command(begin, end);
|
2026-07-18 18:48:23 +02:00
|
|
|
katom_list result {};
|
|
|
|
|
Machine M = m_machine;
|
2026-07-24 21:37:58 +02:00
|
|
|
size_t before = M.m_katoms.size();
|
2026-07-18 18:48:23 +02:00
|
|
|
M.read(eval_result);
|
2026-07-24 21:37:58 +02:00
|
|
|
// If the @eval produced more Klammertext -- the read-back result still
|
|
|
|
|
// holds klammers to reduce (a "generator", e.g. a Python klammer returning
|
|
|
|
|
// a @table call with data) -- then its text is writer content: escape it
|
|
|
|
|
// for the target before applying, exactly as typed input is escaped, so a
|
|
|
|
|
// "%" in the data becomes "\%". If it produced final target markup (no
|
|
|
|
|
// klammers left, a "renderer" such as @table itself emitting \begin{tabular})
|
|
|
|
|
// leave it untouched. ^'...'^ literal spans are skipped by the escape pass,
|
|
|
|
|
// so a generator can still carry raw target markup.
|
|
|
|
|
bool has_klammer = std::any_of(
|
|
|
|
|
M.m_katoms.begin() + before, M.m_katoms.end(), begin_apply);
|
|
|
|
|
M.apply(M.m_state.value("K_target"), false, has_klammer);
|
2026-07-18 18:48:23 +02:00
|
|
|
result = trim(M.m_katoms);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|