diff --git a/luprex/cpp/core/source.cpp b/luprex/cpp/core/source.cpp index 73fb0590..30e4f615 100644 --- a/luprex/cpp/core/source.cpp +++ b/luprex/cpp/core/source.cpp @@ -29,7 +29,7 @@ LuaDefine(makeclass, "classname", "create a class if it doesn't already exist") return LS.result(); } -LuaDefine(getclass, "classname", "get the classtab with the specified name") { +LuaDefine(getclass, "x", "get a class table") { LuaArg classname; LuaRet classtab; LuaDefStack LS(L, classname, classtab); @@ -40,7 +40,7 @@ LuaDefine(getclass, "classname", "get the classtab with the specified name") { return LS.result(); } -LuaDefine(classname, "classtable", "get the class name from a class table") { +LuaDefine(classname, "x", "get a class name") { LuaArg table; LuaRet result; LuaDefStack LS(L, table, result); @@ -53,17 +53,52 @@ LuaDefine(classname, "classtable", "get the class name from a class table") { return LS.result(); } -static void get_reg_name(std::string_view name, std::string_view &classname, std::string_view &funcname) { + +static std::string_view get_reg_classname(std::string_view name) +{ size_t upos = name.find('_'); if (upos == std::string_view::npos) { - funcname = name; - classname = ""; + return ""; } else { - funcname = name.substr(upos + 1); - classname = name.substr(0, upos); + return name.substr(0, upos); } } +static std::string_view get_reg_funcname(std::string_view name) { + size_t upos = name.find('_'); + if (upos == std::string_view::npos) { + return name; + } else { + return name.substr(upos + 1); + } +} + +static eng::string get_reg_fullname(std::string_view name) +{ + size_t upos = name.find('_'); + if (upos == std::string_view::npos) { + return eng::string(name); + } else { + return eng::string(name.substr(0, upos)) + "." + eng::string(name.substr(upos + 1)); + } +} + +static eng::string get_reg_prototype(const LuaFunctionReg *reg) { + eng::ostringstream oss; + oss << "function " << get_reg_fullname(reg->get_name()); + oss << "(" << reg->get_args() << ")"; + return oss.str(); +} + +static bool lines_contain_substring(const util::StringVec &lines, int lo, int hi, const eng::string &substring) { + for (int i = lo; i < hi; i++) { + if (sv::contains_substring_utf8(lines[i], substring)) { + return true; + } + } + return false; +} + static void get_info_table(LuaCoreStack &LS, LuaSlot db, LuaSlot info, const eng::string &fn) { LS.rawget(info, db, fn); if (!LS.istable(info)) { @@ -84,7 +119,6 @@ static void calculate_loadresult(LuaCoreStack &LS0, LuaSlot info, const eng::str } } - void SourceDB::diff(const SourceDB &auth, StreamBuffer *sb) { LuaVar sdb, sfn, sinfo, shash, sseq; LuaVar mdb, mfn, minfo, mhash, mseq, mcode; @@ -260,9 +294,8 @@ static void source_load_cfunctions(lua_State *L) { for (auto r = LuaFunctionReg::All; r != nullptr; r=r->next()) { lua_CFunction func = r->get_func(); if ((func != nullptr) && (!r->get_sandbox())) { - std::string_view classname; - std::string_view funcname; - get_reg_name(r->get_name(), classname, funcname); + std::string_view classname = get_reg_classname(r->get_name()); + std::string_view funcname = get_reg_funcname(r->get_name()); if (classname.empty()) { LS.getglobaltable(classobj); LS.rawset(classobj, funcname, func); @@ -285,9 +318,8 @@ static void source_load_cconstants(lua_State *L) { } else { LS.set(value, r->get_tokenvalue()); } - std::string_view classname; - std::string_view funcname; - get_reg_name(r->get_name(), classname, funcname); + std::string_view classname = get_reg_classname(r->get_name()); + std::string_view funcname = get_reg_funcname(r->get_name()); if (classname.empty()) { LS.getglobaltable(classobj); LS.rawset(classobj, funcname, value); @@ -500,9 +532,8 @@ void SourceDB::register_lua_builtins() { // the prototype lua state into the registry, then remove the closure // from the prototype. for (auto reg = LuaFunctionReg::All; reg != nullptr; reg=reg->next()) { - std::string_view funcname; - std::string_view classname; - get_reg_name(reg->get_name(), classname, funcname); + std::string_view classname = get_reg_classname(reg->get_name()); + std::string_view funcname = get_reg_funcname(reg->get_name()); if (classname.empty()) { LS.getglobaltable(classtab); } else { @@ -545,64 +576,89 @@ void SourceDB::register_lua_builtins() { lua_close(L); } - - -util::StringVec SourceDB::search_docs(const eng::string &substring) { - // This map will hold the results. It maps function name - // to a documentation line. - eng::map results; +bool SourceDB::search_docs(const eng::string &substring, std::ostream &ostream) { + bool found_anything = false; // Search the built-in functions. - // for (const LuaFunctionReg *reg = LuaFunctionReg::All; reg != nullptr; reg=reg->next()) { - // } - - util::StringVec resultvec; - for (const auto &pair : results) { - resultvec.push_back(pair.second); + for (const LuaFunctionReg *reg = LuaFunctionReg::All; reg != nullptr; reg=reg->next()) { + eng::string proto = get_reg_prototype(reg); + if (sv::contains_substring_utf8(proto, substring) || + sv::contains_substring_utf8(reg->get_docs(), substring)) { + ostream << proto; + util::StringVec docs = util::split_docstring(reg->get_docs()); + for (const eng::string &line : docs) { + if (sv::contains_substring_utf8(line, substring)) { + ostream << " -- " << sv::trim(line); + break; + } + } + ostream << std::endl; + found_anything = true; + } } - return resultvec; + + // Search the lua source code. + for (const eng::string &module : modules()) { + if (module == "CORE") continue; + eng::string code = get_source(module); + if (code.empty()) continue; + util::StringVec lines = util::split_lines(code); + int comment_lines = 0; + for (int i = 0; i < int(lines.size()); i++) { + if (sv::is_lua_function_prototype(lines[i])) { + if (lines_contain_substring(lines, i - comment_lines, i+1, substring)) { + ostream << lines[i]; + for (int j = i - comment_lines; j < i; j++) { + if (sv::contains_substring_utf8(lines[j], substring)) { + ostream << " " << sv::trim(lines[j]); + break; + } + } + ostream << std::endl; + found_anything = true; + } + comment_lines = 0; + } else if (sv::is_lua_comment(lines[i])) { + comment_lines++; + } else { + comment_lines = 0; + } + } + } + + return found_anything; } -eng::string SourceDB::function_docs(const LuaCoreStack &LS, LuaSlot fn) { +bool SourceDB::function_docs(const LuaCoreStack &LS, LuaSlot fn, std::ostream &ostream) { lua_State *L = LS.state(); if (LS.iscfunction(fn)) { lua_CFunction cfn = lua_tocfunction(L, fn.index()); const LuaFunctionReg *reg = LuaFunctionReg::lookup(cfn); - if (reg == nullptr) { - return ""; - } - std::string_view classname; - std::string_view funcname; - get_reg_name(reg->get_name(), classname, funcname); - eng::ostringstream oss; + if (reg == nullptr) return false; + ostream << get_reg_prototype(reg) << std::endl; util::StringVec docs = util::split_docstring(reg->get_docs()); - oss << "function "; - if (!classname.empty()) { - oss << classname << "."; - } - oss << funcname << "(" << reg->get_args() << ")" << std::endl; - oss << "--" << std::endl; + ostream << "--" << std::endl; for (const eng::string &line : docs) { - oss << "-- " << line << std::endl; + ostream << "-- " << line << std::endl; } - oss << "--" << std::endl; - return oss.str(); + ostream << "--" << std::endl; + return true; } else if (LS.isfunction(fn)) { lua_Debug ar; lua_pushvalue(L, fn.index()); int status = lua_getinfo(L, ">S", &ar); - if (status == 0) return ""; + if (status == 0) return false; // Get the source code. util::StringVec lines = util::split_lines(get_source(eng::string(ar.short_src))); - if (lines.empty()) return ""; + if (lines.empty()) return false; // Find the line of code containing the function prototype. // Lua numbers source lines from 1, but we number lines from 0, // so we have to subtract one. int linehi = ar.linedefined - 1; if ((linehi < 0) || (linehi >= int(lines.size()))) { - return ""; + return false; } // Incorporate the function comment. @@ -610,16 +666,15 @@ eng::string SourceDB::function_docs(const LuaCoreStack &LS, LuaSlot fn) { while ((linelo > 0) && (sv::is_lua_comment(lines[linelo-1]))) linelo -= 1; // Output the docs. - eng::ostringstream result; - result << lines[linehi] << std::endl; - result << "--" << std::endl; + ostream << lines[linehi] << std::endl; + ostream << "--" << std::endl; for (int i = linelo; i < linehi; i++) { - result << lines[i] << std::endl; + ostream << lines[i] << std::endl; } - result << "--" << std::endl; - return result.str(); + ostream << "--" << std::endl; + return true; } else { - return ""; + return false; } } diff --git a/luprex/cpp/core/source.hpp b/luprex/cpp/core/source.hpp index 41f80a60..b21f0ab6 100644 --- a/luprex/cpp/core/source.hpp +++ b/luprex/cpp/core/source.hpp @@ -202,14 +202,17 @@ public: // Get function documentation. // - eng::string function_docs(const LuaCoreStack &LS, LuaSlot slot); + // Returns false if it has no documentation for the specified function. + // + bool function_docs(const LuaCoreStack &LS, LuaSlot slot, std::ostream &ostream); // Search the documentation. // // Search all the documentation for the specified substring. - // In the result, each line points to a different result. // - util::StringVec search_docs(const eng::string &substring); + // Returns false if it found nothing and output nothing to the stream. + // + bool search_docs(const eng::string &substring, std::ostream &ostream); // Serialize and unserialize a source vector. // diff --git a/luprex/cpp/core/util.cpp b/luprex/cpp/core/util.cpp index fc6e1c9b..650184ae 100644 --- a/luprex/cpp/core/util.cpp +++ b/luprex/cpp/core/util.cpp @@ -200,6 +200,15 @@ bool is_lua_comment(string_view s) { return s.substr(start, 2) == "--"; } +bool is_lua_function_prototype(string_view s) { + int start = 0; + while ((start < int(s.size())) && ((s[start]==' ') || (s[start]=='\t'))) start++; + s.remove_prefix(start); + if (!has_prefix(s, "function")) return false; + s.remove_prefix(8); + return ((!s.empty()) && (ascii_isspace(s[0]))); +} + bool is_whitespace(string_view s) { for (int i = 0; i < int(s.size()); i++) { if (!ascii_isspace(s[i])) { @@ -342,6 +351,12 @@ bool valid_number(string_view s, bool plus, bool minus, bool dec, bool exp) { return s.empty(); } +bool contains_substring_utf8(string_view haystack, string_view needle) +{ + // Case sensitive is easy. Case insensitive is hard. + return haystack.find(needle) != std::string::npos; +} + using UC = UnicodeStuff; int32_t read_codepoint_utf8(string_view &source) { return UC::read_codepoint_utf8(source); } diff --git a/luprex/cpp/core/util.hpp b/luprex/cpp/core/util.hpp index 439a9130..9ec80ac9 100644 --- a/luprex/cpp/core/util.hpp +++ b/luprex/cpp/core/util.hpp @@ -110,6 +110,9 @@ bool is_lua_classname(string_view s); // Return true if the line of code is a lua comment. bool is_lua_comment(string_view s); +// Return true if the line is a lua function prototype. +bool is_lua_function_prototype(string_view s); + // Return true if the line is entirely whitespace. bool is_whitespace(string_view s); @@ -206,6 +209,13 @@ int32_t read_codepoint_utf8(string_view &source); // bool valid_utf8(string_view s); +// Check if a UTF8 string contains a substring. +// +// Eventually, we're going to have a case-insensitive version of this, +// but it's really hard to write! +// +bool contains_substring_utf8(string_view haystack, string_view needle); + // Return true if the number conforms to the spec. // See read_number for more information. // diff --git a/luprex/cpp/core/world-accessor.cpp b/luprex/cpp/core/world-accessor.cpp index 75795c58..1afc7bc1 100644 --- a/luprex/cpp/core/world-accessor.cpp +++ b/luprex/cpp/core/world-accessor.cpp @@ -933,11 +933,23 @@ LuaDefine(doc, "function", std::ostream *ostream = w->lthread_print_stream(); LuaArg func; LuaDefStack LS(L, func); - eng::string doc = w->get_source().function_docs(LS, func); - if (doc == "") { - (*ostream) << "no doc found" << std::endl; + bool ok = w->get_source().function_docs(LS, func, *ostream); + if (!ok) { + (*ostream) << "No documentation found."; + } + return LS.result(); +} + +LuaDefine(docsearch, "search-string", "Search the docs for the specified string") { + World *w = World::fetch_global_pointer(L); + std::ostream *ostream = w->lthread_print_stream(); + LuaArg ss; + LuaDefStack LS(L, ss); + eng::string searchstring = LS.ckstring(ss); + bool ok = w->get_source().search_docs(searchstring, *ostream); + if (!ok) { + (*ostream) << "No documentation found."; } - (*ostream) << doc; return LS.result(); } diff --git a/luprex/ext/unicode-stuff.hpp b/luprex/ext/unicode-stuff.hpp index eb706361..e56044c8 100644 --- a/luprex/ext/unicode-stuff.hpp +++ b/luprex/ext/unicode-stuff.hpp @@ -61,7 +61,7 @@ public: } } - // Read a single codepoint from a UTF16 string. + // Read a single UTF32 codepoint from a UTF16 string. // // Returns -1 if the string is empty. Returns -2 if the string // starts with an invalid sequence. @@ -96,7 +96,7 @@ public: } } - // Read a single codepoint from a UTF8 string. + // Read a single UTF32 codepoint from a UTF8 string. // // If the string_view starts with a valid codepoint, the codepoint // is removed from the string_view and is returned. @@ -109,7 +109,7 @@ public: // If the string_view starts with a finish but invalid codepoint, // returns -2. // - static int32_t read_codepoint_utf8(std::string_view &source) { + static char32_t read_codepoint_utf8(std::string_view &source) { size_t size = source.size(); if (size == 0) return -1; @@ -161,7 +161,7 @@ public: return codepoint; } - // Convert a codepoint string into a UTF8-string. + // Convert a UTF32 string into a UTF8-string. // If the codepoint string contains invalid codepoints, they're silently dropped. // static u8string utf32_to_utf8(const u32string &s) {