Skip to content

Commit ec63cc3

Browse files
authored
Split xinspect.hpp into interface and implementation (#269)
* Split xinspect.hpp into interface and implementation
1 parent 362cf53 commit ec63cc3

File tree

3 files changed

+281
-238
lines changed

3 files changed

+281
-238
lines changed

CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ set(XEUS_CPP_HEADERS
209209
set(XEUS_CPP_SRC
210210
src/xholder.cpp
211211
src/xinput.cpp
212+
src/xinspect.cpp
212213
src/xinterpreter.cpp
213214
src/xoptions.cpp
214215
src/xparser.cpp

src/xinspect.cpp

+266
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
/************************************************************************************
2+
* Copyright (c) 2025, xeus-cpp contributors *
3+
* *
4+
* Distributed under the terms of the BSD 3-Clause License. *
5+
* *
6+
* The full license is in the file LICENSE, distributed with this software. *
7+
************************************************************************************/
8+
9+
#include <filesystem>
10+
#include <fstream>
11+
#include <regex>
12+
#include <string>
13+
#include <utility>
14+
15+
#include "xinspect.hpp"
16+
17+
#include "clang/Interpreter/CppInterOp.h"
18+
19+
namespace xcpp
20+
{
21+
bool node_predicate::operator()(pugi::xml_node node) const
22+
{
23+
return static_cast<std::string>(node.attribute("kind").value()) == kind
24+
&& static_cast<std::string>(node.child("name").child_value()) == child_value;
25+
}
26+
27+
std::string class_member_predicate::get_filename(pugi::xml_node node) const
28+
{
29+
for (pugi::xml_node child : node.children())
30+
{
31+
if (static_cast<std::string>(child.attribute("kind").value()) == kind
32+
&& static_cast<std::string>(child.child("name").child_value()) == child_value)
33+
{
34+
return child.child("anchorfile").child_value();
35+
}
36+
}
37+
return "";
38+
}
39+
40+
bool class_member_predicate::operator()(pugi::xml_node node) const
41+
{
42+
std::string node_kind = node.attribute("kind").value();
43+
std::string node_name = node.child("name").child_value();
44+
45+
bool is_class_or_struct = (node_kind == "class" || node_kind == "struct");
46+
bool name_matches = (node_name == class_name);
47+
bool parent = is_class_or_struct && name_matches;
48+
49+
if (parent)
50+
{
51+
for (pugi::xml_node child : node.children())
52+
{
53+
if (static_cast<std::string>(child.attribute("kind").value()) == kind
54+
&& static_cast<std::string>(child.child("name").child_value()) == child_value)
55+
{
56+
return true;
57+
}
58+
}
59+
}
60+
return false;
61+
}
62+
63+
std::string find_type_slow(const std::string& expression)
64+
{
65+
static unsigned long long var_count = 0;
66+
67+
if (auto* type = Cpp::GetType(expression))
68+
{
69+
return Cpp::GetQualifiedName(type);
70+
}
71+
72+
std::string id = "__Xeus_GetType_" + std::to_string(var_count++);
73+
std::string using_clause = "using " + id + " = __typeof__(" + expression + ");\n";
74+
75+
if (!Cpp::Declare(using_clause.c_str(), false))
76+
{
77+
Cpp::TCppScope_t lookup = Cpp::GetNamed(id, nullptr);
78+
Cpp::TCppType_t lookup_ty = Cpp::GetTypeFromScope(lookup);
79+
return Cpp::GetQualifiedCompleteName(Cpp::GetCanonicalType(lookup_ty));
80+
}
81+
return "";
82+
}
83+
84+
nl::json read_tagconfs(const char* path)
85+
{
86+
nl::json result = nl::json::array();
87+
for (const auto& entry : std::filesystem::directory_iterator(path))
88+
{
89+
if (entry.path().extension() != ".json")
90+
{
91+
continue;
92+
}
93+
std::ifstream i(entry.path());
94+
nl::json json_entry;
95+
i >> json_entry;
96+
result.emplace_back(std::move(json_entry));
97+
}
98+
return result;
99+
}
100+
101+
std::pair<bool, std::smatch> is_inspect_request(const std::string& code, const std::regex& re)
102+
{
103+
std::smatch inspect;
104+
if (std::regex_search(code, inspect, re))
105+
{
106+
return std::make_pair(true, inspect);
107+
}
108+
return std::make_pair(false, inspect);
109+
}
110+
111+
void inspect(const std::string& code, nl::json& kernel_res)
112+
{
113+
std::string tagconf_dir = retrieve_tagconf_dir();
114+
std::string tagfiles_dir = retrieve_tagfile_dir();
115+
116+
nl::json tagconfs = read_tagconfs(tagconf_dir.c_str());
117+
118+
std::vector<std::string> check{"class", "struct", "function"};
119+
120+
std::string url, tagfile;
121+
122+
std::regex re_expression(R"((((?:\w*(?:\:{2}|\<.*\>|\(.*\)|\[.*\])?)\.?)*))");
123+
124+
std::smatch inspect = is_inspect_request(code, re_expression).second;
125+
126+
std::string inspect_result;
127+
128+
std::smatch method;
129+
std::string to_inspect = inspect[1];
130+
131+
// Method or variable of class found (xxxx.yyyy)
132+
if (std::regex_search(to_inspect, method, std::regex(R"((.*)\.(\w*)$)")))
133+
{
134+
std::string type_name = find_type_slow(method[1]);
135+
136+
if (!type_name.empty())
137+
{
138+
for (nl::json::const_iterator it = tagconfs.cbegin(); it != tagconfs.cend(); ++it)
139+
{
140+
url = it->at("url");
141+
tagfile = it->at("tagfile");
142+
std::string filename = tagfiles_dir + "/" + tagfile;
143+
pugi::xml_document doc;
144+
pugi::xml_parse_result result = doc.load_file(filename.c_str());
145+
class_member_predicate predicate{type_name, "function", method[2]};
146+
auto node = doc.find_node(predicate);
147+
if (!node.empty())
148+
{
149+
inspect_result = url + predicate.get_filename(node);
150+
}
151+
}
152+
}
153+
}
154+
else
155+
{
156+
std::string find_string;
157+
158+
// check if we try to find the documentation of a namespace
159+
// if yes, don't try to find the type using typeid
160+
std::regex is_namespace(R"(\w+(\:{2}\w+)+)");
161+
std::smatch namespace_match;
162+
if (std::regex_match(to_inspect, namespace_match, is_namespace))
163+
{
164+
find_string = to_inspect;
165+
}
166+
else
167+
{
168+
std::string type_name = find_type_slow(to_inspect);
169+
find_string = (type_name.empty()) ? to_inspect : type_name;
170+
}
171+
172+
for (nl::json::const_iterator it = tagconfs.cbegin(); it != tagconfs.cend(); ++it)
173+
{
174+
url = it->at("url");
175+
tagfile = it->at("tagfile");
176+
std::string filename = tagfiles_dir + "/" + tagfile;
177+
pugi::xml_document doc;
178+
pugi::xml_parse_result result = doc.load_file(filename.c_str());
179+
for (auto c : check)
180+
{
181+
node_predicate predicate{c, find_string};
182+
std::string node;
183+
184+
if (c == "class" || c == "struct")
185+
{
186+
node = doc.find_node(predicate).child("filename").child_value();
187+
}
188+
else
189+
{
190+
node = doc.find_node(predicate).child("anchorfile").child_value();
191+
}
192+
193+
if (!node.empty())
194+
{
195+
inspect_result = url + node;
196+
}
197+
}
198+
}
199+
}
200+
201+
if (inspect_result.empty())
202+
{
203+
std::cerr << "No documentation found for " << code << "\n";
204+
std::cout << std::flush;
205+
kernel_res["found"] = false;
206+
kernel_res["status"] = "error";
207+
kernel_res["ename"] = "No documentation found";
208+
kernel_res["evalue"] = "";
209+
kernel_res["traceback"] = nl::json::array();
210+
}
211+
else
212+
{
213+
// Format html content.
214+
std::string html_content = R"(<style>
215+
#pager-container {
216+
padding: 0;
217+
margin: 0;
218+
width: 100%;
219+
height: 100%;
220+
}
221+
.xcpp-iframe-pager {
222+
padding: 0;
223+
margin: 0;
224+
width: 100%;
225+
height: 100%;
226+
border: none;
227+
}
228+
</style>
229+
<iframe class="xcpp-iframe-pager" src=")"
230+
+ inspect_result + R"(?action=purge"></iframe>)";
231+
232+
// Note: Adding "?action=purge" suffix to force cppreference's
233+
// Mediawiki to purge the HTTP cache.
234+
235+
kernel_res["payload"] = nl::json::array();
236+
kernel_res["payload"][0] = nl::json::object(
237+
{{"data", {{"text/plain", inspect_result}, {"text/html", html_content}}},
238+
{"source", "page"},
239+
{"start", 0}}
240+
);
241+
kernel_res["user_expressions"] = nl::json::object();
242+
243+
std::cout << std::flush;
244+
kernel_res["found"] = true;
245+
kernel_res["status"] = "ok";
246+
}
247+
}
248+
249+
xintrospection::xintrospection()
250+
{
251+
pattern = spattern;
252+
}
253+
254+
void xintrospection::apply(const std::string& code, nl::json& kernel_res)
255+
{
256+
std::regex re(spattern + R"((.*))");
257+
std::smatch to_inspect;
258+
std::regex_search(code, to_inspect, re);
259+
inspect(to_inspect[1], kernel_res);
260+
}
261+
262+
std::unique_ptr<xpreamble> xintrospection::clone() const
263+
{
264+
return std::make_unique<xintrospection>(*this);
265+
}
266+
}

0 commit comments

Comments
 (0)