From 56f5ee63a603b500615ebd95b8569eae71d8c196 Mon Sep 17 00:00:00 2001 From: Sergey Dolgov Date: Tue, 19 Nov 2024 00:20:57 -0800 Subject: [PATCH 1/2] pln: added plnShell Tcl mode --- planning/src/RS/plnShell.cpp | 345 +++++++++++++++++++++++++++++++++++ planning/src/RS/plnShell.h | 81 ++++++++ planning/src/RS/rsDeal.cpp | 4 - planning/src/RS/rsDeal.h | 1 + planning/src/RS/rsEnv.cpp | 12 +- planning/src/RS/rsOpts.cpp | 26 ++- planning/src/RS/rsOpts.h | 3 +- planning/src/main.cpp | 2 +- planning/src/util/pln_log.h | 11 +- 9 files changed, 469 insertions(+), 16 deletions(-) create mode 100644 planning/src/RS/plnShell.cpp create mode 100644 planning/src/RS/plnShell.h diff --git a/planning/src/RS/plnShell.cpp b/planning/src/RS/plnShell.cpp new file mode 100644 index 00000000..f78f8465 --- /dev/null +++ b/planning/src/RS/plnShell.cpp @@ -0,0 +1,345 @@ +#include "RS/plnShell.h" +#include "RS/rsDeal.h" +#include "RS/rsEnv.h" +#include "file_io/pln_Fio.h" + +#include "globals.h" +#include "read_options.h" + +#include "RS/rsCheck.h" +#include "RS/rsVPR.h" +#include "RS/sta_file_writer.h" + +#include + +#ifdef PLN_ENABLE_TCL +#include +#include +#endif + +namespace pln { + +using std::cout; +using std::endl; +using std::string; + +static constexpr size_t CMD_BUF_CAP = 1048574; // ~ 1 MiB + +bool deal_shell(const rsOpts& opts) { + assert(opts.argv_); + assert(opts.argv_[0]); + assert(opts.argc_ > 0); + + uint16_t tr = ltrace(); + const rsEnv& env = rsEnv::inst(); + const string& a0 = env.abs_arg0_; + assert(!a0.empty()); + + bool ok = false; + flush_out(true); + if (tr >= 4) { + lprintf(" abs_arg0: %s\n", a0.c_str()); + flush_out(true); + } + +#ifdef PLN_ENABLE_TCL + Tcl_FindExecutable(a0.c_str()); + + Tcl_Interp* ip = Tcl_CreateInterp(); + assert(ip); + int init_status = Tcl_Init(ip); + if (init_status != TCL_OK) { + flush_out(true); + err_puts(); + lprintf2("[Error] Tcl_Init failed\n"); + err_puts(); + flush_out(true); + return false; + } + + Shell sh(ip, opts); + + ok = sh.loop(); + if (!ok) { + lprintf2("[Error] sh.loop() failed\n"); + } + + Tcl_Finalize(); +#endif + + flush_out(true); + return ok; +} + +#ifdef PLN_ENABLE_TCL +static int help_proc(void* clientData, Tcl_Interp* ip, int argc, CStr argv[]) { + lprintf("help_proc: need help\n"); + + return 0; +} + +static int reportApp_proc(void* clientData, Tcl_Interp* ip, int argc, CStr argv[]) { + const rsEnv& env = rsEnv::inst(); + lprintf(" ==== reportApp ====\n"); + lprintf(" PLANNER ver. %s\n", env.shortVerCS()); + lprintf(" ====\n"); + + return 0; +} +#endif + +Shell* Shell::instance_ = nullptr; + +// constructor adds commands to interp_ +Shell::Shell(Tcl_Interp* ip, const rsOpts& opts) noexcept : opts_(opts), interp_(ip) { +#ifdef PLN_ENABLE_TCL + assert(interp_); + assert(!instance_); + instance_ = this; + + addCmd("help", help_proc, nullptr, nullptr); + addCmd("report_app", reportApp_proc, nullptr, nullptr); +#endif +} + +Shell::~Shell() { +#ifdef PLN_ENABLE_TCL + // if (interp_) + // Tcl_DeleteInterp(interp_); +#endif + instance_ = nullptr; +} + +int Shell::evalFile(CStr fn, string& result) { + int code = 0; + +#ifdef PLN_ENABLE_TCL + assert(fn and fn[0]); + assert(interp_); + result.clear(); + if (!fn or !fn[0]) { + result = "(evalFile: bad filename)"; + return TCL_ERROR; + } + + code = Tcl_EvalFile(interp_, fn); + + if (code >= TCL_ERROR) + result = tcStackTrace(code); + else + result = Tcl_GetStringResult(interp_); +#endif + + return code; +} + +int Shell::evalCmd(CStr cmd, string& result) { + int code = 0; + +#ifdef PLN_ENABLE_TCL + assert(cmd and cmd[0]); + assert(interp_); + result.clear(); + + code = Tcl_Eval(interp_, cmd); + + if (code >= TCL_ERROR) + result = tcStackTrace(code); + else + result = Tcl_GetStringResult(interp_); +#endif + + return code; +} + +int Shell::evalCmd(CStr cmd) { + int code = 0; +#ifdef PLN_ENABLE_TCL + assert(cmd and cmd[0]); + assert(interp_); + code = Tcl_Eval(interp_, cmd); +#endif + return code; +} + +void Shell::addCmd(CStr cmdName, Tcl_CmdProc proc, + ClientData clientData, Tcl_CmdDeleteProc* delProc) { +#ifdef PLN_ENABLE_TCL + assert(cmdName and cmdName[0]); + assert(interp_); + Tcl_CreateCommand(interp_, cmdName, proc, clientData, delProc); +#endif +} + +string Shell::tcStackTrace(int code) const noexcept { + string output; + +#ifdef PLN_ENABLE_TCL + assert(interp_); + Tcl_Obj* options = Tcl_GetReturnOptions(interp_, code); + Tcl_Obj* key = Tcl_NewStringObj("-errorinfo", -1); + Tcl_Obj* stackTrace = nullptr; + + Tcl_IncrRefCount(key); + Tcl_DictObjGet(nullptr, options, key, &stackTrace); + Tcl_DecrRefCount(key); + + output = Tcl_GetString(stackTrace); + Tcl_DecrRefCount(options); +#endif + + return output; +} + +bool Shell::loop() { +#ifdef PLN_ENABLE_TCL + assert(interp_); + + if (opts_.input_) { + // evaluate the script passed to 'planning' + string output; + int code = evalFile(opts_.input_, output); + if (code == TCL_OK) { + lout() << output << endl; + } else { + flush_out(true); + lprintf2("[Error] (tcl) could not evaluate command line input: %s\n", + opts_.input_); + flush_out(true); + return false; + } + } + + // start interactive UI (readline loop) + Terminal t(interp_); + t.rlLoop(); +#endif + + return true; +} + +Shell::Terminal::Terminal(Tcl_Interp* ip) noexcept + : interp_(ip), cmdObj_(nullptr), + isTerm_(false), isEOF_(false), + contPrompt_(false) { + + partLine_ = (char*)::calloc(CMD_BUF_CAP + 2, 1); + assert(partLine_); + +#ifdef PLN_ENABLE_TCL + isTerm_ = ::isatty(0); + if (isTerm_) Tcl_SetVar(interp_, "::tcl_interactive", "1", TCL_GLOBAL_ONLY); + + resetCmdObj(); +#endif +} + +Shell::Terminal::~Terminal() { + p_free(partLine_); +} + +bool Shell::Terminal::evalCo(CStr lbuf) { + if (!lbuf) return false; + if (!lbuf[0]) return false; + + int ecode = 0; + +#ifdef PLN_ENABLE_TCL + char cbuf[CMD_BUF_CAP + 2]; + cbuf[0] = 0; + cbuf[1] = 0; + cbuf[CMD_BUF_CAP - 1] = 0; + cbuf[CMD_BUF_CAP] = 0; + cbuf[CMD_BUF_CAP + 1] = 0; + + if (partLine_[0]) { + // continue incomplete line + ::strncpy(cbuf, partLine_, CMD_BUF_CAP); + str::chomp(cbuf); + ::strcat(cbuf, " "); + ::strcat(cbuf, lbuf); + partLine_[0] = 0; + } else { + ::strcpy(cbuf, lbuf); + } + + ecode = execCmdObj(cbuf); +#endif + + return ecode == TCL_OK; +} + +int Shell::Terminal::execCmdObj(CStr cbuf) { + assert(partLine_); + int code = 0; + +#ifdef PLN_ENABLE_TCL + Tcl_SetStringObj(cmdObj_, cbuf, ::strlen(cbuf)); + + if (not Tcl_CommandComplete(cbuf)) { + contPrompt_ = true; + ::strcpy(partLine_, cbuf); + ::strcat(partLine_, "\n"); + return TCL_OK; + } + contPrompt_ = false; + + code = Tcl_RecordAndEvalObj(interp_, cmdObj_, TCL_EVAL_GLOBAL); + resetCmdObj(); + + Tcl_Obj* objResult = Tcl_GetObjResult(interp_); + Tcl_IncrRefCount(objResult); + int len = 0; + CStr csResult = Tcl_GetStringFromObj(objResult, &len); + + if (code != TCL_OK) { + if (csResult and len > 0) { + lprintf2("[Error] (Tcl) %s\n", csResult); + } + } + else if (isTerm_) { + if (csResult and len > 0 and Tcl_GetStdChannel(TCL_STDOUT)) { + lprintf("%s\n", csResult); + } + } + Tcl_DecrRefCount(objResult); + Tcl_ResetResult(interp_); +#endif + + return code; +} + +void Shell::Terminal::resetCmdObj() noexcept { +#ifdef PLN_ENABLE_TCL + if (cmdObj_) Tcl_DecrRefCount(cmdObj_); + cmdObj_ = Tcl_NewObj(); + assert(cmdObj_); + Tcl_IncrRefCount(cmdObj_); +#endif +} + +void Shell::Terminal::rlLoop() { +#ifdef PLN_ENABLE_TCL + const Shell& sh = Shell::inst(); + + char lbuf[CMD_BUF_CAP + 2]; + lbuf[0] = 0; + lbuf[1] = 0; + lbuf[CMD_BUF_CAP - 1] = 0; + lbuf[CMD_BUF_CAP] = 0; + lbuf[CMD_BUF_CAP + 1] = 0; + + while (!sh.inExit()) { + if (not Tcl_GetStdChannel(TCL_STDIN)) break; + CStr line = ::readline(contPrompt_ ? ">> " : "pln> "); + if (not line) break; + if (not line[0]) continue; + ::strncpy(lbuf, line, CMD_BUF_CAP); + str::chomp(lbuf); + evalCo(lbuf); + } +#endif +} + +} + diff --git a/planning/src/RS/plnShell.h b/planning/src/RS/plnShell.h new file mode 100644 index 00000000..5850e615 --- /dev/null +++ b/planning/src/RS/plnShell.h @@ -0,0 +1,81 @@ +#pragma once +// +// class Shell - Tcl_Interp wrapper +// based on FOEDAG/src/Tcl/TclInterpreter.h +// + +#ifndef _pln_Shell_H_0df6e17bc86545_ +#define _pln_Shell_H_0df6e17bc86545_ + +#include +#include "RS/rsEnv.h" + +namespace pln { + +class Shell { +public: + using string = std::string; + // using TCallback0 = std::function; + // using TCallback1 = std::function; + // using TCallback2 = std::function; + + struct Terminal; + + Shell(Tcl_Interp* ip, const rsOpts& opts) noexcept; + ~Shell(); + + bool loop(); + bool inExit() const noexcept { return false; } + + int evalFile(CStr fn, string& result); + int evalCmd(CStr cmd, string& result); + int evalCmd(CStr cmd); + + void setResult(const string& result); + + void addCmd(CStr cmdName, Tcl_CmdProc proc, + ClientData clientData, Tcl_CmdDeleteProc* delProc); + + Tcl_Interp* interp() noexcept { return interp_; } + + static Shell* instance() noexcept { return instance_; } + static Shell& inst() noexcept { + assert(instance_); + return *instance_; + } + +private: + string tcHistoryScript(); + string tcStackTrace(int code) const noexcept; + + const rsOpts& opts_; + Tcl_Interp* interp_ = nullptr; + static Shell* instance_; +}; + +struct Shell::Terminal { + Terminal(Tcl_Interp* inter) noexcept; + ~Terminal(); + + void rlLoop(); + + bool evalCo(CStr lbuf); + +private: + int execCmdObj(CStr cbuf); + void resetCmdObj() noexcept; + + // DATA: + Tcl_Interp* interp_ = nullptr; + Tcl_Obj* cmdObj_ = nullptr; + + bool isTerm_ = false, isEOF_ = false; + bool contPrompt_ = false; + + char* partLine_ = nullptr; +}; + +} + +#endif + diff --git a/planning/src/RS/rsDeal.cpp b/planning/src/RS/rsDeal.cpp index ac911575..ec92393d 100644 --- a/planning/src/RS/rsDeal.cpp +++ b/planning/src/RS/rsDeal.cpp @@ -426,10 +426,6 @@ void deal_units(const rsOpts& opts) { #endif } -bool deal_shell(const rsOpts& opts) { - return true; -} - bool validate_partition_opts(const rsOpts& opts) { using namespace fio; uint16_t tr = ltrace(); diff --git a/planning/src/RS/rsDeal.h b/planning/src/RS/rsDeal.h index 1cbce93c..7ddb0308 100644 --- a/planning/src/RS/rsDeal.h +++ b/planning/src/RS/rsDeal.h @@ -1,3 +1,4 @@ +#pragma once // // 'planning' exec modes - entry points to various operations. // 'planning' main() calls one of these deal_ functions. diff --git a/planning/src/RS/rsEnv.cpp b/planning/src/RS/rsEnv.cpp index 40ef89d1..7c33ae93 100644 --- a/planning/src/RS/rsEnv.cpp +++ b/planning/src/RS/rsEnv.cpp @@ -292,12 +292,18 @@ void rsEnv::listDevEnv() const noexcept { cout << endl; -#ifdef _PLN_VEC_BOUNDS_CHECK - printf("\t _PLN_VEC_BOUNDS_CHECK : ON => stl_vector BC enabled\n"); +#ifdef PLN_ENABLE_TCL + printf("\t PLN_ENABLE_TCL : ON => Tcl shell enabled\n"); #else - printf("\t _PLN_VEC_BOUNDS_CHECK : OFF => std_vector BC disabled\n"); + printf("\t PLN_ENABLE_TCL : OFF => Tcl shell disabled\n"); #endif +// #ifdef _PLN_VEC_BOUNDS_CHECK +// printf("\t _PLN_VEC_BOUNDS_CHECK : ON => stl_vector BC enabled\n"); +// #else +// printf("\t _PLN_VEC_BOUNDS_CHECK : OFF => std_vector BC disabled\n"); +// #endif + #ifdef PLN_JEMALLOC LIST_DEC(PLN_JEMALLOC); #endif diff --git a/planning/src/RS/rsOpts.cpp b/planning/src/RS/rsOpts.cpp index 6e560947..1dbb6e7a 100644 --- a/planning/src/RS/rsOpts.cpp +++ b/planning/src/RS/rsOpts.cpp @@ -21,7 +21,7 @@ static CStr _det_ver_[] = { "VV", "vv", "VVV", "vvv", "det_ver", static CStr _help_[] = { "H", "HH", "h", "hh", "help", "hel", "hlp", "he", nullptr }; -static CStr _fun_[] = { "F", "FF", "ff", "fu", "fun", "func", +static CStr _fun_[] = { "F", "FF", "ff", "fn", "fu", "fun", "func", "funct", "function", nullptr }; static CStr _check_[] = { "CH", "CHECK", "ch", "CC", "cc", "che", "chec", @@ -31,6 +31,9 @@ static CStr _clean_[] = { "CLEAN", "CLEANUP", "clean", "cleanup", "clean_up", "cleanup_blif", "cleanupblif", "clean_up_blif", "cle", "clea", nullptr }; +static CStr _shell_[] = { "SHELL", "SH", "shell", "sh", "she", "shel" + "Shell", nullptr }; + static CStr _csv_[] = {"CSV", "cs", "csv", "Csv", nullptr}; static CStr _xml_[] = {"XM", "xm", "xml", "Xml", "XML", nullptr}; @@ -202,7 +205,6 @@ static char* make_file_name(CStr arg) noexcept { void rsOpts::setFunction(CStr fun) noexcept { function_ = nullptr; - if (!fun) return; uint16_t tr = ltrace(); @@ -215,7 +217,15 @@ void rsOpts::setFunction(CStr fun) noexcept { "shell", // 6 nullptr}; string f = str::s2lower(fun); - if (f.empty()) return; + if (f.empty()) { + if (shell_) { + function_ = s_funList[6]; + if (tr >= 5) + lprintf("Opts::setFunction: %s\n", function_); + assert(is_fun_shell()); + } + return; + } if (f == "cmd") { function_ = s_funList[0]; if (tr >= 5) @@ -258,8 +268,8 @@ void rsOpts::setFunction(CStr fun) noexcept { assert(is_fun_route()); return; } - if (f == "sh" or f == "SH" or f == "shell" or - f == "shel" or f == "Shell") { + if (f == "sh" or f == "shell" or + f == "shel" or f == "ss") { function_ = s_funList[6]; if (tr >= 5) lprintf("Opts::setFunction: %s\n", function_); @@ -358,6 +368,10 @@ void rsOpts::parse(int argc, const char** argv) noexcept { cleanup_ = true; continue; } + if (op_match(arg, _shell_)) { + shell_ = true; + continue; + } // -- @@ -482,7 +496,7 @@ void rsOpts::parse(int argc, const char** argv) noexcept { editsFile_ = p_strdup(edtf); cmapFile_ = p_strdup(cmapf); - if (fun) + if (fun or shell_) setFunction(fun); else if (isCmdInput()) setFunction("cmd"); diff --git a/planning/src/RS/rsOpts.h b/planning/src/RS/rsOpts.h index 13eb50bd..b82e77eb 100644 --- a/planning/src/RS/rsOpts.h +++ b/planning/src/RS/rsOpts.h @@ -74,7 +74,7 @@ struct rsOpts { char* output_ = nullptr; char* assignOrder_ = nullptr; - int test_id_ = 0; // TestCase ID + int test_id_ = 0; // TestCase ID int trace_ = 0; int traceIndex_ = 0; @@ -85,6 +85,7 @@ struct rsOpts { bool help_ = false; bool check_ = false; bool cleanup_ = false; + bool shell_ = false; // shortcut for --function shell std::bitset units_; diff --git a/planning/src/main.cpp b/planning/src/main.cpp index 7026721a..827508cf 100644 --- a/planning/src/main.cpp +++ b/planning/src/main.cpp @@ -1,4 +1,4 @@ -static const char* _pln_VERSION_STR = "pln0365"; +static const char* _pln_VERSION_STR = "pln0366"; #include "RS/rsEnv.h" #include "RS/rsDeal.h" diff --git a/planning/src/util/pln_log.h b/planning/src/util/pln_log.h index 87be5110..2f67f9f2 100644 --- a/planning/src/util/pln_log.h +++ b/planning/src/util/pln_log.h @@ -277,12 +277,21 @@ inline string concat(CStr a) noexcept { } inline CStr trimFront(CStr z) noexcept { - if (z && *z) { + if (z and z[0]) { while (std::isspace(*z)) z++; } return z; } +// remove '\n' at the end +inline void chomp(char* z) noexcept { + if (z and z[0]) { + size_t len = ::strlen(z); + if (z[len-1] == '\n') + z[len-1] = 0; + } +} + inline size_t hashf(CStr z) noexcept { if (!z) return 0; if (!z[0]) return 1; From 3b0d0ef6d1b26976644836a2a3de8dcc7af58474 Mon Sep 17 00:00:00 2001 From: Sergey Dolgov Date: Tue, 19 Nov 2024 02:13:54 -0800 Subject: [PATCH 2/2] plnShell: fix compilation in NO_TCL mode --- planning/src/RS/plnShell.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/planning/src/RS/plnShell.h b/planning/src/RS/plnShell.h index 5850e615..295a78a0 100644 --- a/planning/src/RS/plnShell.h +++ b/planning/src/RS/plnShell.h @@ -7,7 +7,17 @@ #ifndef _pln_Shell_H_0df6e17bc86545_ #define _pln_Shell_H_0df6e17bc86545_ -#include +#ifdef PLN_ENABLE_TCL + #include +#else + struct Tcl_Interp; + struct Tcl_Obj; + using Tcl_CmdProc = void*; + using Tcl_CmdDeleteProc = void*; + using ClientData = void*; + #define TCL_OK 0 +#endif + #include "RS/rsEnv.h" namespace pln {