Merge branch 'master' into python_turtle

This commit is contained in:
Léa Saviot
2019-01-08 14:31:49 +01:00
42 changed files with 461 additions and 118 deletions

View File

@@ -86,4 +86,9 @@ $(app_objs): $(app_image_objs)
epsilon.$(EXE): $(app_objs) $(app_image_objs)
TO_REMOVE := apps/main.o apps/i18n.o
TMP := $(app_objs) $(app_image_objs)
VAR := $(filter-out $(TO_REMOVE), $(TMP))
test.$(EXE): $(VAR)
products += epsilon.$(EXE) $(app_objs) $(call INLINER_PRODUCTS,$(app_images))

View File

@@ -152,14 +152,25 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) {
if (event == Ion::Events::USBEnumeration) {
if (Ion::USB::isPlugged()) {
App::Snapshot * activeSnapshot = (activeApp() == nullptr ? appSnapshotAtIndex(0) : activeApp()->snapshot());
/* Just after a software update, the battery timer does not have time to
* fire before the calculator enters DFU mode. As the DFU mode blocks the
* event loop, we update the battery state "manually" here. */
updateBatteryState();
switchTo(usbConnectedAppSnapshot());
Ion::USB::DFU();
switchTo(activeSnapshot);
didProcessEvent = true;
if (activeApp() == nullptr || activeApp()->prepareForExit()) {
/* Just after a software update, the battery timer does not have time to
* fire before the calculator enters DFU mode. As the DFU mode blocks the
* event loop, we update the battery state "manually" here. */
updateBatteryState();
switchTo(usbConnectedAppSnapshot());
Ion::USB::DFU();
switchTo(activeSnapshot);
didProcessEvent = true;
} else {
/* activeApp()->prepareForExit() returned false, which means that the
* app needs another event loop to prepare for being switched off.
* Discard the current enumeration interruption.
* The USB host tries a few times in a row to enumerate the device, so
* hopefully the device will get another enumeration event soon and this
* time the device will be ready to go in DFU mode. Otherwise, the user
* needs to re-plug the device to go into DFU mode. */
Ion::USB::clearEnumerationInterrupt();
}
} else {
/* Sometimes, the device gets an ENUMDNE interrupts when being unplugged
* from a non-USB communicating host (e.g. a USB charger). The interrupt
@@ -212,6 +223,7 @@ bool AppsContainer::processEvent(Ion::Events::Event event) {
}
void AppsContainer::switchTo(App::Snapshot * snapshot) {
assert(activeApp() == nullptr || activeApp()->prepareForExit());
if (activeApp() && snapshot != activeApp()->snapshot()) {
resetShiftAlphaStatus();
}

View File

@@ -24,7 +24,5 @@ i18n_files += $(addprefix apps/calculation/,\
tests += $(addprefix apps/calculation/test/,\
calculation_store.cpp\
)
test_objs += $(addprefix apps/calculation/, calculation.o calculation_store.o)
app_images += apps/calculation/calculation_icon.png

View File

@@ -46,23 +46,23 @@ bool App::Snapshot::lockOnConsole() const {
void App::Snapshot::setOpt(const char * name, char * value) {
if (strcmp(name, "script") == 0) {
m_scriptStore.deleteAllScripts();
char * separator = strchr(value, ':');
if (!separator) {
return;
}
*separator = 0;
const char * scriptName = value;
/* We include the 0 in the scriptContent to represent the importation
* status. It is set to 1 after addScriptFromTemplate. Indeed, this '/0'
* char has two goals: ending the scriptName and representing the
* importation status; we cannot set it to 1 before adding the script to
* storage. */
const char * scriptContent = separator;
Code::ScriptTemplate script(scriptName, scriptContent);
m_scriptStore.addScriptFromTemplate(&script);
m_scriptStore.scriptNamed(scriptName).toggleImportationStatus(); // set Importation Status to 1
m_scriptStore.deleteAllScripts();
char * separator = strchr(value, ':');
if (!separator) {
return;
}
*separator = 0;
const char * scriptName = value;
/* We include the 0 in the scriptContent to represent the importation
* status. It is set to 1 after addScriptFromTemplate. Indeed, this '/0'
* char has two goals: ending the scriptName and representing the
* importation status; we cannot set it to 1 before adding the script to
* storage. */
const char * scriptContent = separator;
Code::ScriptTemplate script(scriptName, scriptContent);
m_scriptStore.addScriptFromTemplate(&script);
m_scriptStore.scriptNamed(scriptName).toggleImportationStatus(); // set Importation Status to 1
return;
}
if (strcmp(name, "lock-on-console") == 0) {
m_lockOnConsole = true;
@@ -88,15 +88,15 @@ App::App(Container * container, Snapshot * snapshot) :
}
App::~App() {
assert(!m_consoleController.inputRunLoopActive());
deinitPython();
}
bool App::handleEvent(Ion::Events::Event event) {
if (event == Ion::Events::Home && m_consoleController.inputRunLoopActive()) {
// We need to return true here because we want to actually exit from the
// input run loop, which requires ending a dispatchEvent cycle.
m_consoleController.askInputRunLoopTermination();
m_consoleController.interrupt();
/* We need to return true here because we want to actually exit from the
* input run loop, which requires ending a dispatchEvent cycle. */
m_consoleController.terminateInputLoop();
if (m_modalViewController.isDisplayingModal()) {
m_modalViewController.dismissModalViewController();
}

View File

@@ -37,6 +37,13 @@ public:
ScriptStore m_scriptStore;
};
~App();
bool prepareForExit() override {
if (m_consoleController.inputRunLoopActive()) {
m_consoleController.terminateInputLoop();
return false;
}
return true;
}
StackViewController * stackViewController() { return &m_codeStackViewController; }
ConsoleController * consoleController() { return &m_consoleController; }

View File

@@ -33,7 +33,7 @@ ConsoleController::ConsoleController(Responder * parentResponder, App * pythonDe
m_sandboxController(this, this),
m_inputRunLoopActive(false)
#if EPSILON_GETOPT
, m_locked(lockOnConsole)
, m_locked(lockOnConsole)
#endif
{
m_selectableTableView.setMargins(0, Metric::CommonRightMargin, 0, Metric::TitleBarExternHorizontalMargin);
@@ -81,21 +81,30 @@ void ConsoleController::runAndPrintForCommand(const char * command) {
m_consoleStore.deleteLastLineIfEmpty();
}
void ConsoleController::terminateInputLoop() {
assert(m_inputRunLoopActive);
m_inputRunLoopActive = false;
interrupt();
}
const char * ConsoleController::inputText(const char * prompt) {
AppsContainer * a = (AppsContainer *)(app()->container());
m_inputRunLoopActive = true;
// Set the prompt text
m_selectableTableView.reloadData();
m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines());
m_editCell.setPrompt(prompt);
m_editCell.setText("");
// Run new input loop
a->redrawWindow();
a->runWhile([](void * a){
ConsoleController * c = static_cast<ConsoleController *>(a);
return c->inputRunLoopActive();
}, this);
// Reset the prompt line
flushOutputAccumulationBufferToStore();
m_consoleStore.deleteLastLineIfEmpty();
m_editCell.setPrompt(sStandardPromptText);
@@ -145,9 +154,8 @@ bool ConsoleController::handleEvent(Ion::Events::Event event) {
}
#if EPSILON_GETOPT
if (m_locked && (event == Ion::Events::Home || event == Ion::Events::Back)) {
if (inputRunLoopActive()) {
askInputRunLoopTermination();
interrupt();
if (m_inputRunLoopActive) {
terminateInputLoop();
}
return true;
}
@@ -234,10 +242,10 @@ bool ConsoleController::textFieldShouldFinishEditing(TextField * textField, Ion:
}
bool ConsoleController::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) {
if (event == Ion::Events::Up && inputRunLoopActive()) {
askInputRunLoopTermination();
// We need to return true here because we want to actually exit from the
// input run loop, which requires ending a dispatchEvent cycle.
if (event == Ion::Events::Up && m_inputRunLoopActive) {
m_inputRunLoopActive = false;
/* We need to return true here because we want to actually exit from the
* input run loop, which requires ending a dispatchEvent cycle. */
return true;
}
if (event == Ion::Events::Up) {
@@ -251,8 +259,8 @@ bool ConsoleController::textFieldDidReceiveEvent(TextField * textField, Ion::Eve
}
bool ConsoleController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) {
if (inputRunLoopActive()) {
askInputRunLoopTermination();
if (m_inputRunLoopActive) {
m_inputRunLoopActive = false;
return false;
}
runAndPrintForCommand(text);
@@ -266,8 +274,8 @@ bool ConsoleController::textFieldDidFinishEditing(TextField * textField, const c
}
bool ConsoleController::textFieldDidAbortEditing(TextField * textField) {
if (inputRunLoopActive()) {
askInputRunLoopTermination();
if (m_inputRunLoopActive) {
m_inputRunLoopActive = false;
} else {
#if EPSILON_GETOPT
/* In order to lock the console controller, we disable poping controllers

View File

@@ -31,8 +31,8 @@ public:
void autoImport();
void autoImportScript(Script script, bool force = false);
void runAndPrintForCommand(const char * command);
bool inputRunLoopActive() { return m_inputRunLoopActive; }
void askInputRunLoopTermination() { m_inputRunLoopActive = false; }
bool inputRunLoopActive() const { return m_inputRunLoopActive; }
void terminateInputLoop();
// ViewController
View * view() override { return &m_selectableTableView; }

View File

@@ -39,7 +39,6 @@ i18n_files += $(addprefix apps/probability/,\
tests += $(addprefix apps/probability/test/,\
erf_inv.cpp\
)
test_objs += $(addprefix apps/probability/law/, erf_inv.o)
app_images += apps/probability/probability_icon.png

View File

@@ -45,33 +45,5 @@ i18n_files += $(addprefix apps/regression/,\
tests += $(addprefix apps/regression/test/,\
model.cpp\
)
test_objs += $(addprefix apps/regression/,\
regression_context.o\
store.o\
)
test_objs += $(addprefix apps/shared/,\
store_context.o\
)
test_objs += $(addprefix apps/shared/,\
curve_view_cursor.o\
curve_view_range.o\
double_pair_store.o\
interactive_curve_view_range.o\
interactive_curve_view_range_delegate.o\
memoized_curve_view_range.o\
store_context.o\
)
test_objs += $(addprefix apps/regression/model/,\
cubic_model.o\
exponential_model.o\
linear_model.o\
logarithmic_model.o\
logistic_model.o\
model.o\
power_model.o\
quadratic_model.o\
quartic_model.o\
trigonometric_model.o\
)
app_images += apps/regression/regression_icon.png

View File

@@ -23,6 +23,11 @@ public:
void viewWillAppear() override;
void selectRegressionCurve();
int selectedSeriesIndex() const { return *m_selectedSeriesIndex; }
// moveCursorHorizontally and Vertically are public to be used in tests
bool moveCursorHorizontally(int direction) override;
bool moveCursorVertically(int direction) override;
private:
constexpr static int k_maxLegendLength = 16;
constexpr static int k_maxNumberOfCharacters = 50;
@@ -35,7 +40,6 @@ private:
// SimpleInteractiveCurveViewController
void reloadBannerView() override;
bool moveCursorHorizontally(int direction) override;
Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override;
Shared::CurveView * curveView() override;
bool handleEnter() override;
@@ -43,7 +47,6 @@ private:
// InteractiveCurveViewController
void initRangeParameters() override;
void initCursorParameters() override;
bool moveCursorVertically(int direction) override;
uint32_t modelVersion() override;
uint32_t rangeVersion() override;
bool isCursorVisible() override;

View File

@@ -0,0 +1,176 @@
#include <quiz.h>
#include <string.h>
#include <assert.h>
#include "../model/model.h"
#include "../graph_controller.h"
#include "../regression_context.h"
#include "../store.h"
using namespace Poincare;
using namespace Regression;
void load_in_store(
Regression::Store * store,
double * x0, double * y0, int numberOfPoints0,
double * x1 = nullptr, double * y1 = nullptr, int numberOfPoints1 = 0,
double * x2 = nullptr, double * y2 = nullptr, int numberOfPoints2 = 0)
{
// Set the points and the regression type
if (numberOfPoints0 != 0 && x0 != nullptr && y0 != nullptr) {
for (int i = 0; i < numberOfPoints0; i++) {
store->set(x0[i], 0, 0, i);
store->set(y0[i], 0, 1, i);
}
}
if (numberOfPoints1 != 0 && x1 != nullptr && y1 != nullptr) {
for (int i = 0; i < numberOfPoints1; i++) {
store->set(x1[i], 1, 0, i);
store->set(y1[i], 1, 1, i);
}
}
if (numberOfPoints2 != 0 && x2 != nullptr && y2 != nullptr) {
for (int i = 0; i < numberOfPoints2; i++) {
store->set(x2[i], 2, 0, i);
store->set(y2[i], 2, 1, i);
}
}
}
class NavigationEvent {
public:
enum class Direction {
Up,
Right,
Down,
Left
};
NavigationEvent(Direction d, int series, int dot) :
direction(d),
expectedSelectedSeries(series),
expectedSelectedDot(dot)
{}
Direction direction;
int expectedSelectedSeries;
int expectedSelectedDot;
};
void assert_navigation_is(
int numberOfEvents,
NavigationEvent * events,
int startingDotSelection,
int startingSeriesSelection,
double * x0, double * y0, int numberOfPoints0,
double * x1 = nullptr, double * y1 = nullptr, int numberOfPoints1 = 0,
double * x2 = nullptr, double * y2 = nullptr, int numberOfPoints2 = 0)
{
assert(startingDotSelection >= 0);
Store store;
Shared::CurveViewCursor cursor;
uint32_t dummy;
int selectedDotIndex = startingDotSelection;
int selectedSeriesIndex = startingSeriesSelection;
load_in_store(&store, x0, y0, numberOfPoints0, x1, y1, numberOfPoints1, x2, y2, numberOfPoints2);
if (selectedDotIndex < store.numberOfPairsOfSeries(selectedSeriesIndex)) {
cursor.moveTo(
store.get(selectedSeriesIndex, 0, selectedDotIndex),
store.get(selectedSeriesIndex, 1, selectedDotIndex));
} else {
cursor.moveTo(
store.meanOfColumn(selectedSeriesIndex, 0),
store.meanOfColumn(selectedSeriesIndex, 1));
}
AppsContainerStorage container;
// We do not really care about the snapshot
App app(&container, container.appSnapshotAtIndex(0));
GraphController graphController(
&app, &app, nullptr,
&store,
&cursor,
&dummy, &dummy,
&selectedDotIndex,
&selectedSeriesIndex);
for (int i = 0; i < numberOfEvents; i++) {
NavigationEvent event = events[i];
if (event.direction == NavigationEvent::Direction::Up) {
graphController.moveCursorVertically(1);
} else if (event.direction == NavigationEvent::Direction::Right) {
graphController.moveCursorHorizontally(1);
} else if (event.direction == NavigationEvent::Direction::Down) {
graphController.moveCursorVertically(-1);
} else {
assert(event.direction == NavigationEvent::Direction::Left);
graphController.moveCursorHorizontally(-1);
}
quiz_assert(event.expectedSelectedDot == selectedDotIndex);
quiz_assert(event.expectedSelectedSeries == selectedSeriesIndex);
}
}
QUIZ_CASE(regression_navigation_1) {
constexpr int numberOfPoints0 = 4;
double x0[numberOfPoints0] = {2.0, 3.0, 2.0, 1.0};
double y0[numberOfPoints0] = {0.0, 1.0, 0.5, 5.0};
constexpr int numberOfPoints1 = 3;
double x1[numberOfPoints1] = {1.0, 10.0, 2.0};
double y1[numberOfPoints1] = {0.0, 25.0, 0.5};
constexpr int numberOfEvents = 7;
NavigationEvent events[numberOfEvents] = {
NavigationEvent(NavigationEvent::Direction::Down, 0, -1),
NavigationEvent(NavigationEvent::Direction::Down, 0, 2),
NavigationEvent(NavigationEvent::Direction::Down, 1, 2),
NavigationEvent(NavigationEvent::Direction::Down, 0, 0),
NavigationEvent(NavigationEvent::Direction::Down, 1, 0),
NavigationEvent(NavigationEvent::Direction::Down, 1, -1),
NavigationEvent(NavigationEvent::Direction::Down, 1, -1)
};
assert_navigation_is(numberOfEvents, events, numberOfPoints0, 0, x0, y0, numberOfPoints0, x1, y1, numberOfPoints1);
}
QUIZ_CASE(regression_navigation_2) {
constexpr int numberOfPoints0 = 4;
double x0[numberOfPoints0] = {8.0, 9.0, 10.0, 10.0};
double y0[numberOfPoints0] = {2.0, 5.0, 1.0, 2.0};
constexpr int numberOfPoints1 = 3;
double x1[numberOfPoints1] = {10.0, 12.0, 5.0};
double y1[numberOfPoints1] = {2.0, 0.0, 8.0};
constexpr int numberOfEvents = 6;
NavigationEvent events[numberOfEvents] = {
/* FIXME
* Because of double computation error, the regression curve of the series 0
* is above its mean point.
NavigationEvent(NavigationEvent::Direction::Down, 1, -1),
* */
NavigationEvent(NavigationEvent::Direction::Down, 0, -1),
NavigationEvent(NavigationEvent::Direction::Down, 0, numberOfPoints0),
NavigationEvent(NavigationEvent::Direction::Down, 0, -1),
NavigationEvent(NavigationEvent::Direction::Down, 0, 3),
NavigationEvent(NavigationEvent::Direction::Down, 1, 0),
NavigationEvent(NavigationEvent::Direction::Down, 0, 2)
};
assert_navigation_is(numberOfEvents, events, numberOfPoints1, 1, x0, y0, numberOfPoints0, x1, y1, numberOfPoints1);
}
QUIZ_CASE(regression_navigation_3) {
constexpr int numberOfPoints = 4;
double x[numberOfPoints] = {1.0, 2.0, 3.0, 4.0};
double y[numberOfPoints] = {2.0, 2.0, 2.0, 2.0};
constexpr int numberOfEvents = 3;
NavigationEvent events[numberOfEvents] = {
NavigationEvent(NavigationEvent::Direction::Down, 0, -1),
NavigationEvent(NavigationEvent::Direction::Down, 1, -1),
NavigationEvent(NavigationEvent::Direction::Down, 2, -1),
};
assert_navigation_is(numberOfEvents, events, 2, 0, x, y, numberOfPoints, x, y, numberOfPoints, x, y, numberOfPoints);
}

View File

@@ -34,7 +34,5 @@ i18n_files += $(addprefix apps/sequence/,\
tests += $(addprefix apps/sequence/test/,\
sequence.cpp\
)
test_objs += $(addprefix apps/sequence/, sequence.o sequence_store.o sequence_context.o cache_context.o)
test_objs += $(addprefix apps/shared/, expression_model.o expression_model_store.o function.o function_store.o)
app_images += apps/sequence/sequence_icon.png

View File

@@ -96,14 +96,14 @@ void Sequence::setInitialRank(int rank) {
Poincare::Expression Sequence::firstInitialConditionExpression(Context * context) const {
if (m_firstInitialConditionExpression.isUninitialized()) {
m_firstInitialConditionExpression = PoincareHelpers::ParseAndReduce(m_firstInitialConditionText, *context);
m_firstInitialConditionExpression = PoincareHelpers::ParseAndSimplify(m_firstInitialConditionText, *context);
}
return m_firstInitialConditionExpression;
}
Poincare::Expression Sequence::secondInitialConditionExpression(Context * context) const {
if (m_secondInitialConditionExpression.isUninitialized()) {
m_secondInitialConditionExpression = PoincareHelpers::ParseAndReduce(m_secondInitialConditionText, *context);
m_secondInitialConditionExpression = PoincareHelpers::ParseAndSimplify(m_secondInitialConditionText, *context);
}
return m_secondInitialConditionExpression;
}

View File

@@ -86,5 +86,3 @@ app_objs += $(addprefix apps/shared/,\
vertical_cursor_view.o\
zoom_parameter_controller.o\
)
test_objs += $(addprefix apps/shared/, storage_expression_model.o storage_function.o storage_cartesian_function.o)

View File

@@ -21,7 +21,7 @@ const char * ExpressionModel::text() const {
Poincare::Expression ExpressionModel::expression(Poincare::Context * context) const {
if (m_expression.isUninitialized()) {
m_expression = PoincareHelpers::ParseAndReduce(m_text, *context);
m_expression = PoincareHelpers::ParseAndSimplify(m_text, *context);
}
return m_expression;
}

View File

@@ -37,10 +37,6 @@ inline T ApproximateToScalar(const char * text, Poincare::Context & context) {
return Poincare::Expression::approximateToScalar<T>(text, context, Poincare::Preferences::sharedPreferences()->angleUnit());
}
inline Poincare::Expression ParseAndReduce(const char * text, Poincare::Context & context) {
return Poincare::Expression::ParseAndReduce(text, context, Poincare::Preferences::sharedPreferences()->angleUnit());
}
inline Poincare::Expression ParseAndSimplify(const char * text, Poincare::Context & context) {
return Poincare::Expression::ParseAndSimplify(text, context, Poincare::Preferences::sharedPreferences()->angleUnit());
}

View File

@@ -39,7 +39,12 @@ Expression StorageExpressionModel::expressionReduced(Poincare::Context * context
if (m_expression.isUninitialized()) {
assert(!isNull());
Ion::Storage::Record::Data recordData = value();
m_expression = Expression::ExpressionFromAddress(expressionAddressForValue(recordData), expressionSizeForValue(recordData)).reduce(*context, Preferences::sharedPreferences()->angleUnit());
m_expression = Expression::ExpressionFromAddress(expressionAddressForValue(recordData), expressionSizeForValue(recordData));
PoincareHelpers::Simplify(&m_expression, *context);
// simplify might return an uninitialized Expression if interrupted
if (m_expression.isUninitialized()) {
m_expression = Expression::ExpressionFromAddress(expressionAddressForValue(recordData), expressionSizeForValue(recordData));
}
}
return m_expression;
}
@@ -82,8 +87,7 @@ Ion::Storage::Record::ErrorStatus StorageExpressionModel::setContent(const char
// Compute the expression to store, without replacing symbols
expressionToStore = Expression::Parse(c);
if (!expressionToStore.isUninitialized()) {
Symbol xUnknown = Symbol(Symbol::SpecialSymbols::UnknownX);
expressionToStore = expressionToStore.replaceSymbolWithExpression(Symbol("x", 1), xUnknown);
expressionToStore = expressionToStore.replaceUnknown(Symbol('x')); //TODO Beware of non x unknowns! (for instance whe sequences are in the storage)
}
}
return setExpressionContent(expressionToStore);

View File

@@ -8,10 +8,9 @@ namespace Shared {
StorageExpressionModelListController::StorageExpressionModelListController(Responder * parentResponder, I18n::Message text) :
ViewController(parentResponder),
m_addNewModel(),
m_memoizedCellHeight {-1, -1, -1, -1, -1},
m_cumulatedHeightForSelectedIndex(-1)
m_addNewModel()
{
resetMemoization();
m_addNewModel.setMessage(text);
}
@@ -20,7 +19,7 @@ void StorageExpressionModelListController::tableSelectionDidChange(int previousS
int currentSelectedRow = selectedRow();
// The previously selected cell's height might have changed.
m_memoizedCellHeight[currentSelectedMemoizedIndex] = -1;
m_memoizedCellHeight[currentSelectedMemoizedIndex] = k_resetedMemoizedValue;
// Update m_cumulatedHeightForSelectedIndex if we scrolled one cell up/down
if (currentSelectedRow == previousSelectedRow + 1) {
@@ -29,7 +28,7 @@ void StorageExpressionModelListController::tableSelectionDidChange(int previousS
for (int i = 0; i < k_memoizedCellHeightsCount - 1; i++) {
m_memoizedCellHeight[i] = m_memoizedCellHeight[i+1];
}
m_memoizedCellHeight[k_memoizedCellHeightsCount-1] = -1;
m_memoizedCellHeight[k_memoizedCellHeightsCount-1] = k_resetedMemoizedValue;
// Update m_cumulatedHeightForSelectedIndex
if (previousSelectedRow >= 0) {
m_cumulatedHeightForSelectedIndex+= memoizedRowHeight(previousSelectedRow);
@@ -43,7 +42,7 @@ void StorageExpressionModelListController::tableSelectionDidChange(int previousS
for (int i = k_memoizedCellHeightsCount - 1; i > 0; i--) {
m_memoizedCellHeight[i] = m_memoizedCellHeight[i-1];
}
m_memoizedCellHeight[0] = -1;
m_memoizedCellHeight[0] = k_resetedMemoizedValue;
// Update m_cumulatedHeightForSelectedIndex
if (currentSelectedRow >= 0) {
m_cumulatedHeightForSelectedIndex-= memoizedRowHeight(currentSelectedRow);
@@ -63,7 +62,7 @@ KDCoordinate StorageExpressionModelListController::memoizedRowHeight(int j) {
constexpr int halfMemoizationCount = k_memoizedCellHeightsCount/2;
if (j >= currentSelectedRow - halfMemoizationCount && j <= currentSelectedRow + halfMemoizationCount) {
int memoizedIndex = j - (currentSelectedRow - halfMemoizationCount);
if (m_memoizedCellHeight[memoizedIndex] < 0) {
if (m_memoizedCellHeight[memoizedIndex] == k_resetedMemoizedValue) {
m_memoizedCellHeight[memoizedIndex] = expressionRowHeight(j);
}
return m_memoizedCellHeight[memoizedIndex];
@@ -83,7 +82,7 @@ KDCoordinate StorageExpressionModelListController::memoizedCumulatedHeightFromIn
return notMemoizedCumulatedHeightFromIndex(j);
}
// Recompute the memoized cumulatedHeight if needed
if (m_cumulatedHeightForSelectedIndex < 0) {
if (m_cumulatedHeightForSelectedIndex == k_resetedMemoizedValue) {
m_cumulatedHeightForSelectedIndex = notMemoizedCumulatedHeightFromIndex(currentSelectedRow);
}
/* Compute the wanted cumulated height by adding/removing memoized cell
@@ -226,9 +225,9 @@ bool StorageExpressionModelListController::isAddEmptyRow(int j) {
}
void StorageExpressionModelListController::resetMemoization() {
m_cumulatedHeightForSelectedIndex = -1;
m_cumulatedHeightForSelectedIndex = k_resetedMemoizedValue;
for (int i = 0; i < k_memoizedCellHeightsCount; i++) {
m_memoizedCellHeight[i] = -1;
m_memoizedCellHeight[i] = k_resetedMemoizedValue;
}
}

View File

@@ -35,11 +35,28 @@ protected:
virtual StorageExpressionModelStore * modelStore() = 0;
virtual InputViewController * inputController() = 0;
EvenOddMessageTextCell m_addNewModel;
protected:
// Memoization
static constexpr int k_memoizedCellHeightsCount = 7;
/* We use memoization to speed up indexFromHeight(offset) in the children
* classes: if offset is "around" the memoized cumulatedHeightForIndex, we can
* compute its value easily by adding/substracting memoized row heights. We
* thus need to memoize 3 cells (see under for explanation on the 3) above the
* selected one, and 3 under, which gives 7 cells.
* 3 is the maximal number of non selected visible rows if the selected cell
* is completely [on top/at the bottom] of the screen. To compute this value:
* (ScreenHeight - Metric::TitleBarHeight - Metric::TabHeight - ButtonRowHeight
* - currentSelectedRowHeight) / Metric::StoreRowHeight
* = (240-18-27-20-50)/50 = 2.5 */
static_assert(StorageExpressionModelListController::k_memoizedCellHeightsCount % 2 == 1, "StorageExpressionModelListController::k_memoizedCellHeightsCount should be odd.");
/* We memoize values for indexes around the selectedRow index.
* k_memoizedCellHeightsCount needs to be odd to compute things such as:
* constexpr int halfMemoizationCount = k_memoizedCellHeightsCount/2;
* if (j < selectedRow - halfMemoizationCount
* || j > selectedRow + halfMemoizationCount) { ... } */
private:
// Memoization
static constexpr int k_memoizedCellHeightsCount = 5;
static_assert(StorageExpressionModelListController::k_memoizedCellHeightsCount == 5, "Wrong array size in initialization of StorageExpressionModelListController::m_memoizedCellHeight.");
static_assert(StorageExpressionModelListController::k_memoizedCellHeightsCount % 2 == 1, "StorageExpressionModelListController::k_memoizedCellHeightsCount should be odd to be able to compute the middle element.");
static constexpr int k_resetedMemoizedValue = -1;
void resetMemoization();
virtual KDCoordinate notMemoizedCumulatedHeightFromIndex(int j) = 0;
KDCoordinate m_memoizedCellHeight[k_memoizedCellHeightsCount];

View File

@@ -4,6 +4,7 @@
namespace Shared {
static inline int max(int x, int y) { return x > y ? x : y; }
static inline int min(int x, int y) { return x < y ? x : y; }
StorageFunctionListController::StorageFunctionListController(Responder * parentResponder, ButtonRowController * header, ButtonRowController * footer, I18n::Message text) :
StorageExpressionModelListController(parentResponder, text),
@@ -81,6 +82,42 @@ int StorageFunctionListController::indexFromCumulatedWidth(KDCoordinate offsetX)
}
}
int StorageFunctionListController::indexFromCumulatedHeight(KDCoordinate offsetY) {
if (offsetY == 0) {
return 0;
}
/* We use memoization to speed up this method: if offsetY is "around" the
* memoized cumulatedHeightForIndex, we can compute its value easily by
* adding/substracting memoized row heights. */
int currentSelectedRow = selectedRow();
int rowsCount = numberOfRows();
if (rowsCount <= 1 || currentSelectedRow < 1) {
return TableViewDataSource::indexFromCumulatedHeight(offsetY);
}
KDCoordinate currentCumulatedHeight = cumulatedHeightFromIndex(currentSelectedRow);
if (offsetY > currentCumulatedHeight) {
int iMax = min(k_memoizedCellHeightsCount/2 + 1, rowsCount - currentSelectedRow);
for (int i = 0; i < iMax; i++) {
currentCumulatedHeight+= rowHeight(currentSelectedRow + i);
if (offsetY <= currentCumulatedHeight) {
return currentSelectedRow + i;
}
}
} else {
int iMax = min(k_memoizedCellHeightsCount/2, currentSelectedRow);
for (int i = 1; i <= iMax; i++) {
currentCumulatedHeight-= rowHeight(currentSelectedRow-i);
if (offsetY > currentCumulatedHeight) {
return currentSelectedRow - i;
}
}
}
return TableViewDataSource::indexFromCumulatedHeight(offsetY);
}
int StorageFunctionListController::typeAtLocation(int i, int j) {
if (isAddEmptyRow(j)){
return i + 2;

View File

@@ -26,6 +26,7 @@ public:
KDCoordinate cumulatedWidthFromIndex(int i) override;
KDCoordinate cumulatedHeightFromIndex(int j) override;
int indexFromCumulatedWidth(KDCoordinate offsetX) override;
int indexFromCumulatedHeight(KDCoordinate offsetY) override;
int typeAtLocation(int i, int j) override;
HighlightCell * reusableCell(int index, int type) override;
int reusableCellCount(int type) override;

View File

@@ -23,7 +23,5 @@ i18n_files += $(addprefix apps/solver/,\
tests += $(addprefix apps/solver/test/,\
equation_store.cpp\
)
test_objs += $(addprefix apps/solver/, equation.o equation_store.o)
test_objs += $(addprefix apps/shared/, expression_model.o expression_model_store.o)
app_images += apps/solver/solver_icon.png

View File

@@ -34,6 +34,5 @@ i18n_files += $(addprefix apps/statistics/,\
tests += $(addprefix apps/statistics/test/,\
store.cpp\
)
test_objs += $(addprefix apps/statistics/, store.o)
app_images += apps/statistics/stat_icon.png

View File

@@ -50,6 +50,9 @@ public:
void setFirstResponder(Responder * responder);
Responder * firstResponder();
virtual bool processEvent(Ion::Events::Event event);
/* prepareForExit returns true if the app can be switched off in the current
* runloop step, else it prepares for a switch off and returns false. */
virtual bool prepareForExit() { return true; }
void displayModalViewController(ViewController * vc, float verticalAlignment, float horizontalAlignment,
KDCoordinate topMargin = 0, KDCoordinate leftMargin = 0, KDCoordinate bottomMargin = 0, KDCoordinate rightMargin = 0);
void dismissModalViewController();

View File

@@ -97,6 +97,7 @@ objs += $(addprefix poincare/src/,\
nth_root.o\
number.o\
opposite.o\
parametered_expression_helper.o\
parenthesis.o\
permute_coefficient.o\
power.o\

View File

@@ -22,6 +22,7 @@ public:
// Properties
Type type() const override { return Type::Derivative; }
int polynomialDegree(Context & context, const char * symbolName) const override;
Expression replaceUnknown(const Symbol & symbol) override;
private:
// Layout

View File

@@ -14,6 +14,7 @@ namespace Poincare {
class Context;
class SymbolAbstract;
class Symbol;
class Expression : public TreeHandle {
friend class AbsoluteValue;
@@ -154,6 +155,8 @@ public:
static constexpr int k_maxNumberOfPolynomialCoefficients = k_maxPolynomialDegree+1;
int getPolynomialReducedCoefficients(const char * symbolName, Expression coefficients[], Context & context, Preferences::AngleUnit angleUnit) const;
Expression replaceSymbolWithExpression(const SymbolAbstract & symbol, const Expression & expression) { return node()->replaceSymbolWithExpression(symbol, expression); }
Expression replaceUnknown(const Symbol & symbol);
Expression defaultReplaceUnknown(const Symbol & symbol);
/* Comparison */
/* isIdenticalTo is the "easy" equality, it returns true if both trees have
@@ -167,7 +170,6 @@ public:
int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode = Preferences::PrintFloatMode::Decimal, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const;
/* Simplification */
static Expression ParseAndReduce(const char * text, Context & context, Preferences::AngleUnit);
static Expression ParseAndSimplify(const char * text, Context & context, Preferences::AngleUnit angleUnit);
Expression simplify(Context & context, Preferences::AngleUnit angleUnit);
Expression reduce(Context & context, Preferences::AngleUnit angleUnit);

View File

@@ -13,6 +13,7 @@ namespace Poincare {
* 'this' outdated. They should only be called in a wrapper on Expression. */
class SymbolAbstract;
class Symbol;
class ExpressionNode : public TreeNode {
friend class AdditionNode;
@@ -103,6 +104,7 @@ public:
virtual Sign sign() const { return Sign::Unknown; }
virtual bool isNumber() const { return false; }
/*!*/ virtual Expression replaceSymbolWithExpression(const SymbolAbstract & symbol, const Expression & expression);
/*!*/ virtual Expression replaceUnknown(const Symbol & symbol);
/*!*/ virtual Expression setSign(Sign s, Context & context, Preferences::AngleUnit angleUnit);
virtual int polynomialDegree(Context & context, const char * symbolName) const;
/*!*/ virtual int getPolynomialCoefficients(Context & context, const char * symbolName, Expression coefficients[]) const;

View File

@@ -21,6 +21,8 @@ public:
// ExpressionNode
Type type() const override { return Type::Integral; }
int polynomialDegree(Context & context, const char * symbolName) const override;
Expression replaceUnknown(const Symbol & symbol) override;
private:
// Layout
Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override;

View File

@@ -0,0 +1,28 @@
#ifndef POINCARE_PARAMETERED_EXPRESSION_HELPER_H
#define POINCARE_PARAMETERED_EXPRESSION_HELPER_H
#include <poincare/expression.h>
namespace Poincare {
class SymbolAbstract;
class Symbol;
// Parametered expressions are Integral, Derivative, Sum and Product.
namespace ParameteredExpressionHelper {
/* We sometimes replace 'x' by 'UnknownX' to differentiate between a variable
* and a function parameter. For instance, when defining the function
* f(x)=cos(x) in Graph, we want x to be an unknown, instead of having f be
* the constant function equal to cos(user variable that is named x).
*
* In parametered expression, we do not want to replace all the 'x' with
* unknowns: for instance, we want to change f(x)=diff(cos(x),x,x) into
* f(X)=diff(cos(x),x,X), X being an unknown. ReplaceUnknownInExpression does
* that. */
Expression ReplaceUnknownInExpression(Expression e, const Symbol & symbolToReplace);
};
}
#endif

View File

@@ -16,6 +16,8 @@ public:
#endif
Type type() const override { return Type::Product; }
Expression replaceUnknown(const Symbol & symbol) override;
private:
float emptySequenceValue() const override { return 1.0f; }
Layout createSequenceLayout(Layout argumentLayout, Layout symbolLayout, Layout subscriptLayout, Layout superscriptLayout) const override;

View File

@@ -16,6 +16,8 @@ public:
#endif
Type type() const override { return Type::Sum; }
Expression replaceUnknown(const Symbol & symbol) override;
private:
float emptySequenceValue() const override { return 0.0f; }
Layout createSequenceLayout(Layout argumentLayout, Layout symbolLayout, Layout subscriptLayout, Layout superscriptLayout) const override;

View File

@@ -20,6 +20,7 @@ public:
// Expression Properties
Type type() const override { return Type::Symbol; }
Expression replaceSymbolWithExpression(const SymbolAbstract & symbol, const Expression & expression) override;
Expression replaceUnknown(const Symbol & symbol) override;
int polynomialDegree(Context & context, const char * symbolName) const override;
int getPolynomialCoefficients(Context & context, const char * symbolName, Expression coefficients[]) const override;
int getVariables(Context & context, isVariableTest isVariable, char * variables, int maxSizeVariable) const override;
@@ -77,6 +78,7 @@ public:
// Expression
Expression shallowReduce(Context & context, Preferences::AngleUnit angleUnit, ExpressionNode::ReductionTarget target);
Expression replaceSymbolWithExpression(const SymbolAbstract & symbol, const Expression & expression);
Expression replaceUnknown(const Symbol & symbol);
int getPolynomialCoefficients(Context & context, const char * symbolName, Expression coefficients[]) const;
Expression shallowReplaceReplaceableSymbols(Context & context);
private:

View File

@@ -1,8 +1,9 @@
#include <poincare/derivative.h>
#include <poincare/symbol.h>
#include <poincare/layout_helper.h>
#include <poincare/parametered_expression_helper.h>
#include <poincare/serialization_helper.h>
#include <poincare/simplification_helper.h>
#include <poincare/symbol.h>
#include <poincare/undefined.h>
#include <cmath>
#include <assert.h>
@@ -26,6 +27,10 @@ int DerivativeNode::polynomialDegree(Context & context, const char * symbolName)
return ExpressionNode::polynomialDegree(context, symbolName);
}
Expression DerivativeNode::replaceUnknown(const Symbol & symbol) {
return ParameteredExpressionHelper::ReplaceUnknownInExpression(Derivative(this), symbol);
}
Layout DerivativeNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const {
return LayoutHelper::Prefix(this, floatDisplayMode, numberOfSignificantDigits, Derivative::s_functionHelper.name());
}

View File

@@ -281,6 +281,18 @@ int Expression::getPolynomialReducedCoefficients(const char * symbolName, Expres
return degree;
}
Expression Expression::replaceUnknown(const Symbol & symbol) {
return node()->replaceUnknown(symbol);
}
Expression Expression::defaultReplaceUnknown(const Symbol & symbol) {
int nbChildren = numberOfChildren();
for (int i = 0; i < nbChildren; i++) {
childAtIndex(i).replaceUnknown(symbol);
}
return *this;
}
/* Comparison */
bool Expression::isIdenticalTo(const Expression e) const {
@@ -312,14 +324,6 @@ int Expression::serialize(char * buffer, int bufferSize, Preferences::PrintFloat
/* Simplification */
Expression Expression::ParseAndReduce(const char * text, Context & context, Preferences::AngleUnit angleUnit) {
Expression exp = Parse(text);
if (exp.isUninitialized()) {
return Undefined();
}
return exp.reduce(context, angleUnit);
}
Expression Expression::ParseAndSimplify(const char * text, Context & context, Preferences::AngleUnit angleUnit) {
Expression exp = Parse(text);
if (exp.isUninitialized()) {
@@ -423,7 +427,7 @@ U Expression::approximateToScalar(Context& context, Preferences::AngleUnit angle
template<typename U>
U Expression::approximateToScalar(const char * text, Context& context, Preferences::AngleUnit angleUnit) {
Expression exp = ParseAndReduce(text, context, angleUnit);
Expression exp = ParseAndSimplify(text, context, angleUnit);
return exp.approximateToScalar<U>(context, angleUnit);
}

View File

@@ -9,6 +9,10 @@ Expression ExpressionNode::replaceSymbolWithExpression(const SymbolAbstract & sy
return Expression(this).defaultReplaceSymbolWithExpression(symbol, expression);
}
Expression ExpressionNode::replaceUnknown(const Symbol & symbol) {
return Expression(this).defaultReplaceUnknown(symbol);
}
Expression ExpressionNode::setSign(Sign s, Context & context, Preferences::AngleUnit angleUnit) {
assert(false);
return Expression();

View File

@@ -323,7 +323,7 @@ T Integer::approximate() const {
*/
if (isInfinity()) {
return INFINITY;
return m_negative ? -INFINITY : INFINITY;
}
native_uint_t lastDigit = m_numberOfDigits > 0 ? digit(m_numberOfDigits-1) : 0;
@@ -334,7 +334,7 @@ T Integer::approximate() const {
/* Escape case if the exponent is too big to be stored */
assert(m_numberOfDigits > 0);
if (((int)m_numberOfDigits-1)*32+numberOfBitsInLastDigit-1> IEEE754<T>::maxExponent()-IEEE754<T>::exponentOffset()) {
return INFINITY;
return m_negative ? -INFINITY : INFINITY;
}
exponent += (m_numberOfDigits-1)*32;
exponent += numberOfBitsInLastDigit-1;

View File

@@ -1,6 +1,7 @@
#include <poincare/integral.h>
#include <poincare/complex.h>
#include <poincare/integral_layout.h>
#include <poincare/parametered_expression_helper.h>
#include <poincare/serialization_helper.h>
#include <poincare/symbol.h>
#include <poincare/undefined.h>
@@ -26,6 +27,10 @@ int IntegralNode::polynomialDegree(Context & context, const char * symbolName) c
return ExpressionNode::polynomialDegree(context, symbolName);
}
Expression IntegralNode::replaceUnknown(const Symbol & symbol) {
return ParameteredExpressionHelper::ReplaceUnknownInExpression(Integral(this), symbol);
}
Layout IntegralNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const {
return IntegralLayout(
childAtIndex(0)->createLayout(floatDisplayMode, numberOfSignificantDigits),

View File

@@ -0,0 +1,33 @@
#include <poincare/parametered_expression_helper.h>
#include <poincare/symbol.h>
#include <string.h>
#include <assert.h>
namespace Poincare {
Expression ParameteredExpressionHelper::ReplaceUnknownInExpression(Expression e, const Symbol & symbolToReplace) {
assert(!e.isUninitialized());
assert(e.type() == ExpressionNode::Type::Integral
|| e.type() == ExpressionNode::Type::Derivative
|| e.type() == ExpressionNode::Type::Sum
|| e.type() == ExpressionNode::Type::Product);
assert(!symbolToReplace.isUninitialized());
assert(symbolToReplace.type() == ExpressionNode::Type::Symbol);
int numberOfChildren = e.numberOfChildren();
assert(numberOfChildren > 2);
Expression child1 = e.childAtIndex(1);
assert(child1.type() == ExpressionNode::Type::Symbol);
Symbol& parameterChild = static_cast<Symbol &>(child1);
if (strcmp(parameterChild.name(), symbolToReplace.name()) != 0) {
return e.defaultReplaceUnknown(symbolToReplace);
}
for (int i = 2; i < numberOfChildren; i++) {
e.childAtIndex(i).replaceUnknown(symbolToReplace);
}
return e;
}
}

View File

@@ -1,5 +1,6 @@
#include <poincare/product.h>
#include <poincare/multiplication.h>
#include <poincare/parametered_expression_helper.h>
#include <poincare/product_layout.h>
#include <poincare/layout_helper.h>
#include <poincare/serialization_helper.h>
@@ -13,6 +14,10 @@ namespace Poincare {
constexpr Expression::FunctionHelper Product::s_functionHelper;
Expression ProductNode::replaceUnknown(const Symbol & symbol) {
return ParameteredExpressionHelper::ReplaceUnknownInExpression(Product(this), symbol);
}
Layout ProductNode::createSequenceLayout(Layout argumentLayout, Layout symbolLayout, Layout subscriptLayout, Layout superscriptLayout) const {
return ProductLayout(argumentLayout, symbolLayout, subscriptLayout, superscriptLayout);
}

View File

@@ -2,6 +2,7 @@
#include <poincare/addition.h>
#include <poincare/sum_layout.h>
#include <poincare/layout_helper.h>
#include <poincare/parametered_expression_helper.h>
#include <poincare/serialization_helper.h>
extern "C" {
#include <assert.h>
@@ -13,6 +14,10 @@ namespace Poincare {
constexpr Expression::FunctionHelper Sum::s_functionHelper;
Expression SumNode::replaceUnknown(const Symbol & symbol) {
return ParameteredExpressionHelper::ReplaceUnknownInExpression(Sum(this), symbol);
}
Layout SumNode::createSequenceLayout(Layout argumentLayout, Layout symbolLayout, Layout subscriptLayout, Layout superscriptLayout) const {
return SumLayout(argumentLayout, symbolLayout, subscriptLayout, superscriptLayout);
}

View File

@@ -27,6 +27,10 @@ Expression SymbolNode::replaceSymbolWithExpression(const SymbolAbstract & symbol
return Symbol(this).replaceSymbolWithExpression(symbol, expression);
}
Expression SymbolNode::replaceUnknown(const Symbol & symbol) {
return Symbol(this).replaceUnknown(symbol);
}
int SymbolNode::polynomialDegree(Context & context, const char * symbolName) const {
if (strcmp(m_name,symbolName) == 0) {
return 1;
@@ -180,6 +184,12 @@ Expression Symbol::replaceSymbolWithExpression(const SymbolAbstract & symbol, co
return *this;
}
Expression Symbol::replaceUnknown(const Symbol & symbol) {
assert(!symbol.isUninitialized());
assert(symbol.type() == ExpressionNode::Type::Symbol);
return replaceSymbolWithExpression(symbol, Symbol(SpecialSymbols::UnknownX));
}
int Symbol::getPolynomialCoefficients(Context & context, const char * symbolName, Expression coefficients[]) const {
if (strcmp(name(), symbolName) == 0) {
coefficients[0] = Rational(0);