[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
This commit is contained in:
Léa Saviot
2017-10-12 14:00:18 +02:00
committed by Romain Goyet
parent 493cef0d4d
commit c57f9cf8b1
18 changed files with 165 additions and 171 deletions

View File

@@ -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\
)

View File

@@ -1,3 +1,4 @@
Console = "Console"
ConsoleError = "Error"
EditProgram = "Programm bearbeiten"
ExecuteProgram = "Programm ausfuhren"

View File

@@ -1,3 +1,4 @@
Console = "Console"
ConsoleError = "Error"
EditProgram = "Edit program"
ExecuteProgram = "Execute program"

View File

@@ -1,3 +1,4 @@
Console = "Console"
ConsoleError = "Error"
EditProgram = "Editar el programa"
ExecuteProgram = "Ejecutar el programa"

View File

@@ -1,3 +1,4 @@
Console = "Console"
ConsoleError = "Error"
EditProgram = "Editer le programme"
ExecuteProgram = "Executer le programme"

View File

@@ -1,3 +1,4 @@
Console = "Console"
ConsoleError = "Error"
EditProgram = "Editar programa"
ExecuteProgram = "Executar programa"

View File

@@ -1,13 +1,61 @@
#include "console_controller.h"
#include <apps/i18n.h>
extern "C" {
#include <stdlib.h>
#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;
}
}

View File

@@ -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;
};
}

View File

@@ -1,5 +1,7 @@
#include "console_edit_cell.h"
#include "console_controller.h"
#include <escher/app.h>
#include <apps/i18n.h>
#include <assert.h>
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() {

View File

@@ -5,6 +5,8 @@
#include <escher/highlight_cell.h>
#include <escher/text_field.h>
#include <escher/text_field_delegate.h>
#include <escher/message_text_view.h>
#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;
};

View File

@@ -1,4 +1,8 @@
#include "console_line_cell.h"
#include "console_controller.h"
#include <kandinsky/point.h>
#include <kandinsky/coordinate.h>
#include <apps/i18n.h>
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) {

View File

@@ -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<k_historySize - indexOfSecondLineMarker; i++) {
m_history[i] = m_history[indexOfSecondLineMarker+i];
secondLineMarkerIndex++;
for (int i=0; i<k_historySize - secondLineMarkerIndex; i++) {
m_history[i] = m_history[secondLineMarkerIndex+i];
}
}
void ConsoleStore::deleteLastLine() {
int lineCount = numberOfLines();
if (lineCount < 0) {
return;
}
if (lineCount == 1) {
deleteFirstLine();
return;
}
int currentLineIndex = 1;
int lastLineMarkerIndex = 0;
for (int i=0; i<k_historySize; i++) {
if (m_history[i] == 0) {
currentLineIndex++;
if (currentLineIndex == lineCount) {
lastLineMarkerIndex = i+1;
break;
}
}
}
m_history[lastLineMarkerIndex] = 0;
}
}

View File

@@ -13,6 +13,7 @@ public:
int numberOfLines() const;
void pushCommand(const char * text, size_t length);
void pushResult(const char * text, size_t length);
void deleteLastLineIfEmpty();
private:
static constexpr char CommandMarker = 0x01;
static constexpr char ResultMarker = 0x02;
@@ -25,6 +26,7 @@ private:
* old ConsoleLines. deleteFirstLine() deletes the first ConsoleLine of
* m_history and shifts the rest of the ConsoleLines towards the beginning of
* m_history. */
void deleteLastLine();
char m_history[k_historySize];
/* The m_history variable sequentially stores an array of ConsoleLine objects.
* Each ConsoleLine is stored as follow:

View File

@@ -1,117 +0,0 @@
#include "executor_controller.h"
extern "C" {
#include <stdlib.h>
#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;
}
}

View File

@@ -1,32 +0,0 @@
#ifndef CODE_EXECUTOR_CONTROLLER_H
#define CODE_EXECUTOR_CONTROLLER_H
#include <escher.h>
#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

View File

@@ -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]);
}

View File

@@ -4,7 +4,6 @@
#include <escher.h>
#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;
};

View File

@@ -106,3 +106,4 @@ AtanhCommandWithArg = "atanh(x)"
Prediction95CommandWithArg = "prediction95(p,n)"
PredictionCommandWithArg = "prediction(p,n)"
ConfidenceCommandWithArg = "confidence(f,n)"
ConsolePrompt = ">>> "