| 47 | } |
| 48 | |
| 49 | ScriptEngine::Outcome ScriptEngine::run(const std::string& path, const std::vector<std::string>& args) |
| 50 | { |
| 51 | // Script printf() and every picoc diagnostic land in ScriptConsole (see |
| 52 | // ckpt_console_stdout): the run's output is readable while it happens, not |
| 53 | // only once it ends. ScriptRunner clears the console before starting. |
| 54 | Picoc pc; |
| 55 | PicocInitialize(&pc, PICOC_STACKSIZE); |
| 56 | |
| 57 | // Anything that fails inside picoc — a parse error, a script exit() — longjmps |
| 58 | // back to here with PicocExitValue set, so no C++ frame holding a resource may |
| 59 | // live between this setjmp and PicocCleanup. |
| 60 | // |
| 61 | // A binding can also throw (std::bad_alloc while a script builds a big string |
| 62 | // or parses a large web response is the realistic one). picoc's C frames are |
| 63 | // compiled with -funwind-tables so the throw can unwind back to here rather |
| 64 | // than std::terminate()ing the app; catch it and report a failed run. The |
| 65 | // catch sits outside the setjmp region so a normal longjmp bypasses it. |
| 66 | try { |
| 67 | if (!PicocPlatformSetExitPoint(&pc)) { |
| 68 | std::vector<char*> argv; |
| 69 | argv.reserve(args.size()); |
| 70 | for (const auto& arg : args) { |
| 71 | argv.push_back(const_cast<char*>(arg.c_str())); |
| 72 | } |
| 73 | |
| 74 | PicocPlatformScanFile(&pc, path.c_str()); |
| 75 | PicocCallMain(&pc, (int)argv.size(), argv.data()); |
| 76 | } |
| 77 | } |
| 78 | catch (const std::exception& e) { |
| 79 | Logging::error("[script] uncaught exception: {}", e.what()); |
| 80 | pc.PicocExitValue = -1; |
| 81 | } |
| 82 | catch (...) { |
| 83 | Logging::error("[script] uncaught non-standard exception"); |
| 84 | pc.PicocExitValue = -1; |
| 85 | } |
| 86 | |
| 87 | Outcome outcome; |
| 88 | outcome.exitValue = pc.PicocExitValue; |
| 89 | |
| 90 | PicocCleanup(&pc); |
| 91 | |
| 92 | outcome.output = ScriptConsole::get().tail(OUTPUT_TAIL); |
| 93 | |
| 94 | Logging::info("[script] {} exited with {}", path, outcome.exitValue); |
| 95 | if (!outcome.output.empty()) { |
| 96 | Logging::info("[script] output: {}", outcome.output); |
| 97 | } |
| 98 | |
| 99 | return outcome; |
| 100 | } |