mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-19 00:37:25 +01:00
[apps/calculation] Calculation store has one big buffer for calculations
This commit is contained in:
@@ -35,7 +35,6 @@ App::Descriptor * App::Snapshot::descriptor() {
|
||||
}
|
||||
|
||||
void App::Snapshot::tidy() {
|
||||
m_calculationStore.tidy();
|
||||
}
|
||||
|
||||
App::App(Snapshot * snapshot) :
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "calculation.h"
|
||||
#include "calculation_store.h"
|
||||
#include "../shared/poincare_helpers.h"
|
||||
#include <poincare/symbol.h>
|
||||
#include <poincare/undefined.h>
|
||||
#include <poincare/unreal.h>
|
||||
#include <string.h>
|
||||
@@ -14,50 +12,70 @@ namespace Calculation {
|
||||
|
||||
static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { return x > y ? x : y; }
|
||||
|
||||
Calculation::Calculation() :
|
||||
m_inputText(),
|
||||
m_exactOutputText(),
|
||||
m_approximateOutputText(),
|
||||
m_displayOutput(DisplayOutput::Unknown),
|
||||
m_height(-1),
|
||||
m_expandedHeight(-1),
|
||||
m_equalSign(EqualSign::Unknown)
|
||||
{
|
||||
}
|
||||
|
||||
bool Calculation::operator==(const Calculation& c) {
|
||||
return strcmp(m_inputText, c.m_inputText) == 0
|
||||
&& strcmp(m_approximateOutputText, c.m_approximateOutputText) == 0
|
||||
return strcmp(inputText(), c.inputText()) == 0
|
||||
&& strcmp(approximateOutputText(), c.approximateOutputText()) == 0
|
||||
/* Some calculations can make appear trigonometric functions in their
|
||||
* exact output. Their argument will be different with the angle unit
|
||||
* preferences but both input and approximate output will be the same.
|
||||
* For example, i^(sqrt(3)) = cos(sqrt(3)*pi/2)+i*sin(sqrt(3)*pi/2) if
|
||||
* angle unit is radian and i^(sqrt(3)) = cos(sqrt(3)*90+i*sin(sqrt(3)*90)
|
||||
* in degree. */
|
||||
&& strcmp(m_exactOutputText, c.m_exactOutputText) == 0;
|
||||
&& strcmp(exactOutputText(), c.exactOutputText()) == 0;
|
||||
}
|
||||
|
||||
void Calculation::reset() {
|
||||
m_inputText[0] = 0;
|
||||
m_exactOutputText[0] = 0;
|
||||
m_approximateOutputText[0] = 0;
|
||||
tidy();
|
||||
}
|
||||
|
||||
void Calculation::setContent(const char * c, Context * context, Expression ansExpression) {
|
||||
reset();
|
||||
{
|
||||
Symbol ansSymbol = Symbol::Ans();
|
||||
Expression input = Expression::Parse(c).replaceSymbolWithExpression(ansSymbol, ansExpression);
|
||||
/* We do not store directly the text enter by the user because we do not want
|
||||
* to keep Ans symbol in the calculation store. */
|
||||
PoincareHelpers::Serialize(input, m_inputText, sizeof(m_inputText));
|
||||
Calculation * Calculation::next() const {
|
||||
const char * result = reinterpret_cast<const char *>(this) + sizeof(Calculation);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
result = result + strlen(result) + 1; // Pass inputText, exactOutputText, ApproximateOutputText
|
||||
}
|
||||
Expression exactOutput;
|
||||
Expression approximateOutput;
|
||||
PoincareHelpers::ParseAndSimplifyAndApproximate(m_inputText, &exactOutput, &approximateOutput, context, false);
|
||||
PoincareHelpers::Serialize(exactOutput, m_exactOutputText, sizeof(m_exactOutputText));
|
||||
PoincareHelpers::Serialize(approximateOutput, m_approximateOutputText, sizeof(m_approximateOutputText));
|
||||
return reinterpret_cast<Calculation *>(const_cast<char *>(result));
|
||||
}
|
||||
|
||||
const char * Calculation::approximateOutputText() const {
|
||||
const char * exactOutput = exactOutputText();
|
||||
return exactOutput + strlen(exactOutput) + 1;
|
||||
}
|
||||
|
||||
Expression Calculation::input() {
|
||||
return Expression::Parse(m_inputText);
|
||||
}
|
||||
|
||||
Expression Calculation::exactOutput() {
|
||||
/* Because the angle unit might have changed, we do not simplify again. We
|
||||
* thereby avoid turning cos(Pi/4) into sqrt(2)/2 and displaying
|
||||
* 'sqrt(2)/2 = 0.999906' (which is totally wrong) instead of
|
||||
* 'cos(pi/4) = 0.999906' (which is true in degree). */
|
||||
Expression exactOutput = Expression::Parse(exactOutputText());
|
||||
if (exactOutput.isUninitialized()) {
|
||||
return Undefined::Builder();
|
||||
}
|
||||
return exactOutput;
|
||||
}
|
||||
|
||||
Expression Calculation::approximateOutput(Context * context) {
|
||||
/* To ensure that the expression 'm_output' is a matrix or a complex, we
|
||||
* call 'evaluate'. */
|
||||
Expression exp = Expression::Parse(approximateOutputText());
|
||||
if (exp.isUninitialized()) {
|
||||
/* TODO LEA replace with assert
|
||||
* exp might be uninitialized because the serialization did not fit in
|
||||
* the buffer. Put a special error instead of "undef". */
|
||||
return Undefined::Builder();
|
||||
}
|
||||
return PoincareHelpers::Approximate<double>(exp, context);
|
||||
}
|
||||
|
||||
Layout Calculation::createInputLayout() {
|
||||
return input().createLayout(Preferences::PrintFloatMode::Decimal, PrintFloat::k_numberOfStoredSignificantDigits);
|
||||
}
|
||||
|
||||
Layout Calculation::createExactOutputLayout() {
|
||||
return PoincareHelpers::CreateLayout(exactOutput());
|
||||
}
|
||||
|
||||
Layout Calculation::createApproximateOutputLayout(Context * context) {
|
||||
return PoincareHelpers::CreateLayout(approximateOutput(context));
|
||||
}
|
||||
|
||||
KDCoordinate Calculation::height(Context * context, bool expanded) {
|
||||
@@ -92,80 +110,6 @@ KDCoordinate Calculation::height(Context * context, bool expanded) {
|
||||
return *memoizedHeight;
|
||||
}
|
||||
|
||||
const char * Calculation::inputText() {
|
||||
return m_inputText;
|
||||
}
|
||||
|
||||
const char * Calculation::exactOutputText() {
|
||||
return m_exactOutputText;
|
||||
}
|
||||
|
||||
const char * Calculation::approximateOutputText() {
|
||||
return m_approximateOutputText;
|
||||
}
|
||||
|
||||
Expression Calculation::input() {
|
||||
return Expression::Parse(m_inputText);
|
||||
}
|
||||
|
||||
Layout Calculation::createInputLayout() {
|
||||
return input().createLayout(Preferences::PrintFloatMode::Decimal, PrintFloat::k_numberOfStoredSignificantDigits);
|
||||
}
|
||||
|
||||
bool Calculation::isEmpty() {
|
||||
/* To test if a calculation is empty, we need to test either m_inputText or
|
||||
* m_exactOutputText or m_approximateOutputText, the only three fields that
|
||||
* are not lazy-loaded. We choose m_exactOutputText to consider that a
|
||||
* calculation being added is still empty until the end of the method
|
||||
* 'setContent'. Indeed, during 'setContent' method, 'ans' evaluation calls
|
||||
* the evaluation of the last calculation only if the calculation being
|
||||
* filled is not taken into account.*/
|
||||
if (strlen(m_approximateOutputText) == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Calculation::tidy() {
|
||||
/* Uninitialized all Expression stored to free the Pool */
|
||||
m_displayOutput = DisplayOutput::Unknown;
|
||||
m_height = -1;
|
||||
m_expandedHeight = -1;
|
||||
m_equalSign = EqualSign::Unknown;
|
||||
}
|
||||
|
||||
Expression Calculation::exactOutput() {
|
||||
/* Because the angle unit might have changed, we do not simplify again. We
|
||||
* thereby avoid turning cos(Pi/4) into sqrt(2)/2 and displaying
|
||||
* 'sqrt(2)/2 = 0.999906' (which is totally wrong) instead of
|
||||
* 'cos(pi/4) = 0.999906' (which is true in degree). */
|
||||
Expression exactOutput = Expression::Parse(m_exactOutputText);
|
||||
if (exactOutput.isUninitialized()) {
|
||||
return Undefined::Builder();
|
||||
}
|
||||
return exactOutput;
|
||||
}
|
||||
|
||||
Layout Calculation::createExactOutputLayout() {
|
||||
return PoincareHelpers::CreateLayout(exactOutput());
|
||||
}
|
||||
|
||||
Expression Calculation::approximateOutput(Context * context) {
|
||||
/* To ensure that the expression 'm_output' is a matrix or a complex, we
|
||||
* call 'evaluate'. */
|
||||
Expression exp = Expression::Parse(m_approximateOutputText);
|
||||
if (exp.isUninitialized()) {
|
||||
/* TODO: exp might be uninitialized because the serialization did not fit in
|
||||
* the buffer. Put a special error instead of "undef". */
|
||||
return Undefined::Builder();
|
||||
}
|
||||
return PoincareHelpers::Approximate<double>(exp, context);
|
||||
}
|
||||
|
||||
Layout Calculation::createApproximateOutputLayout(Context * context) {
|
||||
return PoincareHelpers::CreateLayout(approximateOutput(context));
|
||||
}
|
||||
|
||||
Calculation::DisplayOutput Calculation::displayOutput(Context * context) {
|
||||
if (m_displayOutput != DisplayOutput::Unknown) {
|
||||
return m_displayOutput;
|
||||
@@ -185,15 +129,20 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) {
|
||||
context, true))
|
||||
{
|
||||
m_displayOutput = DisplayOutput::ApproximateOnly;
|
||||
} else if (strcmp(m_exactOutputText, m_approximateOutputText) == 0) {
|
||||
} else if (strcmp(exactOutputText(), approximateOutputText()) == 0) {
|
||||
/* If the exact and approximate results' texts are equal and their layouts
|
||||
* too, do not display the exact result. If the two layouts are not equal
|
||||
* because of the number of significant digits, we display both. */
|
||||
m_displayOutput = exactAndApproximateDisplayedOutputsAreEqual(context) == Calculation::EqualSign::Equal ? DisplayOutput::ApproximateOnly : DisplayOutput::ExactAndApproximate;
|
||||
} else if (strcmp(m_exactOutputText, Undefined::Name()) == 0 || strcmp(m_approximateOutputText, Unreal::Name()) == 0 || exactOutput().type() == ExpressionNode::Type::Undefined) {
|
||||
} else if (strcmp(exactOutputText(), Undefined::Name()) == 0
|
||||
|| strcmp(approximateOutputText(), Unreal::Name()) == 0
|
||||
|| exactOutput().type() == ExpressionNode::Type::Undefined)
|
||||
{
|
||||
// If the approximate result is 'unreal' or the exact result is 'undef'
|
||||
m_displayOutput = DisplayOutput::ApproximateOnly;
|
||||
} else if (input().recursivelyMatches(Expression::IsApproximate, context) || exactOutput().recursivelyMatches(Expression::IsApproximate, context)) {
|
||||
} else if (input().recursivelyMatches(Expression::IsApproximate, context)
|
||||
|| exactOutput().recursivelyMatches(Expression::IsApproximate, context))
|
||||
{
|
||||
m_displayOutput = DisplayOutput::ExactAndApproximateToggle;
|
||||
} else {
|
||||
m_displayOutput = DisplayOutput::ExactAndApproximate;
|
||||
@@ -204,8 +153,9 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) {
|
||||
bool Calculation::shouldOnlyDisplayExactOutput() {
|
||||
/* If the input is a "store in a function", do not display the approximate
|
||||
* result. This prevents x->f(x) from displaying x = undef. */
|
||||
return input().type() == ExpressionNode::Type::Store
|
||||
&& input().childAtIndex(1).type() == ExpressionNode::Type::Function;
|
||||
Expression i = input();
|
||||
return i.type() == ExpressionNode::Type::Store
|
||||
&& i.childAtIndex(1).type() == ExpressionNode::Type::Function;
|
||||
}
|
||||
|
||||
Calculation::EqualSign Calculation::exactAndApproximateDisplayedOutputsAreEqual(Poincare::Context * context) {
|
||||
@@ -215,7 +165,7 @@ Calculation::EqualSign Calculation::exactAndApproximateDisplayedOutputsAreEqual(
|
||||
constexpr int bufferSize = Constant::MaxSerializedExpressionSize;
|
||||
char buffer[bufferSize];
|
||||
Preferences * preferences = Preferences::sharedPreferences();
|
||||
Expression exactOutputExpression = PoincareHelpers::ParseAndSimplify(m_exactOutputText, context, false);
|
||||
Expression exactOutputExpression = PoincareHelpers::ParseAndSimplify(exactOutputText(), context, false);
|
||||
if (exactOutputExpression.isUninitialized()) {
|
||||
exactOutputExpression = Undefined::Builder();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,13 @@ namespace Calculation {
|
||||
|
||||
class CalculationStore;
|
||||
|
||||
|
||||
/* A calculation is:
|
||||
* | uint8_t |KDCoordinate| KDCoordinate | uint8_t | ... | ... | ... |
|
||||
* |m_displayOutput| m_height |m_expandedHeight|m_equalSign|m_inputText|m_exactOuputText|m_approximateOuputText|
|
||||
*
|
||||
* */
|
||||
#pragma pack(push,1)
|
||||
class Calculation {
|
||||
public:
|
||||
enum class EqualSign : uint8_t {
|
||||
@@ -26,23 +33,39 @@ public:
|
||||
ExactAndApproximateToggle
|
||||
};
|
||||
|
||||
Calculation();
|
||||
/* It is not really the minimal size, but it clears enough space for most
|
||||
* calculations instead of clearing less space, then fail to serialize, clear
|
||||
* more space, fail to serialize, clear more space, etc., until reaching
|
||||
* sufficient free space. */
|
||||
static int MinimalSize() { return sizeof(uint8_t) + 2*sizeof(KDCoordinate) + sizeof(uint8_t) + 3*Constant::MaxSerializedExpressionSize; }
|
||||
|
||||
Calculation() :
|
||||
m_displayOutput(DisplayOutput::Unknown),
|
||||
m_height(-1),
|
||||
m_expandedHeight(-1),
|
||||
m_equalSign(EqualSign::Unknown)
|
||||
{
|
||||
assert(sizeof(m_inputText) == 0);
|
||||
}
|
||||
bool operator==(const Calculation& c);
|
||||
/* c.reset() is the equivalent of c = Calculation() without copy assingment. */
|
||||
void reset();
|
||||
void setContent(const char * c, Poincare::Context * context, Poincare::Expression ansExpression);
|
||||
KDCoordinate height(Poincare::Context * context, bool expanded = false);
|
||||
const char * inputText();
|
||||
const char * exactOutputText();
|
||||
const char * approximateOutputText();
|
||||
Calculation * next() const;
|
||||
|
||||
// Texts
|
||||
const char * inputText() const { return m_inputText; }
|
||||
const char * exactOutputText() const { return m_inputText + strlen(m_inputText) + 1; }
|
||||
const char * approximateOutputText() const;
|
||||
|
||||
// Expressions
|
||||
Poincare::Expression input();
|
||||
Poincare::Layout createInputLayout();
|
||||
Poincare::Expression approximateOutput(Poincare::Context * context);
|
||||
Poincare::Expression exactOutput();
|
||||
Poincare::Expression approximateOutput(Poincare::Context * context);
|
||||
|
||||
// Layouts
|
||||
Poincare::Layout createInputLayout();
|
||||
Poincare::Layout createExactOutputLayout();
|
||||
Poincare::Layout createApproximateOutputLayout(Poincare::Context * context);
|
||||
bool isEmpty();
|
||||
void tidy();
|
||||
|
||||
KDCoordinate height(Poincare::Context * context, bool expanded = false);
|
||||
DisplayOutput displayOutput(Poincare::Context * context);
|
||||
bool shouldOnlyDisplayExactOutput();
|
||||
EqualSign exactAndApproximateDisplayedOutputsAreEqual(Poincare::Context * context);
|
||||
@@ -51,14 +74,13 @@ private:
|
||||
/* Buffers holding text expressions have to be longer than the text written
|
||||
* by user (of maximum length TextField::maxBufferSize()) because when we
|
||||
* print an expression we add omitted signs (multiplications, parenthesis...) */
|
||||
char m_inputText[Constant::MaxSerializedExpressionSize];
|
||||
char m_exactOutputText[Constant::MaxSerializedExpressionSize];
|
||||
char m_approximateOutputText[Constant::MaxSerializedExpressionSize];
|
||||
DisplayOutput m_displayOutput;
|
||||
KDCoordinate m_height;
|
||||
KDCoordinate m_expandedHeight;
|
||||
EqualSign m_equalSign;
|
||||
char m_inputText[0]; // MUST be the last member variable
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,108 +1,168 @@
|
||||
#include "calculation_store.h"
|
||||
#include <assert.h>
|
||||
#include "../shared/poincare_helpers.h"
|
||||
#include <poincare/rational.h>
|
||||
#include <poincare/symbol.h>
|
||||
#include <assert.h>
|
||||
|
||||
using namespace Poincare;
|
||||
using namespace Shared;
|
||||
|
||||
namespace Calculation {
|
||||
|
||||
Calculation * CalculationStore::push(const char * text, Context * context) {
|
||||
Calculation * result = &m_calculations[m_startIndex];
|
||||
result->setContent(text, context, ansExpression(context));
|
||||
m_startIndex++;
|
||||
if (m_startIndex >= k_maxNumberOfCalculations) {
|
||||
m_startIndex = 0;
|
||||
ExpiringPointer<Calculation> CalculationStore::calculationAtIndex(int i) {
|
||||
assert(!m_slidedBuffer);
|
||||
assert(i >= 0 && i < m_numberOfCalculations);
|
||||
int currentIndex = 0;
|
||||
for (Calculation * c : *this) {
|
||||
if (currentIndex == i) {
|
||||
return ExpiringPointer<Calculation>(c);
|
||||
}
|
||||
currentIndex++;
|
||||
}
|
||||
return result;
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Calculation * CalculationStore::calculationAtIndex(int i) {
|
||||
int j = 0;
|
||||
Calculation * currentCalc = &m_calculations[m_startIndex];
|
||||
Calculation * previousCalc = nullptr;
|
||||
while (j <= i) {
|
||||
if (!currentCalc++->isEmpty()) {
|
||||
previousCalc = currentCalc - 1;
|
||||
j++;
|
||||
}
|
||||
if (currentCalc >= m_calculations + k_maxNumberOfCalculations) {
|
||||
currentCalc = m_calculations;
|
||||
}
|
||||
}
|
||||
return previousCalc;
|
||||
}
|
||||
ExpiringPointer<Calculation> CalculationStore::push(const char * text, Context * context) {
|
||||
/* Compute ans now, before the buffer is slided and before the calculation
|
||||
* might be deleted */
|
||||
Expression ans = ansExpression(context); // TODO LEA compute ans only if Ans is in the input ?
|
||||
|
||||
int CalculationStore::numberOfCalculations() {
|
||||
Calculation * currentCalc= m_calculations;
|
||||
int numberOfCalculations = 0;
|
||||
while (currentCalc < m_calculations + k_maxNumberOfCalculations) {
|
||||
if (!currentCalc++->isEmpty()) {
|
||||
numberOfCalculations++;
|
||||
}
|
||||
// Prepare the buffer for the new calculation
|
||||
int minSize = Calculation::MinimalSize();
|
||||
assert(k_bufferSize > minSize);
|
||||
while (remainingBufferSize() < minSize) {
|
||||
deleteLastCalculation();
|
||||
}
|
||||
return numberOfCalculations;
|
||||
char * newCalculationsLocation = slideCalculationsToEndOfBuffer();
|
||||
char * nextSerializationLocation = m_buffer;
|
||||
// Add the beginning of the calculation
|
||||
{
|
||||
/* Copy the begining of the calculation. The calculation minimal size is
|
||||
* available, so this memcpy will not overide anything. */
|
||||
Calculation newCalc = Calculation();
|
||||
size_t calcSize = sizeof(newCalc);
|
||||
memcpy(nextSerializationLocation, &newCalc, calcSize);
|
||||
nextSerializationLocation += calcSize;
|
||||
}
|
||||
/* Add the input expression.
|
||||
* We do not store directly the text entered by the user because we do not
|
||||
* want to keep Ans symbol in the calculation store. */
|
||||
Expression input = Expression::Parse(text).replaceSymbolWithExpression(Symbol::Ans(), ans);
|
||||
const char * inputSerialization = nextSerializationLocation;
|
||||
serializeExpression(input, nextSerializationLocation, &newCalculationsLocation);
|
||||
nextSerializationLocation += strlen(nextSerializationLocation) + 1;
|
||||
Expression exactOutput;
|
||||
Expression approximateOutput;
|
||||
PoincareHelpers::ParseAndSimplifyAndApproximate(inputSerialization, &exactOutput, &approximateOutput, context, false);
|
||||
serializeExpression(exactOutput, nextSerializationLocation, &newCalculationsLocation);
|
||||
nextSerializationLocation += strlen(nextSerializationLocation) + 1;
|
||||
serializeExpression(approximateOutput, nextSerializationLocation, &newCalculationsLocation);
|
||||
nextSerializationLocation += strlen(nextSerializationLocation) + 1;
|
||||
size_t slideSize = m_buffer + k_bufferSize - newCalculationsLocation;
|
||||
memcpy(nextSerializationLocation, newCalculationsLocation, slideSize);
|
||||
m_slidedBuffer = false;
|
||||
m_numberOfCalculations++;
|
||||
m_bufferEnd+= nextSerializationLocation - m_buffer;
|
||||
return ExpiringPointer<Calculation>(reinterpret_cast<Calculation *>(m_buffer));
|
||||
}
|
||||
|
||||
void CalculationStore::deleteCalculationAtIndex(int i) {
|
||||
int numberOfCalc = numberOfCalculations();
|
||||
assert(i >= 0 && i < numberOfCalc);
|
||||
int indexFirstCalc = m_startIndex;
|
||||
while (m_calculations[indexFirstCalc].isEmpty()) {
|
||||
indexFirstCalc++;
|
||||
if (indexFirstCalc == k_maxNumberOfCalculations) {
|
||||
indexFirstCalc = 0;
|
||||
}
|
||||
assert(indexFirstCalc != m_startIndex);
|
||||
}
|
||||
int absoluteIndexCalculationI = indexFirstCalc+i;
|
||||
absoluteIndexCalculationI = absoluteIndexCalculationI >= k_maxNumberOfCalculations ? absoluteIndexCalculationI - k_maxNumberOfCalculations : absoluteIndexCalculationI;
|
||||
|
||||
int index = absoluteIndexCalculationI;
|
||||
for (int k = i; k < numberOfCalc-1; k++) {
|
||||
int nextIndex = index+1 >= k_maxNumberOfCalculations ? 0 : index+1;
|
||||
m_calculations[index] = m_calculations[nextIndex];
|
||||
index++;
|
||||
if (index == k_maxNumberOfCalculations) {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
m_calculations[index].reset();
|
||||
m_startIndex--;
|
||||
if (m_startIndex == -1) {
|
||||
m_startIndex = k_maxNumberOfCalculations-1;
|
||||
}
|
||||
assert(i >= 0 && i < m_numberOfCalculations);
|
||||
assert(!m_slidedBuffer);
|
||||
ExpiringPointer<Calculation> calcI = calculationAtIndex(i);
|
||||
char * nextCalc = reinterpret_cast<char *>(calcI->next());
|
||||
assert(m_bufferEnd >= nextCalc);
|
||||
size_t slidingSize = m_bufferEnd - nextCalc;
|
||||
memcpy((char *)(calcI.pointer()), nextCalc, slidingSize);
|
||||
m_bufferEnd -= (nextCalc - (char *)(calcI.pointer()));
|
||||
m_numberOfCalculations--;
|
||||
}
|
||||
|
||||
void CalculationStore::deleteAll() {
|
||||
m_startIndex = 0;
|
||||
for (int i = 0; i < k_maxNumberOfCalculations; i++) {
|
||||
m_calculations[i].reset();
|
||||
}
|
||||
}
|
||||
|
||||
void CalculationStore::tidy() {
|
||||
for (int i = 0; i < k_maxNumberOfCalculations; i++) {
|
||||
m_calculations[i].tidy();
|
||||
}
|
||||
assert(!m_slidedBuffer);
|
||||
m_bufferEnd = m_buffer;
|
||||
m_numberOfCalculations = 0;
|
||||
}
|
||||
|
||||
Expression CalculationStore::ansExpression(Context * context) {
|
||||
if (numberOfCalculations() == 0) {
|
||||
return Rational::Builder(0);
|
||||
}
|
||||
Calculation * lastCalculation = calculationAtIndex(numberOfCalculations()-1);
|
||||
ExpiringPointer<Calculation> mostRecentCalculation = calculationAtIndex(0);
|
||||
/* Special case: the exact output is a Store/Equal expression.
|
||||
* Store/Equal expression can only be at the root of an expression.
|
||||
* To avoid turning 'ans->A' in '2->A->A' or '2=A->A' (which cannot be
|
||||
* parsed), ans is replaced by the approximation output when any Store or
|
||||
* Equal expression appears. */
|
||||
bool exactOuptutInvolvesStoreEqual = lastCalculation->exactOutput().recursivelyMatches([](const Expression e, Context * context) {
|
||||
bool exactOuptutInvolvesStoreEqual = mostRecentCalculation->exactOutput().recursivelyMatches([](const Expression e, Context * context) {
|
||||
return e.type() == ExpressionNode::Type::Store || e.type() == ExpressionNode::Type::Equal;
|
||||
}, context, false);
|
||||
if (lastCalculation->input().recursivelyMatches(Expression::IsApproximate, context) || exactOuptutInvolvesStoreEqual) {
|
||||
return lastCalculation->approximateOutput(context);
|
||||
if (mostRecentCalculation->input().recursivelyMatches(Expression::IsApproximate, context) || exactOuptutInvolvesStoreEqual) {
|
||||
return mostRecentCalculation->approximateOutput(context);
|
||||
}
|
||||
return mostRecentCalculation->exactOutput();
|
||||
}
|
||||
|
||||
void CalculationStore::serializeExpression(Expression e, char * location, char * * newCalculationsLocation) {
|
||||
assert(m_slidedBuffer);
|
||||
pushExpression(
|
||||
[](char * location, size_t locationSize, void * e) {
|
||||
return PoincareHelpers::Serialize(*(Expression *)e, location, locationSize) < locationSize-1; //TODO LEA check the return value
|
||||
},
|
||||
&e, location, newCalculationsLocation);
|
||||
}
|
||||
|
||||
char * CalculationStore::slideCalculationsToEndOfBuffer() {
|
||||
int calculationsSize = m_bufferEnd - m_buffer;
|
||||
char * calculationsNewPosition = m_buffer + k_bufferSize - calculationsSize;
|
||||
memcpy(calculationsNewPosition, m_buffer, calculationsSize);
|
||||
m_slidedBuffer = true;
|
||||
return calculationsNewPosition;
|
||||
}
|
||||
|
||||
size_t CalculationStore::deleteLastCalculation(const char * calculationsStart) {
|
||||
assert(m_numberOfCalculations > 0);
|
||||
size_t result;
|
||||
if (!m_slidedBuffer) {
|
||||
assert(calculationsStart == nullptr);
|
||||
const char * previousBufferEnd = m_bufferEnd;
|
||||
m_bufferEnd = lastCalculationPosition(m_buffer);
|
||||
assert(previousBufferEnd > m_bufferEnd);
|
||||
result = previousBufferEnd - m_bufferEnd;
|
||||
} else {
|
||||
assert(calculationsStart != nullptr);
|
||||
const char * lastCalc = lastCalculationPosition(calculationsStart);
|
||||
assert(*lastCalc == 0);
|
||||
result = m_buffer + k_bufferSize - lastCalc;
|
||||
memcpy(const_cast<char *>(calculationsStart + result), calculationsStart, m_buffer + k_bufferSize - calculationsStart - result);
|
||||
}
|
||||
m_numberOfCalculations--;
|
||||
return result;
|
||||
}
|
||||
|
||||
const char * CalculationStore::lastCalculationPosition(const char * calculationsStart) const {
|
||||
// TODO LEA: Make this faster?
|
||||
assert(calculationsStart >= m_buffer && calculationsStart < m_buffer + k_bufferSize);
|
||||
Calculation * c = reinterpret_cast<Calculation *>(const_cast<char *>(calculationsStart));
|
||||
int calculationIndex = 0;
|
||||
while (calculationIndex < m_numberOfCalculations - 1) {
|
||||
c = c->next();
|
||||
}
|
||||
return reinterpret_cast<const char *>(c);
|
||||
}
|
||||
|
||||
void CalculationStore::pushExpression(ValueCreator valueCreator, Expression * expression, char * location, char * * newCalculationsLocation) {
|
||||
while (!valueCreator(location, *newCalculationsLocation - location, expression)
|
||||
&& *newCalculationsLocation < m_buffer + k_bufferSize)
|
||||
{
|
||||
*newCalculationsLocation = *newCalculationsLocation + deleteLastCalculation();
|
||||
assert(*newCalculationsLocation <= m_buffer + k_bufferSize);
|
||||
}
|
||||
if (*newCalculationsLocation >= m_buffer + k_bufferSize) {
|
||||
//TODO LEA the expression does not fit in the buffer even empty
|
||||
// Push undef if calculation is too big !!! (and push undef before too if needed!!!)
|
||||
}
|
||||
return lastCalculation->exactOutput();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,23 +2,66 @@
|
||||
#define CALCULATION_CALCULATION_STORE_H
|
||||
|
||||
#include "calculation.h"
|
||||
#include <apps/shared/expiring_pointer.h>
|
||||
|
||||
namespace Calculation {
|
||||
|
||||
/* To optimize the storage space, we use one big buffer for all calculations.
|
||||
*
|
||||
* The previous solution was to keep 10 calculations, each containing 3 buffers
|
||||
* (for input and outputs). To optimize the storage, we then wanted to put all
|
||||
* outputs in a cache where they could be deleted to add a new entry, and
|
||||
* recomputed on cache miss. However, the computation depends too much on the
|
||||
* state of the memory for this to be possible. For instance:
|
||||
* 6->a
|
||||
* a+1
|
||||
* Perform some big computations that remove a+1 from the cache
|
||||
* Delete a from the variable box.
|
||||
* Scroll up to display a+1 : a does not exist anymore so the outputs won't be
|
||||
* recomputed correctly.
|
||||
*
|
||||
* Now we do not cap the number of calculations and just delete the oldests to
|
||||
* create space for a new calculation. */
|
||||
|
||||
class CalculationStore {
|
||||
public:
|
||||
CalculationStore() : m_startIndex(0) {}
|
||||
Calculation * calculationAtIndex(int i);
|
||||
Calculation * push(const char * text, Poincare::Context * context);
|
||||
CalculationStore() : m_bufferEnd(m_buffer), m_numberOfCalculations(0), m_slidedBuffer(false) {}
|
||||
Shared::ExpiringPointer<Calculation> calculationAtIndex(int i);
|
||||
Shared::ExpiringPointer<Calculation> push(const char * text, Poincare::Context * context);
|
||||
void deleteCalculationAtIndex(int i);
|
||||
void deleteAll();
|
||||
int numberOfCalculations();
|
||||
void tidy();
|
||||
int numberOfCalculations() const { return m_numberOfCalculations; }
|
||||
Poincare::Expression ansExpression(Poincare::Context * context);
|
||||
static constexpr int k_maxNumberOfCalculations = 10;
|
||||
private:
|
||||
int m_startIndex;
|
||||
Calculation m_calculations[k_maxNumberOfCalculations];
|
||||
static constexpr int k_bufferSize = 10 * 3 * Constant::MaxSerializedExpressionSize;
|
||||
|
||||
class CalculationIterator {
|
||||
public:
|
||||
CalculationIterator(const char * c) : m_calculation(reinterpret_cast<Calculation *>(const_cast<char *>(c))) {}
|
||||
Calculation * operator*() { return m_calculation; }
|
||||
bool operator!=(const CalculationIterator& it) const { return (m_calculation != it.m_calculation); }
|
||||
CalculationIterator & operator++() {
|
||||
m_calculation = m_calculation->next();
|
||||
return *this;
|
||||
}
|
||||
protected:
|
||||
Calculation * m_calculation;
|
||||
};
|
||||
|
||||
CalculationIterator begin() const { return CalculationIterator(m_buffer); }
|
||||
CalculationIterator end() const { return CalculationIterator(m_bufferEnd); }
|
||||
|
||||
int remainingBufferSize() const { assert(m_bufferEnd >= m_buffer); return k_bufferSize - (m_bufferEnd - m_buffer); }
|
||||
void serializeExpression(Poincare::Expression e, char * location, char * * newCalculationsLocation);
|
||||
char * slideCalculationsToEndOfBuffer(); // returns the new position of the calculations
|
||||
size_t deleteLastCalculation(const char * calculationsStart = nullptr);
|
||||
const char * lastCalculationPosition(const char * calculationsStart) const;
|
||||
typedef bool (*ValueCreator)(char * location, size_t locationSize, void * e);
|
||||
void pushExpression(ValueCreator valueCrator, Poincare::Expression * expression, char * location, char * * newCalculationsLocation);
|
||||
char m_buffer[k_bufferSize];
|
||||
const char * m_bufferEnd;
|
||||
int m_numberOfCalculations;
|
||||
bool m_slidedBuffer;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ bool HistoryController::handleEvent(Ion::Events::Event event) {
|
||||
EditExpressionController * editController = (EditExpressionController *)parentResponder();
|
||||
m_selectableTableView.deselectTable();
|
||||
Container::activeApp()->setFirstResponder(editController);
|
||||
Calculation * calculation = m_calculationStore->calculationAtIndex(focusRow);
|
||||
Shared::ExpiringPointer<Calculation> calculation = m_calculationStore->calculationAtIndex(focusRow);
|
||||
if (subviewType == SubviewType::Input) {
|
||||
editController->insertTextBody(calculation->inputText());
|
||||
} else {
|
||||
@@ -141,7 +141,7 @@ int HistoryController::reusableCellCount(int type) {
|
||||
|
||||
void HistoryController::willDisplayCellForIndex(HighlightCell * cell, int index) {
|
||||
HistoryViewCell * myCell = (HistoryViewCell *)cell;
|
||||
myCell->setCalculation(m_calculationStore->calculationAtIndex(index), index == selectedRow() && selectedSubviewType() == SubviewType::Output);
|
||||
myCell->setCalculation((m_calculationStore->calculationAtIndex(index)).pointer(), index == selectedRow() && selectedSubviewType() == SubviewType::Output);
|
||||
myCell->setEven(index%2 == 0);
|
||||
myCell->setHighlighted(myCell->isHighlighted());
|
||||
}
|
||||
@@ -150,7 +150,7 @@ KDCoordinate HistoryController::rowHeight(int j) {
|
||||
if (j >= m_calculationStore->numberOfCalculations()) {
|
||||
return 0;
|
||||
}
|
||||
Calculation * calculation = m_calculationStore->calculationAtIndex(j);
|
||||
Shared::ExpiringPointer<Calculation> calculation = m_calculationStore->calculationAtIndex(j);
|
||||
return calculation->height(App::app()->localContext(), j == selectedRow() && selectedSubviewType() == SubviewType::Output) + 4 * Metric::CommonSmallMargin;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,46 +8,32 @@
|
||||
using namespace Poincare;
|
||||
using namespace Calculation;
|
||||
|
||||
void assert_store_is(CalculationStore * store, const char * result[10]) {
|
||||
void assert_store_is(CalculationStore * store, const char * * result) {
|
||||
for (int i = 0; i < store->numberOfCalculations(); i++) {
|
||||
quiz_assert(strcmp(store->calculationAtIndex(i)->inputText(), result[i]) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
QUIZ_CASE(calculation_store_ring_buffer) {
|
||||
|
||||
QUIZ_CASE(calculation_store) {
|
||||
Shared::GlobalContext globalContext;
|
||||
CalculationStore store;
|
||||
quiz_assert(CalculationStore::k_maxNumberOfCalculations == 10);
|
||||
for (int i = 0; i < CalculationStore::k_maxNumberOfCalculations; i++) {
|
||||
// Store is now {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
|
||||
const char * result[] = {"9", "8", "7", "6", "5", "4", "3", "2", "1", "0"};
|
||||
for (int i = 0; i < 10; i++) {
|
||||
char text[2] = {(char)(i+'0'), 0};
|
||||
store.push(text, &globalContext);
|
||||
quiz_assert(store.numberOfCalculations() == i+1);
|
||||
}
|
||||
/* Store is now {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} */
|
||||
const char * result[10] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
|
||||
assert_store_is(&store, result);
|
||||
|
||||
store.push("10", &globalContext);
|
||||
/* Store is now {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} */
|
||||
const char * result1[10] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"};
|
||||
assert_store_is(&store, result1);
|
||||
|
||||
for (int i = 9; i > 0; i = i-2) {
|
||||
store.deleteCalculationAtIndex(i);
|
||||
}
|
||||
/* Store is now {1, 3, 5, 7, 9} */
|
||||
const char * result2[10] = {"1", "3", "5", "7", "9", "", "", "", "", ""};
|
||||
// Store is now {9, 7, 5, 3, 1}
|
||||
const char * result2[] = {"9", "7", "5", "3", "1"};
|
||||
assert_store_is(&store, result2);
|
||||
|
||||
for (int i = 5; i < CalculationStore::k_maxNumberOfCalculations; i++) {
|
||||
char text[3] = {(char)(i+'0'), 0};
|
||||
store.push(text, &globalContext);
|
||||
quiz_assert(store.numberOfCalculations() == i+1);
|
||||
}
|
||||
/* Store is now {0, 2, 4, 6, 8, 5, 6, 7, 8, 9} */
|
||||
const char * result3[10] = {"1", "3", "5", "7", "9", "5", "6", "7", "8", "9"};
|
||||
assert_store_is(&store, result3);
|
||||
|
||||
store.deleteAll();
|
||||
}
|
||||
|
||||
@@ -57,12 +43,12 @@ QUIZ_CASE(calculation_ans) {
|
||||
|
||||
store.push("1+3/4", &globalContext);
|
||||
store.push("ans+2/3", &globalContext);
|
||||
::Calculation::Calculation * lastCalculation = store.calculationAtIndex(1);
|
||||
Shared::ExpiringPointer<::Calculation::Calculation> lastCalculation = store.calculationAtIndex(0);
|
||||
quiz_assert(lastCalculation->displayOutput(&globalContext) == ::Calculation::Calculation::DisplayOutput::ExactAndApproximate);
|
||||
quiz_assert(strcmp(lastCalculation->exactOutputText(),"29/12") == 0);
|
||||
|
||||
store.push("ans+0.22", &globalContext);
|
||||
lastCalculation = store.calculationAtIndex(2);
|
||||
lastCalculation = store.calculationAtIndex(0);
|
||||
quiz_assert(lastCalculation->displayOutput(&globalContext) == ::Calculation::Calculation::DisplayOutput::ExactAndApproximateToggle);
|
||||
quiz_assert(strcmp(lastCalculation->approximateOutputText(),"2.6366666666667") == 0);
|
||||
|
||||
@@ -71,7 +57,7 @@ QUIZ_CASE(calculation_ans) {
|
||||
|
||||
void assertCalculationDisplay(const char * input, ::Calculation::Calculation::DisplayOutput display, ::Calculation::Calculation::EqualSign sign, const char * exactOutput, const char * approximateOutput, Context * context, CalculationStore * store) {
|
||||
store->push(input, context);
|
||||
::Calculation::Calculation * lastCalculation = store->calculationAtIndex(1);
|
||||
Shared::ExpiringPointer<::Calculation::Calculation> lastCalculation = store->calculationAtIndex(0);
|
||||
quiz_assert(lastCalculation->displayOutput(context) == display);
|
||||
if (sign != ::Calculation::Calculation::EqualSign::Unknown) {
|
||||
quiz_assert(lastCalculation->exactAndApproximateDisplayedOutputsAreEqual(context) == sign);
|
||||
|
||||
Reference in New Issue
Block a user