#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" #include #include 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 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 @\n" << "or\n" << " @eval :cpp @\n" << "In the first case, the library basename is used for the function name."; throw Argument_error(ss.str(), loc, false); } } katom_list Eval::eval(katom_iter begin, katom_iter end) { (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); } katom_list result {}; Machine M = m_machine; size_t before = M.m_katoms.size(); M.read(eval_result); // 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); result = trim(M.m_katoms); return result; }