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

Interactive shell implementation #2617

Merged
merged 9 commits into from
May 12, 2024
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,6 @@ integration_tests/array_02_decl
integration_tests/array_02_decl.c
integration_tests/expr_12
integration_tests/expr_12.c

# Interactive Shell
/input
168 changes: 167 additions & 1 deletion src/bin/lpython.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <lpython/python_serialization.h>
#include <lpython/parser/tokenizer.h>
#include <lpython/parser/parser.h>
#include <libasr/exception.h>

#include <cpp-terminal/terminal.h>
#include <cpp-terminal/prompt0.h>
Expand Down Expand Up @@ -743,6 +744,11 @@ void print_time_report(std::vector<std::pair<std::string, double>> &times, bool

#ifdef HAVE_LFORTRAN_LLVM

void section(const std::string &s)
{
std::cout << color(LCompilers::style::bold) << color(LCompilers::fg::blue) << s << color(LCompilers::style::reset) << color(LCompilers::fg::reset) << std::endl;
}

int emit_llvm(const std::string &infile,
const std::string &runtime_library_dir,
LCompilers::PassManager& pass_manager,
Expand Down Expand Up @@ -792,6 +798,157 @@ int emit_llvm(const std::string &infile,
return 0;
}

int interactive_python_repl(
LCompilers::PassManager& pass_manager,
CompilerOptions &compiler_options,
bool verbose)
{
Allocator al(4*1024);
compiler_options.interactive = true;
LCompilers::PythonCompiler fe(compiler_options);
LCompilers::diag::Diagnostics diagnostics;
LCompilers::LocationManager lm;
std::vector<std::pair<std::string, double>> times;
LCompilers::PythonCompiler::EvalResult r;

std::string code_string;
std::cout << ">>> ";
size_t cell_count = 0;
for (std::string input; std::getline(std::cin, input);) {
if (input == "exit" || input == "quit") {
return 0;
}

if ((input.rfind("def", 0) == 0) ||
(input.rfind("for", 0) == 0) ||
(input.rfind("if", 0) == 0) ||
(input.rfind("else", 0) == 0) ||
(input.rfind("elif", 0) == 0) ||
(input.rfind("class", 0) == 0) ||
(input.rfind('@', 0) == 0) ||
(input.rfind(' ', 0) == 0) ||
(input.rfind('\t', 0) == 0)) {
// start of a block
code_string += input + "\n";
std::cout << "... ";
continue;
}
code_string += input + "\n";

{
cell_count++;
LCompilers::LocationManager::FileLocations fl;
fl.in_filename = "input";
std::ofstream out("input");
out << code_string;
lm.files.push_back(fl);
lm.init_simple(code_string);
lm.file_ends.push_back(code_string.size());
}

try {
auto evaluation_start_time = std::chrono::high_resolution_clock::now();
LCompilers::Result<LCompilers::PythonCompiler::EvalResult>
res = fe.evaluate(code_string, verbose, lm, pass_manager, diagnostics);
if (res.ok) {
r = res.result;
} else {
LCOMPILERS_ASSERT(diagnostics.has_error())
std::cerr << diagnostics.render(lm, compiler_options);
diagnostics.clear();
code_string = "";
std::cout << ">>> ";
continue;
}

auto evaluation_end_time = std::chrono::high_resolution_clock::now();
times.push_back(std::make_pair("evalution " + std::to_string(cell_count), std::chrono::duration
<double, std::milli>(evaluation_start_time - evaluation_end_time).count()));

} catch (const LCompilers::LCompilersException &e) {
std::cerr << "Internal Compiler Error: Unhandled exception" << std::endl;
std::vector<LCompilers::StacktraceItem> d = e.stacktrace_addresses();
get_local_addresses(d);
get_local_info(d);
std::cerr << stacktrace2str(d, LCompilers::stacktrace_depth);
std::cerr << e.name() + ": " << e.msg() << std::endl;

code_string = "";
std::cout << ">>> ";
continue;
}

if (verbose) {
section("AST:");
std::cout << r.ast << std::endl;
section("ASR:");
std::cout << r.asr << std::endl;
section("LLVM IR:");
std::cout << r.llvm_ir << std::endl;
}

switch (r.type) {
case (LCompilers::PythonCompiler::EvalResult::integer4) : {
if (verbose) std::cout << "Return type: integer" << std::endl;
if (verbose) section("Result:");
std::cout << r.i32 << std::endl;
break;
}
case (LCompilers::PythonCompiler::EvalResult::integer8) : {
if (verbose) std::cout << "Return type: integer(8)" << std::endl;
if (verbose) section("Result:");
std::cout << r.i64 << std::endl;
break;
}
case (LCompilers::PythonCompiler::EvalResult::real4) : {
if (verbose) std::cout << "Return type: real" << std::endl;
if (verbose) section("Result:");
std::cout << std::setprecision(8) << r.f32 << std::endl;
break;
}
case (LCompilers::PythonCompiler::EvalResult::real8) : {
if (verbose) std::cout << "Return type: real(8)" << std::endl;
if (verbose) section("Result:");
std::cout << std::setprecision(17) << r.f64 << std::endl;
break;
}
case (LCompilers::PythonCompiler::EvalResult::complex4) : {
if (verbose) std::cout << "Return type: complex" << std::endl;
if (verbose) section("Result:");
std::cout << std::setprecision(8) << "(" << r.c32.re << ", " << r.c32.im << ")" << std::endl;
break;
}
case (LCompilers::PythonCompiler::EvalResult::complex8) : {
if (verbose) std::cout << "Return type: complex(8)" << std::endl;
if (verbose) section("Result:");
std::cout << std::setprecision(17) << "(" << r.c64.re << ", " << r.c64.im << ")" << std::endl;
break;
}
case (LCompilers::PythonCompiler::EvalResult::statement) : {
if (verbose) {
std::cout << "Return type: none" << std::endl;
section("Result:");
std::cout << "(statement)" << std::endl;
}
break;
}
case (LCompilers::PythonCompiler::EvalResult::none) : {
if (verbose) {
std::cout << "Return type: none" << std::endl;
section("Result:");
std::cout << "(nothing to execute)" << std::endl;
}
break;
}
default : throw LCompilers::LCompilersException("Return type not supported");
}

code_string = "";
std::cout << ">>> ";
}
return 0;
}

/*
Compiles python to object file, if `to_jit` is false
otherwise execute python code using llvm JIT
Expand Down Expand Up @@ -1824,8 +1981,17 @@ int main(int argc, char *argv[])
}

if (arg_files.size() == 0) {
std::cerr << "Interactive prompt is not implemented yet in LPython" << std::endl;
#ifdef HAVE_LFORTRAN_LLVM
lpython_pass_manager.parse_pass_arg(arg_pass, skip_pass);
lpython_pass_manager.use_default_passes();
compiler_options.po.disable_main = true;
compiler_options.emit_debug_line_column = false;
compiler_options.generate_object_code = false;
return interactive_python_repl(lpython_pass_manager, compiler_options, arg_v);
#else
std::cerr << "Interactive prompt requires the LLVM backend to be enabled. Recompile with `WITH_LLVM=yes`." << std::endl;
return 1;
#endif
}

// TODO: for now we ignore the other filenames, only handle
Expand Down
4 changes: 4 additions & 0 deletions src/libasr/diagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ struct Diagnostics {
diagnostics.push_back(d);
}

void clear() {
diagnostics.clear();
}

void message_label(const std::string &message,
const std::vector<Location> &locations,
const std::string &error_label,
Expand Down
1 change: 1 addition & 0 deletions src/lpython/parser/parser.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef LPYTHON_PARSER_PARSER_H
#define LPYTHON_PARSER_PARSER_H

#include "lpython/python_ast.h"
#include <libasr/containers.h>
#include <libasr/diagnostics.h>
#include <lpython/parser/tokenizer.h>
Expand Down
137 changes: 131 additions & 6 deletions src/lpython/python_evaluator.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
#include <iostream>
#include <fstream>
#include <string>

#include <lpython/python_evaluator.h>
#include <lpython/semantics/python_ast_to_asr.h>
#include <lpython/python_ast.h>
#include <lpython/pickle.h>
#include <lpython/parser/parser.h>
#include <libasr/codegen/asr_to_cpp.h>
#include <libasr/exception.h>
#include <libasr/asr.h>
#include <libasr/asr_scopes.h>

#ifdef HAVE_LFORTRAN_LLVM
#include <libasr/codegen/evaluator.h>
Expand All @@ -26,16 +32,138 @@ PythonCompiler::PythonCompiler(CompilerOptions compiler_options)
al{1024*1024},
#ifdef HAVE_LFORTRAN_LLVM
e{std::make_unique<LLVMEvaluator>()},
eval_count{0},
#endif
compiler_options{compiler_options}
// symbol_table{nullptr}
eval_count{1},
compiler_options{compiler_options},
symbol_table{nullptr}
{
}

PythonCompiler::~PythonCompiler() = default;


Result<PythonCompiler::EvalResult> PythonCompiler::evaluate(
#ifdef HAVE_LFORTRAN_LLVM
const std::string &code_orig, bool verbose, LocationManager &lm,
LCompilers::PassManager& pass_manager, diag::Diagnostics &diagnostics
#else
const std::string &/*code_orig*/, bool /*verbose*/,
LocationManager &/*lm*/, LCompilers::PassManager& /*pass_manager*/,
diag::Diagnostics &/*diagnostics*/
#endif
)
{
#ifdef HAVE_LFORTRAN_LLVM
EvalResult result;
result.type = EvalResult::none;

// Src -> AST
Result<LCompilers::LPython::AST::ast_t*> res = get_ast2(code_orig, diagnostics);
LCompilers::LPython::AST::ast_t* ast;
if (res.ok) {
ast = res.result;
} else {
return res.error;
}

if (verbose) {
result.ast = LCompilers::LPython::pickle_python(*ast, true, true);
}

// AST -> ASR
Result<ASR::TranslationUnit_t*> res2 = get_asr3(*ast, diagnostics, lm, true);
ASR::TranslationUnit_t* asr;
if (res2.ok) {
asr = res2.result;
} else {
LCOMPILERS_ASSERT(diagnostics.has_error())
return res2.error;
}

if (verbose) {
result.asr = pickle(*asr, true, true, true);
}

// ASR -> LLVM
std::string module_prefix = "__module___main___";
std::string module_name = "__main__";
std::string sym_name = module_name + "global_stmts_" + std::to_string(eval_count) + "__";
run_fn = module_prefix + sym_name;

Result<std::unique_ptr<LLVMModule>> res3 = get_llvm3(*asr,
pass_manager, diagnostics, lm.files.back().in_filename);
std::unique_ptr<LCompilers::LLVMModule> m;
if (res3.ok) {
m = std::move(res3.result);
} else {
LCOMPILERS_ASSERT(diagnostics.has_error())
return res3.error;
}

if (verbose) {
result.llvm_ir = m->str();
}

bool call_run_fn = false;
if (m->get_return_type(run_fn) != "none") {
call_run_fn = true;
}

e->add_module(std::move(m));
if (call_run_fn) {
e->voidfn(run_fn);
}

if (call_run_fn) {
ASR::down_cast<ASR::Module_t>(symbol_table->resolve_symbol(module_name))->m_symtab
->erase_symbol(sym_name);
}

eval_count++;
return result;
#else
throw LCompilersException("LLVM is not enabled");
#endif
}

Result<LCompilers::LPython::AST::ast_t*> PythonCompiler::get_ast2(
const std::string &code_orig, diag::Diagnostics &diagnostics)
{
// Src -> AST
const std::string *code=&code_orig;
std::string tmp;
Result<LCompilers::LPython::AST::Module_t*>
res = LCompilers::LPython::parse(al, *code, 0, diagnostics);
if (res.ok) {
return (LCompilers::LPython::AST::ast_t*)res.result;
} else {
LCOMPILERS_ASSERT(diagnostics.has_error())
return res.error;
}
}

Result<ASR::TranslationUnit_t*> PythonCompiler::get_asr3(
LCompilers::LPython::AST::ast_t &ast, diag::Diagnostics &diagnostics,
LocationManager &lm, bool is_interactive)
{
ASR::TranslationUnit_t* asr;
// AST -> ASR
if (symbol_table) {
symbol_table->mark_all_variables_external(al);
}
auto res = LCompilers::LPython::python_ast_to_asr(al, lm, symbol_table, ast, diagnostics,
compiler_options, true, "__main__", "", false, is_interactive ? eval_count : 0);
if (res.ok) {
asr = res.result;
} else {
LCOMPILERS_ASSERT(diagnostics.has_error())
return res.error;
}
if (!symbol_table) symbol_table = asr->m_symtab;

return asr;
}

Result<std::unique_ptr<LLVMModule>> PythonCompiler::get_llvm3(
#ifdef HAVE_LFORTRAN_LLVM
ASR::TranslationUnit_t &asr, LCompilers::PassManager& lpm,
Expand All @@ -47,9 +175,6 @@ Result<std::unique_ptr<LLVMModule>> PythonCompiler::get_llvm3(
)
{
#ifdef HAVE_LFORTRAN_LLVM
eval_count++;
run_fn = "__lfortran_evaluate_" + std::to_string(eval_count);

Comment on lines -50 to -52
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We later plan to move the interactivity support into the LibASR so that it is shared between LFortran, LPython and LC. So, it is better that we have/use as common/generic code as possible which easily gets shared across all LCompilers.

Assuming it does not affect the newly added interactivity, I think for now it is better to have it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep this as part of the next PR? It may require changes in more than one place.

I was working towards "print the value of the evaluated expression" (#2617 (comment)) goal, and I think there might be some obstacles to doing this. But is doable.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep this as part of the next PR?

We can do that.

if (compiler_options.emit_debug_info) {
if (!compiler_options.emit_debug_line_column) {
diagnostics.add(LCompilers::diag::Diagnostic(
Expand Down
Loading
Loading