Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support --no-indent and --visualize flags #1779

Merged
merged 4 commits into from
May 11, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add support for AST/R Visualization
  • Loading branch information
Shaikh-Ubaid committed May 11, 2023
commit a0441aac7c4c9250fed7f870fbd268492184a227
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ inst/bin/*
*_ldd.txt
*_lines.dat.txt
*__tmp__generated__.c
visualize*.html
a.c
a.h
a.py
Expand Down
50 changes: 40 additions & 10 deletions src/bin/lpython.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,29 @@ std::string get_kokkos_dir()
throw LCompilers::LCompilersException("LFORTRAN_KOKKOS_DIR is not defined");
}

int visualize_json(std::string &astr_data_json, LCompilers::Platform os) {
using namespace LCompilers;
std::string file_loc = LCompilers::LPython::generate_visualize_html(astr_data_json);
std::string open_cmd = "";
switch (os) {
case Linux: open_cmd = "xdg-open"; break;
case Windows: open_cmd = "start"; break;
case macOS_Intel:
case macOS_ARM: open_cmd = "open"; break;
default:
std::cerr << "Unsupported Platform " << pf2s(os) <<std::endl;
std::cerr << "Please open file " << file_loc << " manually" <<std::endl;
return 11;
}
std::string cmd = open_cmd + " " + file_loc;
int err = system(cmd.data());
if (err) {
std::cout << "The command '" + cmd + "' failed." << std::endl;
return 11;
}
return 0;
}

#ifdef HAVE_LFORTRAN_LLVM

#endif
Expand Down Expand Up @@ -158,6 +181,18 @@ int emit_ast(const std::string &infile,
lm.file_ends.push_back(input.size());
}
std::cout << LCompilers::LPython::pickle_json(*ast, lm) << std::endl;
} else if (compiler_options.visualize) {
LCompilers::LocationManager lm;
{
LCompilers::LocationManager::FileLocations fl;
fl.in_filename = infile;
lm.files.push_back(fl);
std::string input = LCompilers::read_file(infile);
lm.init_simple(input);
lm.file_ends.push_back(input.size());
}
LCompilers::Result<std::string> r = LCompilers::LPython::pickle_json(*ast, lm);
return visualize_json(r.result, compiler_options.platform);
} else {
std::cout << LCompilers::LPython::pickle_python(*ast,
compiler_options.use_colors, compiler_options.indent) << std::endl;
Expand Down Expand Up @@ -211,6 +246,9 @@ int emit_asr(const std::string &infile,
compiler_options.use_colors, with_intrinsic_modules) << std::endl;
} else if (compiler_options.json) {
std::cout << LCompilers::LPython::pickle_json(*asr, lm, with_intrinsic_modules) << std::endl;
} else if (compiler_options.visualize) {
std::string astr_data_json = LCompilers::LPython::pickle_json(*asr, lm, with_intrinsic_modules);
return visualize_json(astr_data_json, compiler_options.platform);
} else {
std::cout << LCompilers::LPython::pickle(*asr, compiler_options.use_colors,
compiler_options.indent, with_intrinsic_modules) << std::endl;
Expand Down Expand Up @@ -1499,6 +1537,7 @@ int main(int argc, char *argv[])
app.add_flag("--indent", compiler_options.indent, "Indented print ASR/AST");
app.add_flag("--tree", compiler_options.tree, "Tree structure print ASR/AST");
app.add_flag("--json", compiler_options.json, "Print ASR/AST Json format");
app.add_flag("--visualize", compiler_options.visualize, "Print ASR/AST Visualization");
app.add_option("--pass", arg_pass, "Apply the ASR pass and show ASR (implies --show-asr)");
app.add_option("--skip-pass", skip_pass, "Skip an ASR pass in default pipeline");
app.add_flag("--disable-main", compiler_options.disable_main, "Do not generate any code for the `main` function");
Expand Down Expand Up @@ -1566,16 +1605,7 @@ int main(int argc, char *argv[])
if (arg_version) {
std::string version = LFORTRAN_VERSION;
std::cout << "LPython version: " << version << std::endl;
std::cout << "Platform: ";
switch (compiler_options.platform) {
case (LCompilers::Platform::Linux) : std::cout << "Linux"; break;
case (LCompilers::Platform::macOS_Intel) : std::cout << "macOS Intel"; break;
case (LCompilers::Platform::macOS_ARM) : std::cout << "macOS ARM"; break;
case (LCompilers::Platform::Windows) : std::cout << "Windows"; break;
case (LCompilers::Platform::FreeBSD) : std::cout << "FreeBSD"; break;
case (LCompilers::Platform::OpenBSD) : std::cout << "OpenBSD"; break;
}
std::cout << std::endl;
std::cout << "Platform: " << pf2s(compiler_options.platform) << std::endl;
#ifdef HAVE_LFORTRAN_LLVM
std::cout << "Default target: " << LCompilers::LLVMEvaluator::get_default_target_triple() << std::endl;
#endif
Expand Down
2 changes: 2 additions & 0 deletions src/libasr/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ enum Platform {
OpenBSD,
};

std::string pf2s(Platform);
Platform get_platform();

struct CompilerOptions {
Expand All @@ -37,6 +38,7 @@ struct CompilerOptions {
bool indent = false;
bool json = false;
bool tree = false;
bool visualize = false;
bool fast = false;
bool openmp = false;
bool generate_object_code = false;
Expand Down
12 changes: 12 additions & 0 deletions src/libasr/utils2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ bool present(char** const v, size_t n, const std::string name) {
return false;
}

std::string pf2s(Platform p) {
switch (p) {
case (Platform::Linux) : return "Linux";
case (Platform::macOS_Intel) : return "macOS Intel";
case (Platform::macOS_ARM) : return "macOS ARM";
case (Platform::Windows) : return "Windows";
case (Platform::FreeBSD) : return "FreeBSD";
case (Platform::OpenBSD) : return "OpenBSD";
}
return "Unsupported Platform";
}

Platform get_platform()
{
#if defined(_WIN32)
Expand Down
153 changes: 153 additions & 0 deletions src/lpython/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,157 @@ int32_t get_exit_status(int32_t err) {
return (((err) >> 8) & 0x000000ff);
}

std::string generate_visualize_html(std::string &astr_data_json) {
std::hash<std::string> hasher;
std::ofstream out;
std::string file_name = "visualize" + std::to_string(hasher(astr_data_json)) + ".html";
out.open(file_name);
out << R"(<!DOCTYPE html>
<html>
<head>
<title>LCompilers AST/R Visualization</title>
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>

<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-flow-renderer/10.3.17/umd/index.js"></script>
<script src="https://dagrejs.github.io/project/dagre/latest/dagre.min.js"></script>
<script> )";
out << "var astr_data = " << astr_data_json << "; </script>\n";
out << R"(</head>

<body style="margin: 0px;">
<script type="text/babel" data-type="module">
function TreeNode({ node }) {
if (node.literals.length === 0) return <p><b>{node.node}</b></p>;
return (
<div>
<p><b>{node.node}</b></p>
<div style={{ backgroundColor: "#FBBD23", padding: "2px" }}>
{
node.literals.map((val, idx) => <p style={{ margin: "0px", padding: "1px" }} key={idx}>{val[0]}: {val[1]}</p>)
}
</div>
</div>
);
}

const getLayoutedElements = (nodes, edges, direction = 'TB') => {
const nodeWidth = 180;
const isHorizontal = direction === 'LR';

const dagreGraph = new dagre.graphlib.Graph();
dagreGraph.setDefaultEdgeLabel(() => ({}));
dagreGraph.setGraph({ rankdir: direction });

nodes.forEach(node => dagreGraph.setNode(node.id, { width: nodeWidth, height: node.nodeHeight }));
edges.forEach(edge => dagreGraph.setEdge(edge.source, edge.target));

dagre.layout(dagreGraph);

nodes.forEach((node) => {
const nodeWithPosition = dagreGraph.node(node.id);
node.targetPosition = isHorizontal ? 'left' : 'top';
node.sourcePosition = isHorizontal ? 'right' : 'bottom';
// Shifting the dagre node position (anchor=center center) to the top left
// so it matches the React Flow node anchor point (top left).
node.position = {
x: nodeWithPosition.x - nodeWidth / 2,
y: nodeWithPosition.y - node.nodeHeight / 2,
};
return node;
});

return [nodes, edges];
};

class Graph {
constructor() {
this.nodes = [];
this.edges = [];
this.idx = 1;
return this;
}

createNode(cur_node) {
cur_node.idx = this.idx++;
cur_node.literals = [];
let obj = cur_node.fields;
for (let prop in obj) {
let neigh = obj[prop];
if (typeof neigh === 'object') {
if (neigh.hasOwnProperty("node")) {
this.createEdge(cur_node.idx, neigh, prop);
} else {
if (neigh.length > 0) {
for (let i in neigh) {
let arrayElement = neigh[i];
if (typeof arrayElement === 'object') {
if (arrayElement.hasOwnProperty("node")) {
this.createEdge(cur_node.idx, arrayElement, `${prop}[${i}]`);
} else {
console.log("ERROR: Unexpected 2D Array found");
}
} else {
cur_node.literals.push([`${prop}[${i}]`, `${arrayElement}`]);
}
}
} else {
// 0 length array, show as literal
cur_node.literals.push([prop, "[]"]);
}
}
} else {
cur_node.literals.push([prop, `${neigh}`]);
}
}

this.nodes.push({ id: `${cur_node.idx}`, data: { label: <TreeNode node={cur_node} /> }, nodeHeight: 70 + 20 * (cur_node.literals.length) });
}

createEdge(parent_idx, cur_node, edge_label) {
this.edges.push({
id: `${parent_idx}-${this.idx}`,
source: `${parent_idx}`,
target: `${this.idx}`,
label: edge_label,
labelStyle: { fontWeight: 700 },
labelBgPadding: [8, 4],
labelBgStyle: { fill: '#FBBD23' },
});
this.createNode(cur_node);
}
}

function Flow({ nodes, edges }) {
return (
<div style={{ height: '100vh' }}>
<ReactFlow.default
defaultNodes={nodes}
defaultEdges={edges}
style={{ backgroundColor: '#e5e7eb' }}
>
<ReactFlow.Background />
<ReactFlow.Controls />
<ReactFlow.MiniMap />
</ReactFlow.default>
</div>
);
}

function MyApp() {
var g = new Graph();
g.createNode(astr_data);
var [layoutedNodes, layoutedEdges] = getLayoutedElements(g.nodes, g.edges);
return (<Flow nodes={layoutedNodes} edges={layoutedEdges} />);
}

ReactDOM.render(<MyApp />, document.body);
</script>
</body>

</html>)";
return file_name;
}

}
2 changes: 2 additions & 0 deletions src/lpython/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ bool path_exists(std::string path);
// Decodes the exit status code of the process (in Unix)
int32_t get_exit_status(int32_t err);

std::string generate_visualize_html(std::string &astr_data_json);

} // LFortran

#endif // LFORTRAN_UTILS_H