1
0
mirror of https://github.com/exaloop/codon.git synced 2025-06-03 15:03:52 +08:00
2021-09-27 14:02:44 -04:00

453 lines
14 KiB
C++

/*
* doc.cpp --- Seq documentation generator.
*
* (c) Seq project. All rights reserved.
* This file is subject to the terms and conditions defined in
* file 'LICENSE', which is part of this source code package.
*/
#include <memory>
#include <string>
#include <tuple>
#include <vector>
#include "parser/ast.h"
#include "parser/common.h"
#include "parser/peg/peg.h"
#include "parser/visitors/doc/doc.h"
#include "parser/visitors/format/format.h"
using fmt::format;
using std::get;
using std::move;
using std::ostream;
using std::stack;
using std::to_string;
namespace seq {
namespace ast {
// clang-format off
string json_escape(const string &str) {
string r;
r.reserve(str.size());
for (unsigned char c : str) {
switch (c) {
case '\b': r += "\\b"; break;
case '\f': r += "\\f"; break;
case '\n': r += "\\n"; break;
case '\r': r += "\\r"; break;
case '\t': r += "\\t"; break;
case '\\': r += "\\\\"; break;
case '"': r += "\\\""; break;
default: r += c;
}
}
return r;
}
// clang-format on
json::json() : list(false) {}
json::json(const string &s) : list(false) { values[s] = nullptr; }
json::json(const string &s, const string &v) : list(false) {
values[s] = make_shared<json>(v);
}
json::json(const vector<shared_ptr<json>> &vs) : list(true) {
for (int i = 0; i < vs.size(); i++)
values[std::to_string(i)] = vs[i];
}
json::json(const vector<string> &vs) : list(true) {
for (int i = 0; i < vs.size(); i++)
values[std::to_string(i)] = make_shared<json>(vs[i]);
}
json::json(const unordered_map<string, string> &vs) : list(false) {
for (auto &v : vs)
values[v.first] = make_shared<json>(v.second);
}
string json::toString() {
vector<string> s;
if (values.empty()) {
return "{}";
} else if (values.size() == 1 && !values.begin()->second) {
return fmt::format("\"{}\"", json_escape(values.begin()->first));
} else if (list) {
for (int i = 0; i < values.size(); i++)
s.push_back(values[std::to_string(i)]->toString());
return fmt::format("[ {} ]", join(s, ", "));
} else {
for (auto &v : values)
s.push_back(
fmt::format("\"{}\": {}", json_escape(v.first), v.second->toString()));
return fmt::format("{{ {} }}", join(s, ", "));
}
}
shared_ptr<json> json::get(const string &s) {
auto i = values.find(s);
seqassert(i != values.end(), "cannot find {}", s);
return i->second;
}
shared_ptr<json> json::set(const string &s, const string &value) {
return values[s] = make_shared<json>(value);
}
shared_ptr<json> json::set(const string &s, const shared_ptr<json> &value) {
return values[s] = value;
}
shared_ptr<json> DocVisitor::apply(const string &argv0, const vector<string> &files) {
auto shared = make_shared<DocShared>();
shared->argv0 = argv0;
shared->cache = make_shared<ast::Cache>(argv0);
auto stdlib = getImportFile(argv0, "internal", "", true, "");
auto ast = ast::parseFile(shared->cache, stdlib->path);
shared->modules[""] = make_shared<DocContext>(shared);
shared->modules[""]->setFilename(stdlib->path);
shared->j = make_shared<json>();
for (auto &s :
vector<string>{"void", "byte", "float", "bool", "int", "str", "pyobj", "Ptr",
"Function", "Generator", "Tuple", "Int", "UInt", TYPE_OPTIONAL,
"Callable", "NoneType", "__internal__"}) {
shared->j->set(to_string(shared->itemID),
make_shared<json>(unordered_map<string, string>{
{"kind", "class"}, {"name", s}, {"type", "type"}}));
if (s == "Ptr" || s == "Generator" || s == TYPE_OPTIONAL)
shared->generics[shared->itemID] = {"T"};
if (s == "Int" || s == "UInt")
shared->generics[shared->itemID] = {"N"};
shared->modules[""]->add(s, make_shared<int>(shared->itemID++));
}
DocVisitor(shared->modules[""]).transformModule(move(ast));
auto ctx = make_shared<DocContext>(shared);
char abs[PATH_MAX];
for (auto &f : files) {
realpath(f.c_str(), abs);
ctx->setFilename(abs);
ast = ast::parseFile(shared->cache, abs);
// LOG("parsing {}", f);
DocVisitor(ctx).transformModule(move(ast));
}
return shared->j;
}
shared_ptr<int> DocContext::find(const string &s) const {
auto i = Context<int>::find(s);
if (!i && this != shared->modules[""].get())
return shared->modules[""]->find(s);
return i;
}
string getDocstr(const StmtPtr &s) {
if (auto se = s->getExpr())
if (auto e = se->expr->getString())
return e->getValue();
return "";
}
vector<StmtPtr> DocVisitor::flatten(StmtPtr stmt, string *docstr, bool deep) {
vector<StmtPtr> stmts;
if (auto s = const_cast<SuiteStmt *>(stmt->getSuite())) {
for (int i = 0; i < (deep ? s->stmts.size() : 1); i++) {
for (auto &x : flatten(move(s->stmts[i]), i ? nullptr : docstr, deep))
stmts.push_back(move(x));
}
} else {
if (docstr)
*docstr = getDocstr(stmt);
stmts.push_back(move(stmt));
}
return stmts;
}
shared_ptr<json> DocVisitor::transform(const ExprPtr &expr) {
DocVisitor v(ctx);
v.resultExpr = make_shared<json>();
expr->accept(v);
return v.resultExpr;
}
string DocVisitor::transform(const StmtPtr &stmt) {
DocVisitor v(ctx);
stmt->accept(v);
return v.resultStmt;
}
void DocVisitor::transformModule(StmtPtr stmt) {
vector<string> children;
string docstr;
auto flat = flatten(move(stmt), &docstr);
for (int i = 0; i < flat.size(); i++) {
auto &s = flat[i];
auto id = transform(s);
if (id.empty())
continue;
if (i < (flat.size() - 1) && CAST(s, AssignStmt)) {
auto ds = getDocstr(flat[i + 1]);
if (!ds.empty())
ctx->shared->j->get(id)->set("doc", ds);
}
children.push_back(id);
}
auto id = to_string(ctx->shared->itemID++);
auto ja =
ctx->shared->j->set(id, make_shared<json>(unordered_map<string, string>{
{"kind", "module"}, {"path", ctx->getFilename()}}));
ja->set("children", make_shared<json>(children));
if (!docstr.empty())
ja->set("doc", docstr);
}
void DocVisitor::visit(IntExpr *expr) { resultExpr = make_shared<json>(expr->value); }
void DocVisitor::visit(IdExpr *expr) {
auto i = ctx->find(expr->value);
if (!i)
error("unknown identifier {}", expr->value);
resultExpr = make_shared<json>(*i ? to_string(*i) : expr->value);
}
void DocVisitor::visit(IndexExpr *expr) {
vector<shared_ptr<json>> v;
v.push_back(transform(expr->expr));
if (auto tp = CAST(expr->index, TupleExpr)) {
if (auto l = tp->items[0]->getList()) {
for (auto &e : l->items)
v.push_back(transform(e));
v.push_back(transform(tp->items[1]));
} else
for (auto &e : tp->items)
v.push_back(transform(e));
} else {
v.push_back(transform(expr->index));
}
resultExpr = make_shared<json>(v);
}
bool isValidName(const string &s) {
if (s.empty())
return false;
if (s.size() > 4 && s.substr(0, 2) == "__" && s.substr(s.size() - 2) == "__")
return true;
return s[0] != '_';
}
void DocVisitor::visit(FunctionStmt *stmt) {
int id = ctx->shared->itemID++;
ctx->add(stmt->name, make_shared<int>(id));
auto j = make_shared<json>(
unordered_map<string, string>{{"kind", "function"}, {"name", stmt->name}});
j->set("pos", jsonify(stmt->getSrcInfo()));
vector<shared_ptr<json>> args;
vector<string> generics;
for (auto &a : stmt->args)
if (a.generic || (a.type && (a.type->isId("type") || a.type->isId("TypeVar") ||
(a.type->getIndex() &&
a.type->getIndex()->expr->isId("Static"))))) {
ctx->add(a.name, make_shared<int>(0));
generics.push_back(a.name);
a.generic = true;
}
for (auto &a : stmt->args)
if (!a.generic) {
auto j = make_shared<json>();
j->set("name", a.name);
if (a.type)
j->set("type", transform(a.type));
if (a.deflt) {
j->set("default", FormatVisitor::apply(a.deflt));
}
args.push_back(j);
}
j->set("generics", make_shared<json>(generics));
bool isLLVM = false;
for (auto &d : stmt->decorators)
if (auto e = d->getId()) {
j->set("attrs", make_shared<json>(e->value, ""));
isLLVM |= (e->value == "llvm");
}
if (stmt->ret)
j->set("return", transform(stmt->ret));
j->set("args", make_shared<json>(args));
string docstr;
flatten(move(const_cast<FunctionStmt *>(stmt)->suite), &docstr);
for (auto &g : generics)
ctx->remove(g);
if (!docstr.empty() && !isLLVM)
j->set("doc", docstr);
ctx->shared->j->set(to_string(id), j);
resultStmt = to_string(id);
}
void DocVisitor::visit(ClassStmt *stmt) {
vector<string> generics;
auto j = make_shared<json>(
unordered_map<string, string>{{"name", stmt->name},
{"kind", "class"},
{"type", stmt->isRecord() ? "type" : "class"}});
int id = ctx->shared->itemID++;
bool isExtend = false;
for (auto &d : stmt->decorators)
if (auto e = d->getId())
isExtend |= (e->value == "extend");
if (isExtend) {
j->set("type", "extension");
auto i = ctx->find(stmt->name);
j->set("parent", to_string(*i));
generics = ctx->shared->generics[*i];
} else {
ctx->add(stmt->name, make_shared<int>(id));
}
vector<shared_ptr<json>> args;
for (auto &a : stmt->args)
if (a.generic || (a.type && (a.type->isId("type") || a.type->isId("TypeVar") ||
(a.type->getIndex() &&
a.type->getIndex()->expr->isId("Static"))))) {
a.generic = true;
generics.push_back(a.name);
}
ctx->shared->generics[id] = generics;
for (auto &g : generics)
ctx->add(g, make_shared<int>(0));
for (auto &a : stmt->args)
if (!a.generic) {
auto ja = make_shared<json>();
ja->set("name", a.name);
if (a.type)
ja->set("type", transform(a.type));
args.push_back(ja);
}
j->set("generics", make_shared<json>(generics));
j->set("args", make_shared<json>(args));
j->set("pos", jsonify(stmt->getSrcInfo()));
string docstr;
vector<string> members;
for (auto &f : flatten(move(const_cast<ClassStmt *>(stmt)->suite), &docstr)) {
if (auto ff = CAST(f, FunctionStmt)) {
auto i = transform(f);
if (i != "")
members.push_back(i);
if (isValidName(ff->name))
ctx->remove(ff->name);
}
}
for (auto &g : generics)
ctx->remove(g);
j->set("members", make_shared<json>(members));
if (!docstr.empty())
j->set("doc", docstr);
ctx->shared->j->set(to_string(id), j);
resultStmt = to_string(id);
}
shared_ptr<json> DocVisitor::jsonify(const seq::SrcInfo &s) {
return make_shared<json>(vector<string>{to_string(s.line), to_string(s.len)});
}
void DocVisitor::visit(ImportStmt *stmt) {
if (stmt->from->isId("C") || stmt->from->isId("python")) {
int id = ctx->shared->itemID++;
string name, lib;
if (auto i = stmt->what->getId())
name = i->value;
else if (auto d = stmt->what->getDot())
name = d->member, lib = FormatVisitor::apply(d->expr);
else
seqassert(false, "invalid C import statement");
ctx->add(name, make_shared<int>(id));
name = stmt->as.empty() ? name : stmt->as;
auto j = make_shared<json>(unordered_map<string, string>{
{"name", name}, {"kind", "function"}, {"extern", stmt->from->getId()->value}});
j->set("pos", jsonify(stmt->getSrcInfo()));
vector<shared_ptr<json>> args;
if (stmt->ret)
j->set("return", transform(stmt->ret));
for (auto &a : stmt->args) {
auto ja = make_shared<json>();
ja->set("name", a.name);
ja->set("type", transform(a.type));
args.push_back(ja);
}
j->set("dylib", lib);
j->set("args", make_shared<json>(args));
ctx->shared->j->set(to_string(id), j);
resultStmt = to_string(id);
return;
}
vector<string> dirs; // Path components
Expr *e = stmt->from.get();
while (auto d = e->getDot()) {
dirs.push_back(d->member);
e = d->expr.get();
}
if (!e->getId() || !stmt->args.empty() || stmt->ret ||
(stmt->what && !stmt->what->getId()))
error("invalid import statement");
// We have an empty stmt->from in "from .. import".
if (!e->getId()->value.empty())
dirs.push_back(e->getId()->value);
// Handle dots (e.g. .. in from ..m import x).
seqassert(stmt->dots >= 0, "negative dots in ImportStmt");
for (int i = 0; i < stmt->dots - 1; i++)
dirs.emplace_back("..");
string path;
for (int i = int(dirs.size()) - 1; i >= 0; i--)
path += dirs[i] + (i ? "/" : "");
// Fetch the import!
auto file = getImportFile(ctx->shared->argv0, path, ctx->getFilename());
if (!file)
error(stmt, "cannot locate import '{}'", path);
auto ictx = ctx;
auto it = ctx->shared->modules.find(file->path);
if (it == ctx->shared->modules.end()) {
ictx = make_shared<DocContext>(ctx->shared);
ictx->setFilename(file->path);
auto tmp = parseFile(ctx->shared->cache, file->path);
DocVisitor(ictx).transformModule(move(tmp));
} else {
ictx = it->second;
}
if (!stmt->what) {
// TODO: implement this corner case
} else if (stmt->what->isId("*")) {
for (auto &i : *ictx)
ctx->add(i.first, i.second[0].second);
} else {
auto i = stmt->what->getId();
if (auto c = ictx->find(i->value))
ctx->add(stmt->as.empty() ? i->value : stmt->as, c);
else
error(stmt, "symbol '{}' not found in {}", i->value, file->path);
}
}
void DocVisitor::visit(AssignStmt *stmt) {
auto e = CAST(stmt->lhs, IdExpr);
if (!e)
return;
int id = ctx->shared->itemID++;
ctx->add(e->value, make_shared<int>(id));
auto j = make_shared<json>(
unordered_map<string, string>{{"name", e->value}, {"kind", "variable"}});
j->set("pos", jsonify(stmt->getSrcInfo()));
ctx->shared->j->set(to_string(id), j);
resultStmt = to_string(id);
}
} // namespace ast
} // namespace seq