From c57f9cf8b1ee8c2abc6ef4445697f8ca7954e09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 12 Oct 2017 14:00:18 +0200 Subject: [PATCH] [code] The Python console compiles and executes commands entered. It stores and displays the result in the console store. An empty line returned at the end of the execution is deleted. Change-Id: Ic90e02e2d91d0a0033413da0588032d9450aefd0 --- apps/code/Makefile | 1 - apps/code/base.de.i18n | 1 + apps/code/base.en.i18n | 1 + apps/code/base.es.i18n | 1 + apps/code/base.fr.i18n | 1 + apps/code/base.pt.i18n | 1 + apps/code/console_controller.cpp | 74 ++++++++++++++++++- apps/code/console_controller.h | 15 +++- apps/code/console_edit_cell.cpp | 19 +++-- apps/code/console_edit_cell.h | 3 + apps/code/console_line_cell.cpp | 14 +++- apps/code/console_store.cpp | 44 +++++++++-- apps/code/console_store.h | 2 + apps/code/executor_controller.cpp | 117 ------------------------------ apps/code/executor_controller.h | 32 -------- apps/code/menu_controller.cpp | 5 +- apps/code/menu_controller.h | 4 +- apps/shared.universal.i18n | 1 + 18 files changed, 165 insertions(+), 171 deletions(-) delete mode 100644 apps/code/executor_controller.cpp delete mode 100644 apps/code/executor_controller.h diff --git a/apps/code/Makefile b/apps/code/Makefile index 2f7448978..f2d18db06 100644 --- a/apps/code/Makefile +++ b/apps/code/Makefile @@ -8,7 +8,6 @@ app_objs += $(addprefix apps/code/,\ console_line_cell.o\ console_store.o\ editor_controller.o\ - executor_controller.o\ menu_controller.o\ program.o\ ) diff --git a/apps/code/base.de.i18n b/apps/code/base.de.i18n index 04c1c8871..49ef71391 100644 --- a/apps/code/base.de.i18n +++ b/apps/code/base.de.i18n @@ -1,3 +1,4 @@ Console = "Console" +ConsoleError = "Error" EditProgram = "Programm bearbeiten" ExecuteProgram = "Programm ausfuhren" diff --git a/apps/code/base.en.i18n b/apps/code/base.en.i18n index c45470d9d..fdc231ce1 100644 --- a/apps/code/base.en.i18n +++ b/apps/code/base.en.i18n @@ -1,3 +1,4 @@ Console = "Console" +ConsoleError = "Error" EditProgram = "Edit program" ExecuteProgram = "Execute program" diff --git a/apps/code/base.es.i18n b/apps/code/base.es.i18n index fb36d39d4..3e2584359 100644 --- a/apps/code/base.es.i18n +++ b/apps/code/base.es.i18n @@ -1,3 +1,4 @@ Console = "Console" +ConsoleError = "Error" EditProgram = "Editar el programa" ExecuteProgram = "Ejecutar el programa" diff --git a/apps/code/base.fr.i18n b/apps/code/base.fr.i18n index b9a2ffe32..3542f462e 100644 --- a/apps/code/base.fr.i18n +++ b/apps/code/base.fr.i18n @@ -1,3 +1,4 @@ Console = "Console" +ConsoleError = "Error" EditProgram = "Editer le programme" ExecuteProgram = "Executer le programme" diff --git a/apps/code/base.pt.i18n b/apps/code/base.pt.i18n index 5d78fa2df..b34989fe3 100644 --- a/apps/code/base.pt.i18n +++ b/apps/code/base.pt.i18n @@ -1,3 +1,4 @@ Console = "Console" +ConsoleError = "Error" EditProgram = "Editar programa" ExecuteProgram = "Executar programa" diff --git a/apps/code/console_controller.cpp b/apps/code/console_controller.cpp index fb5a29e92..1ebe3867d 100644 --- a/apps/code/console_controller.cpp +++ b/apps/code/console_controller.cpp @@ -1,13 +1,61 @@ #include "console_controller.h" +#include + +extern "C" { +#include +#include "port.h" +#include "py/mphal.h" +#include "py/compile.h" +#include "py/runtime.h" +#include "py/gc.h" +#include "py/stackctrl.h" +} namespace Code { +/* mp_hal_stdout_tx_strn_cooked symbol required by micropython at printing + * needs to access information about where to print. This 'context' is provided + * by the global sCurrentConsoleStore that points to the console store. */ + +static ConsoleStore * sCurrentConsoleStore = nullptr; + +extern "C" +void mp_hal_stdout_tx_strn_cooked(const char * str, size_t len) { + assert(sCurrentConsoleStore != nullptr); + sCurrentConsoleStore->pushResult(str, len); +} + +mp_obj_t execute_from_str(const char *str) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_lexer_t *lex = mp_lexer_new_from_str_len(0, str, strlen(str), false); + mp_parse_tree_t pt = mp_parse(lex, MP_PARSE_SINGLE_INPUT); + mp_obj_t module_fun = mp_compile(&pt, lex->source_name, MP_EMIT_OPT_NONE, true); + mp_hal_set_interrupt_char((int)Ion::Keyboard::Key::A6); + mp_call_function_0(module_fun); + mp_hal_set_interrupt_char(-1); // Disable interrupt + nlr_pop(); + return 0; + } else { + // Uncaught exception + return (mp_obj_t) nlr.ret_val; + } +} + ConsoleController::ConsoleController(Responder * parentResponder) : ViewController(parentResponder), TextFieldDelegate(), + m_rowHeight(KDText::charSize(k_fontSize).height()), m_tableView(this, this, 0, 0), m_editCell(this, this) { + assert(sCurrentConsoleStore == nullptr); + sCurrentConsoleStore = &m_consoleStore; + initPython(); +} + +ConsoleController::~ConsoleController() { + stopPython(); } void ConsoleController::viewWillAppear() { @@ -23,7 +71,7 @@ int ConsoleController::numberOfRows() { } KDCoordinate ConsoleController::rowHeight(int j) { - return k_rowHeight; + return m_rowHeight; } KDCoordinate ConsoleController::cumulatedHeightFromIndex(int j) { @@ -82,9 +130,11 @@ bool ConsoleController::textFieldDidReceiveEvent(TextField * textField, Ion::Eve bool ConsoleController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { m_consoleStore.pushCommand(text, strlen(text)); + executePython(text); textField->setText(""); m_tableView.reloadData(); m_tableView.scrollToCell(0, m_consoleStore.numberOfLines()); + m_editCell.setEditing(true); return true; } @@ -92,4 +142,26 @@ Toolbox * ConsoleController::toolboxForTextField(TextField * textFied) { return nullptr; } +void ConsoleController::initPython() { + mp_stack_set_limit(40000); + mp_port_init_stack_top(); + + m_pythonHeap = (char *)malloc(16384); + gc_init(m_pythonHeap, m_pythonHeap + 16384); + + mp_init(); +} + +void ConsoleController::executePython(const char * str) { + if (execute_from_str(str)) { + mp_hal_stdout_tx_strn_cooked(I18n::translate(I18n::Message::ConsoleError), 5); + } + m_consoleStore.deleteLastLineIfEmpty(); +} + +void ConsoleController::stopPython() { + free(m_pythonHeap); + sCurrentConsoleStore = nullptr; +} + } diff --git a/apps/code/console_controller.h b/apps/code/console_controller.h index a517a3a02..2e6f45d0e 100644 --- a/apps/code/console_controller.h +++ b/apps/code/console_controller.h @@ -12,6 +12,11 @@ namespace Code { class ConsoleController : public ViewController, public ListViewDataSource, public ScrollViewDataSource, public TextFieldDelegate { public: ConsoleController(Responder * parentResponder); + ~ConsoleController(); + ConsoleController(const ConsoleController& other) = delete; + ConsoleController(ConsoleController&& other) = delete; + ConsoleController operator=(const ConsoleController& other) = delete; + ConsoleController& operator=(ConsoleController&& other) = delete; // ViewController View * view() override { return &m_tableView; } @@ -34,15 +39,23 @@ public: bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; Toolbox * toolboxForTextField(TextField * textFied) override; + // Python + void initPython(); + void executePython(const char * str); + void stopPython(); + + // Other + static constexpr KDText::FontSize k_fontSize = KDText::FontSize::Large; private: static constexpr int LineCellType = 0; static constexpr int EditCellType = 1; static constexpr int k_numberOfLineCells = 15; // May change depending on the height - static constexpr int k_rowHeight = 20; + int m_rowHeight; ConsoleStore m_consoleStore; TableView m_tableView; ConsoleLineCell m_cells[k_numberOfLineCells]; ConsoleEditCell m_editCell; + char * m_pythonHeap; }; } diff --git a/apps/code/console_edit_cell.cpp b/apps/code/console_edit_cell.cpp index ea44299c9..674ecbe1a 100644 --- a/apps/code/console_edit_cell.cpp +++ b/apps/code/console_edit_cell.cpp @@ -1,5 +1,7 @@ #include "console_edit_cell.h" +#include "console_controller.h" #include +#include #include namespace Code { @@ -7,21 +9,28 @@ namespace Code { ConsoleEditCell::ConsoleEditCell(Responder * parentResponder, TextFieldDelegate * delegate) : HighlightCell(), Responder(parentResponder), - m_textField(this, m_textBuffer, m_draftTextBuffer, TextField::maxBufferSize(), delegate) + m_promptView(ConsoleController::k_fontSize, I18n::Message::ConsolePrompt, 0, 0.5), + m_textField(this, m_textBuffer, m_draftTextBuffer, TextField::maxBufferSize(), delegate, true, ConsoleController::k_fontSize) { } int ConsoleEditCell::numberOfSubviews() const { - return 1; + return 2; } View * ConsoleEditCell::subviewAtIndex(int index) { - assert(index == 0); - return &m_textField; + assert(index == 0 || index ==1); + if (index == 0) { + return &m_promptView; + } else { + return &m_textField; + } } void ConsoleEditCell::layoutSubviews() { - m_textField.setFrame(bounds()); + KDSize chevronsSize = KDText::stringSize(I18n::translate(I18n::Message::ConsolePrompt), ConsoleController::k_fontSize); + m_promptView.setFrame(KDRect(KDPointZero, chevronsSize.width(), bounds().height())); + m_textField.setFrame(KDRect(KDPoint(chevronsSize.width(), KDCoordinate(0)), bounds().width() - chevronsSize.width(), bounds().height())); } void ConsoleEditCell::didBecomeFirstResponder() { diff --git a/apps/code/console_edit_cell.h b/apps/code/console_edit_cell.h index a68441b35..034202573 100644 --- a/apps/code/console_edit_cell.h +++ b/apps/code/console_edit_cell.h @@ -5,6 +5,8 @@ #include #include #include +#include +#include "console_line.h" namespace Code { @@ -26,6 +28,7 @@ public: private: char m_textBuffer[TextField::maxBufferSize()]; char m_draftTextBuffer[TextField::maxBufferSize()]; + MessageTextView m_promptView; TextField m_textField; }; diff --git a/apps/code/console_line_cell.cpp b/apps/code/console_line_cell.cpp index 873d0d387..733b00431 100644 --- a/apps/code/console_line_cell.cpp +++ b/apps/code/console_line_cell.cpp @@ -1,4 +1,8 @@ #include "console_line_cell.h" +#include "console_controller.h" +#include +#include +#include namespace Code { @@ -9,8 +13,14 @@ ConsoleLineCell::ConsoleLineCell() : } void ConsoleLineCell::drawRect(KDContext * ctx, KDRect rect) const { - ctx->fillRect(bounds(), KDColorRed); - ctx->drawString(m_line.text(), KDPointZero); + ctx->fillRect(bounds(), KDColorWhite); + if (m_line.type() == ConsoleLine::Type::Command) { + ctx->drawString(I18n::translate(I18n::Message::ConsolePrompt), KDPointZero, ConsoleController::k_fontSize); + KDCoordinate chevronsWidth = KDText::stringSize(I18n::translate(I18n::Message::ConsolePrompt), ConsoleController::k_fontSize).width(); + ctx->drawString(m_line.text(), KDPoint(chevronsWidth, KDCoordinate(0)), ConsoleController::k_fontSize); + } else { + ctx->drawString(m_line.text(), KDPointZero, ConsoleController::k_fontSize); + } } void ConsoleLineCell::setLine(ConsoleLine line) { diff --git a/apps/code/console_store.cpp b/apps/code/console_store.cpp index 6bb10523e..b6c3ad1ea 100644 --- a/apps/code/console_store.cpp +++ b/apps/code/console_store.cpp @@ -50,6 +50,15 @@ void ConsoleStore::pushResult(const char * text, size_t length) { push(ResultMarker, text, length); } + +void ConsoleStore::deleteLastLineIfEmpty() { + ConsoleLine lastLine = lineAtIndex(numberOfLines()-1); + char lastLineFirstChar = lastLine.text()[0]; + if (lastLineFirstChar == 0 || lastLineFirstChar == '\n') { + deleteLastLine(); + } +} + void ConsoleStore::push(const char marker, const char * text, size_t length) { // TODO: Verify that the text field does not accept texts that are bigger than // k_historySize, or put an alert message if the command is too big. @@ -88,14 +97,37 @@ void ConsoleStore::deleteFirstLine() { if (m_history[0] == 0) { return; } - int indexOfSecondLineMarker = 1; - while (m_history[indexOfSecondLineMarker] != 0) { - indexOfSecondLineMarker++; + int secondLineMarkerIndex = 1; + while (m_history[secondLineMarkerIndex] != 0) { + secondLineMarkerIndex++; } - indexOfSecondLineMarker++; - for (int i=0; i -#include "port.h" -#include "py/mphal.h" -#include "py/compile.h" -#include "py/runtime.h" -#include "py/gc.h" -#include "py/stackctrl.h" -} - -namespace Code { - -mp_obj_t execute_from_str(const char *str) { - nlr_buf_t nlr; - if (nlr_push(&nlr) == 0) { - mp_lexer_t *lex = mp_lexer_new_from_str_len(0/*MP_QSTR_*/, str, strlen(str), false); - mp_parse_tree_t pt = mp_parse(lex, MP_PARSE_FILE_INPUT); - mp_obj_t module_fun = mp_compile(&pt, lex->source_name, MP_EMIT_OPT_NONE, false); - mp_hal_set_interrupt_char((int)Ion::Keyboard::Key::A6); - mp_call_function_0(module_fun); - mp_hal_set_interrupt_char(-1); // disable interrupt - nlr_pop(); - return 0; - } else { - // uncaught exception - return (mp_obj_t)nlr.ret_val; - } -} - -/* mp_hal_stdout_tx_strn_cooked symbol required by micropython at printing - * needs to access information about where to print (depending on the strings - * printed before). This 'context' is provided by the global sCurrentView that - * points to the content view only within the drawRect method (as runPython is - * called within drawRect). */ -static const ExecutorController::ContentView * sCurrentView = nullptr; - -extern "C" -void mp_hal_stdout_tx_strn_cooked(const char * str, size_t len) { - assert(sCurrentView != nullptr); - sCurrentView->print(str); -} - -ExecutorController::ContentView::ContentView(Program * program) : - View(), - m_program(program), - m_printLocation(KDPointZero) -{ -} - -void ExecutorController::ContentView::drawRect(KDContext * ctx, KDRect rect) const { - assert(ctx == KDIonContext::sharedContext()); - clearScreen(ctx); - - assert(sCurrentView == nullptr); - sCurrentView = this; - - // Reinitialize the print location - m_printLocation = KDPointZero; - runPython(); - - sCurrentView = nullptr; -} - -void ExecutorController::ContentView::print(const char * str) const { - KDContext * ctx = KDIonContext::sharedContext(); - m_printLocation = ctx->drawString(str, m_printLocation); - if (bounds().height() < m_printLocation.y()) { - clearScreen(ctx); - m_printLocation = KDPoint(m_printLocation.x(), 0); - } -} - -void ExecutorController::ContentView::runPython() const { - // Initialized stack limit - mp_stack_set_limit(40000); - - mp_port_init_stack_top(); - - char * pythonHeap = (char *)malloc(16384); - - gc_init(pythonHeap, pythonHeap + 16384); - - // Initialize interpreter - mp_init(); - - if (execute_from_str(m_program->readOnlyContent())) { - mp_hal_stdout_tx_strn_cooked("Error", 0); - } - - free(pythonHeap); -} - -void ExecutorController::ContentView::clearScreen(KDContext * ctx) const { - ctx->fillRect(bounds(), KDColorWhite); -} - -ExecutorController::ExecutorController(Program * program) : - ViewController(nullptr), - m_view(program) -{ -} - -View * ExecutorController::view() { - return &m_view; -} - -bool ExecutorController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::OK) { - app()->dismissModalViewController(); - return true; - } - return false; -} - -} diff --git a/apps/code/executor_controller.h b/apps/code/executor_controller.h deleted file mode 100644 index a1345b8d3..000000000 --- a/apps/code/executor_controller.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef CODE_EXECUTOR_CONTROLLER_H -#define CODE_EXECUTOR_CONTROLLER_H - -#include -#include "program.h" - -namespace Code { - -class ExecutorController : public ViewController { -public: - ExecutorController(Program * program); - View * view() override; - bool handleEvent(Ion::Events::Event event) override; - class ContentView : public View { - public: - ContentView(Program * program); - void drawRect(KDContext * ctx, KDRect rect) const override; - void print(const char * str) const; - private: - void runPython() const; - void clearScreen(KDContext * ctx) const; - Program * m_program; - mutable KDPoint m_printLocation; - }; -private: - ContentView m_view; -}; - -} - -#endif - diff --git a/apps/code/menu_controller.cpp b/apps/code/menu_controller.cpp index 67cd8ecbc..a257dc084 100644 --- a/apps/code/menu_controller.cpp +++ b/apps/code/menu_controller.cpp @@ -8,7 +8,6 @@ MenuController::MenuController(Responder * parentResponder, Program * program) : ViewController(parentResponder), m_selectableTableView(this, this, 0, 1, Metric::CommonTopMargin, Metric::CommonRightMargin, Metric::CommonBottomMargin, Metric::CommonLeftMargin, this), m_editorController(program), - m_executorController(program), m_consoleController(parentResponder) { } @@ -23,7 +22,7 @@ void MenuController::didBecomeFirstResponder() { } bool MenuController::handleEvent(Ion::Events::Event event) { - ViewController * vc[3] = {&m_editorController, &m_executorController, &m_consoleController}; + ViewController * vc[2] = {&m_editorController, &m_consoleController}; if (event == Ion::Events::OK || event == Ion::Events::EXE) { app()->displayModalViewController(vc[selectedRow()], 0.5f, 0.5f); return true; @@ -52,7 +51,7 @@ KDCoordinate MenuController::cellHeight() { void MenuController::willDisplayCellForIndex(HighlightCell * cell, int index) { MessageTableCell * myCell = (MessageTableCell *)cell; - I18n::Message titles[k_totalNumberOfCells] = {I18n::Message::EditProgram, I18n::Message::ExecuteProgram, I18n::Message::Console}; + I18n::Message titles[k_totalNumberOfCells] = {I18n::Message::EditProgram, I18n::Message::Console}; // TODO: translate Console in the .i18n myCell->setMessage(titles[index]); } diff --git a/apps/code/menu_controller.h b/apps/code/menu_controller.h index c20ede7a2..be43ba2ae 100644 --- a/apps/code/menu_controller.h +++ b/apps/code/menu_controller.h @@ -4,7 +4,6 @@ #include #include "console_controller.h" #include "editor_controller.h" -#include "executor_controller.h" #include "program.h" namespace Code { @@ -21,11 +20,10 @@ public: int reusableCellCount() override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; private: - constexpr static int k_totalNumberOfCells = 3; + constexpr static int k_totalNumberOfCells = 2; MessageTableCell m_cells[k_totalNumberOfCells]; SelectableTableView m_selectableTableView; EditorController m_editorController; - ExecutorController m_executorController; ConsoleController m_consoleController; }; diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index 7f8ffce98..38b794ea3 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -106,3 +106,4 @@ AtanhCommandWithArg = "atanh(x)" Prediction95CommandWithArg = "prediction95(p,n)" PredictionCommandWithArg = "prediction(p,n)" ConfidenceCommandWithArg = "confidence(f,n)" +ConsolePrompt = ">>> "