Skip to content

Commit 476d9bb

Browse files
Interactive shell implementation (#2617)
* Initial interactive shell implementation * remember global functions and variables declared * avoid printing AST, ASR and LLVM IR * fixed bug where error from previous cell is printed * detect decorators as incomplete input * clean up * update according to code review suggestion * removed Interactive_t related stuff by merging required changes into SymbolTable.visit_Module * update according to code review suggestion
1 parent 21e27d8 commit 476d9bb

File tree

8 files changed

+368
-42
lines changed

8 files changed

+368
-42
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,6 @@ integration_tests/array_02_decl
234234
integration_tests/array_02_decl.c
235235
integration_tests/expr_12
236236
integration_tests/expr_12.c
237+
238+
# Interactive Shell
239+
/input

src/bin/lpython.cpp

Lines changed: 167 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include <lpython/python_serialization.h>
3434
#include <lpython/parser/tokenizer.h>
3535
#include <lpython/parser/parser.h>
36+
#include <libasr/exception.h>
3637

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

744745
#ifdef HAVE_LFORTRAN_LLVM
745746

747+
void section(const std::string &s)
748+
{
749+
std::cout << color(LCompilers::style::bold) << color(LCompilers::fg::blue) << s << color(LCompilers::style::reset) << color(LCompilers::fg::reset) << std::endl;
750+
}
751+
746752
int emit_llvm(const std::string &infile,
747753
const std::string &runtime_library_dir,
748754
LCompilers::PassManager& pass_manager,
@@ -792,6 +798,157 @@ int emit_llvm(const std::string &infile,
792798
return 0;
793799
}
794800

801+
int interactive_python_repl(
802+
LCompilers::PassManager& pass_manager,
803+
CompilerOptions &compiler_options,
804+
bool verbose)
805+
{
806+
Allocator al(4*1024);
807+
compiler_options.interactive = true;
808+
LCompilers::PythonCompiler fe(compiler_options);
809+
LCompilers::diag::Diagnostics diagnostics;
810+
LCompilers::LocationManager lm;
811+
std::vector<std::pair<std::string, double>> times;
812+
LCompilers::PythonCompiler::EvalResult r;
813+
814+
std::string code_string;
815+
std::cout << ">>> ";
816+
size_t cell_count = 0;
817+
for (std::string input; std::getline(std::cin, input);) {
818+
if (input == "exit" || input == "quit") {
819+
return 0;
820+
}
821+
822+
if ((input.rfind("def", 0) == 0) ||
823+
(input.rfind("for", 0) == 0) ||
824+
(input.rfind("if", 0) == 0) ||
825+
(input.rfind("else", 0) == 0) ||
826+
(input.rfind("elif", 0) == 0) ||
827+
(input.rfind("class", 0) == 0) ||
828+
(input.rfind('@', 0) == 0) ||
829+
(input.rfind(' ', 0) == 0) ||
830+
(input.rfind('\t', 0) == 0)) {
831+
// start of a block
832+
code_string += input + "\n";
833+
std::cout << "... ";
834+
continue;
835+
}
836+
code_string += input + "\n";
837+
838+
{
839+
cell_count++;
840+
LCompilers::LocationManager::FileLocations fl;
841+
fl.in_filename = "input";
842+
std::ofstream out("input");
843+
out << code_string;
844+
lm.files.push_back(fl);
845+
lm.init_simple(code_string);
846+
lm.file_ends.push_back(code_string.size());
847+
}
848+
849+
try {
850+
auto evaluation_start_time = std::chrono::high_resolution_clock::now();
851+
LCompilers::Result<LCompilers::PythonCompiler::EvalResult>
852+
res = fe.evaluate(code_string, verbose, lm, pass_manager, diagnostics);
853+
if (res.ok) {
854+
r = res.result;
855+
} else {
856+
LCOMPILERS_ASSERT(diagnostics.has_error())
857+
std::cerr << diagnostics.render(lm, compiler_options);
858+
diagnostics.clear();
859+
code_string = "";
860+
std::cout << ">>> ";
861+
continue;
862+
}
863+
864+
auto evaluation_end_time = std::chrono::high_resolution_clock::now();
865+
times.push_back(std::make_pair("evalution " + std::to_string(cell_count), std::chrono::duration
866+
<double, std::milli>(evaluation_start_time - evaluation_end_time).count()));
867+
868+
} catch (const LCompilers::LCompilersException &e) {
869+
std::cerr << "Internal Compiler Error: Unhandled exception" << std::endl;
870+
std::vector<LCompilers::StacktraceItem> d = e.stacktrace_addresses();
871+
get_local_addresses(d);
872+
get_local_info(d);
873+
std::cerr << stacktrace2str(d, LCompilers::stacktrace_depth);
874+
std::cerr << e.name() + ": " << e.msg() << std::endl;
875+
876+
code_string = "";
877+
std::cout << ">>> ";
878+
continue;
879+
}
880+
881+
if (verbose) {
882+
section("AST:");
883+
std::cout << r.ast << std::endl;
884+
section("ASR:");
885+
std::cout << r.asr << std::endl;
886+
section("LLVM IR:");
887+
std::cout << r.llvm_ir << std::endl;
888+
}
889+
890+
switch (r.type) {
891+
case (LCompilers::PythonCompiler::EvalResult::integer4) : {
892+
if (verbose) std::cout << "Return type: integer" << std::endl;
893+
if (verbose) section("Result:");
894+
std::cout << r.i32 << std::endl;
895+
break;
896+
}
897+
case (LCompilers::PythonCompiler::EvalResult::integer8) : {
898+
if (verbose) std::cout << "Return type: integer(8)" << std::endl;
899+
if (verbose) section("Result:");
900+
std::cout << r.i64 << std::endl;
901+
break;
902+
}
903+
case (LCompilers::PythonCompiler::EvalResult::real4) : {
904+
if (verbose) std::cout << "Return type: real" << std::endl;
905+
if (verbose) section("Result:");
906+
std::cout << std::setprecision(8) << r.f32 << std::endl;
907+
break;
908+
}
909+
case (LCompilers::PythonCompiler::EvalResult::real8) : {
910+
if (verbose) std::cout << "Return type: real(8)" << std::endl;
911+
if (verbose) section("Result:");
912+
std::cout << std::setprecision(17) << r.f64 << std::endl;
913+
break;
914+
}
915+
case (LCompilers::PythonCompiler::EvalResult::complex4) : {
916+
if (verbose) std::cout << "Return type: complex" << std::endl;
917+
if (verbose) section("Result:");
918+
std::cout << std::setprecision(8) << "(" << r.c32.re << ", " << r.c32.im << ")" << std::endl;
919+
break;
920+
}
921+
case (LCompilers::PythonCompiler::EvalResult::complex8) : {
922+
if (verbose) std::cout << "Return type: complex(8)" << std::endl;
923+
if (verbose) section("Result:");
924+
std::cout << std::setprecision(17) << "(" << r.c64.re << ", " << r.c64.im << ")" << std::endl;
925+
break;
926+
}
927+
case (LCompilers::PythonCompiler::EvalResult::statement) : {
928+
if (verbose) {
929+
std::cout << "Return type: none" << std::endl;
930+
section("Result:");
931+
std::cout << "(statement)" << std::endl;
932+
}
933+
break;
934+
}
935+
case (LCompilers::PythonCompiler::EvalResult::none) : {
936+
if (verbose) {
937+
std::cout << "Return type: none" << std::endl;
938+
section("Result:");
939+
std::cout << "(nothing to execute)" << std::endl;
940+
}
941+
break;
942+
}
943+
default : throw LCompilers::LCompilersException("Return type not supported");
944+
}
945+
946+
code_string = "";
947+
std::cout << ">>> ";
948+
}
949+
return 0;
950+
}
951+
795952
/*
796953
Compiles python to object file, if `to_jit` is false
797954
otherwise execute python code using llvm JIT
@@ -1824,8 +1981,17 @@ int main(int argc, char *argv[])
18241981
}
18251982

18261983
if (arg_files.size() == 0) {
1827-
std::cerr << "Interactive prompt is not implemented yet in LPython" << std::endl;
1984+
#ifdef HAVE_LFORTRAN_LLVM
1985+
lpython_pass_manager.parse_pass_arg(arg_pass, skip_pass);
1986+
lpython_pass_manager.use_default_passes();
1987+
compiler_options.po.disable_main = true;
1988+
compiler_options.emit_debug_line_column = false;
1989+
compiler_options.generate_object_code = false;
1990+
return interactive_python_repl(lpython_pass_manager, compiler_options, arg_v);
1991+
#else
1992+
std::cerr << "Interactive prompt requires the LLVM backend to be enabled. Recompile with `WITH_LLVM=yes`." << std::endl;
18281993
return 1;
1994+
#endif
18291995
}
18301996

18311997
// TODO: for now we ignore the other filenames, only handle

src/libasr/diagnostics.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ struct Diagnostics {
127127
diagnostics.push_back(d);
128128
}
129129

130+
void clear() {
131+
diagnostics.clear();
132+
}
133+
130134
void message_label(const std::string &message,
131135
const std::vector<Location> &locations,
132136
const std::string &error_label,

src/lpython/parser/parser.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifndef LPYTHON_PARSER_PARSER_H
22
#define LPYTHON_PARSER_PARSER_H
33

4+
#include "lpython/python_ast.h"
45
#include <libasr/containers.h>
56
#include <libasr/diagnostics.h>
67
#include <lpython/parser/tokenizer.h>

src/lpython/python_evaluator.cpp

Lines changed: 131 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
#include <iostream>
22
#include <fstream>
3+
#include <string>
34

45
#include <lpython/python_evaluator.h>
6+
#include <lpython/semantics/python_ast_to_asr.h>
7+
#include <lpython/python_ast.h>
8+
#include <lpython/pickle.h>
9+
#include <lpython/parser/parser.h>
510
#include <libasr/codegen/asr_to_cpp.h>
611
#include <libasr/exception.h>
712
#include <libasr/asr.h>
13+
#include <libasr/asr_scopes.h>
814

915
#ifdef HAVE_LFORTRAN_LLVM
1016
#include <libasr/codegen/evaluator.h>
@@ -26,16 +32,138 @@ PythonCompiler::PythonCompiler(CompilerOptions compiler_options)
2632
al{1024*1024},
2733
#ifdef HAVE_LFORTRAN_LLVM
2834
e{std::make_unique<LLVMEvaluator>()},
29-
eval_count{0},
3035
#endif
31-
compiler_options{compiler_options}
32-
// symbol_table{nullptr}
36+
eval_count{1},
37+
compiler_options{compiler_options},
38+
symbol_table{nullptr}
3339
{
3440
}
3541

3642
PythonCompiler::~PythonCompiler() = default;
3743

3844

45+
Result<PythonCompiler::EvalResult> PythonCompiler::evaluate(
46+
#ifdef HAVE_LFORTRAN_LLVM
47+
const std::string &code_orig, bool verbose, LocationManager &lm,
48+
LCompilers::PassManager& pass_manager, diag::Diagnostics &diagnostics
49+
#else
50+
const std::string &/*code_orig*/, bool /*verbose*/,
51+
LocationManager &/*lm*/, LCompilers::PassManager& /*pass_manager*/,
52+
diag::Diagnostics &/*diagnostics*/
53+
#endif
54+
)
55+
{
56+
#ifdef HAVE_LFORTRAN_LLVM
57+
EvalResult result;
58+
result.type = EvalResult::none;
59+
60+
// Src -> AST
61+
Result<LCompilers::LPython::AST::ast_t*> res = get_ast2(code_orig, diagnostics);
62+
LCompilers::LPython::AST::ast_t* ast;
63+
if (res.ok) {
64+
ast = res.result;
65+
} else {
66+
return res.error;
67+
}
68+
69+
if (verbose) {
70+
result.ast = LCompilers::LPython::pickle_python(*ast, true, true);
71+
}
72+
73+
// AST -> ASR
74+
Result<ASR::TranslationUnit_t*> res2 = get_asr3(*ast, diagnostics, lm, true);
75+
ASR::TranslationUnit_t* asr;
76+
if (res2.ok) {
77+
asr = res2.result;
78+
} else {
79+
LCOMPILERS_ASSERT(diagnostics.has_error())
80+
return res2.error;
81+
}
82+
83+
if (verbose) {
84+
result.asr = pickle(*asr, true, true, true);
85+
}
86+
87+
// ASR -> LLVM
88+
std::string module_prefix = "__module___main___";
89+
std::string module_name = "__main__";
90+
std::string sym_name = module_name + "global_stmts_" + std::to_string(eval_count) + "__";
91+
run_fn = module_prefix + sym_name;
92+
93+
Result<std::unique_ptr<LLVMModule>> res3 = get_llvm3(*asr,
94+
pass_manager, diagnostics, lm.files.back().in_filename);
95+
std::unique_ptr<LCompilers::LLVMModule> m;
96+
if (res3.ok) {
97+
m = std::move(res3.result);
98+
} else {
99+
LCOMPILERS_ASSERT(diagnostics.has_error())
100+
return res3.error;
101+
}
102+
103+
if (verbose) {
104+
result.llvm_ir = m->str();
105+
}
106+
107+
bool call_run_fn = false;
108+
if (m->get_return_type(run_fn) != "none") {
109+
call_run_fn = true;
110+
}
111+
112+
e->add_module(std::move(m));
113+
if (call_run_fn) {
114+
e->voidfn(run_fn);
115+
}
116+
117+
if (call_run_fn) {
118+
ASR::down_cast<ASR::Module_t>(symbol_table->resolve_symbol(module_name))->m_symtab
119+
->erase_symbol(sym_name);
120+
}
121+
122+
eval_count++;
123+
return result;
124+
#else
125+
throw LCompilersException("LLVM is not enabled");
126+
#endif
127+
}
128+
129+
Result<LCompilers::LPython::AST::ast_t*> PythonCompiler::get_ast2(
130+
const std::string &code_orig, diag::Diagnostics &diagnostics)
131+
{
132+
// Src -> AST
133+
const std::string *code=&code_orig;
134+
std::string tmp;
135+
Result<LCompilers::LPython::AST::Module_t*>
136+
res = LCompilers::LPython::parse(al, *code, 0, diagnostics);
137+
if (res.ok) {
138+
return (LCompilers::LPython::AST::ast_t*)res.result;
139+
} else {
140+
LCOMPILERS_ASSERT(diagnostics.has_error())
141+
return res.error;
142+
}
143+
}
144+
145+
Result<ASR::TranslationUnit_t*> PythonCompiler::get_asr3(
146+
LCompilers::LPython::AST::ast_t &ast, diag::Diagnostics &diagnostics,
147+
LocationManager &lm, bool is_interactive)
148+
{
149+
ASR::TranslationUnit_t* asr;
150+
// AST -> ASR
151+
if (symbol_table) {
152+
symbol_table->mark_all_variables_external(al);
153+
}
154+
auto res = LCompilers::LPython::python_ast_to_asr(al, lm, symbol_table, ast, diagnostics,
155+
compiler_options, true, "__main__", "", false, is_interactive ? eval_count : 0);
156+
if (res.ok) {
157+
asr = res.result;
158+
} else {
159+
LCOMPILERS_ASSERT(diagnostics.has_error())
160+
return res.error;
161+
}
162+
if (!symbol_table) symbol_table = asr->m_symtab;
163+
164+
return asr;
165+
}
166+
39167
Result<std::unique_ptr<LLVMModule>> PythonCompiler::get_llvm3(
40168
#ifdef HAVE_LFORTRAN_LLVM
41169
ASR::TranslationUnit_t &asr, LCompilers::PassManager& lpm,
@@ -47,9 +175,6 @@ Result<std::unique_ptr<LLVMModule>> PythonCompiler::get_llvm3(
47175
)
48176
{
49177
#ifdef HAVE_LFORTRAN_LLVM
50-
eval_count++;
51-
run_fn = "__lfortran_evaluate_" + std::to_string(eval_count);
52-
53178
if (compiler_options.emit_debug_info) {
54179
if (!compiler_options.emit_debug_line_column) {
55180
diagnostics.add(LCompilers::diag::Diagnostic(

0 commit comments

Comments
 (0)