From a0acdc171b44bbe5473bb84e9cfdc996469bd832 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Fri, 13 Nov 2020 00:48:46 +0100 Subject: [PATCH 001/355] Squelette --- apps/reader/Makefile | 14 ++++++++++++++ apps/reader/app.cpp | 37 ++++++++++++++++++++++++++++++++++++ apps/reader/app.h | 28 +++++++++++++++++++++++++++ apps/reader/base.de.i18n | 2 ++ apps/reader/base.en.i18n | 2 ++ apps/reader/base.es.i18n | 2 ++ apps/reader/base.fr.i18n | 2 ++ apps/reader/base.hu.i18n | 2 ++ apps/reader/base.it.i18n | 2 ++ apps/reader/base.nl.i18n | 2 ++ apps/reader/base.pt.i18n | 2 ++ apps/reader/reader_icon.png | Bin 0 -> 1541 bytes build/config.mak | 2 +- 13 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 apps/reader/Makefile create mode 100644 apps/reader/app.cpp create mode 100644 apps/reader/app.h create mode 100644 apps/reader/base.de.i18n create mode 100644 apps/reader/base.en.i18n create mode 100644 apps/reader/base.es.i18n create mode 100644 apps/reader/base.fr.i18n create mode 100644 apps/reader/base.hu.i18n create mode 100644 apps/reader/base.it.i18n create mode 100644 apps/reader/base.nl.i18n create mode 100644 apps/reader/base.pt.i18n create mode 100644 apps/reader/reader_icon.png diff --git a/apps/reader/Makefile b/apps/reader/Makefile new file mode 100644 index 00000000000..cf57b4311aa --- /dev/null +++ b/apps/reader/Makefile @@ -0,0 +1,14 @@ +apps += reader::App +app_headers += apps/reader/app.h + +app_sreader_src = $(addprefix apps/reader/,\ + app.cpp \ +) + +apps_src += $(app_sreader_src) + +app_images += apps/reader/reader_icon.png + +i18n_files += $(call i18n_without_universal_for,reader/base) + +$(eval $(call depends_on_image,apps/reader/app.cpp,apps/reader/reader_icon.png)) \ No newline at end of file diff --git a/apps/reader/app.cpp b/apps/reader/app.cpp new file mode 100644 index 00000000000..8a848bbe8c9 --- /dev/null +++ b/apps/reader/app.cpp @@ -0,0 +1,37 @@ +#include "app.h" +#include "reader_icon.h" +#include "apps/apps_container.h" +#include "apps/i18n.h" + + +namespace reader { + +I18n::Message App::Descriptor::name() { + return I18n::Message::ReaderApp; +} + +I18n::Message App::Descriptor::upperName() { + return I18n::Message::ReaderAppCapital; +} + +const Image * App::Descriptor::icon() { + return ImageStore::ReaderIcon; +} + + +App * App::Snapshot::unpack(Container * container) { + return new (container->currentAppBuffer()) App(this); +} + +App::Descriptor * App::Snapshot::descriptor() { + static Descriptor descriptor; + return &descriptor; +} + + +App::App(Snapshot * snapshot) : + ::App(snapshot, nullptr) +{ +} + +} diff --git a/apps/reader/app.h b/apps/reader/app.h new file mode 100644 index 00000000000..a46ec88b8fd --- /dev/null +++ b/apps/reader/app.h @@ -0,0 +1,28 @@ +#ifndef READER_H +#define READER_H + +#include + +namespace reader { + +class App : public ::App { +public: + class Descriptor : public ::App::Descriptor { + public: + I18n::Message name() override; + I18n::Message upperName() override; + const Image * icon() override; + }; + class Snapshot : public ::App::Snapshot { + public: + App * unpack(Container * container) override; + Descriptor * descriptor() override; + }; +private: + App(Snapshot * snapshot); + +}; + +} + +#endif \ No newline at end of file diff --git a/apps/reader/base.de.i18n b/apps/reader/base.de.i18n new file mode 100644 index 00000000000..ecc03a74956 --- /dev/null +++ b/apps/reader/base.de.i18n @@ -0,0 +1,2 @@ +ReaderApp = "Reader" +ReaderAppCapital = "READER" \ No newline at end of file diff --git a/apps/reader/base.en.i18n b/apps/reader/base.en.i18n new file mode 100644 index 00000000000..ecc03a74956 --- /dev/null +++ b/apps/reader/base.en.i18n @@ -0,0 +1,2 @@ +ReaderApp = "Reader" +ReaderAppCapital = "READER" \ No newline at end of file diff --git a/apps/reader/base.es.i18n b/apps/reader/base.es.i18n new file mode 100644 index 00000000000..ecc03a74956 --- /dev/null +++ b/apps/reader/base.es.i18n @@ -0,0 +1,2 @@ +ReaderApp = "Reader" +ReaderAppCapital = "READER" \ No newline at end of file diff --git a/apps/reader/base.fr.i18n b/apps/reader/base.fr.i18n new file mode 100644 index 00000000000..ecc03a74956 --- /dev/null +++ b/apps/reader/base.fr.i18n @@ -0,0 +1,2 @@ +ReaderApp = "Reader" +ReaderAppCapital = "READER" \ No newline at end of file diff --git a/apps/reader/base.hu.i18n b/apps/reader/base.hu.i18n new file mode 100644 index 00000000000..ecc03a74956 --- /dev/null +++ b/apps/reader/base.hu.i18n @@ -0,0 +1,2 @@ +ReaderApp = "Reader" +ReaderAppCapital = "READER" \ No newline at end of file diff --git a/apps/reader/base.it.i18n b/apps/reader/base.it.i18n new file mode 100644 index 00000000000..ecc03a74956 --- /dev/null +++ b/apps/reader/base.it.i18n @@ -0,0 +1,2 @@ +ReaderApp = "Reader" +ReaderAppCapital = "READER" \ No newline at end of file diff --git a/apps/reader/base.nl.i18n b/apps/reader/base.nl.i18n new file mode 100644 index 00000000000..ecc03a74956 --- /dev/null +++ b/apps/reader/base.nl.i18n @@ -0,0 +1,2 @@ +ReaderApp = "Reader" +ReaderAppCapital = "READER" \ No newline at end of file diff --git a/apps/reader/base.pt.i18n b/apps/reader/base.pt.i18n new file mode 100644 index 00000000000..ecc03a74956 --- /dev/null +++ b/apps/reader/base.pt.i18n @@ -0,0 +1,2 @@ +ReaderApp = "Reader" +ReaderAppCapital = "READER" \ No newline at end of file diff --git a/apps/reader/reader_icon.png b/apps/reader/reader_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..78e23e1395151985a715f9ae71b8113af7fbd712 GIT binary patch literal 1541 zcmV+g2KxDlP)Px)zez+vRA>d=T1!kMWe~3J8JGvmvJ%2G5d(W5yus{%=mFGl@Bn0WqlSZr-4I{F z7%#Yq9+dUs#ej(rSeFEii4wyBE(8K-U@^dguqy%9fV>%y83P#@9)ndKhRlxBFT0!R z*-CnP{=e$4s;~e0-#-HZw7k4*VOh40VVIYYm4(mc01-VUUFggM^tCTBG4Tt0ANo7T z_$SuZ)~Ydm$7C{j5g>sCEZ{GqtN*8`r{MK^;pF6G|LEw*i2vV8NlE#^=j6HP8yg!R zU}l}wYEASp!|lDlzrTS1KTAqV`i6XG>F2D*J_=`Ggu`O7Bx1DP!isvG(hRjRgTdg8 zj*d1_Mj$mbG{EriaA0;w($qw*zjht2y?Ra1{@&i+G1i_HfwFbj#Cd+SQnm8 zZS2vXk3{W8Mn*cY_C7WuxaaXn7AldRo(?x}+!PYgl(+cTA~ZKQgUx1x>T2rqgb5=t z>G1IIIqG2w5!`vl608`8Eac|p24h`F-pFqwoSmwwD!|@Q)aDr;JBxArl%BhLLPAZ$ zXf#4;X{jhM5;(&X%k`F$N`y!V!wa^zw;>@R0kAih<%Lm}bbv4poY2+P1#Y)HfTLcL zY>@us0+|tq#w6?ZNHl{=n2WZ*F6@g1jON09QdN?>ZkZo8_fu^P=aJgJ? ze0S-L;^e{t zoZW5*9GGEnaF7d4sR$*7<<`~~bar-1)v3B1O}k=aV_|i56{z-#i;E@M5Cuo=3Rex# z($XSHq1Hk@Jw57ytEi}ezP>(gQK+@!V)}7H3}t0yVq)if25aS%=8jJq;j#xr!_ptL z7ss>Rmb>6}{o|haeK1Gb#6m)!xh=|~d&>BtF~bW`R#+NRp9VHT-o_}e zWfd5V)d0Bvz^h62dhP_5`loLBdx*RqC}1#F3mACL4rH(A$PYec3VhV8sNnQ#cXt;W z8ykT(i1a{ladA*vTgx3~N%2Pz+|<+*(4!YxUtfp$`FSWTER^JuAUNODth~G&Qd3hU zDbzxU_ABS+<{%>@1G2M)mpT6S1;P0QX`isDsK}SR(6`6qfsT$2z@u%bsi^_l0h3Cy zh61Td)s$OYTm+}n2{}1Avd#&}2i=2PSy|z}6@imWlERWtDSc6`JDkbMNzQysObnkJ zy!nd2wY9ZDZ*MQJay1!rd@Pp~fvc>n4J<7pN{%Bf{o$^P5{1?c_>%}D4*!|3Q}09P=%L)W7w za3=C1om}$K!omVr%oaYj=J|9@gQ&Z^8%jz_xKmaMM(Mha)tE2uF>sWL!$JhzGR61Hn55b2 rWy;qnIkDrQtGvBj8)KNq&IbMk=AgRv>wm2i00000NkvXXu0mjf!Ls{N literal 0 HcmV?d00001 diff --git a/build/config.mak b/build/config.mak index 3f104d92a92..f5f93b0b3e9 100644 --- a/build/config.mak +++ b/build/config.mak @@ -7,7 +7,7 @@ HOME_DISPLAY_EXTERNALS ?= 1 EPSILON_VERSION ?= 14.4.1 OMEGA_VERSION ?= 1.20.3 # OMEGA_USERNAME ?= N/A -EPSILON_APPS ?= calculation rpn graph code statistics probability solver atom sequence regression settings external +EPSILON_APPS ?= reader calculation rpn graph code statistics probability solver atom sequence regression settings external EPSILON_I18N ?= en fr nl pt it de es hu EPSILON_GETOPT ?= 0 EPSILON_TELEMETRY ?= 0 From c37b4bd1f4f2aac3f22e02c360c432dbce22e271 Mon Sep 17 00:00:00 2001 From: Fournier Gabriel <> Date: Sat, 14 Nov 2020 13:39:11 +0100 Subject: [PATCH 002/355] ListBookController - partie 1 --- apps/reader/Makefile | 1 + apps/reader/app.cpp | 3 ++- apps/reader/app.h | 3 ++- apps/reader/list_book_controller.cpp | 37 ++++++++++++++++++++++++++++ apps/reader/list_book_controller.h | 25 +++++++++++++++++++ themes | 2 +- 6 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 apps/reader/list_book_controller.cpp create mode 100644 apps/reader/list_book_controller.h diff --git a/apps/reader/Makefile b/apps/reader/Makefile index cf57b4311aa..a3ddd6cd8e3 100644 --- a/apps/reader/Makefile +++ b/apps/reader/Makefile @@ -3,6 +3,7 @@ app_headers += apps/reader/app.h app_sreader_src = $(addprefix apps/reader/,\ app.cpp \ + list_book_controller.cpp \ ) apps_src += $(app_sreader_src) diff --git a/apps/reader/app.cpp b/apps/reader/app.cpp index 8a848bbe8c9..68ca4111501 100644 --- a/apps/reader/app.cpp +++ b/apps/reader/app.cpp @@ -30,7 +30,8 @@ App::Descriptor * App::Snapshot::descriptor() { App::App(Snapshot * snapshot) : - ::App(snapshot, nullptr) + ::App(snapshot, &m_listBookController), + m_listBookController(nullptr) { } diff --git a/apps/reader/app.h b/apps/reader/app.h index a46ec88b8fd..d2f4fbc1d56 100644 --- a/apps/reader/app.h +++ b/apps/reader/app.h @@ -2,6 +2,7 @@ #define READER_H #include +#include "list_book_controller.h" namespace reader { @@ -20,7 +21,7 @@ class App : public ::App { }; private: App(Snapshot * snapshot); - + ListBookController m_listBookController; }; } diff --git a/apps/reader/list_book_controller.cpp b/apps/reader/list_book_controller.cpp new file mode 100644 index 00000000000..67e5246bf45 --- /dev/null +++ b/apps/reader/list_book_controller.cpp @@ -0,0 +1,37 @@ +#include "list_book_controller.h" + +namespace reader +{ + +View* ListBookController::view() +{ + return &m_tableView; +} + +ListBookController::ListBookController(Responder * parentResponder): + ViewController(parentResponder), + m_tableView(this, this) +{ +} + +int ListBookController::numberOfRows() const +{ + return 0; +} + +KDCoordinate ListBookController::cellHeight() +{ + return 50; +} + +HighlightCell * ListBookController::reusableCell(int index) +{ + return nullptr; +} + +int ListBookController::reusableCellCount() const +{ + return 0; +} + +} \ No newline at end of file diff --git a/apps/reader/list_book_controller.h b/apps/reader/list_book_controller.h new file mode 100644 index 00000000000..7e868fe76e0 --- /dev/null +++ b/apps/reader/list_book_controller.h @@ -0,0 +1,25 @@ +#ifndef __LIST_BOOK_CONTROLLER_H__ +#define __LIST_BOOK_CONTROLLER_H__ + +#include + +namespace reader +{ + +class ListBookController : public ViewController, public SimpleListViewDataSource, public ScrollViewDataSource +{ +public: + ListBookController(Responder * parentResponder); + View* view() override; + + int numberOfRows() const override; + KDCoordinate cellHeight() override; + HighlightCell * reusableCell(int index) override; + int reusableCellCount() const override; +private: + TableView m_tableView; +}; + +} + +#endif \ No newline at end of file diff --git a/themes b/themes index 48fc1cc7273..20073ead092 160000 --- a/themes +++ b/themes @@ -1 +1 @@ -Subproject commit 48fc1cc72739ad766abbf161d78c2f98cf2f797d +Subproject commit 20073ead0928aa88bbc370de3d412c11cf8b30f5 From b4e9ceed18528443aa9e5f650cda48732df9a5ef Mon Sep 17 00:00:00 2001 From: Fournier Gabriel <> Date: Sun, 15 Nov 2020 18:55:34 +0100 Subject: [PATCH 003/355] ListBookController - partie 2 --- apps/reader/list_book_controller.cpp | 16 +++++++++++++--- apps/reader/list_book_controller.h | 9 +++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/reader/list_book_controller.cpp b/apps/reader/list_book_controller.cpp index 67e5246bf45..d785f6e5b6d 100644 --- a/apps/reader/list_book_controller.cpp +++ b/apps/reader/list_book_controller.cpp @@ -12,11 +12,13 @@ ListBookController::ListBookController(Responder * parentResponder): ViewController(parentResponder), m_tableView(this, this) { + m_files[0].name= "Harry Potter 1.txt"; + m_nbFiles = 1; } int ListBookController::numberOfRows() const { - return 0; + return m_nbFiles; } KDCoordinate ListBookController::cellHeight() @@ -26,12 +28,20 @@ KDCoordinate ListBookController::cellHeight() HighlightCell * ListBookController::reusableCell(int index) { - return nullptr; + return &m_cells[index]; } int ListBookController::reusableCellCount() const { - return 0; + return NB_CELLS; +} + +void ListBookController::willDisplayCellForIndex(HighlightCell * cell, int index) +{ + MessageTableCell* myTextCell = static_cast(cell); + MessageTextView* textView = static_cast(myTextCell->labelView()); + textView->setText(m_files[index].name); + myTextCell->setMessageFont(KDFont::LargeFont); } } \ No newline at end of file diff --git a/apps/reader/list_book_controller.h b/apps/reader/list_book_controller.h index 7e868fe76e0..4c5050c9840 100644 --- a/apps/reader/list_book_controller.h +++ b/apps/reader/list_book_controller.h @@ -2,6 +2,7 @@ #define __LIST_BOOK_CONTROLLER_H__ #include +#include namespace reader { @@ -16,8 +17,16 @@ class ListBookController : public ViewController, public SimpleListViewDataSourc KDCoordinate cellHeight() override; HighlightCell * reusableCell(int index) override; int reusableCellCount() const override; + void willDisplayCellForIndex(HighlightCell * cell, int index) override; private: TableView m_tableView; + + static const int NB_FILES = 20; + External::Archive::File m_files[NB_FILES]; + int m_nbFiles = 0; + + static const int NB_CELLS = 6; + MessageTableCell m_cells[NB_CELLS]; }; } From dd258a79753bac031d5223eaf4e8776a4cc05159 Mon Sep 17 00:00:00 2001 From: Fournier Gabriel <> Date: Mon, 16 Nov 2020 23:37:45 +0100 Subject: [PATCH 004/355] Lister des fichiers --- apps/reader/Makefile | 1 + apps/reader/list_book_controller.cpp | 16 +++++++++++++--- apps/reader/utility.cpp | 27 +++++++++++++++++++++++++++ apps/reader/utility.h | 10 ++++++++++ 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 apps/reader/utility.cpp create mode 100644 apps/reader/utility.h diff --git a/apps/reader/Makefile b/apps/reader/Makefile index a3ddd6cd8e3..0d5267e5fd7 100644 --- a/apps/reader/Makefile +++ b/apps/reader/Makefile @@ -4,6 +4,7 @@ app_headers += apps/reader/app.h app_sreader_src = $(addprefix apps/reader/,\ app.cpp \ list_book_controller.cpp \ + utility.cpp \ ) apps_src += $(app_sreader_src) diff --git a/apps/reader/list_book_controller.cpp b/apps/reader/list_book_controller.cpp index d785f6e5b6d..150d46bffea 100644 --- a/apps/reader/list_book_controller.cpp +++ b/apps/reader/list_book_controller.cpp @@ -1,5 +1,5 @@ #include "list_book_controller.h" - +#include "utility.h" namespace reader { @@ -12,8 +12,18 @@ ListBookController::ListBookController(Responder * parentResponder): ViewController(parentResponder), m_tableView(this, this) { - m_files[0].name= "Harry Potter 1.txt"; - m_nbFiles = 1; + size_t nbTotalFiles = External::Archive::numberOfFiles(); + + for(size_t i=0; i < nbTotalFiles; ++i) + { + External::Archive::File file; + External::Archive::fileAtIndex(i, file); + if(stringEndsWith(file.name, ".txt")) + { + m_files[m_nbFiles] = file; + m_nbFiles++; + } + } } int ListBookController::numberOfRows() const diff --git a/apps/reader/utility.cpp b/apps/reader/utility.cpp new file mode 100644 index 00000000000..40eceaea7ac --- /dev/null +++ b/apps/reader/utility.cpp @@ -0,0 +1,27 @@ +#include "utility.h" +#include + +namespace reader +{ + +bool stringEndsWith(const char* str, const char* pattern) +{ + int strLength = strlen(str); + int patternLength = strlen(pattern); + if (patternLength > strLength) + return false; + + const char* strIter = str + strlen(str); + const char* patternIter = pattern + strlen(pattern); + + while(*strIter == *patternIter) + { + if(patternIter == pattern) + return true; + strIter--; + patternIter--; + } + return false; +} + +} \ No newline at end of file diff --git a/apps/reader/utility.h b/apps/reader/utility.h new file mode 100644 index 00000000000..b4d41761894 --- /dev/null +++ b/apps/reader/utility.h @@ -0,0 +1,10 @@ +#ifndef __UTILITY_H__ +#define __UTILITY_H__ + +namespace reader +{ + +bool stringEndsWith(const char* str, const char* end); + +} +#endif \ No newline at end of file From aacd1a6f32c673e9b5cb97608f6c26ba02708e6d Mon Sep 17 00:00:00 2001 From: Fournier Gabriel <> Date: Mon, 16 Nov 2020 23:37:45 +0100 Subject: [PATCH 005/355] Lister des fichiers --- apps/reader/Makefile | 1 + apps/reader/list_book_controller.cpp | 18 +++++++++++++++--- apps/reader/utility.cpp | 27 +++++++++++++++++++++++++++ apps/reader/utility.h | 10 ++++++++++ 4 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 apps/reader/utility.cpp create mode 100644 apps/reader/utility.h diff --git a/apps/reader/Makefile b/apps/reader/Makefile index a3ddd6cd8e3..0d5267e5fd7 100644 --- a/apps/reader/Makefile +++ b/apps/reader/Makefile @@ -4,6 +4,7 @@ app_headers += apps/reader/app.h app_sreader_src = $(addprefix apps/reader/,\ app.cpp \ list_book_controller.cpp \ + utility.cpp \ ) apps_src += $(app_sreader_src) diff --git a/apps/reader/list_book_controller.cpp b/apps/reader/list_book_controller.cpp index d785f6e5b6d..4818df4f397 100644 --- a/apps/reader/list_book_controller.cpp +++ b/apps/reader/list_book_controller.cpp @@ -1,5 +1,5 @@ #include "list_book_controller.h" - +#include "utility.h" namespace reader { @@ -12,8 +12,20 @@ ListBookController::ListBookController(Responder * parentResponder): ViewController(parentResponder), m_tableView(this, this) { - m_files[0].name= "Harry Potter 1.txt"; - m_nbFiles = 1; + size_t nbTotalFiles = External::Archive::numberOfFiles(); + + for(size_t i=0; i < nbTotalFiles; ++i) + { + External::Archive::File file; + External::Archive::fileAtIndex(i, file); + if(stringEndsWith(file.name, ".txt")) + { + m_files[m_nbFiles] = file; + m_nbFiles++; + if(m_nbFiles == NB_FILES) + break; + } + } } int ListBookController::numberOfRows() const diff --git a/apps/reader/utility.cpp b/apps/reader/utility.cpp new file mode 100644 index 00000000000..40eceaea7ac --- /dev/null +++ b/apps/reader/utility.cpp @@ -0,0 +1,27 @@ +#include "utility.h" +#include + +namespace reader +{ + +bool stringEndsWith(const char* str, const char* pattern) +{ + int strLength = strlen(str); + int patternLength = strlen(pattern); + if (patternLength > strLength) + return false; + + const char* strIter = str + strlen(str); + const char* patternIter = pattern + strlen(pattern); + + while(*strIter == *patternIter) + { + if(patternIter == pattern) + return true; + strIter--; + patternIter--; + } + return false; +} + +} \ No newline at end of file diff --git a/apps/reader/utility.h b/apps/reader/utility.h new file mode 100644 index 00000000000..b4d41761894 --- /dev/null +++ b/apps/reader/utility.h @@ -0,0 +1,10 @@ +#ifndef __UTILITY_H__ +#define __UTILITY_H__ + +namespace reader +{ + +bool stringEndsWith(const char* str, const char* end); + +} +#endif \ No newline at end of file From 76beed20da3e12ae26fe7503d045513691369df9 Mon Sep 17 00:00:00 2001 From: Fournier Gabriel <> Date: Tue, 17 Nov 2020 00:04:15 +0100 Subject: [PATCH 006/355] fix nb_Files limit --- apps/reader/list_book_controller.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/reader/list_book_controller.cpp b/apps/reader/list_book_controller.cpp index 150d46bffea..4818df4f397 100644 --- a/apps/reader/list_book_controller.cpp +++ b/apps/reader/list_book_controller.cpp @@ -22,6 +22,8 @@ ListBookController::ListBookController(Responder * parentResponder): { m_files[m_nbFiles] = file; m_nbFiles++; + if(m_nbFiles == NB_FILES) + break; } } } From 5b21c57e4c412eb7d73353898746752206e3f606 Mon Sep 17 00:00:00 2001 From: Fournier Gabriel <> Date: Tue, 17 Nov 2020 22:44:35 +0100 Subject: [PATCH 007/355] list files on simulator --- apps/reader/list_book_controller.cpp | 15 +------- apps/reader/utility.cpp | 56 +++++++++++++++++++++++++++- apps/reader/utility.h | 3 ++ 3 files changed, 59 insertions(+), 15 deletions(-) diff --git a/apps/reader/list_book_controller.cpp b/apps/reader/list_book_controller.cpp index 4818df4f397..bbc327f1696 100644 --- a/apps/reader/list_book_controller.cpp +++ b/apps/reader/list_book_controller.cpp @@ -12,20 +12,7 @@ ListBookController::ListBookController(Responder * parentResponder): ViewController(parentResponder), m_tableView(this, this) { - size_t nbTotalFiles = External::Archive::numberOfFiles(); - - for(size_t i=0; i < nbTotalFiles; ++i) - { - External::Archive::File file; - External::Archive::fileAtIndex(i, file); - if(stringEndsWith(file.name, ".txt")) - { - m_files[m_nbFiles] = file; - m_nbFiles++; - if(m_nbFiles == NB_FILES) - break; - } - } + m_nbFiles = filesWithExtension(".txt", m_files, NB_FILES); } int ListBookController::numberOfRows() const diff --git a/apps/reader/utility.cpp b/apps/reader/utility.cpp index 40eceaea7ac..7a9779909c9 100644 --- a/apps/reader/utility.cpp +++ b/apps/reader/utility.cpp @@ -1,6 +1,13 @@ #include "utility.h" + #include +#ifndef DEVICE +#include +#include +#include +#endif + namespace reader { @@ -10,7 +17,7 @@ bool stringEndsWith(const char* str, const char* pattern) int patternLength = strlen(pattern); if (patternLength > strLength) return false; - + const char* strIter = str + strlen(str); const char* patternIter = pattern + strlen(pattern); @@ -24,4 +31,51 @@ bool stringEndsWith(const char* str, const char* pattern) return false; } + +#ifdef DEVICE + +int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize) +{ + size_t nbTotalFiles = External::Archive::numberOfFiles(); + int nbFiles = 0; + for(size_t i=0; i < nbTotalFiles; ++i) + { + External::Archive::File file; + External::Archive::fileAtIndex(i, file); + if(stringEndsWith(file.name, ".txt")) + { + files[nbFiles] = file; + nbFiles++; + if(nbFiles == filesSize) + break; + } + } + return nbFiles; +} +#else + +int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize) +{ + dirent *file; + DIR *d = opendir("."); + int nb = 0; + if (d) + { + while ((file = readdir(d)) != NULL) + { + if(stringEndsWith(dir->d_name, extension)) + { + files[nb].name = strdup(file->d_name);//will probably leak + nb++; + if(nb == filesSize) + break; + } + } + closedir(d); + } + return nb; +} + +#endif + } \ No newline at end of file diff --git a/apps/reader/utility.h b/apps/reader/utility.h index b4d41761894..9cd45500834 100644 --- a/apps/reader/utility.h +++ b/apps/reader/utility.h @@ -1,10 +1,13 @@ #ifndef __UTILITY_H__ #define __UTILITY_H__ +#include + namespace reader { bool stringEndsWith(const char* str, const char* end); +int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize) ; } #endif \ No newline at end of file From 3e2b5178edf62861b1bf0e84ea21e87d52400198 Mon Sep 17 00:00:00 2001 From: Fournier Gabriel <> Date: Sat, 21 Nov 2020 19:29:46 +0100 Subject: [PATCH 008/355] word wrap - part 1 and 2 --- apps/reader/Makefile | 2 + apps/reader/app.cpp | 5 +- apps/reader/app.h | 1 + apps/reader/base.de.i18n | 3 +- apps/reader/base.en.i18n | 3 +- apps/reader/base.es.i18n | 3 +- apps/reader/base.fr.i18n | 3 +- apps/reader/base.hu.i18n | 3 +- apps/reader/base.it.i18n | 3 +- apps/reader/base.nl.i18n | 3 +- apps/reader/base.pt.i18n | 3 +- apps/reader/list_book_controller.cpp | 31 ++++++++++++- apps/reader/list_book_controller.h | 10 +++- apps/reader/normalize.py | 18 ++++++++ apps/reader/read_book_controller.cpp | 21 +++++++++ apps/reader/read_book_controller.h | 23 ++++++++++ apps/reader/utility.cpp | 45 +++++++++++++++++- apps/reader/utility.h | 1 + apps/reader/word_wrap_view.cpp | 68 ++++++++++++++++++++++++++++ apps/reader/word_wrap_view.h | 20 ++++++++ 20 files changed, 254 insertions(+), 15 deletions(-) create mode 100644 apps/reader/normalize.py create mode 100644 apps/reader/read_book_controller.cpp create mode 100644 apps/reader/read_book_controller.h create mode 100644 apps/reader/word_wrap_view.cpp create mode 100644 apps/reader/word_wrap_view.h diff --git a/apps/reader/Makefile b/apps/reader/Makefile index 0d5267e5fd7..71adec9e03b 100644 --- a/apps/reader/Makefile +++ b/apps/reader/Makefile @@ -5,6 +5,8 @@ app_sreader_src = $(addprefix apps/reader/,\ app.cpp \ list_book_controller.cpp \ utility.cpp \ + read_book_controller \ + word_wrap_view.cpp \ ) apps_src += $(app_sreader_src) diff --git a/apps/reader/app.cpp b/apps/reader/app.cpp index 68ca4111501..18ae33da03a 100644 --- a/apps/reader/app.cpp +++ b/apps/reader/app.cpp @@ -30,8 +30,9 @@ App::Descriptor * App::Snapshot::descriptor() { App::App(Snapshot * snapshot) : - ::App(snapshot, &m_listBookController), - m_listBookController(nullptr) + ::App(snapshot, &m_stackViewController), + m_listBookController(&m_stackViewController), + m_stackViewController(nullptr, &m_listBookController) { } diff --git a/apps/reader/app.h b/apps/reader/app.h index d2f4fbc1d56..1204862c4b0 100644 --- a/apps/reader/app.h +++ b/apps/reader/app.h @@ -22,6 +22,7 @@ class App : public ::App { private: App(Snapshot * snapshot); ListBookController m_listBookController; + StackViewController m_stackViewController; }; } diff --git a/apps/reader/base.de.i18n b/apps/reader/base.de.i18n index ecc03a74956..68a64986da6 100644 --- a/apps/reader/base.de.i18n +++ b/apps/reader/base.de.i18n @@ -1,2 +1,3 @@ ReaderApp = "Reader" -ReaderAppCapital = "READER" \ No newline at end of file +ReaderAppCapital = "READER" +NoFileToDisplay = "Keine Dateien zum Anzeigen!" \ No newline at end of file diff --git a/apps/reader/base.en.i18n b/apps/reader/base.en.i18n index ecc03a74956..0101ebe2aad 100644 --- a/apps/reader/base.en.i18n +++ b/apps/reader/base.en.i18n @@ -1,2 +1,3 @@ ReaderApp = "Reader" -ReaderAppCapital = "READER" \ No newline at end of file +ReaderAppCapital = "READER" +NoFileToDisplay = "No file to display!" \ No newline at end of file diff --git a/apps/reader/base.es.i18n b/apps/reader/base.es.i18n index ecc03a74956..51be1eaf458 100644 --- a/apps/reader/base.es.i18n +++ b/apps/reader/base.es.i18n @@ -1,2 +1,3 @@ ReaderApp = "Reader" -ReaderAppCapital = "READER" \ No newline at end of file +ReaderAppCapital = "READER" +NoFileToDisplay ="No hay archivos para mostrar!" \ No newline at end of file diff --git a/apps/reader/base.fr.i18n b/apps/reader/base.fr.i18n index ecc03a74956..220c36fa1f5 100644 --- a/apps/reader/base.fr.i18n +++ b/apps/reader/base.fr.i18n @@ -1,2 +1,3 @@ ReaderApp = "Reader" -ReaderAppCapital = "READER" \ No newline at end of file +ReaderAppCapital = "READER" +NoFileToDisplay = "Aucun fichier à afficher!" \ No newline at end of file diff --git a/apps/reader/base.hu.i18n b/apps/reader/base.hu.i18n index ecc03a74956..264d6e8784a 100644 --- a/apps/reader/base.hu.i18n +++ b/apps/reader/base.hu.i18n @@ -1,2 +1,3 @@ ReaderApp = "Reader" -ReaderAppCapital = "READER" \ No newline at end of file +ReaderAppCapital = "READER" +NoFileToDisplay ="Nincs megjeleníthető fájl" \ No newline at end of file diff --git a/apps/reader/base.it.i18n b/apps/reader/base.it.i18n index ecc03a74956..4464ff0771b 100644 --- a/apps/reader/base.it.i18n +++ b/apps/reader/base.it.i18n @@ -1,2 +1,3 @@ ReaderApp = "Reader" -ReaderAppCapital = "READER" \ No newline at end of file +ReaderAppCapital = "READER" +NoFileToDisplay ="essun file da visualizzare" \ No newline at end of file diff --git a/apps/reader/base.nl.i18n b/apps/reader/base.nl.i18n index ecc03a74956..55693de3e16 100644 --- a/apps/reader/base.nl.i18n +++ b/apps/reader/base.nl.i18n @@ -1,2 +1,3 @@ ReaderApp = "Reader" -ReaderAppCapital = "READER" \ No newline at end of file +ReaderAppCapital = "READER" +NoFileToDisplay ="Geen bestanden om weer te geven" \ No newline at end of file diff --git a/apps/reader/base.pt.i18n b/apps/reader/base.pt.i18n index ecc03a74956..886d4930cbe 100644 --- a/apps/reader/base.pt.i18n +++ b/apps/reader/base.pt.i18n @@ -1,2 +1,3 @@ ReaderApp = "Reader" -ReaderAppCapital = "READER" \ No newline at end of file +ReaderAppCapital = "READER" +NoFileToDisplay ="Nenhum arquivo para exibir" \ No newline at end of file diff --git a/apps/reader/list_book_controller.cpp b/apps/reader/list_book_controller.cpp index bbc327f1696..a963ad0ba0e 100644 --- a/apps/reader/list_book_controller.cpp +++ b/apps/reader/list_book_controller.cpp @@ -1,5 +1,7 @@ #include "list_book_controller.h" #include "utility.h" +#include "apps/i18n.h" + namespace reader { @@ -10,7 +12,8 @@ View* ListBookController::view() ListBookController::ListBookController(Responder * parentResponder): ViewController(parentResponder), - m_tableView(this, this) + m_tableView(this, this, this), + m_readBookController(this) { m_nbFiles = filesWithExtension(".txt", m_files, NB_FILES); } @@ -43,4 +46,30 @@ void ListBookController::willDisplayCellForIndex(HighlightCell * cell, int index myTextCell->setMessageFont(KDFont::LargeFont); } +void ListBookController::didBecomeFirstResponder() +{ + if (selectedRow() < 0) { + selectCellAtLocation(0, 0); + } + Container::activeApp()->setFirstResponder(&m_tableView); + if(m_nbFiles == 0) + { + Container::activeApp()->displayWarning(I18n::Message::NoFileToDisplay); + } +} + +bool ListBookController::handleEvent(Ion::Events::Event event) +{ + if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) + { + + m_readBookController.setBook(m_files[selectedRow()]); + static_cast(parentResponder())->push(&m_readBookController); + Container::activeApp()->setFirstResponder(&m_readBookController); + return true; + } + + return false; +} + } \ No newline at end of file diff --git a/apps/reader/list_book_controller.h b/apps/reader/list_book_controller.h index 4c5050c9840..9fdd11262a9 100644 --- a/apps/reader/list_book_controller.h +++ b/apps/reader/list_book_controller.h @@ -4,10 +4,12 @@ #include #include +#include "read_book_controller.h" + namespace reader { -class ListBookController : public ViewController, public SimpleListViewDataSource, public ScrollViewDataSource +class ListBookController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource { public: ListBookController(Responder * parentResponder); @@ -18,8 +20,10 @@ class ListBookController : public ViewController, public SimpleListViewDataSourc HighlightCell * reusableCell(int index) override; int reusableCellCount() const override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; + void didBecomeFirstResponder() override; + bool handleEvent(Ion::Events::Event event) override; private: - TableView m_tableView; + SelectableTableView m_tableView; static const int NB_FILES = 20; External::Archive::File m_files[NB_FILES]; @@ -27,6 +31,8 @@ class ListBookController : public ViewController, public SimpleListViewDataSourc static const int NB_CELLS = 6; MessageTableCell m_cells[NB_CELLS]; + + ReadBookController m_readBookController; }; } diff --git a/apps/reader/normalize.py b/apps/reader/normalize.py new file mode 100644 index 00000000000..fff638b333f --- /dev/null +++ b/apps/reader/normalize.py @@ -0,0 +1,18 @@ +import sys +import unicodedata +import argparse +import io +import shutil + +filename = sys.argv[1] + +print("Normalization of "+filename) + +output = open(filename+".tmp", "wb") + +with io.open(filename, "r", encoding='utf-8') as file: + for line in file: + unicodeLine = unicodedata.normalize("NFKD", line) + output.write(unicodeLine.encode("UTF-8")) +output.close() +shutil.move(filename+".tmp",filename) diff --git a/apps/reader/read_book_controller.cpp b/apps/reader/read_book_controller.cpp new file mode 100644 index 00000000000..75288029fd2 --- /dev/null +++ b/apps/reader/read_book_controller.cpp @@ -0,0 +1,21 @@ +#include "read_book_controller.h" + +namespace reader +{ + +ReadBookController::ReadBookController(Responder * parentResponder) : + ViewController(parentResponder) +{ +} + +View * ReadBookController::view() +{ + return &m_readerView; +} + +void ReadBookController::setBook(const External::Archive::File& file) +{ + m_readerView.setText(reinterpret_cast(file.data)); +} + +} \ No newline at end of file diff --git a/apps/reader/read_book_controller.h b/apps/reader/read_book_controller.h new file mode 100644 index 00000000000..3c734f1a47b --- /dev/null +++ b/apps/reader/read_book_controller.h @@ -0,0 +1,23 @@ +#ifndef _READ_BOOK_CONTROLLER_H_ +#define _READ_BOOK_CONTROLLER_H_ + +#include +#include "apps/external/archive.h" +#include "word_wrap_view.h" + +namespace reader { + +class ReadBookController : public ViewController { +public: + ReadBookController(Responder * parentResponder); + View * view() override; + + void setBook(const External::Archive::File& file); + +private: + WordWrapTextView m_readerView; +}; + +} + +#endif \ No newline at end of file diff --git a/apps/reader/utility.cpp b/apps/reader/utility.cpp index 7a9779909c9..2f82b61f38c 100644 --- a/apps/reader/utility.cpp +++ b/apps/reader/utility.cpp @@ -31,6 +31,19 @@ bool stringEndsWith(const char* str, const char* pattern) return false; } +void stringNCopy(char* dest, int max, const char* src, int len) +{ + while(len>0 && max >1 && *src) + { + *dest = *src; + dest++; + src++; + len--; + max--; + } + *dest=0; +} + #ifdef DEVICE @@ -54,6 +67,34 @@ int filesWithExtension(const char* extension, External::Archive::File* files, in } #else +static void fillFileData(External::Archive::File& file) +{ + file.data = nullptr; + file.dataLength = 0; + + struct stat info; + if (stat(file.name, &info) != 0) + { + return; + } + + unsigned char* content = new unsigned char[info.st_size]; + if (content == NULL) + { + return ; + } + FILE *fp = fopen(file.name, "rb"); + if (fp == NULL) + { + return ; + } + + fread(content, info.st_size, 1, fp); + fclose(fp); + file.data = content; + file.dataLength = info.st_size; +} + int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize) { dirent *file; @@ -63,9 +104,10 @@ int filesWithExtension(const char* extension, External::Archive::File* files, in { while ((file = readdir(d)) != NULL) { - if(stringEndsWith(dir->d_name, extension)) + if(stringEndsWith(file->d_name, extension)) { files[nb].name = strdup(file->d_name);//will probably leak + fillFileData(files[nb]); nb++; if(nb == filesSize) break; @@ -75,7 +117,6 @@ int filesWithExtension(const char* extension, External::Archive::File* files, in } return nb; } - #endif } \ No newline at end of file diff --git a/apps/reader/utility.h b/apps/reader/utility.h index 9cd45500834..6893ab95c1d 100644 --- a/apps/reader/utility.h +++ b/apps/reader/utility.h @@ -8,6 +8,7 @@ namespace reader bool stringEndsWith(const char* str, const char* end); int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize) ; +void stringNCopy(char* dest, int max, const char* src, int len); } #endif \ No newline at end of file diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp new file mode 100644 index 00000000000..f0f673a1a2a --- /dev/null +++ b/apps/reader/word_wrap_view.cpp @@ -0,0 +1,68 @@ +#include "word_wrap_view.h" + +#include "utility.h" + +#include + +namespace reader +{ +void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const +{ + ctx->fillRect(KDRect(0, 0, bounds().width(), bounds().height()), m_backgroundColor); + + + const char * current = text(); + const char * startOfWord = current; + const char * endOfWord = UTF8Helper::EndOfWord(startOfWord); + KDPoint textPosition(0, 0); + + const int wordMaxLength = 128; + char word[wordMaxLength]; + + const int spaceWidth = m_font->stringSize(" ").width(); + + while(*startOfWord != 0) + { + + KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + KDPoint nextTextPosition = KDPoint(textPosition.x()+textSize.width(), textPosition.y()); + + if(nextTextPosition.x() > m_frame.width()) + { + textPosition = KDPoint(0, textPosition.y() + textSize.height()); + nextTextPosition = KDPoint(textSize.width(), textPosition.y()); + } + + stringNCopy(word, wordMaxLength, startOfWord, endOfWord-startOfWord); + ctx->drawString(word, textPosition, m_font, m_textColor, m_backgroundColor); + + while(*endOfWord == ' ') + { + nextTextPosition = KDPoint(nextTextPosition.x() + spaceWidth, nextTextPosition.y()); + ++endOfWord; + } + if(nextTextPosition.x() > m_frame.width()) + { + nextTextPosition = KDPoint(0, nextTextPosition.y() + textSize.height()); + } + + while(*endOfWord == '\n') + { + nextTextPosition = KDPoint(0, nextTextPosition.y() + textSize.height()); + ++endOfWord; + } + + if(nextTextPosition.y() + textSize.height() > m_frame.height()) + { + break; + } + + textPosition = nextTextPosition; + startOfWord = endOfWord; + endOfWord = UTF8Helper::EndOfWord(startOfWord); + + std::cout< + +namespace reader +{ + +class WordWrapTextView : public PointerTextView { +public: + + void drawRect(KDContext * ctx, KDRect rect) const override; + +protected: + +}; + +} + +#endif \ No newline at end of file From 92ee632ce20a4838dd9d27bfaaeca755055964b8 Mon Sep 17 00:00:00 2001 From: Fournier Gabriel <> Date: Sat, 5 Dec 2020 22:24:13 +0100 Subject: [PATCH 009/355] word wrapping --- apps/reader/read_book_controller.cpp | 17 +++- apps/reader/read_book_controller.h | 2 +- apps/reader/word_wrap_view.cpp | 111 +++++++++++++++++++++++---- apps/reader/word_wrap_view.h | 14 +++- 4 files changed, 123 insertions(+), 21 deletions(-) diff --git a/apps/reader/read_book_controller.cpp b/apps/reader/read_book_controller.cpp index 75288029fd2..522491bfb46 100644 --- a/apps/reader/read_book_controller.cpp +++ b/apps/reader/read_book_controller.cpp @@ -15,7 +15,22 @@ View * ReadBookController::view() void ReadBookController::setBook(const External::Archive::File& file) { - m_readerView.setText(reinterpret_cast(file.data)); + m_readerView.setText(reinterpret_cast(file.data), file.dataLength); +} + +bool ReadBookController::handleEvent(Ion::Events::Event event) +{ + if(event == Ion::Events::Down) + { + m_readerView.nextPage(); + return true; + } + if(event == Ion::Events::Up) + { + m_readerView.previousPage(); + return true; + } + return false; } } \ No newline at end of file diff --git a/apps/reader/read_book_controller.h b/apps/reader/read_book_controller.h index 3c734f1a47b..d83be7f6ec8 100644 --- a/apps/reader/read_book_controller.h +++ b/apps/reader/read_book_controller.h @@ -13,7 +13,7 @@ class ReadBookController : public ViewController { View * view() override; void setBook(const External::Archive::File& file); - + bool handleEvent(Ion::Events::Event event) override; private: WordWrapTextView m_readerView; }; diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index f0f673a1a2a..9f4ed284ff9 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -6,33 +6,115 @@ namespace reader { + +void WordWrapTextView::nextPage() +{ + if(m_nextPageOffset >= m_length) + return; + m_pageOffset = m_nextPageOffset; + markRectAsDirty(bounds()); +} + +void WordWrapTextView::setText(const char* text, int length) +{ + PointerTextView::setText(text); + m_length = length; +} + +void WordWrapTextView::previousPage() +{ + if(m_pageOffset <= 0) + return; + + const int spaceWidth = m_font->stringSize(" ").width(); + + const char * endOfWord = text() + m_pageOffset; + const char * startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); + + KDPoint textPosition(m_frame.width() - margin, m_frame.height() - margin); + + while(startOfWord>=text()) + { + endOfWord = UTF8Helper::EndOfWord(startOfWord); + KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + KDPoint previousTextPosition = KDPoint(textPosition.x()-textSize.width(), textPosition.y()); + + if(previousTextPosition.x() < margin) + { + std::cout<<"|||\n"<= text() && *startOfWord == ' ') + { + previousTextPosition = KDPoint(previousTextPosition.x() - spaceWidth, previousTextPosition.y()); + --startOfWord; + } + if(previousTextPosition.x() < margin) + { + previousTextPosition = KDPoint(m_frame.width() - margin, previousTextPosition.y() - textSize.height()); + } + + + while(startOfWord >= text() && *startOfWord == '\n') + { + previousTextPosition = KDPoint(m_frame.width() - margin, previousTextPosition.y() - textSize.height()); + --startOfWord; + } + + if(previousTextPosition.y() - textSize.height() < margin) + { + break; + } + + textPosition = previousTextPosition; + endOfWord = startOfWord; + startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); + } + if(startOfWord == text()) + m_pageOffset = 0; + else + m_pageOffset = UTF8Helper::EndOfWord(startOfWord) - text() + 1; + markRectAsDirty(bounds()); +} + void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(KDRect(0, 0, bounds().width(), bounds().height()), m_backgroundColor); - - const char * current = text(); - const char * startOfWord = current; + const char * endOfFile = text() + m_length; + const char * startOfWord = text() + m_pageOffset; const char * endOfWord = UTF8Helper::EndOfWord(startOfWord); - KDPoint textPosition(0, 0); + KDPoint textPosition(margin, margin); const int wordMaxLength = 128; char word[wordMaxLength]; const int spaceWidth = m_font->stringSize(" ").width(); - while(*startOfWord != 0) + while(startOfWord < endOfFile) { KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); KDPoint nextTextPosition = KDPoint(textPosition.x()+textSize.width(), textPosition.y()); - if(nextTextPosition.x() > m_frame.width()) + if(nextTextPosition.x() > m_frame.width() - margin) { - textPosition = KDPoint(0, textPosition.y() + textSize.height()); - nextTextPosition = KDPoint(textSize.width(), textPosition.y()); + textPosition = KDPoint(margin, textPosition.y() + textSize.height()); + nextTextPosition = KDPoint(margin + textSize.width(), textPosition.y()); } - + if(textPosition.y() + textSize.height() > m_frame.height() - margin) + { + break; + } + stringNCopy(word, wordMaxLength, startOfWord, endOfWord-startOfWord); ctx->drawString(word, textPosition, m_font, m_textColor, m_backgroundColor); @@ -41,18 +123,18 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const nextTextPosition = KDPoint(nextTextPosition.x() + spaceWidth, nextTextPosition.y()); ++endOfWord; } - if(nextTextPosition.x() > m_frame.width()) + if(nextTextPosition.x() > m_frame.width() - margin) { - nextTextPosition = KDPoint(0, nextTextPosition.y() + textSize.height()); + nextTextPosition = KDPoint(margin, nextTextPosition.y() + textSize.height()); } while(*endOfWord == '\n') { - nextTextPosition = KDPoint(0, nextTextPosition.y() + textSize.height()); + nextTextPosition = KDPoint(margin, nextTextPosition.y() + textSize.height()); ++endOfWord; } - if(nextTextPosition.y() + textSize.height() > m_frame.height()) + if(nextTextPosition.y() + textSize.height() > m_frame.height() - margin) { break; } @@ -60,9 +142,8 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const textPosition = nextTextPosition; startOfWord = endOfWord; endOfWord = UTF8Helper::EndOfWord(startOfWord); - - std::cout< Date: Sun, 13 Dec 2020 21:55:03 +0100 Subject: [PATCH 010/355] saving --- apps/reader/list_book_controller.cpp | 27 +++++++++++++++++++++++ apps/reader/list_book_controller.h | 2 ++ apps/reader/read_book_controller.cpp | 33 ++++++++++++++++++++++++++++ apps/reader/read_book_controller.h | 4 ++++ apps/reader/word_wrap_view.cpp | 10 +++++++++ apps/reader/word_wrap_view.h | 3 ++- 6 files changed, 78 insertions(+), 1 deletion(-) diff --git a/apps/reader/list_book_controller.cpp b/apps/reader/list_book_controller.cpp index a963ad0ba0e..d2635129c69 100644 --- a/apps/reader/list_book_controller.cpp +++ b/apps/reader/list_book_controller.cpp @@ -16,6 +16,7 @@ ListBookController::ListBookController(Responder * parentResponder): m_readBookController(this) { m_nbFiles = filesWithExtension(".txt", m_files, NB_FILES); + cleanRemovedBookRecord(); } int ListBookController::numberOfRows() const @@ -72,4 +73,30 @@ bool ListBookController::handleEvent(Ion::Events::Event event) return false; } + +bool ListBookController::hasBook(const char* filename) const +{ + for(int i=0;inumberOfRecordsWithExtension("txt"); + for(int i=0; irecordWithExtensionAtIndex("txt", i); + if(!hasBook(r.fullName())) + { + r.destroy(); + } + } +} + } \ No newline at end of file diff --git a/apps/reader/list_book_controller.h b/apps/reader/list_book_controller.h index 9fdd11262a9..82eecf20717 100644 --- a/apps/reader/list_book_controller.h +++ b/apps/reader/list_book_controller.h @@ -22,6 +22,8 @@ class ListBookController : public ViewController, public SimpleListViewDataSourc void willDisplayCellForIndex(HighlightCell * cell, int index) override; void didBecomeFirstResponder() override; bool handleEvent(Ion::Events::Event event) override; + bool hasBook(const char* filename) const; + void cleanRemovedBookRecord(); private: SelectableTableView m_tableView; diff --git a/apps/reader/read_book_controller.cpp b/apps/reader/read_book_controller.cpp index 522491bfb46..7bdd9380e5f 100644 --- a/apps/reader/read_book_controller.cpp +++ b/apps/reader/read_book_controller.cpp @@ -15,6 +15,8 @@ View * ReadBookController::view() void ReadBookController::setBook(const External::Archive::File& file) { + m_file = &file; + loadPosition(); m_readerView.setText(reinterpret_cast(file.data), file.dataLength); } @@ -30,7 +32,38 @@ bool ReadBookController::handleEvent(Ion::Events::Event event) m_readerView.previousPage(); return true; } + if(event == Ion::Events::Back || event == Ion::Events::Home) + { + savePosition(); + } return false; } +void ReadBookController::savePosition() const +{ + int pageOffset = m_readerView.getPageOffset(); + Ion::Storage::Record::ErrorStatus status = Ion::Storage::sharedStorage()->createRecordWithFullName(m_file->name, &pageOffset, sizeof(pageOffset)); + if(Ion::Storage::Record::ErrorStatus::NameTaken == status) + { + Ion::Storage::Record::Data data; + data.buffer = &pageOffset; + data.size = sizeof(pageOffset); + status = Ion::Storage::sharedStorage()->recordNamed(m_file->name).setValue(data); + } +} + +void ReadBookController::loadPosition() +{ + Ion::Storage::Record r = Ion::Storage::sharedStorage()->recordNamed(m_file->name); + if(Ion::Storage::sharedStorage()->hasRecord(r)) + { + int pageOffset = *(static_cast(r.value().buffer)); + m_readerView.setPageOffset(pageOffset); + } + else + { + m_readerView.setPageOffset(0); + } +} + } \ No newline at end of file diff --git a/apps/reader/read_book_controller.h b/apps/reader/read_book_controller.h index d83be7f6ec8..c4349be4e0d 100644 --- a/apps/reader/read_book_controller.h +++ b/apps/reader/read_book_controller.h @@ -14,8 +14,12 @@ class ReadBookController : public ViewController { void setBook(const External::Archive::File& file); bool handleEvent(Ion::Events::Event event) override; + + void savePosition() const; + void loadPosition(); private: WordWrapTextView m_readerView; + const External::Archive::File* m_file; }; } diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index 9f4ed284ff9..610b5712839 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -146,4 +146,14 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const m_nextPageOffset = startOfWord - text(); } +int WordWrapTextView::getPageOffset() const +{ + return m_pageOffset; +} + +void WordWrapTextView::setPageOffset(int o) +{ + m_pageOffset = o; +} + } \ No newline at end of file diff --git a/apps/reader/word_wrap_view.h b/apps/reader/word_wrap_view.h index d71fcb15e46..4075ff4e229 100644 --- a/apps/reader/word_wrap_view.h +++ b/apps/reader/word_wrap_view.h @@ -12,7 +12,8 @@ class WordWrapTextView : public PointerTextView { void setText(const char*, int length); void nextPage(); void previousPage(); - + int getPageOffset() const; + void setPageOffset(int o); protected: int m_pageOffset = 0; mutable int m_nextPageOffset = 0; From 890592854a71abe9a7e691dc3da3a007007f3298 Mon Sep 17 00:00:00 2001 From: Fournier Gabriel <> Date: Sun, 13 Dec 2020 22:13:33 +0100 Subject: [PATCH 011/355] fix for device --- apps/reader/word_wrap_view.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index 610b5712839..9e4cccd62cb 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -2,8 +2,6 @@ #include "utility.h" -#include - namespace reader { @@ -41,7 +39,6 @@ void WordWrapTextView::previousPage() if(previousTextPosition.x() < margin) { - std::cout<<"|||\n"< Date: Wed, 23 Jun 2021 16:55:37 +0200 Subject: [PATCH 012/355] Add a new entry in about menu to see current voltage - Add a new traduction for this entry - Add the entry in submenu --- apps/settings/base.de.i18n | 1 + apps/settings/base.en.i18n | 1 + apps/settings/base.es.i18n | 1 + apps/settings/base.fr.i18n | 1 + apps/settings/base.hu.i18n | 1 + apps/settings/base.it.i18n | 1 + apps/settings/base.nl.i18n | 1 + apps/settings/base.pt.i18n | 1 + apps/settings/main_controller.cpp | 2 +- apps/settings/main_controller.h | 2 +- apps/settings/sub_menu/about_controller.cpp | 11 +++++++++++ 11 files changed, 21 insertions(+), 2 deletions(-) diff --git a/apps/settings/base.de.i18n b/apps/settings/base.de.i18n index 0f469929be3..d263f8d58cd 100644 --- a/apps/settings/base.de.i18n +++ b/apps/settings/base.de.i18n @@ -43,6 +43,7 @@ SerialNumber = "Seriennummer" UpdatePopUp = "Erinnerung: Update" BetaPopUp = "Beta pop-up" Contributors = "Beiträger" +Battery = "Batteriestatus" Accessibility = "Barrierefreiheit" AccessibilityInvertColors = "Farbumkehrung" AccessibilityMagnify = "Lupe" diff --git a/apps/settings/base.en.i18n b/apps/settings/base.en.i18n index b5d07690485..c3c8ba0014a 100644 --- a/apps/settings/base.en.i18n +++ b/apps/settings/base.en.i18n @@ -42,6 +42,7 @@ SmallFont = "Small " SerialNumber = "Serial number" UpdatePopUp = "Update pop-up" BetaPopUp = "Beta pop-up" +Battery = "Battery" Contributors = "Contributors" Accessibility = "Accessibility" AccessibilityInvertColors = "Invert colors" diff --git a/apps/settings/base.es.i18n b/apps/settings/base.es.i18n index 944709570f0..67aa27710b3 100644 --- a/apps/settings/base.es.i18n +++ b/apps/settings/base.es.i18n @@ -43,6 +43,7 @@ SerialNumber = "Número serie" UpdatePopUp = "Pop-up de actualización" BetaPopUp = "Beta pop-up" Contributors = "Contribuyentes" +Battery = "Batería" Accessibility = "Accesibilidad" AccessibilityInvertColors = "Colores invertidos" AccessibilityMagnify = "Lupa" diff --git a/apps/settings/base.fr.i18n b/apps/settings/base.fr.i18n index ac76c7dca0d..a044126ea0b 100644 --- a/apps/settings/base.fr.i18n +++ b/apps/settings/base.fr.i18n @@ -43,6 +43,7 @@ SerialNumber = "Numéro de série" UpdatePopUp = "Rappel de mise à jour" BetaPopUp = "Rappel de version bêta" Contributors = "Contributeurs" +Battery = "Batterie" Accessibility = "Accessibilité" AccessibilityInvertColors = "Inverser couleurs" AccessibilityMagnify = "Loupe" diff --git a/apps/settings/base.hu.i18n b/apps/settings/base.hu.i18n index 19ed3a67f63..938e8227788 100644 --- a/apps/settings/base.hu.i18n +++ b/apps/settings/base.hu.i18n @@ -43,6 +43,7 @@ SerialNumber = "Sorozatszám" UpdatePopUp = "Frissítés figyelmeztetés" BetaPopUp = "Béta figyelmeztetés" Contributors = "Közremüködök" +Battery = "Akkumulátor" Accessibility = "Több vizuális beállitások" AccessibilityInvertColors = "Invertált színek" AccessibilityMagnify = "Nagyító" diff --git a/apps/settings/base.it.i18n b/apps/settings/base.it.i18n index 5c4dcc0a47c..0e7896c565d 100644 --- a/apps/settings/base.it.i18n +++ b/apps/settings/base.it.i18n @@ -43,6 +43,7 @@ SerialNumber = "Numero di serie" UpdatePopUp = "Promemoria aggiornamento" BetaPopUp = "Promemoria beta" Contributors = "Contributors" +Battery = "Batteria" Accessibility = "Accessibility" AccessibilityInvertColors = "Invert colors" AccessibilityMagnify = "Magnify" diff --git a/apps/settings/base.nl.i18n b/apps/settings/base.nl.i18n index 75939d6a877..32ac2c006cd 100644 --- a/apps/settings/base.nl.i18n +++ b/apps/settings/base.nl.i18n @@ -43,6 +43,7 @@ SerialNumber = "Serienummer" UpdatePopUp = "Update pop-up" BetaPopUp = "Bèta pop-up" Contributors = "Contributors" +Battery = "Batterij" Accessibility = "Accessibility" AccessibilityInvertColors = "Invert colors" AccessibilityMagnify = "Magnify" diff --git a/apps/settings/base.pt.i18n b/apps/settings/base.pt.i18n index f33006f4ad6..a9b3788a337 100644 --- a/apps/settings/base.pt.i18n +++ b/apps/settings/base.pt.i18n @@ -43,6 +43,7 @@ SerialNumber = "Número serie" UpdatePopUp = "Alertas de atualização" BetaPopUp = "Beta pop-up" Contributors = "Contribuidores" +Battery = "Bateria" Accessibility = "Acessibilidade" AccessibilityInvertColors = "Cores invertidas" AccessibilityMagnify = "Lupa" diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index 6b65f15d3ea..e6f004dd8a7 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -20,7 +20,7 @@ constexpr SettingsMessageTree s_modelMathOptionsChildren[6] = {SettingsMessageTr constexpr SettingsMessageTree s_modelFontChildren[2] = {SettingsMessageTree(I18n::Message::LargeFont), SettingsMessageTree(I18n::Message::SmallFont)}; constexpr SettingsMessageTree s_accessibilityChildren[6] = {SettingsMessageTree(I18n::Message::AccessibilityInvertColors), SettingsMessageTree(I18n::Message::AccessibilityMagnify),SettingsMessageTree(I18n::Message::AccessibilityGamma),SettingsMessageTree(I18n::Message::AccessibilityGammaRed),SettingsMessageTree(I18n::Message::AccessibilityGammaGreen),SettingsMessageTree(I18n::Message::AccessibilityGammaBlue)}; constexpr SettingsMessageTree s_contributorsChildren[23] = {SettingsMessageTree(I18n::Message::Developers), SettingsMessageTree(I18n::Message::QuentinGuidee), SettingsMessageTree(I18n::Message::JoachimLeFournis), SettingsMessageTree(I18n::Message::MaximeFriess), SettingsMessageTree(I18n::Message::JeanBaptisteBoric), SettingsMessageTree(I18n::Message::SandraSimmons), SettingsMessageTree(I18n::Message::David), SettingsMessageTree(I18n::Message::DamienNicolet), SettingsMessageTree(I18n::Message::EvannDreumont), SettingsMessageTree(I18n::Message::SzaboLevente), SettingsMessageTree(I18n::Message::VenceslasDuet), SettingsMessageTree(I18n::Message::CharlotteThomas), SettingsMessageTree(I18n::Message::AntoninLoubiere), SettingsMessageTree(I18n::Message::CyprienMejat), SettingsMessageTree(I18n::Message::BetaTesters), SettingsMessageTree(I18n::Message::TimeoArnouts), SettingsMessageTree(I18n::Message::JulieC), SettingsMessageTree(I18n::Message::LelahelHideux), SettingsMessageTree(I18n::Message::Madil), SettingsMessageTree(I18n::Message::HilaireLeRoux), SettingsMessageTree(I18n::Message::HectorNussbaumer), SettingsMessageTree(I18n::Message::RaphaelDyda), SettingsMessageTree(I18n::Message::ThibautC)}; -constexpr SettingsMessageTree s_modelAboutChildren[8] = {SettingsMessageTree(I18n::Message::Username), SettingsMessageTree(I18n::Message::SoftwareVersion), SettingsMessageTree(I18n::Message::OmegaVersion), SettingsMessageTree(I18n::Message::MicroPythonVersion), SettingsMessageTree(I18n::Message::MemUse), SettingsMessageTree(I18n::Message::SerialNumber), SettingsMessageTree(I18n::Message::FccId), SettingsMessageTree(I18n::Message::Contributors, s_contributorsChildren)}; +constexpr SettingsMessageTree s_modelAboutChildren[9] = {SettingsMessageTree(I18n::Message::Username), SettingsMessageTree(I18n::Message::SoftwareVersion), SettingsMessageTree(I18n::Message::OmegaVersion), SettingsMessageTree(I18n::Message::MicroPythonVersion), SettingsMessageTree(I18n::Message::Battery), SettingsMessageTree(I18n::Message::MemUse), SettingsMessageTree(I18n::Message::SerialNumber), SettingsMessageTree(I18n::Message::FccId), SettingsMessageTree(I18n::Message::Contributors, s_contributorsChildren)}; MainController::MainController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate) : ViewController(parentResponder), diff --git a/apps/settings/main_controller.h b/apps/settings/main_controller.h index fd638a0cd9e..796f50bbd52 100644 --- a/apps/settings/main_controller.h +++ b/apps/settings/main_controller.h @@ -25,7 +25,7 @@ extern const Shared::SettingsMessageTree s_modelFontChildren[2]; extern const Shared::SettingsMessageTree s_modelDateTimeChildren[3]; extern const Shared::SettingsMessageTree s_accessibilityChildren[6]; extern const Shared::SettingsMessageTree s_contributorsChildren[23]; -extern const Shared::SettingsMessageTree s_modelAboutChildren[8]; +extern const Shared::SettingsMessageTree s_modelAboutChildren[9]; extern const Shared::SettingsMessageTree s_model; class MainController : public ViewController, public ListViewDataSource, public SelectableTableViewDataSource { diff --git a/apps/settings/sub_menu/about_controller.cpp b/apps/settings/sub_menu/about_controller.cpp index 220a2c39647..36ab641e69e 100644 --- a/apps/settings/sub_menu/about_controller.cpp +++ b/apps/settings/sub_menu/about_controller.cpp @@ -4,8 +4,11 @@ #include #include #include +#include #include +#include + #define MP_STRINGIFY_HELPER(x) #x #define MP_STRINGIFY(x) MP_STRINGIFY_HELPER(x) @@ -13,6 +16,8 @@ #error This file expects OMEGA_STATE to be defined #endif + +using namespace Shared; namespace Settings { AboutController::AboutController(Responder * parentResponder) : @@ -159,11 +164,17 @@ void AboutController::willDisplayCellForIndex(HighlightCell * cell, int index) { } else { MessageTableCellWithBuffer * myCell = (MessageTableCellWithBuffer *)cell; static const char * mpVersion = MICROPY_VERSION_STRING; + + static char batteryLevel[15]; + int batteryLen = Poincare::Number::FloatNumber(Ion::Battery::voltage()).serialize(batteryLevel, 15, Poincare::Preferences::PrintFloatMode::Decimal, 3); + batteryLevel[batteryLen] = 'V'; + static const char * messages[] = { (const char*) Ion::username(), Ion::softwareVersion(), Ion::omegaVersion(), mpVersion, + batteryLevel, "", Ion::serialNumber(), Ion::fccId(), From cf9142b5a293182fa1cedf7831799f4896ead752 Mon Sep 17 00:00:00 2001 From: FaustinM Date: Wed, 23 Jun 2021 16:57:42 +0200 Subject: [PATCH 013/355] Add "ion.battery()" for obtain the current voltage of the battery Ion.battery() return a float with the current battery voltage TODO : Update the translation of the toolbox (only FR and EN currently) --- apps/code/catalog.de.i18n | 1 + apps/code/catalog.en.i18n | 1 + apps/code/catalog.es.i18n | 1 + apps/code/catalog.fr.i18n | 1 + apps/code/catalog.hu.i18n | 1 + apps/code/catalog.it.i18n | 1 + apps/code/catalog.nl.i18n | 1 + apps/code/catalog.pt.i18n | 1 + apps/code/catalog.universal.i18n | 1 + apps/code/python_toolbox.cpp | 2 ++ python/port/genhdr/qstrdefs.in.h | 1 + python/port/mod/ion/modion.cpp | 4 ++++ python/port/mod/ion/modion.h | 3 ++- python/port/mod/ion/modion_table.cpp | 6 ++++++ python/port/mod/os/modos.cpp | 3 +-- 15 files changed, 25 insertions(+), 3 deletions(-) diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index 9cb321ab8e4..da3c572fbba 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -86,6 +86,7 @@ PythonIsFinite = "Prüft ob x endlich ist" PythonIsInfinite = "Prüft ob x unendlich ist" PythonIsNaN = "Prüft ob x NaN ist" PythonIsKeyDown = "true wenn k gedrückt ist" +PythonBattery = "Return battery voltage" PythonKandinskyFunction = "kandinsky module function prefix" PythonLdexp = "Return x*(2**i), inverse of frexp" PythonLength = "Length of an object" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index 819887dbbef..c3dbf902c90 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -80,6 +80,7 @@ PythonIonFunction = "ion module function prefix" PythonIsFinite = "Check if x is finite" PythonIsInfinite = "Check if x is infinity" PythonIsKeyDown = "Return True if the k key is down" +PythonBattery = "Return battery voltage" PythonIsNaN = "Check if x is a NaN" PythonKandinskyFunction = "kandinsky module function prefix" PythonLdexp = "Return x*(2**i), inverse of frexp" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index 960dd7eb9bc..147ffd829b9 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -80,6 +80,7 @@ PythonIonFunction = "ion module function prefix" PythonIsFinite = "Check if x is finite" PythonIsInfinite = "Check if x is infinity" PythonIsKeyDown = "Return True if the k key is down" +PythonBattery = "Return battery voltage" PythonIsNaN = "Check if x is a NaN" PythonKandinskyFunction = "kandinsky module function prefix" PythonLdexp = "Return x*(2**i), inverse of frexp" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index 756ef882e0f..3f756c94f77 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -80,6 +80,7 @@ PythonIonFunction = "Préfixe fonction module ion" PythonIsFinite = "Teste si x est fini" PythonIsInfinite = "Teste si x est infini" PythonIsKeyDown = "Renvoie True si touche k enfoncée" +PythonBattery = "Renvoie le voltage de la batterie" PythonIsNaN = "Teste si x est NaN" PythonKandinskyFunction = "Préfixe fonction module kandinsky" PythonLdexp = "Inverse de frexp : x*(2**i)" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index a837db91b58..d58d2acb458 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -80,6 +80,7 @@ PythonIonFunction = "ion modul funkció elötag" PythonIsFinite = "x véges-e" PythonIsInfinite = "x végtelen-e" PythonIsKeyDown = "True-t válaszol ha a k gomb le van nyomva" +PythonBattery = "Return battery voltage" PythonIsNaN = "Ellenörizze hogy x nem NaN" PythonKandinskyFunction = "kandinsky modul funkció elötag" PythonLdexp = "frexp ellentéte : x*(2**i)" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index 94eacc262f0..c4b753fc913 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -85,6 +85,7 @@ PythonIonFunction = "Prefisso di funzione modulo ion" PythonIsFinite = "Testa se x è finito" PythonIsInfinite = "Testa se x est infinito" PythonIsKeyDown = "Restituisce True premendo tasto k" +PythonBattery = "Return battery voltage" PythonIsNaN = "Testa se x è NaN" PythonKandinskyFunction = "Prefisso funzione modulo kandinsky" PythonLdexp = "Inversa di frexp : x*(2**i)" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index 1ee1f667ccb..f3b5d23a281 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -85,6 +85,7 @@ PythonIonFunction = "ion module voorvoegsel" PythonIsFinite = "Controleer of x eindig is" PythonIsInfinite = "Controleer of x oneindig is" PythonIsKeyDown = "Geef True als k toets omlaag is" +PythonBattery = "Return battery voltage" PythonIsNaN = "Controleer of x geen getal is" PythonKandinskyFunction = "kandinsky module voorvoegsel" PythonLdexp = "Geeft x*(2**i), inversie van frexp" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index a17ea0ad5c0..8698fd5b59e 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -80,6 +80,7 @@ PythonIonFunction = "Prefixo da função do módulo ion" PythonIsFinite = "Verificar se x é finito" PythonIsInfinite = "Verificar se x é infinito" PythonIsKeyDown = "Devolve True se tecla k pressionada" +PythonBattery = "Return battery voltage" PythonIsNaN = "Verificar se x é um NaN" PythonKandinskyFunction = "Prefixo da função do módulo kandinsky" PythonLdexp = "Devolve x*(2**i), inverso de frexp" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index 39d1ff3b494..e3401534570 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -151,6 +151,7 @@ PythonCommandKeyEe = "KEY_EE" PythonCommandKeyAns = "KEY_ANS" PythonCommandKeyExe = "KEY_EXE" PythonCommandIsKeyDown = "keydown(k)" +PythonCommandBattery = "battery()" PythonCommandLdexp = "ldexp(x,i)" PythonCommandLength = "len(object)" PythonCommandLgamma = "lgamma(x)" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 568e1f22c37..a305c2587ea 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -202,6 +202,7 @@ const ToolboxMessageTree IonModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromIon, I18n::Message::PythonImportIon, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIonFunction, I18n::Message::PythonIonFunction, false, I18n::Message::PythonCommandIonFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIsKeyDown, I18n::Message::PythonIsKeyDown), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBattery, I18n::Message::PythonBattery), ToolboxMessageTree::Leaf(I18n::Message::IonSelector, I18n::Message::IonSelector) }; @@ -323,6 +324,7 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIsNaN, I18n::Message::PythonIsNaN), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKandinskyFunction, I18n::Message::PythonKandinskyFunction, false, I18n::Message::PythonCommandKandinskyFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIsKeyDown, I18n::Message::PythonIsKeyDown), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBattery, I18n::Message::PythonBattery), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandLdexp, I18n::Message::PythonLdexp), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandLeft, I18n::Message::PythonTurtleLeft), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandLength, I18n::Message::PythonLength), diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index c580bcb8ea4..d620e08fcf3 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -323,6 +323,7 @@ Q(zip) // Ion QSTR Q(ion) Q(keydown) +Q(battery) Q(KEY_LEFT) Q(KEY_UP) Q(KEY_DOWN) diff --git a/python/port/mod/ion/modion.cpp b/python/port/mod/ion/modion.cpp index 8ab1ab14263..dc6ca004573 100644 --- a/python/port/mod/ion/modion.cpp +++ b/python/port/mod/ion/modion.cpp @@ -12,3 +12,7 @@ mp_obj_t modion_keyboard_keydown(mp_obj_t key_o) { micropython_port_interrupt_if_needed(); return mp_obj_new_bool(state.keyDown(key)); } + +mp_obj_t modion_battery() { + return mp_obj_new_float(Ion::Battery::voltage()); +} \ No newline at end of file diff --git a/python/port/mod/ion/modion.h b/python/port/mod/ion/modion.h index 4b4558be5fd..4afa4035839 100644 --- a/python/port/mod/ion/modion.h +++ b/python/port/mod/ion/modion.h @@ -1,4 +1,5 @@ #include mp_obj_t modion_keyboard_keydown(mp_obj_t key_o); -extern const mp_obj_type_t file_type; +mp_obj_t modion_battery(); +extern const mp_obj_type_t file_type; \ No newline at end of file diff --git a/python/port/mod/ion/modion_table.cpp b/python/port/mod/ion/modion_table.cpp index 7e6a53bdd33..751d29443df 100644 --- a/python/port/mod/ion/modion_table.cpp +++ b/python/port/mod/ion/modion_table.cpp @@ -16,8 +16,14 @@ const mp_obj_fun_builtin_fixed_t modion_keyboard_keydown_obj = { {(mp_fun_0_t)modion_keyboard_keydown} }; +const mp_obj_fun_builtin_fixed_t modion_battery_obj = { + {&mp_type_fun_builtin_0}, + {(mp_fun_0_t)modion_battery} +}; + extern "C" const mp_rom_map_elem_t modion_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ion) }, + { MP_ROM_QSTR(MP_QSTR_battery), MP_ROM_PTR(&modion_battery_obj) }, { MP_ROM_QSTR(MP_QSTR_keydown), MP_ROM_PTR(&modion_keyboard_keydown_obj) }, { MP_ROM_QSTR(MP_QSTR_KEY_LEFT), MP_OBJ_NEW_SMALL_INT(Ion::Keyboard::Key::Left) }, { MP_ROM_QSTR(MP_QSTR_KEY_UP), MP_OBJ_NEW_SMALL_INT(Ion::Keyboard::Key::Up) }, diff --git a/python/port/mod/os/modos.cpp b/python/port/mod/os/modos.cpp index 6bf5124d1f9..f9cbc926699 100644 --- a/python/port/mod/os/modos.cpp +++ b/python/port/mod/os/modos.cpp @@ -111,5 +111,4 @@ mp_obj_t modos_listdir(void) { } return list; -} - +} \ No newline at end of file From b0befbdbc5c66e5f0eeb880d0e9afe6ff13e6d87 Mon Sep 17 00:00:00 2001 From: ArtichOwO Date: Wed, 23 Jun 2021 22:40:41 +0200 Subject: [PATCH 014/355] [MPY/MOD] Added uLab --- python/Makefile | 26 + python/port/genhdr/qstrdefs.in.h | 117 + python/port/mod/ulab/ndarray.c | 2180 +++++++++++++++++ python/port/mod/ulab/ndarray.h | 736 ++++++ python/port/mod/ulab/ndarray_operators.c | 807 ++++++ python/port/mod/ulab/ndarray_operators.h | 277 +++ python/port/mod/ulab/ndarray_properties.c | 100 + python/port/mod/ulab/ndarray_properties.h | 92 + python/port/mod/ulab/numpy/approx/approx.c | 221 ++ python/port/mod/ulab/numpy/approx/approx.h | 29 + python/port/mod/ulab/numpy/compare/compare.c | 417 ++++ python/port/mod/ulab/numpy/compare/compare.h | 150 ++ python/port/mod/ulab/numpy/fft/fft.c | 82 + python/port/mod/ulab/numpy/fft/fft.h | 24 + python/port/mod/ulab/numpy/fft/fft_tools.c | 165 ++ python/port/mod/ulab/numpy/fft/fft_tools.h | 23 + python/port/mod/ulab/numpy/filter/filter.c | 84 + python/port/mod/ulab/numpy/filter/filter.h | 20 + python/port/mod/ulab/numpy/linalg/linalg.c | 397 +++ python/port/mod/ulab/numpy/linalg/linalg.h | 26 + .../port/mod/ulab/numpy/linalg/linalg_tools.c | 171 ++ .../port/mod/ulab/numpy/linalg/linalg_tools.h | 28 + .../port/mod/ulab/numpy/numerical/numerical.c | 1338 ++++++++++ .../port/mod/ulab/numpy/numerical/numerical.h | 652 +++++ python/port/mod/ulab/numpy/numpy.c | 336 +++ python/port/mod/ulab/numpy/numpy.h | 21 + python/port/mod/ulab/numpy/poly/poly.c | 232 ++ python/port/mod/ulab/numpy/poly/poly.h | 21 + python/port/mod/ulab/numpy/stats/stats.c | 52 + python/port/mod/ulab/numpy/stats/stats.h | 20 + .../port/mod/ulab/numpy/transform/transform.c | 89 + .../port/mod/ulab/numpy/transform/transform.h | 28 + python/port/mod/ulab/numpy/vector/vector.c | 635 +++++ python/port/mod/ulab/numpy/vector/vector.h | 156 ++ python/port/mod/ulab/scipy/linalg/linalg.c | 279 +++ python/port/mod/ulab/scipy/linalg/linalg.h | 21 + .../port/mod/ulab/scipy/optimize/optimize.c | 414 ++++ .../port/mod/ulab/scipy/optimize/optimize.h | 41 + python/port/mod/ulab/scipy/scipy.c | 51 + python/port/mod/ulab/scipy/scipy.h | 21 + python/port/mod/ulab/scipy/signal/signal.c | 153 ++ python/port/mod/ulab/scipy/signal/signal.h | 24 + python/port/mod/ulab/scipy/special/special.c | 42 + python/port/mod/ulab/scipy/special/special.h | 21 + python/port/mod/ulab/ulab.c | 162 ++ python/port/mod/ulab/ulab.h | 658 +++++ python/port/mod/ulab/ulab_create.c | 706 ++++++ python/port/mod/ulab/ulab_create.h | 78 + python/port/mod/ulab/ulab_tools.c | 233 ++ python/port/mod/ulab/ulab_tools.h | 37 + python/port/mod/ulab/user/user.c | 95 + python/port/mod/ulab/user/user.h | 20 + python/port/mod/ulab/utils/utils.c | 215 ++ python/port/mod/ulab/utils/utils.h | 19 + python/port/mpconfigport.h | 2 + python/port/port.cpp | 1 + 56 files changed, 13045 insertions(+) create mode 100644 python/port/mod/ulab/ndarray.c create mode 100644 python/port/mod/ulab/ndarray.h create mode 100644 python/port/mod/ulab/ndarray_operators.c create mode 100644 python/port/mod/ulab/ndarray_operators.h create mode 100644 python/port/mod/ulab/ndarray_properties.c create mode 100644 python/port/mod/ulab/ndarray_properties.h create mode 100644 python/port/mod/ulab/numpy/approx/approx.c create mode 100644 python/port/mod/ulab/numpy/approx/approx.h create mode 100644 python/port/mod/ulab/numpy/compare/compare.c create mode 100644 python/port/mod/ulab/numpy/compare/compare.h create mode 100644 python/port/mod/ulab/numpy/fft/fft.c create mode 100644 python/port/mod/ulab/numpy/fft/fft.h create mode 100644 python/port/mod/ulab/numpy/fft/fft_tools.c create mode 100644 python/port/mod/ulab/numpy/fft/fft_tools.h create mode 100644 python/port/mod/ulab/numpy/filter/filter.c create mode 100644 python/port/mod/ulab/numpy/filter/filter.h create mode 100644 python/port/mod/ulab/numpy/linalg/linalg.c create mode 100644 python/port/mod/ulab/numpy/linalg/linalg.h create mode 100644 python/port/mod/ulab/numpy/linalg/linalg_tools.c create mode 100644 python/port/mod/ulab/numpy/linalg/linalg_tools.h create mode 100644 python/port/mod/ulab/numpy/numerical/numerical.c create mode 100644 python/port/mod/ulab/numpy/numerical/numerical.h create mode 100644 python/port/mod/ulab/numpy/numpy.c create mode 100644 python/port/mod/ulab/numpy/numpy.h create mode 100644 python/port/mod/ulab/numpy/poly/poly.c create mode 100644 python/port/mod/ulab/numpy/poly/poly.h create mode 100644 python/port/mod/ulab/numpy/stats/stats.c create mode 100644 python/port/mod/ulab/numpy/stats/stats.h create mode 100644 python/port/mod/ulab/numpy/transform/transform.c create mode 100644 python/port/mod/ulab/numpy/transform/transform.h create mode 100644 python/port/mod/ulab/numpy/vector/vector.c create mode 100644 python/port/mod/ulab/numpy/vector/vector.h create mode 100644 python/port/mod/ulab/scipy/linalg/linalg.c create mode 100644 python/port/mod/ulab/scipy/linalg/linalg.h create mode 100644 python/port/mod/ulab/scipy/optimize/optimize.c create mode 100644 python/port/mod/ulab/scipy/optimize/optimize.h create mode 100644 python/port/mod/ulab/scipy/scipy.c create mode 100644 python/port/mod/ulab/scipy/scipy.h create mode 100644 python/port/mod/ulab/scipy/signal/signal.c create mode 100644 python/port/mod/ulab/scipy/signal/signal.h create mode 100644 python/port/mod/ulab/scipy/special/special.c create mode 100644 python/port/mod/ulab/scipy/special/special.h create mode 100644 python/port/mod/ulab/ulab.c create mode 100644 python/port/mod/ulab/ulab.h create mode 100644 python/port/mod/ulab/ulab_create.c create mode 100644 python/port/mod/ulab/ulab_create.h create mode 100644 python/port/mod/ulab/ulab_tools.c create mode 100644 python/port/mod/ulab/ulab_tools.h create mode 100644 python/port/mod/ulab/user/user.c create mode 100644 python/port/mod/ulab/user/user.h create mode 100644 python/port/mod/ulab/utils/utils.c create mode 100644 python/port/mod/ulab/utils/utils.h diff --git a/python/Makefile b/python/Makefile index 6957ed58695..15a770c736d 100644 --- a/python/Makefile +++ b/python/Makefile @@ -154,6 +154,32 @@ port_src += $(addprefix python/port/,\ mod/turtle/modturtle.cpp \ mod/turtle/modturtle_table.c \ mod/turtle/turtle.cpp \ + mod/ulab/scipy/linalg/linalg.c \ + mod/ulab/scipy/optimize/optimize.c \ + mod/ulab/scipy/signal/signal.c \ + mod/ulab/scipy/special/special.c \ + mod/ulab/ndarray_operators.c \ + mod/ulab/ulab_tools.c \ + mod/ulab/ndarray.c \ + mod/ulab/ndarray_properties.c \ + mod/ulab/numpy/approx/approx.c \ + mod/ulab/numpy/compare/compare.c \ + mod/ulab/ulab_create.c \ + mod/ulab/numpy/fft/fft.c \ + mod/ulab/numpy/fft/fft_tools.c \ + mod/ulab/numpy/filter/filter.c \ + mod/ulab/numpy/linalg/linalg.c \ + mod/ulab/numpy/linalg/linalg_tools.c \ + mod/ulab/numpy/numerical/numerical.c \ + mod/ulab/numpy/poly/poly.c \ + mod/ulab/numpy/stats/stats.c \ + mod/ulab/numpy/transform/transform.c \ + mod/ulab/numpy/vector/vector.c \ + mod/ulab/numpy/numpy.c \ + mod/ulab/scipy/scipy.c \ + mod/ulab/user/user.c \ + mod/ulab/utils/utils.c \ + mod/ulab/ulab.c \ mphalport.c \ ) diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index c580bcb8ea4..f6d637bffb6 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -545,3 +545,120 @@ Q(machine) Q(rename) Q(listdir) +// ulab QSTRs +Q(threshold) +Q(edgeitems) +Q(inplace) +Q(dtype) +Q(order) +Q(C) +Q(shape) +Q(strides) +Q(itemsize) +Q(size) +Q(T) +Q(x) +Q(dx) +Q(fft) +Q(ifft) +Q(a) +Q(v) +Q(linalg) +Q(cholesky) +Q(det) +Q(eig) +Q(inv) +Q(norm) +Q(n) +Q(ddof) +Q(numpy) +Q(ndarray) +Q(array) +Q(frombuffer) +Q(inf) +Q(nan) +Q(uint8) +Q(int8) +Q(uint16) +Q(int16) +Q(set_printoptions) +Q(get_printoptions) +Q(ndinfo) +Q(arange) +Q(concatenate) +Q(diag) +Q(empty) +Q(eye) +Q(interp) +Q(trapz) +Q(full) +Q(linspace) +Q(logspace) +Q(ones) +Q(zeros) +Q(clip) +Q(equal) +Q(not_equal) +Q(maximum) +Q(minimum) +Q(where) +Q(convolve) +Q(argmax) +Q(argmin) +Q(argsort) +Q(cross) +Q(diff) +Q(dot) +Q(decimals) +Q(otypes) +Q(solve_triangular) +Q(cho_solve) +Q(trace) +Q(flip) +Q(mean) +Q(median) +Q(roll) +Q(std) +Q(polyfit) +Q(polyval) +Q(arctan2) +Q(around) +Q(vectorize) +Q(xtol) +Q(maxiter) +Q(xatol) +Q(fatol) +Q(tol) +Q(rtol) +Q(scipy) +Q(optimize) +Q(signal) +Q(bisect) +Q(special) +Q(fmin) +Q(newton) +Q(sos) +Q(zi) +Q(spectrogram) +Q(sosfilt) +Q(gammaln) +Q(reshape) +Q(transpose) +Q(byteswap) +Q(flatten) +Q(k) +Q(tobytes) +Q(M) +Q(ulab) +Q(num) +Q(endpoint) +Q(__version__) +Q(utils) +Q(retstep) +Q(base) +Q(offset) +Q(out) +Q(from_int16_buffer) +Q(from_uint16_buffer) +Q(from_int32_buffer) +Q(from_uint32_buffer) diff --git a/python/port/mod/ulab/ndarray.c b/python/port/mod/ulab/ndarray.c new file mode 100644 index 00000000000..e37802efe16 --- /dev/null +++ b/python/port/mod/ulab/ndarray.c @@ -0,0 +1,2180 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Jeff Epler for Adafruit Industries + * 2020 Taku Fukada +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ulab_tools.h" +#include "ndarray.h" +#include "ndarray_operators.h" + +mp_uint_t ndarray_print_threshold = NDARRAY_PRINT_THRESHOLD; +mp_uint_t ndarray_print_edgeitems = NDARRAY_PRINT_EDGEITEMS; + +//| """Manipulate numeric data similar to numpy +//| +//| `ulab` is a numpy-like module for micropython, meant to simplify and +//| speed up common mathematical operations on arrays. The primary goal was to +//| implement a small subset of numpy that might be useful in the context of a +//| microcontroller. This means low-level data processing of linear (array) and +//| two-dimensional (matrix) data. +//| +//| `ulab` is adapted from micropython-ulab, and the original project's +//| documentation can be found at +//| https://micropython-ulab.readthedocs.io/en/latest/ +//| +//| `ulab` is modeled after numpy, and aims to be a compatible subset where +//| possible. Numpy's documentation can be found at +//| https://docs.scipy.org/doc/numpy/index.html""" +//| +//| from typing import Dict +//| +//| _DType = int +//| """`ulab.int8`, `ulab.uint8`, `ulab.int16`, `ulab.uint16`, `ulab.float` or `ulab.bool`""" +//| +//| _float = float +//| """Type alias of the bulitin float""" +//| +//| _bool = bool +//| """Type alias of the bulitin bool""" +//| +//| _Index = Union[int, slice, ulab.ndarray, Tuple[Union[int, slice], ...]] +//| + +//| class ndarray: +//| """1- and 2- dimensional ndarray""" +//| +//| def __init__( +//| self, +//| values: Union[ndarray, Iterable[Union[_float, _bool, Iterable[Any]]]], +//| *, +//| dtype: _DType = ulab.float +//| ) -> None: +//| """:param sequence values: Sequence giving the initial content of the ndarray. +//| :param ~ulab._DType dtype: The type of ndarray values, `ulab.int8`, `ulab.uint8`, `ulab.int16`, `ulab.uint16`, `ulab.float` or `ulab.bool` +//| +//| The ``values`` sequence can either be another ~ulab.ndarray, sequence of numbers +//| (in which case a 1-dimensional ndarray is created), or a sequence where each +//| subsequence has the same length (in which case a 2-dimensional ndarray is +//| created). +//| +//| Passing a `ulab.ndarray` and a different dtype can be used to convert an ndarray +//| from one dtype to another. +//| +//| In many cases, it is more convenient to create an ndarray from a function +//| like `zeros` or `linspace`. +//| +//| `ulab.ndarray` implements the buffer protocol, so it can be used in many +//| places an `array.array` can be used.""" +//| ... +//| +//| shape: Tuple[int, ...] +//| """The size of the array, a tuple of length 1 or 2""" +//| +//| size: int +//| """The number of elements in the array""" +//| +//| itemsize: int +//| """The size of a single item in the array""" +//| +//| strides: Tuple[int, ...] +//| """Tuple of bytes to step in each dimension, a tuple of length 1 or 2""" +//| +//| def copy(self) -> ulab.ndarray: +//| """Return a copy of the array""" +//| ... +//| +//| def dtype(self) -> _DType: +//| """Returns the dtype of the array""" +//| ... +//| +//| def flatten(self, *, order: str = "C") -> ulab.ndarray: +//| """:param order: Whether to flatten by rows ('C') or columns ('F') +//| +//| Returns a new `ulab.ndarray` object which is always 1 dimensional. +//| If order is 'C' (the default", then the data is ordered in rows; +//| If it is 'F', then the data is ordered in columns. "C" and "F" refer +//| to the typical storage organization of the C and Fortran languages.""" +//| ... +//| +//| def reshape(self, shape: Tuple[int, ...]) -> ulab.ndarray: +//| """Returns an ndarray containing the same data with a new shape.""" +//| ... +//| +//| def sort(self, *, axis: Optional[int] = 1) -> None: +//| """:param axis: Whether to sort elements within rows (0), columns (1), or elements (None)""" +//| ... +//| +//| def tobytes(self) -> bytearray: +//| """Return the raw data bytes in the ndarray""" +//| ... +//| +//| def transpose(self) -> ulab.ndarray: +//| """Swap the rows and columns of a 2-dimensional ndarray""" +//| ... +//| +//| def __add__(self, other: Union[ndarray, _float]) -> ulab.ndarray: +//| """Adds corresponding elements of the two ndarrays, or adds a number to all +//| elements of the ndarray. If both arguments are ndarrays, their sizes must match.""" +//| ... +//| def __radd__(self, other: _float) -> ulab.ndarray: ... +//| +//| def __sub__(self, other: Union[ndarray, _float]) -> ulab.ndarray: +//| """Subtracts corresponding elements of the two ndarrays, or subtracts a number from all +//| elements of the ndarray. If both arguments are ndarrays, their sizes must match.""" +//| ... +//| def __rsub__(self, other: _float) -> ulab.ndarray: ... +//| +//| def __mul__(self, other: Union[ndarray, _float]) -> ulab.ndarray: +//| """Multiplies corresponding elements of the two ndarrays, or multiplies +//| all elements of the ndarray by a number. If both arguments are ndarrays, +//| their sizes must match.""" +//| ... +//| def __rmul__(self, other: _float) -> ulab.ndarray: ... +//| +//| def __div__(self, other: Union[ndarray, _float]) -> ulab.ndarray: +//| """Multiplies corresponding elements of the two ndarrays, or divides +//| all elements of the ndarray by a number. If both arguments are ndarrays, +//| their sizes must match.""" +//| ... +//| def __rdiv__(self, other: _float) -> ulab.ndarray: ... +//| +//| def __pow__(self, other: Union[ndarray, _float]) -> ulab.ndarray: +//| """Computes the power (x**y) of corresponding elements of the the two ndarrays, +//| or one number and one ndarray. If both arguments are ndarrays, their sizes +//| must match.""" +//| ... +//| def __rpow__(self, other: _float) -> ulab.ndarray: ... +//| +//| def __inv__(self) -> ulab.ndarray: +//| ... +//| def __neg__(self) -> ulab.ndarray: +//| ... +//| def __pos__(self) -> ulab.ndarray: +//| ... +//| def __abs__(self) -> ulab.ndarray: +//| ... +//| def __len__(self) -> int: +//| ... +//| def __lt__(self, other: Union[ndarray, _float]) -> ulab.ndarray: +//| ... +//| def __le__(self, other: Union[ndarray, _float]) -> ulab.ndarray: +//| ... +//| def __gt__(self, other: Union[ndarray, _float]) -> ulab.ndarray: +//| ... +//| def __ge__(self, other: Union[ndarray, _float]) -> ulab.ndarray: +//| ... +//| +//| def __iter__(self) -> Union[Iterator[ndarray], Iterator[_float]]: +//| ... +//| +//| def __getitem__(self, index: _Index) -> Union[ndarray, _float]: +//| """Retrieve an element of the ndarray.""" +//| ... +//| +//| def __setitem__(self, index: _Index, value: Union[ndarray, _float]) -> None: +//| """Set an element of the ndarray.""" +//| ... +//| +//| _ArrayLike = Union[ndarray, List[_float], Tuple[_float], range] +//| """`ulab.ndarray`, ``List[float]``, ``Tuple[float]`` or `range`""" +//| +//| int8: _DType +//| """Type code for signed integers in the range -128 .. 127 inclusive, like the 'b' typecode of `array.array`""" +//| +//| int16: _DType +//| """Type code for signed integers in the range -32768 .. 32767 inclusive, like the 'h' typecode of `array.array`""" +//| +//| float: _DType +//| """Type code for floating point values, like the 'f' typecode of `array.array`""" +//| +//| uint8: _DType +//| """Type code for unsigned integers in the range 0 .. 255 inclusive, like the 'H' typecode of `array.array`""" +//| +//| uint16: _DType +//| """Type code for unsigned integers in the range 0 .. 65535 inclusive, like the 'h' typecode of `array.array`""" +//| +//| bool: _DType +//| """Type code for boolean values""" +//| +//| def get_printoptions() -> Dict[str, int]: +//| """Get printing options""" +//| ... +//| +//| def set_printoptions(threshold: Optional[int] = None, edgeitems: Optional[int] = None) -> None: +//| """Set printing options""" +//| ... +//| +//| def ndinfo(array: ulab.ndarray) -> None: +//| ... +//| +//| def array( +//| values: Union[ndarray, Iterable[Union[_float, _bool, Iterable[Any]]]], +//| *, +//| dtype: _DType = ulab.float +//| ) -> ulab.ndarray: +//| """alternate constructor function for `ulab.ndarray`. Mirrors numpy.array""" +//| ... + + +#ifdef CIRCUITPY +void ndarray_set_value(char typecode, void *p, size_t index, mp_obj_t val_in) { + switch (typecode) { + case NDARRAY_INT8: + ((signed char *)p)[index] = mp_obj_get_int(val_in); + break; + case NDARRAY_UINT8: + ((unsigned char *)p)[index] = mp_obj_get_int(val_in); + break; + case NDARRAY_INT16: + ((short *)p)[index] = mp_obj_get_int(val_in); + break; + case NDARRAY_UINT16: + ((unsigned short *)p)[index] = mp_obj_get_int(val_in); + break; + case NDARRAY_FLOAT: + ((mp_float_t *)p)[index] = mp_obj_get_float(val_in); + break; + } +} +#endif + +#if defined(MICROPY_VERSION_MAJOR) && MICROPY_VERSION_MAJOR == 1 && MICROPY_VERSION_MINOR == 12 + +void mp_obj_slice_indices(mp_obj_t self_in, mp_int_t length, mp_bound_slice_t *result) { + mp_obj_slice_t *self = MP_OBJ_TO_PTR(self_in); + mp_int_t start, stop, step; + + if (self->step == mp_const_none) { + step = 1; + } else { + step = mp_obj_get_int(self->step); + if (step == 0) { + mp_raise_ValueError(translate("slice step can't be zero")); + } + } + + if (step > 0) { + // Positive step + if (self->start == mp_const_none) { + start = 0; + } else { + start = mp_obj_get_int(self->start); + if (start < 0) { + start += length; + } + start = MIN(length, MAX(start, 0)); + } + + if (self->stop == mp_const_none) { + stop = length; + } else { + stop = mp_obj_get_int(self->stop); + if (stop < 0) { + stop += length; + } + stop = MIN(length, MAX(stop, 0)); + } + } else { + // Negative step + if (self->start == mp_const_none) { + start = length - 1; + } else { + start = mp_obj_get_int(self->start); + if (start < 0) { + start += length; + } + start = MIN(length - 1, MAX(start, -1)); + } + + if (self->stop == mp_const_none) { + stop = -1; + } else { + stop = mp_obj_get_int(self->stop); + if (stop < 0) { + stop += length; + } + stop = MIN(length - 1, MAX(stop, -1)); + } + } + + result->start = start; + result->stop = stop; + result->step = step; +} +#endif /* MICROPY_VERSION v1.11 */ + +void ndarray_fill_array_iterable(mp_float_t *array, mp_obj_t iterable) { + mp_obj_iter_buf_t x_buf; + mp_obj_t x_item, x_iterable = mp_getiter(iterable, &x_buf); + while ((x_item = mp_iternext(x_iterable)) != MP_OBJ_STOP_ITERATION) { + *array++ = (mp_float_t)mp_obj_get_float(x_item); + } +} + +#if ULAB_HAS_FUNCTION_ITERATOR +size_t *ndarray_new_coords(uint8_t ndim) { + size_t *coords = m_new(size_t, ndim); + memset(coords, 0, ndim*sizeof(size_t)); + return coords; +} + +void ndarray_rewind_array(uint8_t ndim, uint8_t *array, size_t *shape, int32_t *strides, size_t *coords) { + // resets the data pointer of a single array, whenever an axis is full + // since we always iterate over the very last axis, we have to keep track of + // the last ndim-2 axes only + array -= shape[ULAB_MAX_DIMS - 1] * strides[ULAB_MAX_DIMS - 1]; + array += strides[ULAB_MAX_DIMS - 2]; + for(uint8_t i=1; i < ndim-1; i++) { + coords[ULAB_MAX_DIMS - 1 - i] += 1; + if(coords[ULAB_MAX_DIMS - 1 - i] == shape[ULAB_MAX_DIMS - 1 - i]) { // we are at a dimension boundary + array -= shape[ULAB_MAX_DIMS - 1 - i] * strides[ULAB_MAX_DIMS - 1 - i]; + array += strides[ULAB_MAX_DIMS - 2 - i]; + coords[ULAB_MAX_DIMS - 1 - i] = 0; + coords[ULAB_MAX_DIMS - 2 - i] += 1; + } else { // coordinates can change only, if the last coordinate changes + return; + } + } +} +#endif + +static int32_t *strides_from_shape(size_t *shape, uint8_t dtype) { + // returns a strides array that corresponds to a dense array with the prescribed shape + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + strides[ULAB_MAX_DIMS-1] = (int32_t)mp_binary_get_size('@', dtype, NULL); + for(uint8_t i=ULAB_MAX_DIMS; i > 1; i--) { + strides[i-2] = strides[i-1] * shape[i-1]; + } + return strides; +} + +size_t *ndarray_shape_vector(size_t a, size_t b, size_t c, size_t d) { + // returns a ULAB_MAX_DIMS-aware array of shapes + // WARNING: this assumes that the maximum possible dimension is 4! + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + shape[ULAB_MAX_DIMS - 1] = d; + #if ULAB_MAX_DIMS > 1 + shape[ULAB_MAX_DIMS - 2] = c; + #endif + #if ULAB_MAX_DIMS > 2 + shape[ULAB_MAX_DIMS - 3] = b; + #endif + #if ULAB_MAX_DIMS > 3 + shape[ULAB_MAX_DIMS - 4] = a; + #endif + return shape; +} + +bool ndarray_object_is_array_like(mp_obj_t o_in) { + if(mp_obj_is_type(o_in, &ulab_ndarray_type) || + mp_obj_is_type(o_in, &mp_type_tuple) || + mp_obj_is_type(o_in, &mp_type_list) || + mp_obj_is_type(o_in, &mp_type_range)) { + return true; + } + return false; +} + +void fill_array_iterable(mp_float_t *array, mp_obj_t iterable) { + mp_obj_iter_buf_t x_buf; + mp_obj_t x_item, x_iterable = mp_getiter(iterable, &x_buf); + size_t i=0; + while ((x_item = mp_iternext(x_iterable)) != MP_OBJ_STOP_ITERATION) { + array[i] = (mp_float_t)mp_obj_get_float(x_item); + i++; + } +} + +#if NDARRAY_HAS_DTYPE +#if ULAB_HAS_DTYPE_OBJECT +void ndarray_dtype_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + dtype_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_print_str(print, "dtype('"); + if(self->dtype == NDARRAY_BOOLEAN) { + mp_print_str(print, "bool')"); + } else if(self->dtype == NDARRAY_UINT8) { + mp_print_str(print, "uint8')"); + } else if(self->dtype == NDARRAY_INT8) { + mp_print_str(print, "int8')"); + } else if(self->dtype == NDARRAY_UINT16) { + mp_print_str(print, "uint16')"); + } else if(self->dtype == NDARRAY_INT16) { + mp_print_str(print, "int16')"); + } else { + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + mp_print_str(print, "float32')"); + #else + mp_print_str(print, "float64')"); + #endif + } +} + +mp_obj_t ndarray_dtype_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void) type; + mp_arg_check_num(n_args, n_kw, 0, 1, true); + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_OBJ, { .u_obj = mp_const_none } }, + }; + mp_arg_val_t _args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, args, &kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, _args); + + dtype_obj_t *dtype = m_new_obj(dtype_obj_t); + dtype->base.type = &ulab_dtype_type; + + if(mp_obj_is_type(args[0], &ulab_ndarray_type)) { + // return the dtype of the array + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0]); + dtype->dtype = ndarray->dtype; + } else { + uint8_t _dtype; + if(mp_obj_is_int(_args[0].u_obj)) { + _dtype = mp_obj_get_int(_args[0].u_obj); + if((_dtype != NDARRAY_BOOL) && (_dtype != NDARRAY_UINT8) + && (_dtype != NDARRAY_INT8) && (_dtype != NDARRAY_UINT16) + && (_dtype != NDARRAY_INT16) && (_dtype != NDARRAY_FLOAT)) { + mp_raise_TypeError(translate("data type not understood")); + } + } else { + GET_STR_DATA_LEN(_args[0].u_obj, _dtype_, len); + if(memcmp(_dtype_, "uint8", 5) == 0) { + _dtype = NDARRAY_UINT8; + } else if(memcmp(_dtype_, "int8", 4) == 0) { + _dtype = NDARRAY_INT8; + } else if(memcmp(_dtype_, "uint16", 6) == 0) { + _dtype = NDARRAY_UINT16; + } else if(memcmp(_dtype_, "int16", 5) == 0) { + _dtype = NDARRAY_INT16; + } else if(memcmp(_dtype_, "float", 5) == 0) { + _dtype = NDARRAY_FLOAT; + } else { + mp_raise_TypeError(translate("data type not understood")); + } + } + dtype->dtype = _dtype; + } + return dtype; +} + +mp_obj_t ndarray_dtype(mp_obj_t self_in) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + dtype_obj_t *dtype = m_new_obj(dtype_obj_t); + dtype->base.type = &ulab_dtype_type; + dtype->dtype = self->dtype; + return dtype; +} + +#else +// this is the cheap implementation of tbe dtype +mp_obj_t ndarray_dtype(mp_obj_t self_in) { + uint8_t dtype; + if(mp_obj_is_type(self_in, &ulab_ndarray_type)) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + dtype = self->dtype; + } else { // we assume here that the input is a single character + GET_STR_DATA_LEN(self_in, _dtype, len); + if((len != 1) || ((*_dtype != NDARRAY_BOOL) && (*_dtype != NDARRAY_UINT8) + && (*_dtype != NDARRAY_INT8) && (*_dtype != NDARRAY_UINT16) + && (*_dtype != NDARRAY_INT16) && (*_dtype != NDARRAY_FLOAT))) { + mp_raise_TypeError(translate("data type not understood")); + } + dtype = *_dtype; + } + return mp_obj_new_int(dtype); +} +#endif /* ULAB_HAS_DTYPE_OBJECT */ +#endif /* NDARRAY_HAS_DTYPE */ + +#if ULAB_HAS_PRINTOPTIONS +mp_obj_t ndarray_set_printoptions(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_threshold, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} }, + { MP_QSTR_edgeitems, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + if(args[0].u_rom_obj != mp_const_none) { + ndarray_print_threshold = mp_obj_get_int(args[0].u_rom_obj); + } + if(args[1].u_rom_obj != mp_const_none) { + ndarray_print_edgeitems = mp_obj_get_int(args[1].u_rom_obj); + } + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(ndarray_set_printoptions_obj, 0, ndarray_set_printoptions); + +mp_obj_t ndarray_get_printoptions(void) { + mp_obj_t dict = mp_obj_new_dict(2); + mp_obj_dict_store(MP_OBJ_FROM_PTR(dict), MP_OBJ_NEW_QSTR(MP_QSTR_threshold), mp_obj_new_int(ndarray_print_threshold)); + mp_obj_dict_store(MP_OBJ_FROM_PTR(dict), MP_OBJ_NEW_QSTR(MP_QSTR_edgeitems), mp_obj_new_int(ndarray_print_edgeitems)); + return dict; +} + +MP_DEFINE_CONST_FUN_OBJ_0(ndarray_get_printoptions_obj, ndarray_get_printoptions); +#endif + +mp_obj_t ndarray_get_item(ndarray_obj_t *ndarray, void *array) { + // returns a proper micropython object from an array + if(!ndarray->boolean) { + return mp_binary_get_val_array(ndarray->dtype, array, 0); + } else { + if(*(uint8_t *)array) { + return mp_const_true; + } else { + return mp_const_false; + } + } +} + +static void ndarray_print_row(const mp_print_t *print, ndarray_obj_t * ndarray, uint8_t *array, size_t stride, size_t n) { + if(n == 0) { + return; + } + mp_print_str(print, "["); + if((n <= ndarray_print_threshold) || (n <= 2*ndarray_print_edgeitems)) { // if the array is short, print everything + mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR); + array += stride; + for(size_t i=1; i < n; i++, array += stride) { + mp_print_str(print, ", "); + mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR); + } + } else { + mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR); + array += stride; + for(size_t i=1; i < ndarray_print_edgeitems; i++, array += stride) { + mp_print_str(print, ", "); + mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR); + } + mp_printf(print, ", ..., "); + array += stride * (n - 2 * ndarray_print_edgeitems); + mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR); + array += stride; + for(size_t i=1; i < ndarray_print_edgeitems; i++, array += stride) { + mp_print_str(print, ", "); + mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR); + } + } + mp_print_str(print, "]"); +} + +static void ndarray_print_bracket(const mp_print_t *print, const size_t condition, const size_t shape, const char *string) { + if(condition < shape) { + mp_print_str(print, string); + } +} + +void ndarray_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + uint8_t *array = (uint8_t *)self->array; + mp_print_str(print, "array("); + if(self->len == 0) { + mp_print_str(print, "[]"); + if(self->ndim > 1) { + mp_print_str(print, ", shape=("); + for(uint8_t ndim = self->ndim; ndim > 1; ndim--) { + mp_printf(MP_PYTHON_PRINTER, "%d,", self->shape[ULAB_MAX_DIMS - ndim]); + } + mp_printf(MP_PYTHON_PRINTER, "%d)", self->shape[ULAB_MAX_DIMS - 1]); + } + } else { + #if ULAB_MAX_DIMS > 3 + size_t i=0; + ndarray_print_bracket(print, 0, self->shape[ULAB_MAX_DIMS-4], "["); + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + ndarray_print_bracket(print, 0, self->shape[ULAB_MAX_DIMS-3], "["); + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + ndarray_print_bracket(print, 0, self->shape[ULAB_MAX_DIMS-2], "["); + do { + #endif + ndarray_print_row(print, self, array, self->strides[ULAB_MAX_DIMS-1], self->shape[ULAB_MAX_DIMS-1]); + #if ULAB_MAX_DIMS > 1 + array += self->strides[ULAB_MAX_DIMS-2]; + k++; + ndarray_print_bracket(print, k, self->shape[ULAB_MAX_DIMS-2], ",\n "); + } while(k < self->shape[ULAB_MAX_DIMS-2]); + ndarray_print_bracket(print, 0, self->shape[ULAB_MAX_DIMS-2], "]"); + #endif + #if ULAB_MAX_DIMS > 2 + j++; + ndarray_print_bracket(print, j, self->shape[ULAB_MAX_DIMS-3], ",\n\n "); + array -= self->strides[ULAB_MAX_DIMS-2] * self->shape[ULAB_MAX_DIMS-2]; + array += self->strides[ULAB_MAX_DIMS-3]; + } while(j < self->shape[ULAB_MAX_DIMS-3]); + ndarray_print_bracket(print, 0, self->shape[ULAB_MAX_DIMS-3], "]"); + #endif + #if ULAB_MAX_DIMS > 3 + array -= self->strides[ULAB_MAX_DIMS-3] * self->shape[ULAB_MAX_DIMS-3]; + array += self->strides[ULAB_MAX_DIMS-4]; + i++; + ndarray_print_bracket(print, i, self->shape[ULAB_MAX_DIMS-4], ",\n\n "); + } while(i < self->shape[ULAB_MAX_DIMS-4]); + ndarray_print_bracket(print, 0, self->shape[ULAB_MAX_DIMS-4], "]"); + #endif + } + if(self->boolean) { + mp_print_str(print, ", dtype=bool)"); + } else if(self->dtype == NDARRAY_UINT8) { + mp_print_str(print, ", dtype=uint8)"); + } else if(self->dtype == NDARRAY_INT8) { + mp_print_str(print, ", dtype=int8)"); + } else if(self->dtype == NDARRAY_UINT16) { + mp_print_str(print, ", dtype=uint16)"); + } else if(self->dtype == NDARRAY_INT16) { + mp_print_str(print, ", dtype=int16)"); + } else { + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + mp_print_str(print, ", dtype=float32)"); + #else + mp_print_str(print, ", dtype=float64)"); + #endif + } +} + +void ndarray_assign_elements(ndarray_obj_t *ndarray, mp_obj_t iterable, uint8_t dtype, size_t *idx) { + // assigns a single row in the tensor + mp_obj_t item; + if(ndarray->boolean) { + uint8_t *array = (uint8_t *)ndarray->array; + array += *idx; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + // TODO: this might be wrong here: we have to check for the trueness of item + if(mp_obj_is_true(item)) { + *array = 1; + } + array++; + (*idx)++; + } + } else { + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + ndarray_set_value(dtype, ndarray->array, (*idx)++, item); + } + } +} + +bool ndarray_is_dense(ndarray_obj_t *ndarray) { + // returns true, if the array is dense, false otherwise + // the array should be dense, if the very first stride can be calculated from shape + int32_t stride = ndarray->itemsize; + for(uint8_t i = ULAB_MAX_DIMS - 1; i > ULAB_MAX_DIMS-ndarray->ndim; i--) { + stride *= ndarray->shape[i]; + } + return stride == ndarray->strides[ULAB_MAX_DIMS-ndarray->ndim] ? true : false; +} + + +ndarray_obj_t *ndarray_new_ndarray(uint8_t ndim, size_t *shape, int32_t *strides, uint8_t dtype) { + // Creates the base ndarray with shape, and initialises the values to straight 0s + ndarray_obj_t *ndarray = m_new_obj(ndarray_obj_t); + ndarray->base.type = &ulab_ndarray_type; + ndarray->dtype = dtype == NDARRAY_BOOL ? NDARRAY_UINT8 : dtype; + ndarray->boolean = dtype == NDARRAY_BOOL ? NDARRAY_BOOLEAN : NDARRAY_NUMERIC; + ndarray->ndim = ndim; + ndarray->len = ndim == 0 ? 0 : 1; + ndarray->itemsize = mp_binary_get_size('@', ndarray->dtype, NULL); + int32_t *_strides; + if(strides == NULL) { + _strides = strides_from_shape(shape, ndarray->dtype); + } else { + _strides = strides; + } + for(uint8_t i=ULAB_MAX_DIMS; i > ULAB_MAX_DIMS-ndim; i--) { + ndarray->shape[i-1] = shape[i-1]; + ndarray->strides[i-1] = _strides[i-1]; + ndarray->len *= shape[i-1]; + } + + // if the length is 0, still allocate a single item, so that contractions can be handled + size_t len = ndarray->itemsize * MAX(1, ndarray->len); + uint8_t *array = m_new(byte, len); + // this should set all elements to 0, irrespective of the of the dtype (all bits are zero) + // we could, perhaps, leave this step out, and initialise the array only, when needed + memset(array, 0, len); + ndarray->array = array; + ndarray->origin = array; + return ndarray; +} + +ndarray_obj_t *ndarray_new_dense_ndarray(uint8_t ndim, size_t *shape, uint8_t dtype) { + // creates a dense array, i.e., one, where the strides are derived directly from the shapes + // the function should work in the general n-dimensional case + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + strides[ULAB_MAX_DIMS-1] = dtype == NDARRAY_BOOL ? 1 : mp_binary_get_size('@', dtype, NULL); + for(size_t i=ULAB_MAX_DIMS; i > 1; i--) { + strides[i-2] = strides[i-1] * MAX(1, shape[i-1]); + } + return ndarray_new_ndarray(ndim, shape, strides, dtype); +} + +ndarray_obj_t *ndarray_new_ndarray_from_tuple(mp_obj_tuple_t *_shape, uint8_t dtype) { + // creates a dense array from a tuple + // the function should work in the general n-dimensional case + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + for(size_t i=0; i < ULAB_MAX_DIMS; i++) { + if(i < ULAB_MAX_DIMS - _shape->len) { + shape[i] = 0; + } else { + shape[i] = mp_obj_get_int(_shape->items[i]); + } + } + return ndarray_new_dense_ndarray(_shape->len, shape, dtype); +} + +void ndarray_copy_array(ndarray_obj_t *source, ndarray_obj_t *target) { + // TODO: if the array is dense, the content could be copied in a single pass + // copies the content of source->array into a new dense void pointer + // it is assumed that the dtypes in source and target are the same + // Since the target is a new array, it is supposed to be dense + uint8_t *sarray = (uint8_t *)source->array; + uint8_t *tarray = (uint8_t *)target->array; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + memcpy(tarray, sarray, source->itemsize); + tarray += target->itemsize; + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif +} + +ndarray_obj_t *ndarray_new_view(ndarray_obj_t *source, uint8_t ndim, size_t *shape, int32_t *strides, int32_t offset) { + // creates a new view from the input arguments + ndarray_obj_t *ndarray = m_new_obj(ndarray_obj_t); + ndarray->base.type = &ulab_ndarray_type; + ndarray->boolean = source->boolean; + ndarray->dtype = source->dtype; + ndarray->ndim = ndim; + ndarray->itemsize = source->itemsize; + ndarray->len = ndim == 0 ? 0 : 1; + for(uint8_t i=ULAB_MAX_DIMS; i > ULAB_MAX_DIMS-ndim; i--) { + ndarray->shape[i-1] = shape[i-1]; + ndarray->strides[i-1] = strides[i-1]; + ndarray->len *= shape[i-1]; + } + uint8_t *pointer = (uint8_t *)source->array; + pointer += offset; + ndarray->array = pointer; + ndarray->origin = source->origin; + return ndarray; +} + +ndarray_obj_t *ndarray_copy_view(ndarray_obj_t *source) { + // creates a one-to-one deep copy of the input ndarray or its view + // the function should work in the general n-dimensional case + // In order to make it dtype-agnostic, we copy the memory content + // instead of reading out the values + + int32_t *strides = strides_from_shape(source->shape, source->dtype); + + uint8_t dtype = source->dtype; + if(source->boolean) { + dtype = NDARRAY_BOOLEAN; + } + ndarray_obj_t *ndarray = ndarray_new_ndarray(source->ndim, source->shape, strides, dtype); + ndarray_copy_array(source, ndarray); + return ndarray; +} + +#if NDARRAY_HAS_BYTESWAP +mp_obj_t ndarray_byteswap(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + // changes the endiannes of an array + // if the dtype of the input uint8/int8/bool, simply return a copy or view + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_inplace, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_false } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + ndarray_obj_t *self = MP_OBJ_TO_PTR(args[0].u_obj); + ndarray_obj_t *ndarray = NULL; + if(args[1].u_obj == mp_const_false) { + ndarray = ndarray_copy_view(self); + } else { + ndarray = ndarray_new_view(self, self->ndim, self->shape, self->strides, 0); + } + if((self->dtype == NDARRAY_BOOL) || (self->dtype == NDARRAY_UINT8) || (self->dtype == NDARRAY_INT8)) { + return MP_OBJ_FROM_PTR(ndarray); + } else { + uint8_t *array = (uint8_t *)ndarray->array; + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + if(self->dtype == NDARRAY_FLOAT) { + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + SWAP(uint8_t, array[0], array[3]); + SWAP(uint8_t, array[1], array[2]); + #else + SWAP(uint8_t, array[0], array[7]); + SWAP(uint8_t, array[1], array[6]); + SWAP(uint8_t, array[2], array[5]); + SWAP(uint8_t, array[3], array[4]); + #endif + } else { + SWAP(uint8_t, array[0], array[1]); + } + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < ndarray->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * ndarray->shape[ULAB_MAX_DIMS-1]; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < ndarray->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= ndarray->strides[ULAB_MAX_DIMS - 2] * ndarray->shape[ULAB_MAX_DIMS-2]; + array += ndarray->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < ndarray->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= ndarray->strides[ULAB_MAX_DIMS - 3] * ndarray->shape[ULAB_MAX_DIMS-3]; + array += ndarray->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < ndarray->shape[ULAB_MAX_DIMS - 4]); + #endif + } + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(ndarray_byteswap_obj, 1, ndarray_byteswap); +#endif + +#if NDARRAY_HAS_COPY +mp_obj_t ndarray_copy(mp_obj_t self_in) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_FROM_PTR(ndarray_copy_view(self)); +} + +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_copy_obj, ndarray_copy); +#endif + +ndarray_obj_t *ndarray_new_linear_array(size_t len, uint8_t dtype) { + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + if(len == 0) { + return ndarray_new_dense_ndarray(0, shape, dtype); + } + shape[ULAB_MAX_DIMS-1] = len; + return ndarray_new_dense_ndarray(1, shape, dtype); +} + +ndarray_obj_t *ndarray_from_iterable(mp_obj_t obj, uint8_t dtype) { + // returns an ndarray from an iterable micropython object + // if the input is an ndarray, returns the input... + if(mp_obj_is_type(obj, &ulab_ndarray_type)) { + return MP_OBJ_TO_PTR(obj); + } + // ... otherwise, takes the values from the iterable, and creates the corresponding ndarray + + // First, we have to figure out, whether the elements of the iterable are iterables themself + uint8_t ndim = 0; + size_t shape[ULAB_MAX_DIMS]; + mp_obj_iter_buf_t iter_buf[ULAB_MAX_DIMS]; + mp_obj_t iterable[ULAB_MAX_DIMS]; + // inspect only the very first element in each dimension; this is fast, + // but not completely safe, e.g., length compatibility is not checked + mp_obj_t item = obj; + + while(1) { + if(mp_obj_len_maybe(item) == MP_OBJ_NULL) { + break; + } + if(ndim == ULAB_MAX_DIMS) { + mp_raise_ValueError(translate("too many dimensions")); + } + shape[ndim] = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(item)); + if(shape[ndim] == 0) { + ndim++; + break; + } + iterable[ndim] = mp_getiter(item, &iter_buf[ndim]); + item = mp_iternext(iterable[ndim]); + ndim++; + } + for(uint8_t i = 0; i < ndim; i++) { + // align all values to the right + shape[ULAB_MAX_DIMS - i - 1] = shape[ndim - 1 - i]; + } + + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(ndim, shape, dtype); + item = obj; + for(uint8_t i = 0; i < ndim - 1; i++) { + // if ndim > 1, descend into the hierarchy + iterable[ULAB_MAX_DIMS - ndim + i] = mp_getiter(item, &iter_buf[ULAB_MAX_DIMS - ndim + i]); + item = mp_iternext(iterable[ULAB_MAX_DIMS - ndim + i]); + } + + size_t idx = 0; + // TODO: this could surely be done in a more elegant way... + #if ULAB_MAX_DIMS > 3 + do { + #endif + #if ULAB_MAX_DIMS > 2 + do { + #endif + #if ULAB_MAX_DIMS > 1 + do { + #endif + iterable[ULAB_MAX_DIMS - 1] = mp_getiter(item, &iter_buf[ULAB_MAX_DIMS - 1]); + ndarray_assign_elements(ndarray, iterable[ULAB_MAX_DIMS - 1], ndarray->dtype, &idx); + #if ULAB_MAX_DIMS > 1 + item = ndim > 1 ? mp_iternext(iterable[ULAB_MAX_DIMS - 2]) : MP_OBJ_STOP_ITERATION; + } while(item != MP_OBJ_STOP_ITERATION); + #endif + #if ULAB_MAX_DIMS > 2 + item = ndim > 2 ? mp_iternext(iterable[ULAB_MAX_DIMS - 3]) : MP_OBJ_STOP_ITERATION; + if(item != MP_OBJ_STOP_ITERATION) { + iterable[ULAB_MAX_DIMS - 2] = mp_getiter(item, &iter_buf[ULAB_MAX_DIMS - 2]); + item = mp_iternext(iterable[ULAB_MAX_DIMS - 2]); + } else { + iterable[ULAB_MAX_DIMS - 2] = MP_OBJ_STOP_ITERATION; + } + } while(iterable[ULAB_MAX_DIMS - 2] != MP_OBJ_STOP_ITERATION); + #endif + #if ULAB_MAX_DIMS > 3 + item = ndim > 3 ? mp_iternext(iterable[ULAB_MAX_DIMS - 4]) : MP_OBJ_STOP_ITERATION; + if(item != MP_OBJ_STOP_ITERATION) { + iterable[ULAB_MAX_DIMS - 3] = mp_getiter(item, &iter_buf[ULAB_MAX_DIMS - 3]); + item = mp_iternext(iterable[ULAB_MAX_DIMS - 3]); + } else { + iterable[ULAB_MAX_DIMS - 3] = MP_OBJ_STOP_ITERATION; + } + } while(iterable[ULAB_MAX_DIMS - 3] != MP_OBJ_STOP_ITERATION); + #endif + + return ndarray; +} + +STATIC uint8_t ndarray_init_helper(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_ROM_INT(NDARRAY_FLOAT) } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t _dtype; + #if ULAB_HAS_DTYPE_OBJECT + if(mp_obj_is_type(args[1].u_obj, &ulab_dtype_type)) { + dtype_obj_t *dtype = MP_OBJ_TO_PTR(args[1].u_obj); + _dtype = dtype->dtype; + } else { // this must be an integer defined as a class constant (ulba.uint8 etc.) + _dtype = mp_obj_get_int(args[1].u_obj); + } + #else + _dtype = mp_obj_get_int(args[1].u_obj); + #endif + return _dtype; +} + +STATIC mp_obj_t ndarray_make_new_core(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args, mp_map_t *kw_args) { + uint8_t dtype = ndarray_init_helper(n_args, args, kw_args); + + if(mp_obj_is_type(args[0], &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(args[0]); + if(dtype == source->dtype) { + return ndarray_copy_view(source); + } + ndarray_obj_t *target = ndarray_new_dense_ndarray(source->ndim, source->shape, dtype); + uint8_t *sarray = (uint8_t *)source->array; + uint8_t *tarray = (uint8_t *)target->array; + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_obj_t item; + if((source->dtype == NDARRAY_FLOAT) && (dtype != NDARRAY_FLOAT)) { + // floats must be treated separately, because they can't directly be converted to integer types + mp_float_t f = ndarray_get_float_value(sarray, source->dtype); + item = mp_obj_new_int((int32_t)MICROPY_FLOAT_C_FUN(floor)(f)); + } else { + item = mp_binary_get_val_array(source->dtype, sarray, 0); + } + ndarray_set_value(dtype, tarray, 0, item); + tarray += target->itemsize; + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif + return MP_OBJ_FROM_PTR(target); + } else { + // assume that the input is an iterable + return MP_OBJ_FROM_PTR(ndarray_from_iterable(args[0], dtype)); + } +} + +mp_obj_t ndarray_array_constructor(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + // array constructor for ndarray, equivalent to numpy.array(...) + return ndarray_make_new_core(&ulab_ndarray_type, n_args, kw_args->used, pos_args, kw_args); +} +MP_DEFINE_CONST_FUN_OBJ_KW(ndarray_array_constructor_obj, 1, ndarray_array_constructor); + +#ifdef CIRCUITPY +mp_obj_t ndarray_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + (void) type; + mp_arg_check_num(n_args, kw_args, 1, 2, true); + size_t n_kw = 0; + if (kw_args != 0) { + n_kw = kw_args->used; + } + mp_map_init_fixed_table(kw_args, n_kw, args + n_args); + return ndarray_make_new_core(type, n_args, n_kw, args, kw_args); +} +#else +mp_obj_t ndarray_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void) type; + mp_arg_check_num(n_args, n_kw, 1, 2, true); + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + return ndarray_make_new_core(type, n_args, n_kw, args, &kw_args); +} +#endif + +// broadcasting is used at a number of places, always include +bool ndarray_can_broadcast(ndarray_obj_t *lhs, ndarray_obj_t *rhs, uint8_t *ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + // Returns true or false, depending on, whether the two arrays can be broadcast together + // with numpy's broadcasting rules. These are as follows: + // + // 1. the two shapes are either equal + // 2. one of the shapes is 1 + memset(lstrides, 0, sizeof(size_t)*ULAB_MAX_DIMS); + memset(rstrides, 0, sizeof(size_t)*ULAB_MAX_DIMS); + lstrides[ULAB_MAX_DIMS - 1] = lhs->strides[ULAB_MAX_DIMS - 1]; + rstrides[ULAB_MAX_DIMS - 1] = rhs->strides[ULAB_MAX_DIMS - 1]; + for(uint8_t i=ULAB_MAX_DIMS; i > 0; i--) { + if((lhs->shape[i-1] == rhs->shape[i-1]) || (lhs->shape[i-1] == 0) || (lhs->shape[i-1] == 1) || + (rhs->shape[i-1] == 0) || (rhs->shape[i-1] == 1)) { + shape[i-1] = MAX(lhs->shape[i-1], rhs->shape[i-1]); + if(shape[i-1] > 0) (*ndim)++; + if(lhs->shape[i-1] < 2) { + lstrides[i-1] = 0; + } else { + lstrides[i-1] = lhs->strides[i-1]; + } + if(rhs->shape[i-1] < 2) { + rstrides[i-1] = 0; + } else { + rstrides[i-1] = rhs->strides[i-1]; + } + } else { + return false; + } + } + return true; +} + +#if NDARRAY_HAS_INPLACE_OPS +bool ndarray_can_broadcast_inplace(ndarray_obj_t *lhs, ndarray_obj_t *rhs, int32_t *rstrides) { + // returns true or false, depending on, whether the two arrays can be broadcast together inplace + // this means that the right hand side always must be "smaller" than the left hand side, i.e. + // the broadcasting rules are as follows: + // + // 1. the two shapes are either equal + // 2. the shapes on the right hand side is 1 + memset(rstrides, 0, sizeof(size_t)*ULAB_MAX_DIMS); + rstrides[ULAB_MAX_DIMS - 1] = rhs->strides[ULAB_MAX_DIMS - 1]; + for(uint8_t i=ULAB_MAX_DIMS; i > 0; i--) { + if((lhs->shape[i-1] == rhs->shape[i-1]) || (rhs->shape[i-1] == 0) || (rhs->shape[i-1] == 1)) { + if(rhs->shape[i-1] < 2) { + rstrides[i-1] = 0; + } else { + rstrides[i-1] = rhs->strides[i-1]; + } + } else { + return false; + } + } + return true; +} +#endif + +#if NDARRAY_IS_SLICEABLE +static size_t slice_length(mp_bound_slice_t slice) { + ssize_t len, correction = 1; + if(slice.step > 0) correction = -1; + len = (ssize_t)(slice.stop - slice.start + (slice.step + correction)) / slice.step; + if(len < 0) return 0; + return (size_t)len; +} + +static mp_bound_slice_t generate_slice(mp_int_t n, mp_obj_t index) { + mp_bound_slice_t slice; + if(mp_obj_is_type(index, &mp_type_slice)) { + mp_obj_slice_indices(index, n, &slice); + } else if(mp_obj_is_int(index)) { + mp_int_t _index = mp_obj_get_int(index); + if(_index < 0) { + _index += n; + } + if((_index >= n) || (_index < 0)) { + mp_raise_msg(&mp_type_IndexError, translate("index is out of bounds")); + } + slice.start = _index; + slice.stop = _index + 1; + slice.step = 1; + } else { + mp_raise_msg(&mp_type_IndexError, translate("indices must be integers, slices, or Boolean lists")); + } + return slice; +} + +static ndarray_obj_t *ndarray_view_from_slices(ndarray_obj_t *ndarray, mp_obj_tuple_t *tuple) { + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + memset(strides, 0, sizeof(size_t)*ULAB_MAX_DIMS); + + uint8_t ndim = ndarray->ndim; + + for(uint8_t i=0; i < ndim; i++) { + // copy from the end + shape[ULAB_MAX_DIMS - 1 - i] = ndarray->shape[ULAB_MAX_DIMS - 1 - i]; + strides[ULAB_MAX_DIMS - 1 - i] = ndarray->strides[ULAB_MAX_DIMS - 1 - i]; + } + int32_t offset = 0; + for(uint8_t i=0; i < tuple->len; i++) { + if(mp_obj_is_int(tuple->items[i])) { + // if item is an int, the dimension will first be reduced ... + ndim--; + int32_t k = mp_obj_get_int(tuple->items[i]); + if(k < 0) { + k += ndarray->shape[ULAB_MAX_DIMS - ndarray->ndim + i]; + } + if((k >= (int32_t)ndarray->shape[ULAB_MAX_DIMS - ndarray->ndim + i]) || (k < 0)) { + mp_raise_msg(&mp_type_IndexError, translate("index is out of bounds")); + } + offset += ndarray->strides[ULAB_MAX_DIMS - ndarray->ndim + i] * k; + // ... and then we have to shift the shapes to the right + for(uint8_t j=0; j < i; j++) { + shape[ULAB_MAX_DIMS - ndarray->ndim + i - j] = shape[ULAB_MAX_DIMS - ndarray->ndim + i - j - 1]; + strides[ULAB_MAX_DIMS - ndarray->ndim + i - j] = strides[ULAB_MAX_DIMS - ndarray->ndim + i - j - 1]; + } + } else { + mp_bound_slice_t slice = generate_slice(shape[ULAB_MAX_DIMS - ndarray->ndim + i], tuple->items[i]); + shape[ULAB_MAX_DIMS - ndarray->ndim + i] = slice_length(slice); + offset += ndarray->strides[ULAB_MAX_DIMS - ndarray->ndim + i] * (int32_t)slice.start; + strides[ULAB_MAX_DIMS - ndarray->ndim + i] = (int32_t)slice.step * ndarray->strides[ULAB_MAX_DIMS - ndarray->ndim + i]; + } + } + return ndarray_new_view(ndarray, ndim, shape, strides, offset); +} + +void ndarray_assign_view(ndarray_obj_t *view, ndarray_obj_t *values) { + if(values->len == 0) { + return; + } + uint8_t ndim = 0; + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + int32_t *lstrides = m_new(int32_t, ULAB_MAX_DIMS); + int32_t *rstrides = m_new(int32_t, ULAB_MAX_DIMS); + if(!ndarray_can_broadcast(view, values, &ndim, shape, lstrides, rstrides)) { + mp_raise_ValueError(translate("operands could not be broadcast together")); + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(int32_t, lstrides, ULAB_MAX_DIMS); + m_del(int32_t, rstrides, ULAB_MAX_DIMS); + } + + uint8_t *rarray = (uint8_t *)values->array; + // since in ASSIGNMENT_LOOP the array has a type, we have to divide the strides by the itemsize + for(uint8_t i=0; i < ULAB_MAX_DIMS; i++) { + lstrides[i] /= view->itemsize; + } + + if(view->dtype == NDARRAY_UINT8) { + if(values->dtype == NDARRAY_UINT8) { + ASSIGNMENT_LOOP(view, uint8_t, uint8_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_INT8) { + ASSIGNMENT_LOOP(view, uint8_t, int8_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_UINT16) { + ASSIGNMENT_LOOP(view, uint8_t, uint16_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_INT16) { + ASSIGNMENT_LOOP(view, uint8_t, int16_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_FLOAT) { + ASSIGNMENT_LOOP(view, uint8_t, mp_float_t, lstrides, rarray, rstrides); + } + } else if(view->dtype == NDARRAY_INT8) { + if(values->dtype == NDARRAY_UINT8) { + ASSIGNMENT_LOOP(view, int8_t, uint8_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_INT8) { + ASSIGNMENT_LOOP(view, int8_t, int8_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_UINT16) { + ASSIGNMENT_LOOP(view, int8_t, uint16_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_INT16) { + ASSIGNMENT_LOOP(view, int8_t, int16_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_FLOAT) { + ASSIGNMENT_LOOP(view, int8_t, mp_float_t, lstrides, rarray, rstrides); + } + } else if(view->dtype == NDARRAY_UINT16) { + if(values->dtype == NDARRAY_UINT8) { + ASSIGNMENT_LOOP(view, uint16_t, uint8_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_INT8) { + ASSIGNMENT_LOOP(view, uint16_t, int8_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_UINT16) { + ASSIGNMENT_LOOP(view, uint16_t, uint16_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_INT16) { + ASSIGNMENT_LOOP(view, uint16_t, int16_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_FLOAT) { + ASSIGNMENT_LOOP(view, uint16_t, mp_float_t, lstrides, rarray, rstrides); + } + } else if(view->dtype == NDARRAY_INT16) { + if(values->dtype == NDARRAY_UINT8) { + ASSIGNMENT_LOOP(view, int16_t, uint8_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_INT8) { + ASSIGNMENT_LOOP(view, int16_t, int8_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_UINT16) { + ASSIGNMENT_LOOP(view, int16_t, uint16_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_INT16) { + ASSIGNMENT_LOOP(view, int16_t, int16_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_FLOAT) { + ASSIGNMENT_LOOP(view, int16_t, mp_float_t, lstrides, rarray, rstrides); + } + } else { // the dtype must be an mp_float_t now + if(values->dtype == NDARRAY_UINT8) { + ASSIGNMENT_LOOP(view, mp_float_t, uint8_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_INT8) { + ASSIGNMENT_LOOP(view, mp_float_t, int8_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_UINT16) { + ASSIGNMENT_LOOP(view, mp_float_t, uint16_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_INT16) { + ASSIGNMENT_LOOP(view, mp_float_t, int16_t, lstrides, rarray, rstrides); + } else if(values->dtype == NDARRAY_FLOAT) { + ASSIGNMENT_LOOP(view, mp_float_t, mp_float_t, lstrides, rarray, rstrides); + } + } +} + +static mp_obj_t ndarray_from_boolean_index(ndarray_obj_t *ndarray, ndarray_obj_t *index) { + // returns a 1D array, indexed by a Boolean array + if(ndarray->len != index->len) { + mp_raise_ValueError(translate("array and index length must be equal")); + } + uint8_t *iarray = (uint8_t *)index->array; + // first we have to find out how many trues there are + size_t count = 0; + for(size_t i=0; i < index->len; i++) { + count += *iarray; + iarray += index->strides[ULAB_MAX_DIMS - 1]; + } + ndarray_obj_t *results = ndarray_new_linear_array(count, ndarray->dtype); + uint8_t *rarray = (uint8_t *)results->array; + uint8_t *array = (uint8_t *)ndarray->array; + // re-wind the index array + iarray = index->array; + for(size_t i=0; i < index->len; i++) { + if(*iarray) { + memcpy(rarray, array, results->itemsize); + rarray += results->itemsize; + count++; + } + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + iarray += index->strides[ULAB_MAX_DIMS - 1]; + } + return MP_OBJ_FROM_PTR(results); +} + +static mp_obj_t ndarray_assign_from_boolean_index(ndarray_obj_t *ndarray, ndarray_obj_t *index, ndarray_obj_t *values) { + // assigns values to a Boolean-indexed array + // first we have to find out how many trues there are + uint8_t *iarray = (uint8_t *)index->array; + size_t count = 0; + for(size_t i=0; i < index->len; i++) { + count += *iarray; + iarray += index->strides[ULAB_MAX_DIMS - 1]; + } + // re-wind the index array + iarray = index->array; + uint8_t *varray = (uint8_t *)values->array; + size_t vstride; + size_t istride = index->strides[ULAB_MAX_DIMS - 1]; + + if(count == values->len) { + // there are as many values as true indices + vstride = values->strides[ULAB_MAX_DIMS - 1]; + } else { + // there is a single value + vstride = 0; + } + if(ndarray->dtype == NDARRAY_UINT8) { + if(values->dtype == NDARRAY_UINT8) { + BOOLEAN_ASSIGNMENT_LOOP(uint8_t, uint8_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT8) { + BOOLEAN_ASSIGNMENT_LOOP(uint8_t, int8_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_UINT16) { + BOOLEAN_ASSIGNMENT_LOOP(uint8_t, uint16_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT16) { + BOOLEAN_ASSIGNMENT_LOOP(uint8_t, int16_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_FLOAT) { + BOOLEAN_ASSIGNMENT_LOOP(uint8_t, mp_float_t, ndarray, iarray, istride, varray, vstride); + } + } else if(ndarray->dtype == NDARRAY_INT8) { + if(values->dtype == NDARRAY_UINT8) { + BOOLEAN_ASSIGNMENT_LOOP(int8_t, uint8_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT8) { + BOOLEAN_ASSIGNMENT_LOOP(int8_t, int8_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_UINT16) { + BOOLEAN_ASSIGNMENT_LOOP(int8_t, uint16_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT16) { + BOOLEAN_ASSIGNMENT_LOOP(int8_t, int16_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_FLOAT) { + BOOLEAN_ASSIGNMENT_LOOP(int8_t, mp_float_t, ndarray, iarray, istride, varray, vstride); + } + } else if(ndarray->dtype == NDARRAY_UINT16) { + if(values->dtype == NDARRAY_UINT8) { + BOOLEAN_ASSIGNMENT_LOOP(uint16_t, uint8_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT8) { + BOOLEAN_ASSIGNMENT_LOOP(uint16_t, int8_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_UINT16) { + BOOLEAN_ASSIGNMENT_LOOP(uint16_t, uint16_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT16) { + BOOLEAN_ASSIGNMENT_LOOP(uint16_t, int16_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_FLOAT) { + BOOLEAN_ASSIGNMENT_LOOP(uint16_t, mp_float_t, ndarray, iarray, istride, varray, vstride); + } + } else if(ndarray->dtype == NDARRAY_INT16) { + if(values->dtype == NDARRAY_UINT8) { + BOOLEAN_ASSIGNMENT_LOOP(int16_t, uint8_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT8) { + BOOLEAN_ASSIGNMENT_LOOP(int16_t, int8_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_UINT16) { + BOOLEAN_ASSIGNMENT_LOOP(int16_t, uint16_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT16) { + BOOLEAN_ASSIGNMENT_LOOP(int16_t, int16_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_FLOAT) { + BOOLEAN_ASSIGNMENT_LOOP(int16_t, mp_float_t, ndarray, iarray, istride, varray, vstride); + } + } else { + if(values->dtype == NDARRAY_UINT8) { + BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, uint8_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT8) { + BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, int8_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_UINT16) { + BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, uint16_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT16) { + BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, int16_t, ndarray, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_FLOAT) { + BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, mp_float_t, ndarray, iarray, istride, varray, vstride); + } + } + return MP_OBJ_FROM_PTR(ndarray); +} + +static mp_obj_t ndarray_get_slice(ndarray_obj_t *ndarray, mp_obj_t index, ndarray_obj_t *values) { + if(mp_obj_is_type(index, &ulab_ndarray_type)) { + ndarray_obj_t *nindex = MP_OBJ_TO_PTR(index); + if((nindex->ndim > 1) || (nindex->boolean == false)) { + mp_raise_NotImplementedError(translate("operation is implemented for 1D Boolean arrays only")); + } + if(values == NULL) { // return value(s) + return ndarray_from_boolean_index(ndarray, nindex); + } else { // assign value(s) + ndarray_assign_from_boolean_index(ndarray, index, values); + } + } + if(mp_obj_is_type(index, &mp_type_tuple) || mp_obj_is_int(index) || mp_obj_is_type(index, &mp_type_slice)) { + mp_obj_tuple_t *tuple; + if(mp_obj_is_type(index, &mp_type_tuple)) { + tuple = MP_OBJ_TO_PTR(index); + if(tuple->len > ndarray->ndim) { + mp_raise_msg(&mp_type_IndexError, translate("too many indices")); + } + } else { + mp_obj_t *items = m_new(mp_obj_t, 1); + items[0] = index; + tuple = mp_obj_new_tuple(1, items); + } + ndarray_obj_t *view = ndarray_view_from_slices(ndarray, tuple); + if(values == NULL) { // return value(s) + // if the view has been reduced to nothing, return a single value + if(view->ndim == 0) { + return mp_binary_get_val_array(view->dtype, view->array, 0); + } else { + return MP_OBJ_FROM_PTR(view); + } + } else { // assign value(s) + ndarray_assign_view(view, values); + } + } + return mp_const_none; +} + +mp_obj_t ndarray_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (value == MP_OBJ_SENTINEL) { // return value(s) + return ndarray_get_slice(self, index, NULL); + } else { // assignment to slices; the value must be an ndarray, or a scalar + ndarray_obj_t *values = ndarray_from_mp_obj(value, 0); + return ndarray_get_slice(self, index, values); + } + return mp_const_none; +} +#endif /* NDARRAY_IS_SLICEABLE */ + +#if NDARRAY_IS_ITERABLE + +// itarray iterator +mp_obj_t ndarray_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { + return ndarray_new_ndarray_iterator(o_in, iter_buf); +} + +typedef struct _mp_obj_ndarray_it_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + mp_obj_t ndarray; + size_t cur; +} mp_obj_ndarray_it_t; + +mp_obj_t ndarray_iternext(mp_obj_t self_in) { + mp_obj_ndarray_it_t *self = MP_OBJ_TO_PTR(self_in); + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(self->ndarray); + uint8_t *array = (uint8_t *)ndarray->array; + + size_t iter_end = ndarray->shape[ULAB_MAX_DIMS-ndarray->ndim]; + if(self->cur < iter_end) { + // separating this case out saves 50 bytes for 1D arrays + #if ULAB_MAX_DIMS == 1 + array += self->cur * ndarray->strides[0]; + self->cur++; + return ndarray_get_item(ndarray, array); + #else + if(ndarray->ndim == 1) { // we have a linear array + array += self->cur * ndarray->strides[ULAB_MAX_DIMS - 1]; + self->cur++; + return ndarray_get_item(ndarray, array); + } else { // we have a tensor, return the reduced view + size_t offset = self->cur * ndarray->strides[ULAB_MAX_DIMS - ndarray->ndim]; + self->cur++; + return MP_OBJ_FROM_PTR(ndarray_new_view(ndarray, ndarray->ndim-1, ndarray->shape, ndarray->strides, offset)); + } + #endif + } else { + return MP_OBJ_STOP_ITERATION; + } +} + +mp_obj_t ndarray_new_ndarray_iterator(mp_obj_t ndarray, mp_obj_iter_buf_t *iter_buf) { + assert(sizeof(mp_obj_ndarray_it_t) <= sizeof(mp_obj_iter_buf_t)); + mp_obj_ndarray_it_t *o = (mp_obj_ndarray_it_t*)iter_buf; + o->base.type = &mp_type_polymorph_iter; + o->iternext = ndarray_iternext; + o->ndarray = ndarray; + o->cur = 0; + return MP_OBJ_FROM_PTR(o); +} +#endif /* NDARRAY_IS_ITERABLE */ + +#if NDARRAY_HAS_FLATTEN +mp_obj_t ndarray_flatten(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_order, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR_C)} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + ndarray_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + GET_STR_DATA_LEN(args[0].u_obj, order, len); + if((len != 1) || ((memcmp(order, "C", 1) != 0) && (memcmp(order, "F", 1) != 0))) { + mp_raise_ValueError(translate("flattening order must be either 'C', or 'F'")); + } + + uint8_t *sarray = (uint8_t *)self->array; + ndarray_obj_t *ndarray = ndarray_new_linear_array(self->len, self->dtype); + uint8_t *array = (uint8_t *)ndarray->array; + + if(memcmp(order, "C", 1) == 0) { // C-type ordering + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + memcpy(array, sarray, self->itemsize); + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + sarray += self->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < self->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= self->strides[ULAB_MAX_DIMS - 1] * self->shape[ULAB_MAX_DIMS-1]; + sarray += self->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < self->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + sarray -= self->strides[ULAB_MAX_DIMS - 2] * self->shape[ULAB_MAX_DIMS-2]; + sarray += self->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < self->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + sarray -= self->strides[ULAB_MAX_DIMS - 3] * self->shape[ULAB_MAX_DIMS-3]; + sarray += self->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < self->shape[ULAB_MAX_DIMS - 4]); + #endif + } else { // 'F', Fortran-type ordering + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + memcpy(array, sarray, self->itemsize); + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + sarray += self->strides[0]; + l++; + } while(l < self->shape[0]); + #if ULAB_MAX_DIMS > 1 + sarray -= self->strides[0] * self->shape[0]; + sarray += self->strides[1]; + k++; + } while(k < self->shape[1]); + #endif + #if ULAB_MAX_DIMS > 2 + sarray -= self->strides[1] * self->shape[1]; + sarray += self->strides[2]; + j++; + } while(j < self->shape[2]); + #endif + #if ULAB_MAX_DIMS > 3 + sarray -= self->strides[2] * self->shape[2]; + sarray += self->strides[3]; + i++; + } while(i < self->shape[3]); + #endif + } + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(ndarray_flatten_obj, 1, ndarray_flatten); +#endif + +#if NDARRAY_HAS_ITEMSIZE +mp_obj_t ndarray_itemsize(mp_obj_t self_in) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_NEW_SMALL_INT(self->itemsize); +} +#endif + +#if NDARRAY_HAS_SHAPE +mp_obj_t ndarray_shape(mp_obj_t self_in) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t *items = m_new(mp_obj_t, self->ndim); + for(uint8_t i=0; i < self->ndim; i++) { + items[self->ndim - i - 1] = mp_obj_new_int(self->shape[ULAB_MAX_DIMS - i - 1]); + } + mp_obj_t tuple = mp_obj_new_tuple(self->ndim, items); + m_del(mp_obj_t, items, self->ndim); + return tuple; +} +#endif + +#if NDARRAY_HAS_SIZE +mp_obj_t ndarray_size(mp_obj_t self_in) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->len); +} +#endif + +#if NDARRAY_HAS_STRIDES +mp_obj_t ndarray_strides(mp_obj_t self_in) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t *items = m_new(mp_obj_t, self->ndim); + for(int8_t i=0; i < self->ndim; i++) { + items[i] = mp_obj_new_int(self->strides[ULAB_MAX_DIMS - self->ndim + i]); + } + mp_obj_t tuple = mp_obj_new_tuple(self->ndim, items); + m_del(mp_obj_t, items, self->ndim); + return tuple; +} +#endif + +#if NDARRAY_HAS_TOBYTES +mp_obj_t ndarray_tobytes(mp_obj_t self_in) { + // As opposed to numpy, this function returns a bytearray object with the data pointer (i.e., not a copy) + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + // Piping into a bytearray makes sense for dense arrays only, + // so bail out, if that is not the case + if(!ndarray_is_dense(self)) { + mp_raise_ValueError(translate("tobytes can be invoked for dense arrays only")); + } + return mp_obj_new_bytearray_by_ref(self->itemsize * self->len, self->array); +} + +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_tobytes_obj, ndarray_tobytes); +#endif + +// Binary operations +ndarray_obj_t *ndarray_from_mp_obj(mp_obj_t obj, uint8_t other_type) { + // creates an ndarray from a micropython int or float + // if the input is an ndarray, it is returned + // if other_type is 0, return the smallest type that can accommodate the object + ndarray_obj_t *ndarray; + + if(mp_obj_is_int(obj)) { + int32_t ivalue = mp_obj_get_int(obj); + if((ivalue < -32767) || (ivalue > 32767)) { + // the integer value clearly does not fit the ulab integer types, so move on to float + ndarray = ndarray_new_linear_array(1, NDARRAY_FLOAT); + mp_float_t *array = (mp_float_t *)ndarray->array; + array[0] = (mp_float_t)ivalue; + } else { + uint8_t dtype; + if(ivalue < 0) { + if(ivalue > -128) { + dtype = NDARRAY_INT8; + } else { + dtype = NDARRAY_INT16; + } + } else { // ivalue >= 0 + if((other_type == NDARRAY_INT8) || (other_type == NDARRAY_INT16)) { + if(ivalue < 128) { + dtype = NDARRAY_INT8; + } else { + dtype = NDARRAY_INT16; + } + } else { // other_type = 0 is also included here + if(ivalue < 256) { + dtype = NDARRAY_UINT8; + } else { + dtype = NDARRAY_UINT16; + } + } + } + ndarray = ndarray_new_linear_array(1, dtype); + ndarray_set_value(dtype, ndarray->array, 0, obj); + } + } else if(mp_obj_is_float(obj)) { + ndarray = ndarray_new_linear_array(1, NDARRAY_FLOAT); + mp_float_t *array = (mp_float_t *)ndarray->array; + array[0] = mp_obj_get_float(obj); + } else if(mp_obj_is_type(obj, &ulab_ndarray_type)){ + return obj; + } else { + // assume that the input is an iterable (raises an exception, if it is not the case) + ndarray = ndarray_from_iterable(obj, NDARRAY_FLOAT); + } + return ndarray; +} + +#if NDARRAY_HAS_BINARY_OPS || NDARRAY_HAS_INPLACE_OPS +mp_obj_t ndarray_binary_op(mp_binary_op_t _op, mp_obj_t lobj, mp_obj_t robj) { + // TODO: implement in-place operators + // if the ndarray stands on the right hand side of the expression, simply swap the operands + ndarray_obj_t *lhs, *rhs; + mp_binary_op_t op = _op; + if((op == MP_BINARY_OP_REVERSE_ADD) || (op == MP_BINARY_OP_REVERSE_MULTIPLY) || + (op == MP_BINARY_OP_REVERSE_POWER) || (op == MP_BINARY_OP_REVERSE_SUBTRACT) || + (op == MP_BINARY_OP_REVERSE_TRUE_DIVIDE)) { + lhs = ndarray_from_mp_obj(robj, 0); + rhs = ndarray_from_mp_obj(lobj, lhs->dtype); + } else { + lhs = ndarray_from_mp_obj(lobj, 0); + rhs = ndarray_from_mp_obj(robj, lhs->dtype); + } + if(op == MP_BINARY_OP_REVERSE_ADD) { + op = MP_BINARY_OP_ADD; + } else if(op == MP_BINARY_OP_REVERSE_MULTIPLY) { + op = MP_BINARY_OP_MULTIPLY; + } else if(op == MP_BINARY_OP_REVERSE_POWER) { + op = MP_BINARY_OP_POWER; + } else if(op == MP_BINARY_OP_REVERSE_SUBTRACT) { + op = MP_BINARY_OP_SUBTRACT; + } else if(op == MP_BINARY_OP_REVERSE_TRUE_DIVIDE) { + op = MP_BINARY_OP_TRUE_DIVIDE; + } + + uint8_t ndim = 0; + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + int32_t *lstrides = m_new(int32_t, ULAB_MAX_DIMS); + int32_t *rstrides = m_new(int32_t, ULAB_MAX_DIMS); + uint8_t broadcastable; + if((op == MP_BINARY_OP_INPLACE_ADD) || (op == MP_BINARY_OP_INPLACE_MULTIPLY) || (op == MP_BINARY_OP_INPLACE_POWER) || + (op == MP_BINARY_OP_INPLACE_SUBTRACT) || (op == MP_BINARY_OP_INPLACE_TRUE_DIVIDE)) { + broadcastable = ndarray_can_broadcast_inplace(lhs, rhs, rstrides); + } else { + broadcastable = ndarray_can_broadcast(lhs, rhs, &ndim, shape, lstrides, rstrides); + } + if(!broadcastable) { + mp_raise_ValueError(translate("operands could not be broadcast together")); + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(int32_t, lstrides, ULAB_MAX_DIMS); + m_del(int32_t, rstrides, ULAB_MAX_DIMS); + } + // the empty arrays have to be treated separately + uint8_t dtype = NDARRAY_INT16; + ndarray_obj_t *nd; + if((lhs->ndim == 0) || (rhs->ndim == 0)) { + switch(op) { + case MP_BINARY_OP_INPLACE_ADD: + case MP_BINARY_OP_INPLACE_MULTIPLY: + case MP_BINARY_OP_INPLACE_SUBTRACT: + case MP_BINARY_OP_ADD: + case MP_BINARY_OP_MULTIPLY: + case MP_BINARY_OP_SUBTRACT: + // here we don't have to list those cases that result in an int16, + // because dtype is initialised with that NDARRAY_INT16 + if(lhs->dtype == rhs->dtype) { + dtype = rhs->dtype; + } else if((lhs->dtype == NDARRAY_FLOAT) || (rhs->dtype == NDARRAY_FLOAT)) { + dtype = NDARRAY_FLOAT; + } else if(((lhs->dtype == NDARRAY_UINT8) && (rhs->dtype == NDARRAY_UINT16)) || + ((lhs->dtype == NDARRAY_INT8) && (rhs->dtype == NDARRAY_UINT16)) || + ((rhs->dtype == NDARRAY_UINT8) && (lhs->dtype == NDARRAY_UINT16)) || + ((rhs->dtype == NDARRAY_INT8) && (lhs->dtype == NDARRAY_UINT16))) { + dtype = NDARRAY_UINT16; + } + return MP_OBJ_FROM_PTR(ndarray_new_linear_array(0, dtype)); + break; + + case MP_BINARY_OP_INPLACE_POWER: + case MP_BINARY_OP_INPLACE_TRUE_DIVIDE: + case MP_BINARY_OP_POWER: + case MP_BINARY_OP_TRUE_DIVIDE: + return MP_OBJ_FROM_PTR(ndarray_new_linear_array(0, NDARRAY_FLOAT)); + break; + + case MP_BINARY_OP_LESS: + case MP_BINARY_OP_LESS_EQUAL: + case MP_BINARY_OP_MORE: + case MP_BINARY_OP_MORE_EQUAL: + case MP_BINARY_OP_EQUAL: + case MP_BINARY_OP_NOT_EQUAL: + nd = ndarray_new_linear_array(0, NDARRAY_UINT8); + nd->boolean = true; + return MP_OBJ_FROM_PTR(nd); + + default: + return mp_const_none; + break; + } + } + + switch(op) { + // first the in-place operators + #if NDARRAY_HAS_INPLACE_ADD + case MP_BINARY_OP_INPLACE_ADD: + return ndarray_inplace_ams(lhs, rhs, rstrides, op); + break; + #endif + #if NDARRAY_HAS_INPLACE_MULTIPLY + case MP_BINARY_OP_INPLACE_MULTIPLY: + return ndarray_inplace_ams(lhs, rhs, rstrides, op); + break; + #endif + #if NDARRAY_HAS_INPLACE_POWER + case MP_BINARY_OP_INPLACE_POWER: + return ndarray_inplace_power(lhs, rhs, rstrides); + break; + #endif + #if NDARRAY_HAS_INPLACE_SUBTRACT + case MP_BINARY_OP_INPLACE_SUBTRACT: + return ndarray_inplace_ams(lhs, rhs, rstrides, op); + break; + #endif + #if NDARRAY_HAS_INPLACE_TRUE_DIVIDE + case MP_BINARY_OP_INPLACE_TRUE_DIVIDE: + return ndarray_inplace_divide(lhs, rhs, rstrides); + break; + #endif + // end if in-place operators + + #if NDARRAY_HAS_BINARY_OP_LESS + case MP_BINARY_OP_LESS: + // here we simply swap the operands + return ndarray_binary_more(rhs, lhs, ndim, shape, rstrides, lstrides, MP_BINARY_OP_MORE); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_LESS_EQUAL + case MP_BINARY_OP_LESS_EQUAL: + // here we simply swap the operands + return ndarray_binary_more(rhs, lhs, ndim, shape, rstrides, lstrides, MP_BINARY_OP_MORE_EQUAL); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_EQUAL + case MP_BINARY_OP_EQUAL: + return ndarray_binary_equality(lhs, rhs, ndim, shape, lstrides, rstrides, MP_BINARY_OP_EQUAL); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_NOT_EQUAL + case MP_BINARY_OP_NOT_EQUAL: + return ndarray_binary_equality(lhs, rhs, ndim, shape, lstrides, rstrides, MP_BINARY_OP_NOT_EQUAL); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_ADD + case MP_BINARY_OP_ADD: + return ndarray_binary_add(lhs, rhs, ndim, shape, lstrides, rstrides); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_MULTIPLY + case MP_BINARY_OP_MULTIPLY: + return ndarray_binary_multiply(lhs, rhs, ndim, shape, lstrides, rstrides); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_MORE + case MP_BINARY_OP_MORE: + return ndarray_binary_more(lhs, rhs, ndim, shape, lstrides, rstrides, MP_BINARY_OP_MORE); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_MORE_EQUAL + case MP_BINARY_OP_MORE_EQUAL: + return ndarray_binary_more(lhs, rhs, ndim, shape, lstrides, rstrides, MP_BINARY_OP_MORE_EQUAL); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_SUBTRACT + case MP_BINARY_OP_SUBTRACT: + return ndarray_binary_subtract(lhs, rhs, ndim, shape, lstrides, rstrides); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_TRUE_DIVIDE + case MP_BINARY_OP_TRUE_DIVIDE: + return ndarray_binary_true_divide(lhs, rhs, ndim, shape, lstrides, rstrides); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_POWER + case MP_BINARY_OP_POWER: + return ndarray_binary_power(lhs, rhs, ndim, shape, lstrides, rstrides); + break; + #endif + default: + return MP_OBJ_NULL; // op not supported + break; + } + return MP_OBJ_NULL; +} +#endif /* NDARRAY_HAS_BINARY_OPS || NDARRAY_HAS_INPLACE_OPS */ + +#if NDARRAY_HAS_UNARY_OPS +mp_obj_t ndarray_unary_op(mp_unary_op_t op, mp_obj_t self_in) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + ndarray_obj_t *ndarray = NULL; + + switch (op) { + #if NDARRAY_HAS_UNARY_OP_ABS + case MP_UNARY_OP_ABS: + ndarray = ndarray_copy_view(self); + // if Boolean, NDARRAY_UINT8, or NDARRAY_UINT16, there is nothing to do + if(self->dtype == NDARRAY_INT8) { + int8_t *array = (int8_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) { + if(*array < 0) *array = -(*array); + } + } else if(self->dtype == NDARRAY_INT16) { + int16_t *array = (int16_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) { + if(*array < 0) *array = -(*array); + } + } else { + mp_float_t *array = (mp_float_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) { + if(*array < 0) *array = -(*array); + } + } + return MP_OBJ_FROM_PTR(ndarray); + break; + #endif + #if NDARRAY_HAS_UNARY_OP_INVERT + case MP_UNARY_OP_INVERT: + if(self->dtype == NDARRAY_FLOAT) { + mp_raise_ValueError(translate("operation is not supported for given type")); + } + // we can invert the content byte by byte, no need to distinguish between different dtypes + ndarray = ndarray_copy_view(self); // from this point, this is a dense copy + uint8_t *array = (uint8_t *)ndarray->array; + if(ndarray->boolean) { + for(size_t i=0; i < ndarray->len; i++, array++) *array = *array ^ 0x01; + } else { + uint8_t itemsize = mp_binary_get_size('@', self->dtype, NULL); + for(size_t i=0; i < ndarray->len*itemsize; i++, array++) *array ^= 0xFF; + } + return MP_OBJ_FROM_PTR(ndarray); + break; + #endif + #if NDARRAY_HAS_UNARY_OP_LEN + case MP_UNARY_OP_LEN: + return mp_obj_new_int(self->shape[ULAB_MAX_DIMS - self->ndim]); + break; + #endif + #if NDARRAY_HAS_UNARY_OP_NEGATIVE + case MP_UNARY_OP_NEGATIVE: + ndarray = ndarray_copy_view(self); // from this point, this is a dense copy + if(self->dtype == NDARRAY_UINT8) { + uint8_t *array = (uint8_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) *array = -(*array); + } else if(self->dtype == NDARRAY_INT8) { + int8_t *array = (int8_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) *array = -(*array); + } else if(self->dtype == NDARRAY_UINT16) { + uint16_t *array = (uint16_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) *array = -(*array); + } else if(self->dtype == NDARRAY_INT16) { + int16_t *array = (int16_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) *array = -(*array); + } else { + mp_float_t *array = (mp_float_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) *array = -(*array); + } + return MP_OBJ_FROM_PTR(ndarray); + break; + #endif + #if NDARRAY_HAS_UNARY_OP_POSITIVE + case MP_UNARY_OP_POSITIVE: + return MP_OBJ_FROM_PTR(ndarray_copy_view(self)); + #endif + + default: + return MP_OBJ_NULL; // operator not supported + break; + } +} +#endif /* NDARRAY_HAS_UNARY_OPS */ + +#if NDARRAY_HAS_TRANSPOSE +mp_obj_t ndarray_transpose(mp_obj_t self_in) { + #if ULAB_MAX_DIMS == 1 + return self_in; + #endif + // TODO: check, what happens to the offset here, if we have a view + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + if(self->ndim == 1) { + return self_in; + } + size_t *shape = m_new(size_t, self->ndim); + int32_t *strides = m_new(int32_t, self->ndim); + for(uint8_t i=0; i < self->ndim; i++) { + shape[ULAB_MAX_DIMS - 1 - i] = self->shape[ULAB_MAX_DIMS - self->ndim + i]; + strides[ULAB_MAX_DIMS - 1 - i] = self->strides[ULAB_MAX_DIMS - self->ndim + i]; + } + // TODO: I am not sure ndarray_new_view is OK here... + // should be deep copy... + ndarray_obj_t *ndarray = ndarray_new_view(self, self->ndim, shape, strides, 0); + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_transpose_obj, ndarray_transpose); +#endif /* NDARRAY_HAS_TRANSPOSE */ + +#if ULAB_MAX_DIMS > 1 +#if NDARRAY_HAS_RESHAPE +mp_obj_t ndarray_reshape_core(mp_obj_t oin, mp_obj_t _shape, bool inplace) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(oin); + if(!mp_obj_is_type(_shape, &mp_type_tuple)) { + mp_raise_TypeError(translate("shape must be a tuple")); + } + + mp_obj_tuple_t *shape = MP_OBJ_TO_PTR(_shape); + if(shape->len > ULAB_MAX_DIMS) { + mp_raise_ValueError(translate("maximum number of dimensions is 4")); + } + size_t *new_shape = m_new(size_t, ULAB_MAX_DIMS); + memset(new_shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); + size_t new_length = 1; + for(uint8_t i=0; i < shape->len; i++) { + new_shape[ULAB_MAX_DIMS - i - 1] = mp_obj_get_int(shape->items[shape->len - i - 1]); + new_length *= new_shape[ULAB_MAX_DIMS - i - 1]; + } + if(source->len != new_length) { + mp_raise_ValueError(translate("input and output shapes are not compatible")); + } + ndarray_obj_t *ndarray; + if(ndarray_is_dense(source)) { + int32_t *new_strides = strides_from_shape(new_shape, source->dtype); + if(inplace) { + for(uint8_t i = 0; i < ULAB_MAX_DIMS; i++) { + source->shape[i] = new_shape[i]; + source->strides[i] = new_strides[i]; + } + return MP_OBJ_FROM_PTR(oin); + } else { + ndarray = ndarray_new_view(source, shape->len, new_shape, new_strides, 0); + } + } else { + if(inplace) { + mp_raise_ValueError(translate("cannot assign new shape")); + } + ndarray = ndarray_new_ndarray_from_tuple(shape, source->dtype); + ndarray_copy_array(source, ndarray); + } + return MP_OBJ_FROM_PTR(ndarray); +} + +mp_obj_t ndarray_reshape(mp_obj_t oin, mp_obj_t _shape) { + return ndarray_reshape_core(oin, _shape, 0); +} + +MP_DEFINE_CONST_FUN_OBJ_2(ndarray_reshape_obj, ndarray_reshape); +#endif /* NDARRAY_HAS_RESHAPE */ +#endif /* ULAB_MAX_DIMS > 1 */ + +#if ULAB_NUMPY_HAS_NDINFO +mp_obj_t ndarray_info(mp_obj_t obj_in) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(obj_in); + if(!mp_obj_is_type(ndarray, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("function is defined for ndarrays only")); + } + mp_printf(MP_PYTHON_PRINTER, "class: ndarray\n"); + mp_printf(MP_PYTHON_PRINTER, "shape: ("); + if(ndarray->ndim == 1) { + mp_printf(MP_PYTHON_PRINTER, "%d,", ndarray->shape[ULAB_MAX_DIMS-1]); + } else { + for(uint8_t i=0; i < ndarray->ndim-1; i++) mp_printf(MP_PYTHON_PRINTER, "%d, ", ndarray->shape[i]); + mp_printf(MP_PYTHON_PRINTER, "%d", ndarray->shape[ULAB_MAX_DIMS-1]); + } + mp_printf(MP_PYTHON_PRINTER, ")\n"); + mp_printf(MP_PYTHON_PRINTER, "strides: ("); + if(ndarray->ndim == 1) { + mp_printf(MP_PYTHON_PRINTER, "%d,", ndarray->strides[ULAB_MAX_DIMS-1]); + } else { + for(uint8_t i=0; i < ndarray->ndim-1; i++) mp_printf(MP_PYTHON_PRINTER, "%d, ", ndarray->strides[i]); + mp_printf(MP_PYTHON_PRINTER, "%d", ndarray->strides[ULAB_MAX_DIMS-1]); + } + mp_printf(MP_PYTHON_PRINTER, ")\n"); + mp_printf(MP_PYTHON_PRINTER, "itemsize: %d\n", ndarray->itemsize); + mp_printf(MP_PYTHON_PRINTER, "data pointer: 0x%p\n", ndarray->array); + mp_printf(MP_PYTHON_PRINTER, "type: "); + if(ndarray->boolean) { + mp_printf(MP_PYTHON_PRINTER, "bool\n"); + } else if(ndarray->dtype == NDARRAY_UINT8) { + mp_printf(MP_PYTHON_PRINTER, "uint8\n"); + } else if(ndarray->dtype == NDARRAY_INT8) { + mp_printf(MP_PYTHON_PRINTER, "int8\n"); + } else if(ndarray->dtype == NDARRAY_UINT16) { + mp_printf(MP_PYTHON_PRINTER, "uint16\n"); + } else if(ndarray->dtype == NDARRAY_INT16) { + mp_printf(MP_PYTHON_PRINTER, "int16\n"); + } else if(ndarray->dtype == NDARRAY_FLOAT) { + mp_printf(MP_PYTHON_PRINTER, "float\n"); + } + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_info_obj, ndarray_info); +#endif + +// (the get_buffer protocol returns 0 for success, 1 for failure) +mp_int_t ndarray_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + if(!ndarray_is_dense(self)) { + return 1; + } + bufinfo->len = self->itemsize * self->len; + bufinfo->buf = self->array; + bufinfo->typecode = self->dtype; + return 0; +} diff --git a/python/port/mod/ulab/ndarray.h b/python/port/mod/ulab/ndarray.h new file mode 100644 index 00000000000..aa7f499af01 --- /dev/null +++ b/python/port/mod/ulab/ndarray.h @@ -0,0 +1,736 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Jeff Epler for Adafruit Industries +*/ + +#ifndef _NDARRAY_ +#define _NDARRAY_ + +#include "py/objarray.h" +#include "py/binary.h" +#include "py/objstr.h" +#include "py/objlist.h" + +#include "ulab.h" + +#ifndef MP_PI +#define MP_PI MICROPY_FLOAT_CONST(3.14159265358979323846) +#endif +#ifndef MP_E +#define MP_E MICROPY_FLOAT_CONST(2.71828182845904523536) +#endif + +#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT +#define FLOAT_TYPECODE 'f' +#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE +#define FLOAT_TYPECODE 'd' +#endif + +// this typedef is lifted from objfloat.c, because mp_obj_float_t is not exposed +typedef struct _mp_obj_float_t { + mp_obj_base_t base; + mp_float_t value; +} mp_obj_float_t; + +#if defined(MICROPY_VERSION_MAJOR) && MICROPY_VERSION_MAJOR == 1 && MICROPY_VERSION_MINOR == 12 +typedef struct _mp_obj_slice_t { + mp_obj_base_t base; + mp_obj_t start; + mp_obj_t stop; + mp_obj_t step; +} mp_obj_slice_t; +#define MP_ERROR_TEXT(x) x +#endif + +#if !CIRCUITPY +#define translate(x) MP_ERROR_TEXT(x) +#define ndarray_set_value(a, b, c, d) mp_binary_set_val_array(a, b, c, d) +#else +void ndarray_set_value(char , void *, size_t , mp_obj_t ); +#endif + +#define NDARRAY_NUMERIC 0 +#define NDARRAY_BOOLEAN 1 + +#define NDARRAY_NDARRAY_TYPE 1 +#define NDARRAY_ITERABLE_TYPE 2 + +extern const mp_obj_type_t ulab_ndarray_type; + +enum NDARRAY_TYPE { + NDARRAY_BOOL = '?', // this must never be assigned to the dtype! + NDARRAY_UINT8 = 'B', + NDARRAY_INT8 = 'b', + NDARRAY_UINT16 = 'H', + NDARRAY_INT16 = 'h', + NDARRAY_FLOAT = FLOAT_TYPECODE, +}; + +typedef struct _ndarray_obj_t { + mp_obj_base_t base; + uint8_t dtype; + uint8_t itemsize; + uint8_t boolean; + uint8_t ndim; + size_t len; + size_t shape[ULAB_MAX_DIMS]; + int32_t strides[ULAB_MAX_DIMS]; + void *array; + void *origin; +} ndarray_obj_t; + +#if ULAB_HAS_DTYPE_OBJECT +extern const mp_obj_type_t ulab_dtype_type; + +typedef struct _dtype_obj_t { + mp_obj_base_t base; + uint8_t dtype; +} dtype_obj_t; + +void ndarray_dtype_print(const mp_print_t *, mp_obj_t , mp_print_kind_t ); + +#ifdef CIRCUITPY +mp_obj_t ndarray_dtype_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args); +#else +mp_obj_t ndarray_dtype_make_new(const mp_obj_type_t *, size_t , size_t , const mp_obj_t *); +#endif /* CIRCUITPY */ +#endif /* ULAB_HAS_DTYPE_OBJECT */ + +mp_obj_t ndarray_new_ndarray_iterator(mp_obj_t , mp_obj_iter_buf_t *); + +mp_float_t ndarray_get_float_value(void *, uint8_t ); +mp_float_t ndarray_get_float_index(void *, uint8_t , size_t ); +bool ndarray_object_is_array_like(mp_obj_t ); +void fill_array_iterable(mp_float_t *, mp_obj_t ); +size_t *ndarray_shape_vector(size_t , size_t , size_t , size_t ); + +void ndarray_print(const mp_print_t *, mp_obj_t , mp_print_kind_t ); + +#if ULAB_HAS_PRINTOPTIONS +mp_obj_t ndarray_set_printoptions(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(ndarray_set_printoptions_obj); + +mp_obj_t ndarray_get_printoptions(void); +MP_DECLARE_CONST_FUN_OBJ_0(ndarray_get_printoptions_obj); +#endif + +void ndarray_assign_elements(ndarray_obj_t *, mp_obj_t , uint8_t , size_t *); +size_t *ndarray_contract_shape(ndarray_obj_t *, uint8_t ); +int32_t *ndarray_contract_strides(ndarray_obj_t *, uint8_t ); + +ndarray_obj_t *ndarray_new_dense_ndarray(uint8_t , size_t *, uint8_t ); +ndarray_obj_t *ndarray_new_ndarray_from_tuple(mp_obj_tuple_t *, uint8_t ); +ndarray_obj_t *ndarray_new_ndarray(uint8_t , size_t *, int32_t *, uint8_t ); +ndarray_obj_t *ndarray_new_linear_array(size_t , uint8_t ); +ndarray_obj_t *ndarray_new_view(ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t ); +bool ndarray_is_dense(ndarray_obj_t *); +ndarray_obj_t *ndarray_copy_view(ndarray_obj_t *); +void ndarray_copy_array(ndarray_obj_t *, ndarray_obj_t *); + +MP_DECLARE_CONST_FUN_OBJ_KW(ndarray_array_constructor_obj); +#ifdef CIRCUITPY +mp_obj_t ndarray_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args); +#else +mp_obj_t ndarray_make_new(const mp_obj_type_t *, size_t , size_t , const mp_obj_t *); +#endif +mp_obj_t ndarray_subscr(mp_obj_t , mp_obj_t , mp_obj_t ); +mp_obj_t ndarray_getiter(mp_obj_t , mp_obj_iter_buf_t *); +bool ndarray_can_broadcast(ndarray_obj_t *, ndarray_obj_t *, uint8_t *, size_t *, int32_t *, int32_t *); +bool ndarray_can_broadcast_inplace(ndarray_obj_t *, ndarray_obj_t *, int32_t *); +mp_obj_t ndarray_binary_op(mp_binary_op_t , mp_obj_t , mp_obj_t ); +mp_obj_t ndarray_unary_op(mp_unary_op_t , mp_obj_t ); + +size_t *ndarray_new_coords(uint8_t ); +void ndarray_rewind_array(uint8_t , uint8_t *, size_t *, int32_t *, size_t *); + +// various ndarray methods +#if NDARRAY_HAS_BYTESWAP +mp_obj_t ndarray_byteswap(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(ndarray_byteswap_obj); +#endif + +#if NDARRAY_HAS_COPY +mp_obj_t ndarray_copy(mp_obj_t ); +MP_DECLARE_CONST_FUN_OBJ_1(ndarray_copy_obj); +#endif + +#if NDARRAY_HAS_FLATTEN +mp_obj_t ndarray_flatten(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(ndarray_flatten_obj); +#endif + +mp_obj_t ndarray_dtype(mp_obj_t ); +mp_obj_t ndarray_itemsize(mp_obj_t ); +mp_obj_t ndarray_size(mp_obj_t ); +mp_obj_t ndarray_shape(mp_obj_t ); +mp_obj_t ndarray_strides(mp_obj_t ); + +#if NDARRAY_HAS_RESHAPE +mp_obj_t ndarray_reshape_core(mp_obj_t , mp_obj_t , bool ); +mp_obj_t ndarray_reshape(mp_obj_t , mp_obj_t ); +MP_DECLARE_CONST_FUN_OBJ_2(ndarray_reshape_obj); +#endif + +#if NDARRAY_HAS_TOBYTES +mp_obj_t ndarray_tobytes(mp_obj_t ); +MP_DECLARE_CONST_FUN_OBJ_1(ndarray_tobytes_obj); +#endif + +#if NDARRAY_HAS_TRANSPOSE +mp_obj_t ndarray_transpose(mp_obj_t ); +MP_DECLARE_CONST_FUN_OBJ_1(ndarray_transpose_obj); +#endif + +#if ULAB_NUMPY_HAS_NDINFO +mp_obj_t ndarray_info(mp_obj_t ); +MP_DECLARE_CONST_FUN_OBJ_1(ndarray_info_obj); +#endif + +mp_int_t ndarray_get_buffer(mp_obj_t , mp_buffer_info_t *, mp_uint_t ); +//void ndarray_attributes(mp_obj_t , qstr , mp_obj_t *); + +ndarray_obj_t *ndarray_from_mp_obj(mp_obj_t , uint8_t ); + + +#define BOOLEAN_ASSIGNMENT_LOOP(type_left, type_right, ndarray, iarray, istride, varray, vstride)\ + type_left *array = (type_left *)(ndarray)->array;\ + for(size_t i=0; i < (ndarray)->len; i++) {\ + if(*(iarray)) {\ + *array = (type_left)(*((type_right *)(varray)));\ + }\ + array += (ndarray)->strides[ULAB_MAX_DIMS - 1] / (ndarray)->itemsize;\ + (iarray) += (istride);\ + (varray) += (vstride);\ + } while(0) + +#if ULAB_HAS_FUNCTION_ITERATOR +#define BINARY_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + type_out *array = (type_out *)(results)->array;\ + size_t *lcoords = ndarray_new_coords((results)->ndim);\ + size_t *rcoords = ndarray_new_coords((results)->ndim);\ + for(size_t i=0; i < (results)->len/(results)->shape[ULAB_MAX_DIMS -1]; i++) {\ + size_t l = 0;\ + do {\ + *array++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + ndarray_rewind_array((results)->ndim, (larray), (results)->shape, (lstrides), lcoords);\ + ndarray_rewind_array((results)->ndim, (rarray), (results)->shape, (rstrides), rcoords);\ + } while(0) + +#define INPLACE_LOOP(results, type_left, type_right, larray, rarray, rstrides, OPERATOR)\ + size_t *lcoords = ndarray_new_coords((results)->ndim);\ + size_t *rcoords = ndarray_new_coords((results)->ndim);\ + for(size_t i=0; i < (results)->len/(results)->shape[ULAB_MAX_DIMS -1]; i++) {\ + size_t l = 0;\ + do {\ + *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + ndarray_rewind_array((results)->ndim, (larray), (results)->shape, (results)->strides, lcoords);\ + ndarray_rewind_array((results)->ndim, (rarray), (results)->shape, (rstrides), rcoords);\ + } while(0) + +#define EQUALITY_LOOP(results, array, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t *lcoords = ndarray_new_coords((results)->ndim);\ + size_t *rcoords = ndarray_new_coords((results)->ndim);\ + for(size_t i=0; i < (results)->len/(results)->shape[ULAB_MAX_DIMS -1]; i++) {\ + size_t l = 0;\ + do {\ + *(array)++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? 1 : 0;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + ndarray_rewind_array((results)->ndim, (larray), (results)->shape, (lstrides), lcoords);\ + ndarray_rewind_array((results)->ndim, (rarray), (results)->shape, (rstrides), rcoords);\ + } while(0) + +#define POWER_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides)\ + type_out *array = (type_out *)(results)->array;\ + size_t *lcoords = ndarray_new_coords((results)->ndim);\ + size_t *rcoords = ndarray_new_coords((results)->ndim);\ + for(size_t i=0; i < (results)->len/(results)->shape[ULAB_MAX_DIMS -1]; i++) {\ + size_t l = 0;\ + do {\ + *array++ = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + ndarray_rewind_array((results)->ndim, (larray), (results)->shape, (lstrides), lcoords);\ + ndarray_rewind_array((results)->ndim, (rarray), (results)->shape, (rstrides), rcoords);\ + } while(0) + +#else + +#if ULAB_MAX_DIMS == 1 +#define BINARY_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + type_out *array = (type_out *)results->array;\ + size_t l = 0;\ + do {\ + *array++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + +#define INPLACE_LOOP(results, type_left, type_right, larray, rarray, rstrides, OPERATOR)\ + size_t l = 0;\ + do {\ + *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + +#define EQUALITY_LOOP(results, array, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t l = 0;\ + do {\ + *(array)++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? 1 : 0;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + +#define POWER_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides)\ + type_out *array = (type_out *)results->array;\ + size_t l = 0;\ + do {\ + *array++ = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + +#endif /* ULAB_MAX_DIMS == 1 */ + +#if ULAB_MAX_DIMS == 2 +#define BINARY_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + type_out *array = (type_out *)(results)->array;\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *array++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + +#define INPLACE_LOOP(results, type_left, type_right, larray, rarray, rstrides, OPERATOR)\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + +#define EQUALITY_LOOP(results, array, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *(array)++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? 1 : 0;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + +#define POWER_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides)\ + type_out *array = (type_out *)(results)->array;\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *array++ = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + +#endif /* ULAB_MAX_DIMS == 2 */ + +#if ULAB_MAX_DIMS == 3 +#define BINARY_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + type_out *array = (type_out *)results->array;\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *array++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + +#define INPLACE_LOOP(results, type_left, type_right, larray, rarray, rstrides, OPERATOR)\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + +#define EQUALITY_LOOP(results, array, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *(array)++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? 1 : 0;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + +#define POWER_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides)\ + type_out *array = (type_out *)results->array;\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *array++ = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + +#endif /* ULAB_MAX_DIMS == 3 */ + +#if ULAB_MAX_DIMS == 4 +#define BINARY_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + type_out *array = (type_out *)results->array;\ + size_t i = 0;\ + do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *array++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ + +#define INPLACE_LOOP(results, type_left, type_right, larray, rarray, rstrides, OPERATOR)\ + size_t i = 0;\ + do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ + +#define EQUALITY_LOOP(results, array, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t i = 0;\ + do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *(array)++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? 1 : 0;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ + +#define POWER_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides)\ + type_out *array = (type_out *)results->array;\ + size_t i = 0;\ + do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *array++ = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ + +#endif /* ULAB_MAX_DIMS == 4 */ +#endif /* ULAB_HAS_FUNCTION_ITERATOR */ + + +#if ULAB_MAX_DIMS == 1 +#define ASSIGNMENT_LOOP(results, type_left, type_right, lstrides, rarray, rstrides)\ + type_left *larray = (type_left *)(results)->array;\ + size_t l = 0;\ + do {\ + *larray = (type_left)(*((type_right *)(rarray)));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + +#endif /* ULAB_MAX_DIMS == 1 */ + +#if ULAB_MAX_DIMS == 2 +#define ASSIGNMENT_LOOP(results, type_left, type_right, lstrides, rarray, rstrides)\ + type_left *larray = (type_left *)(results)->array;\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *larray = (type_left)(*((type_right *)(rarray)));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + +#endif /* ULAB_MAX_DIMS == 2 */ + +#if ULAB_MAX_DIMS == 3 +#define ASSIGNMENT_LOOP(results, type_left, type_right, lstrides, rarray, rstrides)\ + type_left *larray = (type_left *)(results)->array;\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *larray = (type_left)(*((type_right *)(rarray)));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + +#endif /* ULAB_MAX_DIMS == 3 */ + +#if ULAB_MAX_DIMS == 4 +#define ASSIGNMENT_LOOP(results, type_left, type_right, lstrides, rarray, rstrides)\ + type_left *larray = (type_left *)(results)->array;\ + size_t i = 0;\ + do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *larray = (type_left)(*((type_right *)(rarray)));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ + +#endif /* ULAB_MAX_DIMS == 4 */ + +#endif diff --git a/python/port/mod/ulab/ndarray_operators.c b/python/port/mod/ulab/ndarray_operators.c new file mode 100644 index 00000000000..ac238605307 --- /dev/null +++ b/python/port/mod/ulab/ndarray_operators.c @@ -0,0 +1,807 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + + +#include + +#include +#include +#include "ndarray.h" +#include "ndarray_operators.h" +#include "ulab.h" +#include "ulab_tools.h" + +/* + This file contains the actual implementations of the various + ndarray operators. + + These are the upcasting rules of the binary operators + + - if one of the operarands is a float, the result is always float + - operation on identical types preserves type + + uint8 + int8 => int16 + uint8 + int16 => int16 + uint8 + uint16 => uint16 + int8 + int16 => int16 + int8 + uint16 => uint16 + uint16 + int16 => float +*/ + +#if NDARRAY_HAS_BINARY_OP_EQUAL | NDARRAY_HAS_BINARY_OP_NOT_EQUAL +mp_obj_t ndarray_binary_equality(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides, mp_binary_op_t op) { + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT8); + results->boolean = 1; + uint8_t *array = (uint8_t *)results->array; + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + #if NDARRAY_HAS_BINARY_OP_EQUAL + if(op == MP_BINARY_OP_EQUAL) { + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, uint8_t, int8_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, uint8_t, int16_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, ==); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, int8_t, int8_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, int8_t, uint16_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, int8_t, int16_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, ==); + } else { + return ndarray_binary_op(op, rhs, lhs); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, uint16_t, int16_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, ==); + } else { + return ndarray_binary_op(op, rhs, lhs); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, int16_t, int16_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, int16_t, mp_float_t, larray, lstrides, rarray, rstrides, ==); + } else { + return ndarray_binary_op(op, rhs, lhs); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, ==); + } else { + return ndarray_binary_op(op, rhs, lhs); + } + } + } + #endif /* NDARRAY_HAS_BINARY_OP_EQUAL */ + + #if NDARRAY_HAS_BINARY_OP_NOT_EQUAL + if(op == MP_BINARY_OP_NOT_EQUAL) { + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, uint8_t, int8_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, uint8_t, int16_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, !=); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, int8_t, int8_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, int8_t, uint16_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, int8_t, int16_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, !=); + } else { + return ndarray_binary_op(op, rhs, lhs); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, uint16_t, int16_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, !=); + } else { + return ndarray_binary_op(op, rhs, lhs); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, int16_t, int16_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, int16_t, mp_float_t, larray, lstrides, rarray, rstrides, !=); + } else { + return ndarray_binary_op(op, rhs, lhs); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, !=); + } else { + return ndarray_binary_op(op, rhs, lhs); + } + } + } + #endif /* NDARRAY_HAS_BINARY_OP_NOT_EQUAL */ + + return MP_OBJ_FROM_PTR(results); +} +#endif /* NDARRAY_HAS_BINARY_OP_EQUAL | NDARRAY_HAS_BINARY_OP_NOT_EQUAL */ + +#if NDARRAY_HAS_BINARY_OP_ADD +mp_obj_t ndarray_binary_add(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + ndarray_obj_t *results = NULL; + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, uint8_t, int8_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, uint8_t, int16_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, +); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT8); + BINARY_LOOP(results, int8_t, int8_t, int8_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int8_t, uint16_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int8_t, int16_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, +); + } else { + return ndarray_binary_op(MP_BINARY_OP_ADD, rhs, lhs); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint16_t, int16_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, +); + } else { + return ndarray_binary_op(MP_BINARY_OP_ADD, rhs, lhs); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int16_t, int16_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, int16_t, mp_float_t, larray, lstrides, rarray, rstrides, +); + } else { + return ndarray_binary_op(MP_BINARY_OP_ADD, rhs, lhs); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, +); + } else { + return ndarray_binary_op(MP_BINARY_OP_ADD, rhs, lhs); + } + } + + return MP_OBJ_FROM_PTR(results); +} +#endif /* NDARRAY_HAS_BINARY_OP_ADD */ + +#if NDARRAY_HAS_BINARY_OP_MULTIPLY +mp_obj_t ndarray_binary_multiply(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + ndarray_obj_t *results = NULL; + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, uint8_t, int8_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, uint8_t, int16_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, *); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT8); + BINARY_LOOP(results, int8_t, int8_t, int8_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int8_t, uint16_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int8_t, int16_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, *); + } else { + return ndarray_binary_op(MP_BINARY_OP_MULTIPLY, rhs, lhs); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint16_t, int16_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, *); + } else { + return ndarray_binary_op(MP_BINARY_OP_MULTIPLY, rhs, lhs); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int16_t, int16_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, int16_t, mp_float_t, larray, lstrides, rarray, rstrides, *); + } else { + return ndarray_binary_op(MP_BINARY_OP_MULTIPLY, rhs, lhs); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, *); + } else { + return ndarray_binary_op(MP_BINARY_OP_MULTIPLY, rhs, lhs); + } + } + + return MP_OBJ_FROM_PTR(results); +} +#endif /* NDARRAY_HAS_BINARY_OP_MULTIPLY */ + +#if NDARRAY_HAS_BINARY_OP_MORE | NDARRAY_HAS_BINARY_OP_MORE_EQUAL | NDARRAY_HAS_BINARY_OP_LESS | NDARRAY_HAS_BINARY_OP_LESS_EQUAL +mp_obj_t ndarray_binary_more(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides, mp_binary_op_t op) { + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT8); + results->boolean = 1; + uint8_t *array = (uint8_t *)results->array; + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + #if NDARRAY_HAS_BINARY_OP_MORE | NDARRAY_HAS_BINARY_OP_LESS + if(op == MP_BINARY_OP_MORE) { + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, uint8_t, int8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, uint8_t, int16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, >); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, int8_t, uint8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, int8_t, int8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, int8_t, uint16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, int8_t, int16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, >); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, uint16_t, uint8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, uint16_t, int8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, uint16_t, int16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, >); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, int16_t, uint8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, int16_t, int8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, int16_t, uint16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, int16_t, int16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, >); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, mp_float_t, uint8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, mp_float_t, int8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, mp_float_t, uint16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, mp_float_t, int16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, >); + } + } + } + #endif /* NDARRAY_HAS_BINARY_OP_MORE | NDARRAY_HAS_BINARY_OP_LESS*/ + #if NDARRAY_HAS_BINARY_OP_MORE_EQUAL | NDARRAY_HAS_BINARY_OP_LESS_EQUAL + if(op == MP_BINARY_OP_MORE_EQUAL) { + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, uint8_t, int8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, uint8_t, int16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, >=); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, int8_t, uint8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, int8_t, int8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, int8_t, uint16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, int8_t, int16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, >=); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, uint16_t, uint8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, uint16_t, int8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, uint16_t, int16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, >=); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, int16_t, uint8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, int16_t, int8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, int16_t, uint16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, int16_t, int16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, >=); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, mp_float_t, uint8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, mp_float_t, int8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, mp_float_t, uint16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, mp_float_t, int16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, >=); + } + } + } + #endif /* NDARRAY_HAS_BINARY_OP_MORE_EQUAL | NDARRAY_HAS_BINARY_OP_LESS_EQUAL */ + + return MP_OBJ_FROM_PTR(results); +} +#endif /* NDARRAY_HAS_BINARY_OP_MORE | NDARRAY_HAS_BINARY_OP_MORE_EQUAL | NDARRAY_HAS_BINARY_OP_LESS | NDARRAY_HAS_BINARY_OP_LESS_EQUAL */ + +#if NDARRAY_HAS_BINARY_OP_SUBTRACT +mp_obj_t ndarray_binary_subtract(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + ndarray_obj_t *results = NULL; + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT8); + BINARY_LOOP(results, uint8_t, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, uint8_t, int8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, uint8_t, int16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, -); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int8_t, uint8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT8); + BINARY_LOOP(results, int8_t, int8_t, int8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int8_t, uint16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int8_t, int16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, -); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint16_t, uint8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint16_t, int8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint16_t, int16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, -); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int16_t, uint8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int16_t, int8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, int16_t, uint16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int16_t, int16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, -); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, mp_float_t, uint8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, mp_float_t, int8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, mp_float_t, uint16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, mp_float_t, int16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, -); + } + } + + return MP_OBJ_FROM_PTR(results); +} +#endif /* NDARRAY_HAS_BINARY_OP_SUBTRACT */ + +#if NDARRAY_HAS_BINARY_OP_TRUE_DIVIDE +mp_obj_t ndarray_binary_true_divide(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + #if NDARRAY_BINARY_USES_FUN_POINTER + mp_float_t (*get_lhs)(void *) = ndarray_get_float_function(lhs->dtype); + mp_float_t (*get_rhs)(void *) = ndarray_get_float_function(rhs->dtype); + + uint8_t *array = (uint8_t *)results->array; + void (*set_result)(void *, mp_float_t ) = ndarray_set_float_function(NDARRAY_FLOAT); + + // Note that lvalue and rvalue are local variables in the macro itself + FUNC_POINTER_LOOP(results, array, get_lhs, get_rhs, larray, lstrides, rarray, rstrides, lvalue/rvalue); + + #else + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + BINARY_LOOP(results, mp_float_t, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT8) { + BINARY_LOOP(results, mp_float_t, uint8_t, int8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_UINT16) { + BINARY_LOOP(results, mp_float_t, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT16) { + BINARY_LOOP(results, mp_float_t, uint8_t, int16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_FLOAT) { + BINARY_LOOP(results, mp_float_t, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, /); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_UINT8) { + BINARY_LOOP(results, mp_float_t, int8_t, uint8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT8) { + BINARY_LOOP(results, mp_float_t, int8_t, int8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_UINT16) { + BINARY_LOOP(results, mp_float_t, int8_t, uint16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT16) { + BINARY_LOOP(results, mp_float_t, int8_t, int16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_FLOAT) { + BINARY_LOOP(results, mp_float_t, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, /); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT8) { + BINARY_LOOP(results, mp_float_t, uint16_t, uint8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT8) { + BINARY_LOOP(results, mp_float_t, uint16_t, int8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_UINT16) { + BINARY_LOOP(results, mp_float_t, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT16) { + BINARY_LOOP(results, mp_float_t, uint16_t, int16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_FLOAT) { + BINARY_LOOP(results, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, /); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_UINT8) { + BINARY_LOOP(results, mp_float_t, int16_t, uint8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT8) { + BINARY_LOOP(results, mp_float_t, int16_t, int8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_UINT16) { + BINARY_LOOP(results, mp_float_t, int16_t, uint16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT16) { + BINARY_LOOP(results, mp_float_t, int16_t, int16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_FLOAT) { + BINARY_LOOP(results, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, /); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_UINT8) { + BINARY_LOOP(results, mp_float_t, mp_float_t, uint8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT8) { + BINARY_LOOP(results, mp_float_t, mp_float_t, int8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_UINT16) { + BINARY_LOOP(results, mp_float_t, mp_float_t, uint16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT16) { + BINARY_LOOP(results, mp_float_t, mp_float_t, int16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_FLOAT) { + BINARY_LOOP(results, mp_float_t, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, /); + } + } + #endif /* NDARRAY_BINARY_USES_FUN_POINTER */ + + return MP_OBJ_FROM_PTR(results); +} +#endif /* NDARRAY_HAS_BINARY_OP_TRUE_DIVIDE */ + +#if NDARRAY_HAS_BINARY_OP_POWER +mp_obj_t ndarray_binary_power(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + // Note that numpy upcasts the results to int64, if the inputs are of integer type, + // while we always return a float array. + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + #if NDARRAY_BINARY_USES_FUN_POINTER + mp_float_t (*get_lhs)(void *) = ndarray_get_float_function(lhs->dtype); + mp_float_t (*get_rhs)(void *) = ndarray_get_float_function(rhs->dtype); + + uint8_t *array = (uint8_t *)results->array; + void (*set_result)(void *, mp_float_t ) = ndarray_set_float_function(NDARRAY_FLOAT); + + // Note that lvalue and rvalue are local variables in the macro itself + FUNC_POINTER_LOOP(results, array, get_lhs, get_rhs, larray, lstrides, rarray, rstrides, MICROPY_FLOAT_C_FUN(pow)(lvalue, rvalue)); + + #else + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + POWER_LOOP(results, mp_float_t, uint8_t, uint8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT8) { + POWER_LOOP(results, mp_float_t, uint8_t, int8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_UINT16) { + POWER_LOOP(results, mp_float_t, uint8_t, uint16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT16) { + POWER_LOOP(results, mp_float_t, uint8_t, int16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_FLOAT) { + POWER_LOOP(results, mp_float_t, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_UINT8) { + POWER_LOOP(results, mp_float_t, int8_t, uint8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT8) { + POWER_LOOP(results, mp_float_t, int8_t, int8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_UINT16) { + POWER_LOOP(results, mp_float_t, int8_t, uint16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT16) { + POWER_LOOP(results, mp_float_t, int8_t, int16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_FLOAT) { + POWER_LOOP(results, mp_float_t, int8_t, mp_float_t, larray, lstrides, rarray, rstrides); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT8) { + POWER_LOOP(results, mp_float_t, uint16_t, uint8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT8) { + POWER_LOOP(results, mp_float_t, uint16_t, int8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_UINT16) { + POWER_LOOP(results, mp_float_t, uint16_t, uint16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT16) { + POWER_LOOP(results, mp_float_t, uint16_t, int16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_FLOAT) { + POWER_LOOP(results, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_UINT8) { + POWER_LOOP(results, mp_float_t, int16_t, uint8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT8) { + POWER_LOOP(results, mp_float_t, int16_t, int8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_UINT16) { + POWER_LOOP(results, mp_float_t, int16_t, uint16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT16) { + POWER_LOOP(results, mp_float_t, int16_t, int16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_FLOAT) { + POWER_LOOP(results, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_UINT8) { + POWER_LOOP(results, mp_float_t, mp_float_t, uint8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT8) { + POWER_LOOP(results, mp_float_t, mp_float_t, int8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_UINT16) { + POWER_LOOP(results, mp_float_t, mp_float_t, uint16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT16) { + POWER_LOOP(results, mp_float_t, mp_float_t, int16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_FLOAT) { + POWER_LOOP(results, mp_float_t, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides); + } + } + #endif /* NDARRAY_BINARY_USES_FUN_POINTER */ + + return MP_OBJ_FROM_PTR(results); +} +#endif /* NDARRAY_HAS_BINARY_OP_POWER */ + +#if NDARRAY_HAS_INPLACE_ADD || NDARRAY_HAS_INPLACE_MULTIPLY || NDARRAY_HAS_INPLACE_SUBTRACT +mp_obj_t ndarray_inplace_ams(ndarray_obj_t *lhs, ndarray_obj_t *rhs, int32_t *rstrides, uint8_t optype) { + + if((lhs->dtype != NDARRAY_FLOAT) && (rhs->dtype == NDARRAY_FLOAT)) { + mp_raise_TypeError(translate("cannot cast output with casting rule")); + } + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + #if NDARRAY_HAS_INPLACE_ADD + if(optype == MP_BINARY_OP_INPLACE_ADD) { + UNWRAP_INPLACE_OPERATOR(lhs, larray, rarray, rstrides, +=); + } + #endif + #if NDARRAY_HAS_INPLACE_ADD + if(optype == MP_BINARY_OP_INPLACE_MULTIPLY) { + UNWRAP_INPLACE_OPERATOR(lhs, larray, rarray, rstrides, *=); + } + #endif + #if NDARRAY_HAS_INPLACE_SUBTRACT + if(optype == MP_BINARY_OP_INPLACE_SUBTRACT) { + UNWRAP_INPLACE_OPERATOR(lhs, larray, rarray, rstrides, -=); + } + #endif + + return MP_OBJ_FROM_PTR(lhs); +} +#endif /* NDARRAY_HAS_INPLACE_ADD || NDARRAY_HAS_INPLACE_MULTIPLY || NDARRAY_HAS_INPLACE_SUBTRACT */ + +#if NDARRAY_HAS_INPLACE_TRUE_DIVIDE +mp_obj_t ndarray_inplace_divide(ndarray_obj_t *lhs, ndarray_obj_t *rhs, int32_t *rstrides) { + + if((lhs->dtype != NDARRAY_FLOAT)) { + mp_raise_TypeError(translate("results cannot be cast to specified type")); + } + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + if(rhs->dtype == NDARRAY_UINT8) { + INPLACE_LOOP(lhs, mp_float_t, uint8_t, larray, rarray, rstrides, /=); + } else if(rhs->dtype == NDARRAY_INT8) { + INPLACE_LOOP(lhs, mp_float_t, int8_t, larray, rarray, rstrides, /=); + } else if(rhs->dtype == NDARRAY_UINT16) { + INPLACE_LOOP(lhs, mp_float_t, uint16_t, larray, rarray, rstrides, /=); + } else if(rhs->dtype == NDARRAY_INT16) { + INPLACE_LOOP(lhs, mp_float_t, int16_t, larray, rarray, rstrides, /=); + } else if(lhs->dtype == NDARRAY_FLOAT) { + INPLACE_LOOP(lhs, mp_float_t, mp_float_t, larray, rarray, rstrides, /=); + } + return MP_OBJ_FROM_PTR(lhs); +} +#endif /* NDARRAY_HAS_INPLACE_DIVIDE */ + +#if NDARRAY_HAS_INPLACE_POWER +mp_obj_t ndarray_inplace_power(ndarray_obj_t *lhs, ndarray_obj_t *rhs, int32_t *rstrides) { + + if((lhs->dtype != NDARRAY_FLOAT)) { + mp_raise_TypeError(translate("results cannot be cast to specified type")); + } + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + if(rhs->dtype == NDARRAY_UINT8) { + INPLACE_POWER(lhs, mp_float_t, uint8_t, larray, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT8) { + INPLACE_POWER(lhs, mp_float_t, int8_t, larray, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_UINT16) { + INPLACE_POWER(lhs, mp_float_t, uint16_t, larray, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT16) { + INPLACE_POWER(lhs, mp_float_t, int16_t, larray, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_FLOAT) { + INPLACE_POWER(lhs, mp_float_t, mp_float_t, larray, rarray, rstrides); + } + return MP_OBJ_FROM_PTR(lhs); +} +#endif /* NDARRAY_HAS_INPLACE_POWER */ diff --git a/python/port/mod/ulab/ndarray_operators.h b/python/port/mod/ulab/ndarray_operators.h new file mode 100644 index 00000000000..7849e03097b --- /dev/null +++ b/python/port/mod/ulab/ndarray_operators.h @@ -0,0 +1,277 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + +#include "ndarray.h" + +mp_obj_t ndarray_binary_equality(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *, mp_binary_op_t ); +mp_obj_t ndarray_binary_add(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); +mp_obj_t ndarray_binary_multiply(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); +mp_obj_t ndarray_binary_more(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *, mp_binary_op_t ); +mp_obj_t ndarray_binary_power(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); +mp_obj_t ndarray_binary_subtract(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); +mp_obj_t ndarray_binary_true_divide(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); + +mp_obj_t ndarray_inplace_ams(ndarray_obj_t *, ndarray_obj_t *, int32_t *, uint8_t ); +mp_obj_t ndarray_inplace_power(ndarray_obj_t *, ndarray_obj_t *, int32_t *); +mp_obj_t ndarray_inplace_divide(ndarray_obj_t *, ndarray_obj_t *, int32_t *); + +#define UNWRAP_INPLACE_OPERATOR(lhs, larray, rarray, rstrides, OPERATOR)\ +({\ + if((lhs)->dtype == NDARRAY_UINT8) {\ + if((rhs)->dtype == NDARRAY_UINT8) {\ + INPLACE_LOOP((lhs), uint8_t, uint8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_INT8) {\ + INPLACE_LOOP((lhs), uint8_t, int8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_UINT16) {\ + INPLACE_LOOP((lhs), uint8_t, uint16_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else {\ + INPLACE_LOOP((lhs), uint8_t, int16_t, (larray), (rarray), (rstrides), OPERATOR);\ + }\ + } else if(lhs->dtype == NDARRAY_INT8) {\ + if(rhs->dtype == NDARRAY_UINT8) {\ + INPLACE_LOOP((lhs), int8_t, uint8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_INT8) {\ + INPLACE_LOOP((lhs), int8_t, int8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_UINT16) {\ + INPLACE_LOOP((lhs), int8_t, uint16_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else {\ + INPLACE_LOOP((lhs), int8_t, int16_t, (larray), (rarray), (rstrides), OPERATOR);\ + }\ + } else if(lhs->dtype == NDARRAY_UINT16) {\ + if(rhs->dtype == NDARRAY_UINT8) {\ + INPLACE_LOOP((lhs), uint16_t, uint8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_INT8) {\ + INPLACE_LOOP((lhs), uint16_t, int8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_UINT16) {\ + INPLACE_LOOP((lhs), uint16_t, uint16_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else {\ + INPLACE_LOOP((lhs), uint16_t, int16_t, (larray), (rarray), (rstrides), OPERATOR);\ + }\ + } else if(lhs->dtype == NDARRAY_INT16) {\ + if(rhs->dtype == NDARRAY_UINT8) {\ + INPLACE_LOOP((lhs), int16_t, uint8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_INT8) {\ + INPLACE_LOOP((lhs), int16_t, int8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_UINT16) {\ + INPLACE_LOOP((lhs), int16_t, uint16_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else {\ + INPLACE_LOOP((lhs), int16_t, int16_t, (larray), (rarray), (rstrides), OPERATOR);\ + }\ + } else if(lhs->dtype == NDARRAY_FLOAT) {\ + if(rhs->dtype == NDARRAY_UINT8) {\ + INPLACE_LOOP((lhs), mp_float_t, uint8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_INT8) {\ + INPLACE_LOOP((lhs), mp_float_t, int8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_UINT16) {\ + INPLACE_LOOP((lhs), mp_float_t, uint16_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_INT16) {\ + INPLACE_LOOP((lhs), mp_float_t, int16_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else {\ + INPLACE_LOOP((lhs), mp_float_t, mp_float_t, (larray), (rarray), (rstrides), OPERATOR);\ + }\ + }\ +}) + +#if ULAB_MAX_DIMS == 1 +#define INPLACE_POWER(results, type_left, type_right, larray, rarray, rstrides)\ +({ size_t l = 0;\ + do {\ + *((type_left *)(larray)) = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ +}) + +#define FUNC_POINTER_LOOP(results, array, get_lhs, get_rhs, larray, lstrides, rarray, rstrides, OPERATION)\ +({ size_t l = 0;\ + do {\ + mp_float_t lvalue = (get_lhs)((larray));\ + mp_float_t rvalue = (get_rhs)((rarray));\ + (set_result)((array), OPERATION);\ + (array) += (results)->itemsize;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ +}) +#endif /* ULAB_MAX_DIMS == 1 */ + +#if ULAB_MAX_DIMS == 2 +#define INPLACE_POWER(results, type_left, type_right, larray, rarray, rstrides)\ +({ size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_left *)(larray)) = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ +}) + +#define FUNC_POINTER_LOOP(results, array, get_lhs, get_rhs, larray, lstrides, rarray, rstrides, OPERATION)\ +({ size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + mp_float_t lvalue = (get_lhs)((larray));\ + mp_float_t rvalue = (get_rhs)((rarray));\ + (set_result)((array), OPERATION);\ + (array) += (results)->itemsize;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < results->shape[ULAB_MAX_DIMS - 2]);\ +}) +#endif /* ULAB_MAX_DIMS == 2 */ + +#if ULAB_MAX_DIMS == 3 +#define INPLACE_POWER(results, type_left, type_right, larray, rarray, rstrides)\ +({ size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_left *)(larray)) = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ +}) + + +#define FUNC_POINTER_LOOP(results, array, get_lhs, get_rhs, larray, lstrides, rarray, rstrides, OPERATION)\ +({ size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + mp_float_t lvalue = (get_lhs)((larray));\ + mp_float_t rvalue = (get_rhs)((rarray));\ + (set_result)((array), OPERATION);\ + (array) += (results)->itemsize;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < results->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ +}) +#endif /* ULAB_MAX_DIMS == 3 */ + +#if ULAB_MAX_DIMS == 4 +#define INPLACE_POWER(results, type_left, type_right, larray, rarray, rstrides)\ +({ size_t i = 0;\ + do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_left *)(larray)) = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ +}) + +#define FUNC_POINTER_LOOP(results, array, get_lhs, get_rhs, larray, lstrides, rarray, rstrides, OPERATION)\ +({ size_t i = 0;\ + do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + mp_float_t lvalue = (get_lhs)((larray));\ + mp_float_t rvalue = (get_rhs)((rarray));\ + (set_result)((array), OPERATION);\ + (array) += (results)->itemsize;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < results->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ +}) +#endif /* ULAB_MAX_DIMS == 4 */ diff --git a/python/port/mod/ulab/ndarray_properties.c b/python/port/mod/ulab/ndarray_properties.c new file mode 100644 index 00000000000..0fcf83bca9e --- /dev/null +++ b/python/port/mod/ulab/ndarray_properties.c @@ -0,0 +1,100 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Zoltán Vörös + * +*/ + +#include +#include +#include +#include +#include +#include + +#include "ulab.h" +#include "ndarray.h" + +#ifndef CIRCUITPY + +// a somewhat hackish implementation of property getters/setters; +// this functions is hooked into the attr member of ndarray + +STATIC void call_local_method(mp_obj_t obj, qstr attr, mp_obj_t *dest) { + const mp_obj_type_t *type = mp_obj_get_type(obj); + while (type->locals_dict != NULL) { + assert(type->locals_dict->base.type == &mp_type_dict); // MicroPython restriction, for now + mp_map_t *locals_map = &type->locals_dict->map; + mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP); + if (elem != NULL) { + mp_convert_member_lookup(obj, type, elem->value, dest); + break; + } + if (type->parent == NULL) { + break; + } + type = type->parent; + } +} + + +void ndarray_properties_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] == MP_OBJ_NULL) { + switch(attr) { + #if NDARRAY_HAS_DTYPE + case MP_QSTR_dtype: + dest[0] = ndarray_dtype(self_in); + break; + #endif + #if NDARRAY_HAS_ITEMSIZE + case MP_QSTR_itemsize: + dest[0] = ndarray_itemsize(self_in); + break; + #endif + #if NDARRAY_HAS_SHAPE + case MP_QSTR_shape: + dest[0] = ndarray_shape(self_in); + break; + #endif + #if NDARRAY_HAS_SIZE + case MP_QSTR_size: + dest[0] = ndarray_size(self_in); + break; + #endif + #if NDARRAY_HAS_STRIDES + case MP_QSTR_strides: + dest[0] = ndarray_strides(self_in); + break; + #endif + #if NDARRAY_HAS_TRANSPOSE + case MP_QSTR_T: + dest[0] = ndarray_transpose(self_in); + break; + #endif + default: + call_local_method(self_in, attr, dest); + break; + } + } else { + if(dest[1]) { + switch(attr) { + #if NDARRAY_HAS_RESHAPE + case MP_QSTR_shape: + ndarray_reshape_core(self_in, dest[1], 1); + break; + #endif + default: + return; + break; + } + dest[0] = MP_OBJ_NULL; + } + } +} + +#endif /* CIRCUITPY */ \ No newline at end of file diff --git a/python/port/mod/ulab/ndarray_properties.h b/python/port/mod/ulab/ndarray_properties.h new file mode 100644 index 00000000000..9d3b4ee9b81 --- /dev/null +++ b/python/port/mod/ulab/ndarray_properties.h @@ -0,0 +1,92 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020 Zoltán Vörös +*/ + +#ifndef _NDARRAY_PROPERTIES_ +#define _NDARRAY_PROPERTIES_ + +#include +#include +#include +#include + +#include "ulab.h" +#include "ndarray.h" + +#if CIRCUITPY +typedef struct _mp_obj_property_t { + mp_obj_base_t base; + mp_obj_t proxy[3]; // getter, setter, deleter +} mp_obj_property_t; + +#if NDARRAY_HAS_DTYPE +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_get_dtype_obj, ndarray_dtype); +STATIC const mp_obj_property_t ndarray_dtype_obj = { + .base.type = &mp_type_property, + .proxy = {(mp_obj_t)&ndarray_get_dtype_obj, + mp_const_none, + mp_const_none }, +}; +#endif /* NDARRAY_HAS_DTYPE */ + +#if NDARRAY_HAS_ITEMSIZE +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_get_itemsize_obj, ndarray_itemsize); +STATIC const mp_obj_property_t ndarray_itemsize_obj = { + .base.type = &mp_type_property, + .proxy = {(mp_obj_t)&ndarray_get_itemsize_obj, + mp_const_none, + mp_const_none }, +}; +#endif /* NDARRAY_HAS_ITEMSIZE */ + +#if NDARRAY_HAS_SHAPE +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_get_shape_obj, ndarray_shape); +STATIC const mp_obj_property_t ndarray_shape_obj = { + .base.type = &mp_type_property, + .proxy = {(mp_obj_t)&ndarray_get_shape_obj, + mp_const_none, + mp_const_none }, +}; +#endif /* NDARRAY_HAS_SHAPE */ + +#if NDARRAY_HAS_SIZE +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_get_size_obj, ndarray_size); +STATIC const mp_obj_property_t ndarray_size_obj = { + .base.type = &mp_type_property, + .proxy = {(mp_obj_t)&ndarray_get_size_obj, + mp_const_none, + mp_const_none }, +}; +#endif /* NDARRAY_HAS_SIZE */ + +#if NDARRAY_HAS_STRIDES +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_get_strides_obj, ndarray_strides); +STATIC const mp_obj_property_t ndarray_strides_obj = { + .base.type = &mp_type_property, + .proxy = {(mp_obj_t)&ndarray_get_strides_obj, + mp_const_none, + mp_const_none }, +}; +#endif /* NDARRAY_HAS_STRIDES */ + +#else + +void ndarray_properties_attr(mp_obj_t , qstr , mp_obj_t *); + +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_dtype_obj, ndarray_dtype); +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_itemsize_obj, ndarray_itemsize); +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_shape_obj, ndarray_shape); +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_size_obj, ndarray_size); +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_strides_obj, ndarray_strides); + +#endif /* CIRCUITPY */ + +#endif diff --git a/python/port/mod/ulab/numpy/approx/approx.c b/python/port/mod/ulab/numpy/approx/approx.c new file mode 100644 index 00000000000..706a969a799 --- /dev/null +++ b/python/port/mod/ulab/numpy/approx/approx.c @@ -0,0 +1,221 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös + * 2020 Diego Elio Pettenò + * 2020 Taku Fukada +*/ + +#include +#include +#include +#include +#include +#include + +#include "../../ulab.h" +#include "../../ulab_tools.h" +#include "approx.h" + +//| """Numerical approximation methods""" +//| + +const mp_obj_float_t approx_trapz_dx = {{&mp_type_float}, MICROPY_FLOAT_CONST(1.0)}; + +#if ULAB_NUMPY_HAS_INTERP +//| def interp( +//| x: ulab.ndarray, +//| xp: ulab.ndarray, +//| fp: ulab.ndarray, +//| *, +//| left: Optional[float] = None, +//| right: Optional[float] = None +//| ) -> ulab.ndarray: +//| """ +//| :param ulab.ndarray x: The x-coordinates at which to evaluate the interpolated values. +//| :param ulab.ndarray xp: The x-coordinates of the data points, must be increasing +//| :param ulab.ndarray fp: The y-coordinates of the data points, same length as xp +//| :param left: Value to return for ``x < xp[0]``, default is ``fp[0]``. +//| :param right: Value to return for ``x > xp[-1]``, default is ``fp[-1]``. +//| +//| Returns the one-dimensional piecewise linear interpolant to a function with given discrete data points (xp, fp), evaluated at x.""" +//| ... +//| + +STATIC mp_obj_t approx_interp(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_left, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} }, + { MP_QSTR_right, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + ndarray_obj_t *x = ndarray_from_mp_obj(args[0].u_obj, 0); + ndarray_obj_t *xp = ndarray_from_mp_obj(args[1].u_obj, 0); // xp must hold an increasing sequence of independent values + ndarray_obj_t *fp = ndarray_from_mp_obj(args[2].u_obj, 0); + if((xp->ndim != 1) || (fp->ndim != 1) || (xp->len < 2) || (fp->len < 2) || (xp->len != fp->len)) { + mp_raise_ValueError(translate("interp is defined for 1D iterables of equal length")); + } + + ndarray_obj_t *y = ndarray_new_linear_array(x->len, NDARRAY_FLOAT); + mp_float_t left_value, right_value; + uint8_t *xparray = (uint8_t *)xp->array; + + mp_float_t xp_left = ndarray_get_float_value(xparray, xp->dtype); + xparray += (xp->len-1) * xp->strides[ULAB_MAX_DIMS - 1]; + mp_float_t xp_right = ndarray_get_float_value(xparray, xp->dtype); + + uint8_t *fparray = (uint8_t *)fp->array; + + if(args[3].u_obj == mp_const_none) { + left_value = ndarray_get_float_value(fparray, fp->dtype); + } else { + left_value = mp_obj_get_float(args[3].u_obj); + } + if(args[4].u_obj == mp_const_none) { + fparray += (fp->len-1) * fp->strides[ULAB_MAX_DIMS - 1]; + right_value = ndarray_get_float_value(fparray, fp->dtype); + } else { + right_value = mp_obj_get_float(args[4].u_obj); + } + + xparray = xp->array; + fparray = fp->array; + + uint8_t *xarray = (uint8_t *)x->array; + mp_float_t *yarray = (mp_float_t *)y->array; + uint8_t *temp; + + for(size_t i=0; i < x->len; i++, yarray++) { + mp_float_t x_value = ndarray_get_float_value(xarray, x->dtype); + xarray += x->strides[ULAB_MAX_DIMS - 1]; + if(x_value < xp_left) { + *yarray = left_value; + } else if(x_value > xp_right) { + *yarray = right_value; + } else { // do the binary search here + mp_float_t xp_left_, xp_right_; + mp_float_t fp_left, fp_right; + size_t left_index = 0, right_index = xp->len - 1, middle_index; + while(right_index - left_index > 1) { + middle_index = left_index + (right_index - left_index) / 2; + temp = xparray + middle_index * xp->strides[ULAB_MAX_DIMS - 1]; + mp_float_t xp_middle = ndarray_get_float_value(temp, xp->dtype); + if(x_value <= xp_middle) { + right_index = middle_index; + } else { + left_index = middle_index; + } + } + temp = xparray + left_index * xp->strides[ULAB_MAX_DIMS - 1]; + xp_left_ = ndarray_get_float_value(temp, xp->dtype); + + temp = xparray + right_index * xp->strides[ULAB_MAX_DIMS - 1]; + xp_right_ = ndarray_get_float_value(temp, xp->dtype); + + temp = fparray + left_index * fp->strides[ULAB_MAX_DIMS - 1]; + fp_left = ndarray_get_float_value(temp, fp->dtype); + + temp = fparray + right_index * fp->strides[ULAB_MAX_DIMS - 1]; + fp_right = ndarray_get_float_value(temp, fp->dtype); + + *yarray = fp_left + (x_value - xp_left_) * (fp_right - fp_left) / (xp_right_ - xp_left_); + } + } + return MP_OBJ_FROM_PTR(y); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(approx_interp_obj, 2, approx_interp); +#endif + +#if ULAB_NUMPY_HAS_TRAPZ +//| def trapz(y: ulab.ndarray, x: Optional[ulab.ndarray] = None, dx: float = 1.0) -> float: +//| """ +//| :param 1D ulab.ndarray y: the values of the dependent variable +//| :param 1D ulab.ndarray x: optional, the coordinates of the independent variable. Defaults to uniformly spaced values. +//| :param float dx: the spacing between sample points, if x=None +//| +//| Returns the integral of y(x) using the trapezoidal rule. +//| """ +//| ... +//| + +STATIC mp_obj_t approx_trapz(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_x, MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_dx, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&approx_trapz_dx)} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + ndarray_obj_t *y = ndarray_from_mp_obj(args[0].u_obj, 0); + ndarray_obj_t *x; + mp_float_t mean = MICROPY_FLOAT_CONST(0.0); + if(y->len < 2) { + return mp_obj_new_float(mean); + } + if((y->ndim != 1)) { + mp_raise_ValueError(translate("trapz is defined for 1D iterables")); + } + + mp_float_t (*funcy)(void *) = ndarray_get_float_function(y->dtype); + uint8_t *yarray = (uint8_t *)y->array; + + size_t count = 1; + mp_float_t y1, y2, m; + + if(args[1].u_obj != mp_const_none) { + x = ndarray_from_mp_obj(args[1].u_obj, 0); // x must hold an increasing sequence of independent values + if((x->ndim != 1) || (y->len != x->len)) { + mp_raise_ValueError(translate("trapz is defined for 1D arrays of equal length")); + } + + mp_float_t (*funcx)(void *) = ndarray_get_float_function(x->dtype); + uint8_t *xarray = (uint8_t *)x->array; + mp_float_t x1, x2; + + y1 = funcy(yarray); + yarray += y->strides[ULAB_MAX_DIMS - 1]; + x1 = funcx(xarray); + xarray += x->strides[ULAB_MAX_DIMS - 1]; + + for(size_t i=1; i < y->len; i++) { + y2 = funcy(yarray); + yarray += y->strides[ULAB_MAX_DIMS - 1]; + x2 = funcx(xarray); + xarray += x->strides[ULAB_MAX_DIMS - 1]; + mp_float_t value = (x2 - x1) * (y2 + y1); + m = mean + (value - mean) / (mp_float_t)count; + mean = m; + x1 = x2; + y1 = y2; + count++; + } + } else { + mp_float_t dx = mp_obj_get_float(args[2].u_obj); + y1 = funcy(yarray); + yarray += y->strides[ULAB_MAX_DIMS - 1]; + + for(size_t i=1; i < y->len; i++) { + y2 = ndarray_get_float_index(y->array, y->dtype, i); + mp_float_t value = (y2 + y1); + m = mean + (value - mean) / (mp_float_t)count; + mean = m; + y1 = y2; + count++; + } + mean *= dx; + } + return mp_obj_new_float(MICROPY_FLOAT_CONST(0.5)*mean*(y->len-1)); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(approx_trapz_obj, 1, approx_trapz); +#endif diff --git a/python/port/mod/ulab/numpy/approx/approx.h b/python/port/mod/ulab/numpy/approx/approx.h new file mode 100644 index 00000000000..7708bb78977 --- /dev/null +++ b/python/port/mod/ulab/numpy/approx/approx.h @@ -0,0 +1,29 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + +#ifndef _APPROX_ +#define _APPROX_ + +#include "../../ulab.h" +#include "../../ndarray.h" + +#define APPROX_EPS MICROPY_FLOAT_CONST(1.0e-4) +#define APPROX_NONZDELTA MICROPY_FLOAT_CONST(0.05) +#define APPROX_ZDELTA MICROPY_FLOAT_CONST(0.00025) +#define APPROX_ALPHA MICROPY_FLOAT_CONST(1.0) +#define APPROX_BETA MICROPY_FLOAT_CONST(2.0) +#define APPROX_GAMMA MICROPY_FLOAT_CONST(0.5) +#define APPROX_DELTA MICROPY_FLOAT_CONST(0.5) + +MP_DECLARE_CONST_FUN_OBJ_KW(approx_interp_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(approx_trapz_obj); + +#endif /* _APPROX_ */ diff --git a/python/port/mod/ulab/numpy/compare/compare.c b/python/port/mod/ulab/numpy/compare/compare.c new file mode 100644 index 00000000000..73dfaa135fe --- /dev/null +++ b/python/port/mod/ulab/numpy/compare/compare.c @@ -0,0 +1,417 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös + * 2020 Jeff Epler for Adafruit Industries +*/ + +#include +#include +#include +#include +#include +#include + +#include "../../ulab.h" +#include "../../ndarray_operators.h" +#include "../../ulab_tools.h" +#include "compare.h" + +static mp_obj_t compare_function(mp_obj_t x1, mp_obj_t x2, uint8_t op) { + ndarray_obj_t *lhs = ndarray_from_mp_obj(x1, 0); + ndarray_obj_t *rhs = ndarray_from_mp_obj(x2, 0); + uint8_t ndim = 0; + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + int32_t *lstrides = m_new(int32_t, ULAB_MAX_DIMS); + int32_t *rstrides = m_new(int32_t, ULAB_MAX_DIMS); + if(!ndarray_can_broadcast(lhs, rhs, &ndim, shape, lstrides, rstrides)) { + mp_raise_ValueError(translate("operands could not be broadcast together")); + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(int32_t, lstrides, ULAB_MAX_DIMS); + m_del(int32_t, rstrides, ULAB_MAX_DIMS); + } + + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + if(op == COMPARE_EQUAL) { + return ndarray_binary_equality(lhs, rhs, ndim, shape, lstrides, rstrides, MP_BINARY_OP_EQUAL); + } else if(op == COMPARE_NOT_EQUAL) { + return ndarray_binary_equality(lhs, rhs, ndim, shape, lstrides, rstrides, MP_BINARY_OP_NOT_EQUAL); + } + // These are the upcasting rules + // float always becomes float + // operation on identical types preserves type + // uint8 + int8 => int16 + // uint8 + int16 => int16 + // uint8 + uint16 => uint16 + // int8 + int16 => int16 + // int8 + uint16 => uint16 + // uint16 + int16 => float + // The parameters of RUN_COMPARE_LOOP are + // typecode of result, type_out, type_left, type_right, lhs operand, rhs operand, operator + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + RUN_COMPARE_LOOP(NDARRAY_UINT8, uint8_t, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT8) { + RUN_COMPARE_LOOP(NDARRAY_INT16, int16_t, uint8_t, int8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_UINT16) { + RUN_COMPARE_LOOP(NDARRAY_UINT16, uint16_t, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT16) { + RUN_COMPARE_LOOP(NDARRAY_INT16, int16_t, uint8_t, int16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_FLOAT) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_UINT8) { + RUN_COMPARE_LOOP(NDARRAY_INT16, int16_t, int8_t, uint8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT8) { + RUN_COMPARE_LOOP(NDARRAY_INT8, int8_t, int8_t, int8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_UINT16) { + RUN_COMPARE_LOOP(NDARRAY_INT16, int16_t, int8_t, uint16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT16) { + RUN_COMPARE_LOOP(NDARRAY_INT16, int16_t, int8_t, int16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_FLOAT) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT8) { + RUN_COMPARE_LOOP(NDARRAY_UINT16, uint16_t, uint16_t, uint8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT8) { + RUN_COMPARE_LOOP(NDARRAY_UINT16, uint16_t, uint16_t, int8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_UINT16) { + RUN_COMPARE_LOOP(NDARRAY_UINT16, uint16_t, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT16) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, uint16_t, int16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_FLOAT) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_UINT8) { + RUN_COMPARE_LOOP(NDARRAY_INT16, int16_t, int16_t, uint8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT8) { + RUN_COMPARE_LOOP(NDARRAY_INT16, int16_t, int16_t, int8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_UINT16) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, int16_t, uint16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT16) { + RUN_COMPARE_LOOP(NDARRAY_INT16, int16_t, int16_t, int16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_FLOAT) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_UINT8) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, mp_float_t, uint8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT8) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, mp_float_t, int8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_UINT16) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, mp_float_t, uint16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT16) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, mp_float_t, int16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_FLOAT) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } + } + return mp_const_none; // we should never reach this point +} + +static mp_obj_t compare_equal_helper(mp_obj_t x1, mp_obj_t x2, uint8_t comptype) { + // scalar comparisons should return a single object of mp_obj_t type + mp_obj_t result = compare_function(x1, x2, comptype); + if((mp_obj_is_int(x1) || mp_obj_is_float(x1)) && (mp_obj_is_int(x2) || mp_obj_is_float(x2))) { + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(result, &iter_buf); + mp_obj_t item = mp_iternext(iterable); + return item; + } + return result; +} + +#if ULAB_NUMPY_HAS_CLIP + +mp_obj_t compare_clip(mp_obj_t x1, mp_obj_t x2, mp_obj_t x3) { + // Note: this function could be made faster by implementing a single-loop comparison in + // RUN_COMPARE_LOOP. However, that would add around 2 kB of compile size, while we + // would not gain a factor of two in speed, since the two comparisons should still be + // evaluated. In contrast, calling the function twice adds only 140 bytes to the firmware + if(mp_obj_is_int(x1) || mp_obj_is_float(x1)) { + mp_float_t v1 = mp_obj_get_float(x1); + mp_float_t v2 = mp_obj_get_float(x2); + mp_float_t v3 = mp_obj_get_float(x3); + if(v1 < v2) { + return x2; + } else if(v1 > v3) { + return x3; + } else { + return x1; + } + } else { // assume ndarrays + return compare_function(x2, compare_function(x1, x3, COMPARE_MINIMUM), COMPARE_MAXIMUM); + } +} + +MP_DEFINE_CONST_FUN_OBJ_3(compare_clip_obj, compare_clip); +#endif + +#if ULAB_NUMPY_HAS_EQUAL + +mp_obj_t compare_equal(mp_obj_t x1, mp_obj_t x2) { + return compare_equal_helper(x1, x2, COMPARE_EQUAL); +} + +MP_DEFINE_CONST_FUN_OBJ_2(compare_equal_obj, compare_equal); +#endif + +#if ULAB_NUMPY_HAS_NOTEQUAL + +mp_obj_t compare_not_equal(mp_obj_t x1, mp_obj_t x2) { + return compare_equal_helper(x1, x2, COMPARE_NOT_EQUAL); +} + +MP_DEFINE_CONST_FUN_OBJ_2(compare_not_equal_obj, compare_not_equal); +#endif + +#if ULAB_NUMPY_HAS_ISFINITE | ULAB_NUMPY_HAS_ISINF +static mp_obj_t compare_isinf_isfinite(mp_obj_t _x, uint8_t mask) { + // mask should signify, whether the function is called from isinf (mask = 1), + // or from isfinite (mask = 0) + if(mp_obj_is_int(_x)) { + if(mask) { + return mp_const_false; + } else { + return mp_const_true; + } + } else if(mp_obj_is_float(_x)) { + mp_float_t x = mp_obj_get_float(_x); + if(isnan(x)) { + return mp_const_false; + } + if(mask) { // called from isinf + return isinf(x) ? mp_const_true : mp_const_false; + } else { // called from isfinite + return isinf(x) ? mp_const_false : mp_const_true; + } + } else if(mp_obj_is_type(_x, &ulab_ndarray_type)) { + ndarray_obj_t *x = MP_OBJ_TO_PTR(_x); + ndarray_obj_t *results = ndarray_new_dense_ndarray(x->ndim, x->shape, NDARRAY_BOOL); + // At this point, results is all False + uint8_t *rarray = (uint8_t *)results->array; + if(x->dtype != NDARRAY_FLOAT) { + // int types can never be infinite... + if(!mask) { + // ...so flip all values in the array, if the function was called from isfinite + memset(rarray, 1, results->len); + } + return results; + } + uint8_t *xarray = (uint8_t *)x->array; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t value = *(mp_float_t *)xarray; + if(isnan(value)) { + *rarray++ = 0; + } else { + *rarray++ = isinf(value) ? mask : 1 - mask; + } + xarray += x->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < x->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + xarray -= x->strides[ULAB_MAX_DIMS - 1] * x->shape[ULAB_MAX_DIMS-1]; + xarray += x->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < x->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + xarray -= x->strides[ULAB_MAX_DIMS - 2] * x->shape[ULAB_MAX_DIMS-2]; + xarray += x->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < x->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + xarray -= x->strides[ULAB_MAX_DIMS - 3] * x->shape[ULAB_MAX_DIMS-3]; + xarray += x->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < x->shape[ULAB_MAX_DIMS - 4]); + #endif + + return results; + } else { + mp_raise_TypeError(translate("wrong input type")); + } + return mp_const_none; +} +#endif + +#if ULAB_NUMPY_HAS_ISFINITE +mp_obj_t compare_isfinite(mp_obj_t _x) { + return compare_isinf_isfinite(_x, 0); +} + +MP_DEFINE_CONST_FUN_OBJ_1(compare_isfinite_obj, compare_isfinite); +#endif + +#if ULAB_NUMPY_HAS_ISINF +mp_obj_t compare_isinf(mp_obj_t _x) { + return compare_isinf_isfinite(_x, 1); +} + +MP_DEFINE_CONST_FUN_OBJ_1(compare_isinf_obj, compare_isinf); +#endif + +#if ULAB_NUMPY_HAS_MAXIMUM +mp_obj_t compare_maximum(mp_obj_t x1, mp_obj_t x2) { + // extra round, so that we can return maximum(3, 4) properly + mp_obj_t result = compare_function(x1, x2, COMPARE_MAXIMUM); + if((mp_obj_is_int(x1) || mp_obj_is_float(x1)) && (mp_obj_is_int(x2) || mp_obj_is_float(x2))) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(result); + return mp_binary_get_val_array(ndarray->dtype, ndarray->array, 0); + } + return result; +} + +MP_DEFINE_CONST_FUN_OBJ_2(compare_maximum_obj, compare_maximum); +#endif + +#if ULAB_NUMPY_HAS_MINIMUM + +mp_obj_t compare_minimum(mp_obj_t x1, mp_obj_t x2) { + // extra round, so that we can return minimum(3, 4) properly + mp_obj_t result = compare_function(x1, x2, COMPARE_MINIMUM); + if((mp_obj_is_int(x1) || mp_obj_is_float(x1)) && (mp_obj_is_int(x2) || mp_obj_is_float(x2))) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(result); + return mp_binary_get_val_array(ndarray->dtype, ndarray->array, 0); + } + return result; +} + +MP_DEFINE_CONST_FUN_OBJ_2(compare_minimum_obj, compare_minimum); +#endif + +#if ULAB_NUMPY_HAS_WHERE + +mp_obj_t compare_where(mp_obj_t _condition, mp_obj_t _x, mp_obj_t _y) { + // this implementation will work with ndarrays, and scalars only + ndarray_obj_t *c = ndarray_from_mp_obj(_condition, 0); + ndarray_obj_t *x = ndarray_from_mp_obj(_x, 0); + ndarray_obj_t *y = ndarray_from_mp_obj(_y, 0); + + int32_t *cstrides = m_new(int32_t, ULAB_MAX_DIMS); + int32_t *xstrides = m_new(int32_t, ULAB_MAX_DIMS); + int32_t *ystrides = m_new(int32_t, ULAB_MAX_DIMS); + + size_t *oshape = m_new(size_t, ULAB_MAX_DIMS); + + uint8_t ndim; + + // establish the broadcasting conditions first + // if any two of the arrays can be broadcast together, then + // the three arrays can also be broadcast together + if(!ndarray_can_broadcast(c, x, &ndim, oshape, cstrides, ystrides) || + !ndarray_can_broadcast(c, y, &ndim, oshape, cstrides, ystrides) || + !ndarray_can_broadcast(x, y, &ndim, oshape, xstrides, ystrides)) { + mp_raise_ValueError(translate("operands could not be broadcast together")); + } + + ndim = MAX(MAX(c->ndim, x->ndim), y->ndim); + + for(uint8_t i = 1; i <= ndim; i++) { + cstrides[ULAB_MAX_DIMS - i] = c->shape[ULAB_MAX_DIMS - i] < 2 ? 0 : c->strides[ULAB_MAX_DIMS - i]; + xstrides[ULAB_MAX_DIMS - i] = x->shape[ULAB_MAX_DIMS - i] < 2 ? 0 : x->strides[ULAB_MAX_DIMS - i]; + ystrides[ULAB_MAX_DIMS - i] = y->shape[ULAB_MAX_DIMS - i] < 2 ? 0 : y->strides[ULAB_MAX_DIMS - i]; + oshape[ULAB_MAX_DIMS - i] = MAX(MAX(c->shape[ULAB_MAX_DIMS - i], x->shape[ULAB_MAX_DIMS - i]), y->shape[ULAB_MAX_DIMS - i]); + } + + uint8_t out_dtype = ndarray_upcast_dtype(x->dtype, y->dtype); + ndarray_obj_t *out = ndarray_new_dense_ndarray(ndim, oshape, out_dtype); + + mp_float_t (*cfunc)(void *) = ndarray_get_float_function(c->dtype); + mp_float_t (*xfunc)(void *) = ndarray_get_float_function(x->dtype); + mp_float_t (*yfunc)(void *) = ndarray_get_float_function(y->dtype); + mp_float_t (*ofunc)(void *, mp_float_t ) = ndarray_set_float_function(out->dtype); + + uint8_t *oarray = (uint8_t *)out->array; + uint8_t *carray = (uint8_t *)c->array; + uint8_t *xarray = (uint8_t *)x->array; + uint8_t *yarray = (uint8_t *)y->array; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t value; + mp_float_t cvalue = cfunc(carray); + if(cvalue != MICROPY_FLOAT_CONST(0.0)) { + value = xfunc(xarray); + } else { + value = yfunc(yarray); + } + ofunc(oarray, value); + oarray += out->itemsize; + carray += cstrides[ULAB_MAX_DIMS - 1]; + xarray += xstrides[ULAB_MAX_DIMS - 1]; + yarray += ystrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < out->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + carray -= cstrides[ULAB_MAX_DIMS - 1] * c->shape[ULAB_MAX_DIMS-1]; + carray += cstrides[ULAB_MAX_DIMS - 2]; + xarray -= xstrides[ULAB_MAX_DIMS - 1] * x->shape[ULAB_MAX_DIMS-1]; + xarray += xstrides[ULAB_MAX_DIMS - 2]; + yarray -= ystrides[ULAB_MAX_DIMS - 1] * y->shape[ULAB_MAX_DIMS-1]; + yarray += ystrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < out->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + carray -= cstrides[ULAB_MAX_DIMS - 2] * c->shape[ULAB_MAX_DIMS-2]; + carray += cstrides[ULAB_MAX_DIMS - 3]; + xarray -= xstrides[ULAB_MAX_DIMS - 2] * x->shape[ULAB_MAX_DIMS-2]; + xarray += xstrides[ULAB_MAX_DIMS - 3]; + yarray -= ystrides[ULAB_MAX_DIMS - 2] * y->shape[ULAB_MAX_DIMS-2]; + yarray += ystrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < out->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + carray -= cstrides[ULAB_MAX_DIMS - 3] * c->shape[ULAB_MAX_DIMS-3]; + carray += cstrides[ULAB_MAX_DIMS - 4]; + xarray -= xstrides[ULAB_MAX_DIMS - 3] * x->shape[ULAB_MAX_DIMS-3]; + xarray += xstrides[ULAB_MAX_DIMS - 4]; + yarray -= ystrides[ULAB_MAX_DIMS - 3] * y->shape[ULAB_MAX_DIMS-3]; + yarray += ystrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < out->shape[ULAB_MAX_DIMS - 4]); + #endif + return MP_OBJ_FROM_PTR(out); +} + +MP_DEFINE_CONST_FUN_OBJ_3(compare_where_obj, compare_where); +#endif diff --git a/python/port/mod/ulab/numpy/compare/compare.h b/python/port/mod/ulab/numpy/compare/compare.h new file mode 100644 index 00000000000..12a559e0af7 --- /dev/null +++ b/python/port/mod/ulab/numpy/compare/compare.h @@ -0,0 +1,150 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + +#ifndef _COMPARE_ +#define _COMPARE_ + +#include "../../ulab.h" +#include "../../ndarray.h" + +enum COMPARE_FUNCTION_TYPE { + COMPARE_EQUAL, + COMPARE_NOT_EQUAL, + COMPARE_MINIMUM, + COMPARE_MAXIMUM, + COMPARE_CLIP, +}; + +MP_DECLARE_CONST_FUN_OBJ_3(compare_clip_obj); +MP_DECLARE_CONST_FUN_OBJ_2(compare_equal_obj); +MP_DECLARE_CONST_FUN_OBJ_2(compare_isfinite_obj); +MP_DECLARE_CONST_FUN_OBJ_2(compare_isinf_obj); +MP_DECLARE_CONST_FUN_OBJ_2(compare_minimum_obj); +MP_DECLARE_CONST_FUN_OBJ_2(compare_maximum_obj); +MP_DECLARE_CONST_FUN_OBJ_2(compare_not_equal_obj); +MP_DECLARE_CONST_FUN_OBJ_3(compare_where_obj); + +#if ULAB_MAX_DIMS == 1 +#define COMPARE_LOOP(results, array, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t l = 0;\ + do {\ + *((type_out *)(array)) = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? (type_out)(*((type_left *)(larray))) : (type_out)(*((type_right *)(rarray)));\ + (array) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < results->shape[ULAB_MAX_DIMS - 1]);\ + return MP_OBJ_FROM_PTR(results);\ + +#endif // ULAB_MAX_DIMS == 1 + +#if ULAB_MAX_DIMS == 2 +#define COMPARE_LOOP(results, array, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_out *)(array)) = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? (type_out)(*((type_left *)(larray))) : (type_out)(*((type_right *)(rarray)));\ + (array) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < results->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < results->shape[ULAB_MAX_DIMS - 2]);\ + return MP_OBJ_FROM_PTR(results);\ + +#endif // ULAB_MAX_DIMS == 2 + +#if ULAB_MAX_DIMS == 3 +#define COMPARE_LOOP(results, array, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_out *)(array)) = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? (type_out)(*((type_left *)(larray))) : (type_out)(*((type_right *)(rarray)));\ + (array) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < results->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < results->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < results->shape[ULAB_MAX_DIMS - 3]);\ + return MP_OBJ_FROM_PTR(results);\ + +#endif // ULAB_MAX_DIMS == 3 + +#if ULAB_MAX_DIMS == 4 +#define COMPARE_LOOP(results, array, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t i = 0;\ + do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_out *)(array)) = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? (type_out)(*((type_left *)(larray))) : (type_out)(*((type_right *)(rarray)));\ + (array) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < results->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < results->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < results->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < results->shape[ULAB_MAX_DIMS - 4]);\ + return MP_OBJ_FROM_PTR(results);\ + +#endif // ULAB_MAX_DIMS == 4 + +#define RUN_COMPARE_LOOP(dtype, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, ndim, shape, op) do {\ + ndarray_obj_t *results = ndarray_new_dense_ndarray((ndim), (shape), (dtype));\ + uint8_t *array = (uint8_t *)results->array;\ + if((op) == COMPARE_MINIMUM) {\ + COMPARE_LOOP(results, array, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, <);\ + }\ + if((op) == COMPARE_MAXIMUM) {\ + COMPARE_LOOP(results, array, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, >);\ + }\ +} while(0) + +#endif diff --git a/python/port/mod/ulab/numpy/fft/fft.c b/python/port/mod/ulab/numpy/fft/fft.c new file mode 100644 index 00000000000..ff2373fe0a8 --- /dev/null +++ b/python/port/mod/ulab/numpy/fft/fft.c @@ -0,0 +1,82 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Scott Shawcroft for Adafruit Industries + * 2020 Taku Fukada +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fft.h" + +//| """Frequency-domain functions""" +//| + + +//| def fft(r: ulab.ndarray, c: Optional[ulab.ndarray] = None) -> Tuple[ulab.ndarray, ulab.ndarray]: +//| """ +//| :param ulab.ndarray r: A 1-dimension array of values whose size is a power of 2 +//| :param ulab.ndarray c: An optional 1-dimension array of values whose size is a power of 2, giving the complex part of the value +//| :return tuple (r, c): The real and complex parts of the FFT +//| +//| Perform a Fast Fourier Transform from the time domain into the frequency domain +//| +//| See also ~ulab.extras.spectrum, which computes the magnitude of the fft, +//| rather than separately returning its real and imaginary parts.""" +//| ... +//| +static mp_obj_t fft_fft(size_t n_args, const mp_obj_t *args) { + if(n_args == 2) { + return fft_fft_ifft_spectrogram(n_args, args[0], args[1], FFT_FFT); + } else { + return fft_fft_ifft_spectrogram(n_args, args[0], mp_const_none, FFT_FFT); + } +} + +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_fft_obj, 1, 2, fft_fft); + +//| def ifft(r: ulab.ndarray, c: Optional[ulab.ndarray] = None) -> Tuple[ulab.ndarray, ulab.ndarray]: +//| """ +//| :param ulab.ndarray r: A 1-dimension array of values whose size is a power of 2 +//| :param ulab.ndarray c: An optional 1-dimension array of values whose size is a power of 2, giving the complex part of the value +//| :return tuple (r, c): The real and complex parts of the inverse FFT +//| +//| Perform an Inverse Fast Fourier Transform from the frequeny domain into the time domain""" +//| ... +//| + +static mp_obj_t fft_ifft(size_t n_args, const mp_obj_t *args) { + if(n_args == 2) { + return fft_fft_ifft_spectrogram(n_args, args[0], args[1], FFT_IFFT); + } else { + return fft_fft_ifft_spectrogram(n_args, args[0], mp_const_none, FFT_IFFT); + } +} + +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_ifft_obj, 1, 2, fft_ifft); + +STATIC const mp_rom_map_elem_t ulab_fft_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_fft) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_fft), (mp_obj_t)&fft_fft_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_ifft), (mp_obj_t)&fft_ifft_obj }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_ulab_fft_globals, ulab_fft_globals_table); + +mp_obj_module_t ulab_fft_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_fft_globals, +}; diff --git a/python/port/mod/ulab/numpy/fft/fft.h b/python/port/mod/ulab/numpy/fft/fft.h new file mode 100644 index 00000000000..66acafe1151 --- /dev/null +++ b/python/port/mod/ulab/numpy/fft/fft.h @@ -0,0 +1,24 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#ifndef _FFT_ +#define _FFT_ + +#include "../../ulab.h" +#include "../../ulab_tools.h" +#include "../../ndarray.h" +#include "fft_tools.h" + +extern mp_obj_module_t ulab_fft_module; + +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(fft_fft_obj); +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(fft_ifft_obj); +#endif diff --git a/python/port/mod/ulab/numpy/fft/fft_tools.c b/python/port/mod/ulab/numpy/fft/fft_tools.c new file mode 100644 index 00000000000..ab737a85051 --- /dev/null +++ b/python/port/mod/ulab/numpy/fft/fft_tools.c @@ -0,0 +1,165 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#include +#include + +#include "../../ndarray.h" +#include "../../ulab_tools.h" +#include "fft_tools.h" + +#ifndef MP_PI +#define MP_PI MICROPY_FLOAT_CONST(3.14159265358979323846) +#endif +#ifndef MP_E +#define MP_E MICROPY_FLOAT_CONST(2.71828182845904523536) +#endif + +/* + * The following function takes two arrays, namely, the real and imaginary + * parts of a complex array, and calculates the Fourier transform in place. + * + * The function is basically a modification of four1 from Numerical Recipes, + * has no dependencies beyond micropython itself (for the definition of mp_float_t), + * and can be used independent of ulab. + */ + +void fft_kernel(mp_float_t *real, mp_float_t *imag, size_t n, int isign) { + size_t j, m, mmax, istep; + mp_float_t tempr, tempi; + mp_float_t wtemp, wr, wpr, wpi, wi, theta; + + j = 0; + for(size_t i = 0; i < n; i++) { + if (j > i) { + SWAP(mp_float_t, real[i], real[j]); + SWAP(mp_float_t, imag[i], imag[j]); + } + m = n >> 1; + while (j >= m && m > 0) { + j -= m; + m >>= 1; + } + j += m; + } + + mmax = 1; + while (n > mmax) { + istep = mmax << 1; + theta = MICROPY_FLOAT_CONST(-2.0)*isign*MP_PI/istep; + wtemp = MICROPY_FLOAT_C_FUN(sin)(MICROPY_FLOAT_CONST(0.5) * theta); + wpr = MICROPY_FLOAT_CONST(-2.0) * wtemp * wtemp; + wpi = MICROPY_FLOAT_C_FUN(sin)(theta); + wr = MICROPY_FLOAT_CONST(1.0); + wi = MICROPY_FLOAT_CONST(0.0); + for(m = 0; m < mmax; m++) { + for(size_t i = m; i < n; i += istep) { + j = i + mmax; + tempr = wr * real[j] - wi * imag[j]; + tempi = wr * imag[j] + wi * real[j]; + real[j] = real[i] - tempr; + imag[j] = imag[i] - tempi; + real[i] += tempr; + imag[i] += tempi; + } + wtemp = wr; + wr = wr*wpr - wi*wpi + wr; + wi = wi*wpr + wtemp*wpi + wi; + } + mmax = istep; + } +} + +/* + * The following function is a helper interface to the python side. + * It has been factored out from fft.c, so that the same argument parsing + * routine can be called from scipy.signal.spectrogram. + */ + +mp_obj_t fft_fft_ifft_spectrogram(size_t n_args, mp_obj_t arg_re, mp_obj_t arg_im, uint8_t type) { + if(!mp_obj_is_type(arg_re, &ulab_ndarray_type)) { + mp_raise_NotImplementedError(translate("FFT is defined for ndarrays only")); + } + if(n_args == 2) { + if(!mp_obj_is_type(arg_im, &ulab_ndarray_type)) { + mp_raise_NotImplementedError(translate("FFT is defined for ndarrays only")); + } + } + ndarray_obj_t *re = MP_OBJ_TO_PTR(arg_re); + #if ULAB_MAX_DIMS > 1 + if(re->ndim != 1) { + mp_raise_TypeError(translate("FFT is implemented for linear arrays only")); + } + #endif + size_t len = re->len; + // Check if input is of length of power of 2 + if((len & (len-1)) != 0) { + mp_raise_ValueError(translate("input array length must be power of 2")); + } + + ndarray_obj_t *out_re = ndarray_new_linear_array(len, NDARRAY_FLOAT); + mp_float_t *data_re = (mp_float_t *)out_re->array; + + uint8_t *array = (uint8_t *)re->array; + mp_float_t (*func)(void *) = ndarray_get_float_function(re->dtype); + + for(size_t i=0; i < len; i++) { + *data_re++ = func(array); + array += re->strides[ULAB_MAX_DIMS - 1]; + } + data_re -= len; + ndarray_obj_t *out_im = ndarray_new_linear_array(len, NDARRAY_FLOAT); + mp_float_t *data_im = (mp_float_t *)out_im->array; + + if(n_args == 2) { + ndarray_obj_t *im = MP_OBJ_TO_PTR(arg_im); + #if ULAB_MAX_DIMS > 1 + if(im->ndim != 1) { + mp_raise_TypeError(translate("FFT is implemented for linear arrays only")); + } + #endif + if (re->len != im->len) { + mp_raise_ValueError(translate("real and imaginary parts must be of equal length")); + } + array = (uint8_t *)im->array; + func = ndarray_get_float_function(im->dtype); + for(size_t i=0; i < len; i++) { + *data_im++ = func(array); + array += im->strides[ULAB_MAX_DIMS - 1]; + } + data_im -= len; + } + + if((type == FFT_FFT) || (type == FFT_SPECTROGRAM)) { + fft_kernel(data_re, data_im, len, 1); + if(type == FFT_SPECTROGRAM) { + for(size_t i=0; i < len; i++) { + *data_re = MICROPY_FLOAT_C_FUN(sqrt)(*data_re * *data_re + *data_im * *data_im); + data_re++; + data_im++; + } + } + } else { // inverse transform + fft_kernel(data_re, data_im, len, -1); + // TODO: numpy accepts the norm keyword argument + for(size_t i=0; i < len; i++) { + *data_re++ /= len; + *data_im++ /= len; + } + } + if(type == FFT_SPECTROGRAM) { + return MP_OBJ_TO_PTR(out_re); + } else { + mp_obj_t tuple[2]; + tuple[0] = out_re; + tuple[1] = out_im; + return mp_obj_new_tuple(2, tuple); + } +} diff --git a/python/port/mod/ulab/numpy/fft/fft_tools.h b/python/port/mod/ulab/numpy/fft/fft_tools.h new file mode 100644 index 00000000000..d3b856d0721 --- /dev/null +++ b/python/port/mod/ulab/numpy/fft/fft_tools.h @@ -0,0 +1,23 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#ifndef _FFT_TOOLS_ +#define _FFT_TOOLS_ + +enum FFT_TYPE { + FFT_FFT, + FFT_IFFT, + FFT_SPECTROGRAM, +}; + +void fft_kernel(mp_float_t *, mp_float_t *, size_t , int ); +mp_obj_t fft_fft_ifft_spectrogram(size_t , mp_obj_t , mp_obj_t , uint8_t ); + +#endif /* _FFT_TOOLS_ */ diff --git a/python/port/mod/ulab/numpy/filter/filter.c b/python/port/mod/ulab/numpy/filter/filter.c new file mode 100644 index 00000000000..9be43946b17 --- /dev/null +++ b/python/port/mod/ulab/numpy/filter/filter.c @@ -0,0 +1,84 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020 Scott Shawcroft for Adafruit Industries + * 2020-2021 Zoltán Vörös + * 2020 Taku Fukada +*/ + +#include +#include +#include +#include +#include +#include + +#include "../../ulab.h" +#include "../../scipy/signal/signal.h" +#include "filter.h" + +#if ULAB_NUMPY_HAS_CONVOLVE + +mp_obj_t filter_convolve(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_a, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_v, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type) || !mp_obj_is_type(args[1].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("convolve arguments must be ndarrays")); + } + + ndarray_obj_t *a = MP_OBJ_TO_PTR(args[0].u_obj); + ndarray_obj_t *c = MP_OBJ_TO_PTR(args[1].u_obj); + // deal with linear arrays only + #if ULAB_MAX_DIMS > 1 + if((a->ndim != 1) || (c->ndim != 1)) { + mp_raise_TypeError(translate("convolve arguments must be linear arrays")); + } + #endif + size_t len_a = a->len; + size_t len_c = c->len; + if(len_a == 0 || len_c == 0) { + mp_raise_TypeError(translate("convolve arguments must not be empty")); + } + + int len = len_a + len_c - 1; // convolve mode "full" + ndarray_obj_t *out = ndarray_new_linear_array(len, NDARRAY_FLOAT); + mp_float_t *outptr = (mp_float_t *)out->array; + uint8_t *aarray = (uint8_t *)a->array; + uint8_t *carray = (uint8_t *)c->array; + + int32_t off = len_c - 1; + int32_t as = a->strides[ULAB_MAX_DIMS - 1] / a->itemsize; + int32_t cs = c->strides[ULAB_MAX_DIMS - 1] / c->itemsize; + + for(int32_t k=-off; k < len-off; k++) { + mp_float_t accum = (mp_float_t)0.0; + int32_t top_n = MIN(len_c, len_a - k); + int32_t bot_n = MAX(-k, 0); + for(int32_t n=bot_n; n < top_n; n++) { + int32_t idx_c = (len_c - n - 1) * cs; + int32_t idx_a = (n + k) * as; + mp_float_t ai = ndarray_get_float_index(aarray, a->dtype, idx_a); + mp_float_t ci = ndarray_get_float_index(carray, c->dtype, idx_c); + accum += ai * ci; + } + *outptr++ = accum; + } + + return out; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(filter_convolve_obj, 2, filter_convolve); + +#endif diff --git a/python/port/mod/ulab/numpy/filter/filter.h b/python/port/mod/ulab/numpy/filter/filter.h new file mode 100644 index 00000000000..9b1ad1875bb --- /dev/null +++ b/python/port/mod/ulab/numpy/filter/filter.h @@ -0,0 +1,20 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020-2021 Zoltán Vörös +*/ + +#ifndef _FILTER_ +#define _FILTER_ + +#include "../../ulab.h" +#include "../../ndarray.h" + +MP_DECLARE_CONST_FUN_OBJ_KW(filter_convolve_obj); +#endif diff --git a/python/port/mod/ulab/numpy/linalg/linalg.c b/python/port/mod/ulab/numpy/linalg/linalg.c new file mode 100644 index 00000000000..209330f865c --- /dev/null +++ b/python/port/mod/ulab/numpy/linalg/linalg.c @@ -0,0 +1,397 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Scott Shawcroft for Adafruit Industries + * 2020 Roberto Colistete Jr. + * 2020 Taku Fukada + * +*/ + +#include +#include +#include +#include +#include +#include + +#include "../../ulab.h" +#include "../../ulab_tools.h" +#include "linalg.h" + +#if ULAB_NUMPY_HAS_LINALG_MODULE +//| +//| import ulab.numpy +//| +//| """Linear algebra functions""" +//| + +#if ULAB_MAX_DIMS > 1 +//| def cholesky(A: ulab.numpy.ndarray) -> ulab.numpy.ndarray: +//| """ +//| :param ~ulab.numpy.ndarray A: a positive definite, symmetric square matrix +//| :return ~ulab.numpy.ndarray L: a square root matrix in the lower triangular form +//| :raises ValueError: If the input does not fulfill the necessary conditions +//| +//| The returned matrix satisfies the equation m=LL*""" +//| ... +//| + +static mp_obj_t linalg_cholesky(mp_obj_t oin) { + ndarray_obj_t *ndarray = tools_object_is_square(oin); + ndarray_obj_t *L = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, ndarray->shape[ULAB_MAX_DIMS - 1], ndarray->shape[ULAB_MAX_DIMS - 1]), NDARRAY_FLOAT); + mp_float_t *Larray = (mp_float_t *)L->array; + + size_t N = ndarray->shape[ULAB_MAX_DIMS - 1]; + uint8_t *array = (uint8_t *)ndarray->array; + mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype); + + for(size_t m=0; m < N; m++) { // rows + for(size_t n=0; n < N; n++) { // columns + *Larray++ = func(array); + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + } + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * N; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + } + Larray -= N*N; + // make sure the matrix is symmetric + for(size_t m=0; m < N; m++) { // rows + for(size_t n=m+1; n < N; n++) { // columns + // compare entry (m, n) to (n, m) + if(LINALG_EPSILON < MICROPY_FLOAT_C_FUN(fabs)(Larray[m * N + n] - Larray[n * N + m])) { + mp_raise_ValueError(translate("input matrix is asymmetric")); + } + } + } + + // this is actually not needed, but Cholesky in numpy returns the lower triangular matrix + for(size_t i=0; i < N; i++) { // rows + for(size_t j=i+1; j < N; j++) { // columns + Larray[i*N + j] = MICROPY_FLOAT_CONST(0.0); + } + } + mp_float_t sum = 0.0; + for(size_t i=0; i < N; i++) { // rows + for(size_t j=0; j <= i; j++) { // columns + sum = Larray[i * N + j]; + for(size_t k=0; k < j; k++) { + sum -= Larray[i * N + k] * Larray[j * N + k]; + } + if(i == j) { + if(sum <= MICROPY_FLOAT_CONST(0.0)) { + mp_raise_ValueError(translate("matrix is not positive definite")); + } else { + Larray[i * N + i] = MICROPY_FLOAT_C_FUN(sqrt)(sum); + } + } else { + Larray[i * N + j] = sum / Larray[j * N + j]; + } + } + } + return MP_OBJ_FROM_PTR(L); +} + +MP_DEFINE_CONST_FUN_OBJ_1(linalg_cholesky_obj, linalg_cholesky); + +//| def det(m: ulab.numpy.ndarray) -> float: +//| """ +//| :param: m, a square matrix +//| :return float: The determinant of the matrix +//| +//| Computes the eigenvalues and eigenvectors of a square matrix""" +//| ... +//| + +static mp_obj_t linalg_det(mp_obj_t oin) { + ndarray_obj_t *ndarray = tools_object_is_square(oin); + uint8_t *array = (uint8_t *)ndarray->array; + size_t N = ndarray->shape[ULAB_MAX_DIMS - 1]; + mp_float_t *tmp = m_new(mp_float_t, N * N); + for(size_t m=0; m < N; m++) { // rows + for(size_t n=0; n < N; n++) { // columns + *tmp++ = ndarray_get_float_value(array, ndarray->dtype); + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + } + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * N; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + } + + // re-wind the pointer + tmp -= N*N; + + mp_float_t c; + mp_float_t det_sign = 1.0; + + for(size_t m=0; m < N-1; m++){ + if(MICROPY_FLOAT_C_FUN(fabs)(tmp[m * (N+1)]) < LINALG_EPSILON) { + size_t m1 = m + 1; + for(; m1 < N; m1++) { + if(!(MICROPY_FLOAT_C_FUN(fabs)(tmp[m1*N+m]) < LINALG_EPSILON)) { + //look for a line to swap + for(size_t m2=0; m2 < N; m2++) { + mp_float_t swapVal = tmp[m*N+m2]; + tmp[m*N+m2] = tmp[m1*N+m2]; + tmp[m1*N+m2] = swapVal; + } + det_sign = -det_sign; + break; + } + } + if (m1 >= N) { + m_del(mp_float_t, tmp, N * N); + return mp_obj_new_float(0.0); + } + } + for(size_t n=0; n < N; n++) { + if(m != n) { + c = tmp[N * n + m] / tmp[m * (N+1)]; + for(size_t k=0; k < N; k++){ + tmp[N * n + k] -= c * tmp[N * m + k]; + } + } + } + } + mp_float_t det = det_sign; + + for(size_t m=0; m < N; m++){ + det *= tmp[m * (N+1)]; + } + m_del(mp_float_t, tmp, N * N); + return mp_obj_new_float(det); +} + +MP_DEFINE_CONST_FUN_OBJ_1(linalg_det_obj, linalg_det); + +#endif + +#if ULAB_MAX_DIMS > 1 +//| def eig(m: ulab.numpy.ndarray) -> Tuple[ulab.numpy.ndarray, ulab.numpy.ndarray]: +//| """ +//| :param m: a square matrix +//| :return tuple (eigenvectors, eigenvalues): +//| +//| Computes the eigenvalues and eigenvectors of a square matrix""" +//| ... +//| + +static mp_obj_t linalg_eig(mp_obj_t oin) { + ndarray_obj_t *in = tools_object_is_square(oin); + uint8_t *iarray = (uint8_t *)in->array; + size_t S = in->shape[ULAB_MAX_DIMS - 1]; + mp_float_t *array = m_new(mp_float_t, S*S); + for(size_t i=0; i < S; i++) { // rows + for(size_t j=0; j < S; j++) { // columns + *array++ = ndarray_get_float_value(iarray, in->dtype); + iarray += in->strides[ULAB_MAX_DIMS - 1]; + } + iarray -= in->strides[ULAB_MAX_DIMS - 1] * S; + iarray += in->strides[ULAB_MAX_DIMS - 2]; + } + array -= S * S; + // make sure the matrix is symmetric + for(size_t m=0; m < S; m++) { + for(size_t n=m+1; n < S; n++) { + // compare entry (m, n) to (n, m) + // TODO: this must probably be scaled! + if(LINALG_EPSILON < MICROPY_FLOAT_C_FUN(fabs)(array[m * S + n] - array[n * S + m])) { + mp_raise_ValueError(translate("input matrix is asymmetric")); + } + } + } + + // if we got this far, then the matrix will be symmetric + + ndarray_obj_t *eigenvectors = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, S, S), NDARRAY_FLOAT); + mp_float_t *eigvectors = (mp_float_t *)eigenvectors->array; + + size_t iterations = linalg_jacobi_rotations(array, eigvectors, S); + + if(iterations == 0) { + // the computation did not converge; numpy raises LinAlgError + m_del(mp_float_t, array, in->len); + mp_raise_ValueError(translate("iterations did not converge")); + } + ndarray_obj_t *eigenvalues = ndarray_new_linear_array(S, NDARRAY_FLOAT); + mp_float_t *eigvalues = (mp_float_t *)eigenvalues->array; + for(size_t i=0; i < S; i++) { + eigvalues[i] = array[i * (S + 1)]; + } + m_del(mp_float_t, array, in->len); + + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + tuple->items[0] = MP_OBJ_FROM_PTR(eigenvalues); + tuple->items[1] = MP_OBJ_FROM_PTR(eigenvectors); + return tuple; +} + +MP_DEFINE_CONST_FUN_OBJ_1(linalg_eig_obj, linalg_eig); + +//| def inv(m: ulab.numpy.ndarray) -> ulab.numpy.ndarray: +//| """ +//| :param ~ulab.numpy.ndarray m: a square matrix +//| :return: The inverse of the matrix, if it exists +//| :raises ValueError: if the matrix is not invertible +//| +//| Computes the inverse of a square matrix""" +//| ... +//| +static mp_obj_t linalg_inv(mp_obj_t o_in) { + ndarray_obj_t *ndarray = tools_object_is_square(o_in); + uint8_t *array = (uint8_t *)ndarray->array; + size_t N = ndarray->shape[ULAB_MAX_DIMS - 1]; + ndarray_obj_t *inverted = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, N, N), NDARRAY_FLOAT); + mp_float_t *iarray = (mp_float_t *)inverted->array; + + mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype); + + for(size_t i=0; i < N; i++) { // rows + for(size_t j=0; j < N; j++) { // columns + *iarray++ = func(array); + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + } + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * N; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + } + // re-wind the pointer + iarray -= N*N; + + if(!linalg_invert_matrix(iarray, N)) { + mp_raise_ValueError(translate("input matrix is singular")); + } + return MP_OBJ_FROM_PTR(inverted); +} + +MP_DEFINE_CONST_FUN_OBJ_1(linalg_inv_obj, linalg_inv); +#endif + +//| def norm(x: ulab.numpy.ndarray) -> float: +//| """ +//| :param ~ulab.numpy.ndarray x: a vector or a matrix +//| +//| Computes the 2-norm of a vector or a matrix, i.e., ``sqrt(sum(x*x))``, however, without the RAM overhead.""" +//| ... +//| + +static mp_obj_t linalg_norm(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none} } , + { MP_QSTR_axis, MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t x = args[0].u_obj; + mp_obj_t axis = args[1].u_obj; + + mp_float_t dot = 0.0, value; + size_t count = 1; + + if(mp_obj_is_type(x, &mp_type_tuple) || mp_obj_is_type(x, &mp_type_list) || mp_obj_is_type(x, &mp_type_range)) { + mp_obj_iter_buf_t iter_buf; + mp_obj_t item, iterable = mp_getiter(x, &iter_buf); + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + value = mp_obj_get_float(item); + // we could simply take the sum of value ** 2, + // but this method is numerically stable + dot = dot + (value * value - dot) / count++; + } + return mp_obj_new_float(MICROPY_FLOAT_C_FUN(sqrt)(dot * (count - 1))); + } else if(mp_obj_is_type(x, &ulab_ndarray_type)) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(x); + uint8_t *array = (uint8_t *)ndarray->array; + // always get a float, so that we don't have to resolve the dtype later + mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype); + shape_strides _shape_strides = tools_reduce_axes(ndarray, axis); + ndarray_obj_t *results = ndarray_new_dense_ndarray(_shape_strides.ndim, _shape_strides.shape, NDARRAY_FLOAT); + mp_float_t *rarray = (mp_float_t *)results->array; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + if(axis != mp_const_none) { + count = 1; + dot = 0.0; + } + do { + value = func(array); + dot = dot + (value * value - dot) / count++; + array += _shape_strides.strides[0]; + l++; + } while(l < _shape_strides.shape[0]); + *rarray = MICROPY_FLOAT_C_FUN(sqrt)(dot * (count - 1)); + #if ULAB_MAX_DIMS > 1 + rarray += _shape_strides.increment; + array -= _shape_strides.strides[0] * _shape_strides.shape[0]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 1]; + k++; + } while(k < _shape_strides.shape[ULAB_MAX_DIMS - 1]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= _shape_strides.strides[ULAB_MAX_DIMS - 1] * _shape_strides.shape[ULAB_MAX_DIMS - 1]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 2]; + j++; + } while(j < _shape_strides.shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= _shape_strides.strides[ULAB_MAX_DIMS - 2] * _shape_strides.shape[ULAB_MAX_DIMS - 2]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 3]; + i++; + } while(i < _shape_strides.shape[ULAB_MAX_DIMS - 3]); + #endif + if(results->ndim == 0) { + return mp_obj_new_float(*rarray); + } + return results; + } + return mp_const_none; // we should never reach this point +} + +MP_DEFINE_CONST_FUN_OBJ_KW(linalg_norm_obj, 1, linalg_norm); +// MP_DEFINE_CONST_FUN_OBJ_1(linalg_norm_obj, linalg_norm); + +STATIC const mp_rom_map_elem_t ulab_linalg_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_linalg) }, + #if ULAB_MAX_DIMS > 1 + #if ULAB_LINALG_HAS_CHOLESKY + { MP_ROM_QSTR(MP_QSTR_cholesky), (mp_obj_t)&linalg_cholesky_obj }, + #endif + #if ULAB_LINALG_HAS_DET + { MP_ROM_QSTR(MP_QSTR_det), (mp_obj_t)&linalg_det_obj }, + #endif + #if ULAB_LINALG_HAS_EIG + { MP_ROM_QSTR(MP_QSTR_eig), (mp_obj_t)&linalg_eig_obj }, + #endif + #if ULAB_LINALG_HAS_INV + { MP_ROM_QSTR(MP_QSTR_inv), (mp_obj_t)&linalg_inv_obj }, + #endif + #endif + #if ULAB_LINALG_HAS_NORM + { MP_ROM_QSTR(MP_QSTR_norm), (mp_obj_t)&linalg_norm_obj }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_ulab_linalg_globals, ulab_linalg_globals_table); + +mp_obj_module_t ulab_linalg_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_linalg_globals, +}; + +#endif diff --git a/python/port/mod/ulab/numpy/linalg/linalg.h b/python/port/mod/ulab/numpy/linalg/linalg.h new file mode 100644 index 00000000000..fc1867fb8fd --- /dev/null +++ b/python/port/mod/ulab/numpy/linalg/linalg.h @@ -0,0 +1,26 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#ifndef _LINALG_ +#define _LINALG_ + +#include "../../ulab.h" +#include "../../ndarray.h" +#include "linalg_tools.h" + +extern mp_obj_module_t ulab_linalg_module; + +MP_DECLARE_CONST_FUN_OBJ_1(linalg_cholesky_obj); +MP_DECLARE_CONST_FUN_OBJ_1(linalg_det_obj); +MP_DECLARE_CONST_FUN_OBJ_1(linalg_eig_obj); +MP_DECLARE_CONST_FUN_OBJ_1(linalg_inv_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(linalg_norm_obj); +#endif diff --git a/python/port/mod/ulab/numpy/linalg/linalg_tools.c b/python/port/mod/ulab/numpy/linalg/linalg_tools.c new file mode 100644 index 00000000000..cf48f114031 --- /dev/null +++ b/python/port/mod/ulab/numpy/linalg/linalg_tools.c @@ -0,0 +1,171 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2010 Zoltán Vörös +*/ + +#include +#include +#include + +#include "linalg_tools.h" + +/* + * The following function inverts a matrix, whose entries are given in the input array + * The function has no dependencies beyond micropython itself (for the definition of mp_float_t), + * and can be used independent of ulab. + */ + +bool linalg_invert_matrix(mp_float_t *data, size_t N) { + // returns true, of the inversion was successful, + // false, if the matrix is singular + + // initially, this is the unit matrix: the contents of this matrix is what + // will be returned after all the transformations + mp_float_t *unit = m_new(mp_float_t, N*N); + mp_float_t elem = 1.0; + // initialise the unit matrix + memset(unit, 0, sizeof(mp_float_t)*N*N); + for(size_t m=0; m < N; m++) { + memcpy(&unit[m * (N+1)], &elem, sizeof(mp_float_t)); + } + for(size_t m=0; m < N; m++){ + // this could be faster with ((c < epsilon) && (c > -epsilon)) + if(MICROPY_FLOAT_C_FUN(fabs)(data[m * (N+1)]) < LINALG_EPSILON) { + //look for a line to swap + size_t m1 = m + 1; + for(; m1 < N; m1++) { + if(!(MICROPY_FLOAT_C_FUN(fabs)(data[m1*N + m]) < LINALG_EPSILON)) { + for(size_t m2=0; m2 < N; m2++) { + mp_float_t swapVal = data[m*N+m2]; + data[m*N+m2] = data[m1*N+m2]; + data[m1*N+m2] = swapVal; + swapVal = unit[m*N+m2]; + unit[m*N+m2] = unit[m1*N+m2]; + unit[m1*N+m2] = swapVal; + } + break; + } + } + if (m1 >= N) { + m_del(mp_float_t, unit, N*N); + return false; + } + } + for(size_t n=0; n < N; n++) { + if(m != n){ + elem = data[N * n + m] / data[m * (N+1)]; + for(size_t k=0; k < N; k++) { + data[N * n + k] -= elem * data[N * m + k]; + unit[N * n + k] -= elem * unit[N * m + k]; + } + } + } + } + for(size_t m=0; m < N; m++) { + elem = data[m * (N+1)]; + for(size_t n=0; n < N; n++) { + data[N * m + n] /= elem; + unit[N * m + n] /= elem; + } + } + memcpy(data, unit, sizeof(mp_float_t)*N*N); + m_del(mp_float_t, unit, N * N); + return true; +} + +/* + * The following function calculates the eigenvalues and eigenvectors of a symmetric + * real matrix, whose entries are given in the input array. + * The function has no dependencies beyond micropython itself (for the definition of mp_float_t), + * and can be used independent of ulab. + */ + +size_t linalg_jacobi_rotations(mp_float_t *array, mp_float_t *eigvectors, size_t S) { + // eigvectors should be a 0-array; start out with the unit matrix + for(size_t m=0; m < S; m++) { + eigvectors[m * (S+1)] = 1.0; + } + mp_float_t largest, w, t, c, s, tau, aMk, aNk, vm, vn; + size_t M, N; + size_t iterations = JACOBI_MAX * S * S; + do { + iterations--; + // find the pivot here + M = 0; + N = 0; + largest = 0.0; + for(size_t m=0; m < S-1; m++) { // -1: no need to inspect last row + for(size_t n=m+1; n < S; n++) { + w = MICROPY_FLOAT_C_FUN(fabs)(array[m * S + n]); + if((largest < w) && (LINALG_EPSILON < w)) { + M = m; + N = n; + largest = w; + } + } + } + if(M + N == 0) { // all entries are smaller than epsilon, there is not much we can do... + break; + } + // at this point, we have the pivot, and it is the entry (M, N) + // now we have to find the rotation angle + w = (array[N * S + N] - array[M * S + M]) / (MICROPY_FLOAT_CONST(2.0)*array[M * S + N]); + // The following if/else chooses the smaller absolute value for the tangent + // of the rotation angle. Going with the smaller should be numerically stabler. + if(w > 0) { + t = MICROPY_FLOAT_C_FUN(sqrt)(w*w + MICROPY_FLOAT_CONST(1.0)) - w; + } else { + t = MICROPY_FLOAT_CONST(-1.0)*(MICROPY_FLOAT_C_FUN(sqrt)(w*w + MICROPY_FLOAT_CONST(1.0)) + w); + } + s = t / MICROPY_FLOAT_C_FUN(sqrt)(t*t + MICROPY_FLOAT_CONST(1.0)); // the sine of the rotation angle + c = MICROPY_FLOAT_CONST(1.0) / MICROPY_FLOAT_C_FUN(sqrt)(t*t + MICROPY_FLOAT_CONST(1.0)); // the cosine of the rotation angle + tau = (MICROPY_FLOAT_CONST(1.0)-c)/s; // this is equal to the tangent of the half of the rotation angle + + // at this point, we have the rotation angles, so we can transform the matrix + // first the two diagonal elements + // a(M, M) = a(M, M) - t*a(M, N) + array[M * S + M] = array[M * S + M] - t * array[M * S + N]; + // a(N, N) = a(N, N) + t*a(M, N) + array[N * S + N] = array[N * S + N] + t * array[M * S + N]; + // after the rotation, the a(M, N), and a(N, M) entries should become zero + array[M * S + N] = array[N * S + M] = MICROPY_FLOAT_CONST(0.0); + // then all other elements in the column + for(size_t k=0; k < S; k++) { + if((k == M) || (k == N)) { + continue; + } + aMk = array[M * S + k]; + aNk = array[N * S + k]; + // a(M, k) = a(M, k) - s*(a(N, k) + tau*a(M, k)) + array[M * S + k] -= s * (aNk + tau * aMk); + // a(N, k) = a(N, k) + s*(a(M, k) - tau*a(N, k)) + array[N * S + k] += s * (aMk - tau * aNk); + // a(k, M) = a(M, k) + array[k * S + M] = array[M * S + k]; + // a(k, N) = a(N, k) + array[k * S + N] = array[N * S + k]; + } + // now we have to update the eigenvectors + // the rotation matrix, R, multiplies from the right + // R is the unit matrix, except for the + // R(M,M) = R(N, N) = c + // R(N, M) = s + // (M, N) = -s + // entries. This means that only the Mth, and Nth columns will change + for(size_t m=0; m < S; m++) { + vm = eigvectors[m * S + M]; + vn = eigvectors[m * S + N]; + // the new value of eigvectors(m, M) + eigvectors[m * S + M] = c * vm - s * vn; + // the new value of eigvectors(m, N) + eigvectors[m * S + N] = s * vm + c * vn; + } + } while(iterations > 0); + + return iterations; +} diff --git a/python/port/mod/ulab/numpy/linalg/linalg_tools.h b/python/port/mod/ulab/numpy/linalg/linalg_tools.h new file mode 100644 index 00000000000..942da001c82 --- /dev/null +++ b/python/port/mod/ulab/numpy/linalg/linalg_tools.h @@ -0,0 +1,28 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#ifndef _TOOLS_TOOLS_ +#define _TOOLS_TOOLS_ + +#ifndef LINALG_EPSILON +#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT +#define LINALG_EPSILON MICROPY_FLOAT_CONST(1.2e-7) +#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE +#define LINALG_EPSILON MICROPY_FLOAT_CONST(2.3e-16) +#endif +#endif /* LINALG_EPSILON */ + +#define JACOBI_MAX 20 + +bool linalg_invert_matrix(mp_float_t *, size_t ); +size_t linalg_jacobi_rotations(mp_float_t *, mp_float_t *, size_t ); + +#endif /* _TOOLS_TOOLS_ */ + diff --git a/python/port/mod/ulab/numpy/numerical/numerical.c b/python/port/mod/ulab/numpy/numerical/numerical.c new file mode 100644 index 00000000000..af9ff1c028d --- /dev/null +++ b/python/port/mod/ulab/numpy/numerical/numerical.c @@ -0,0 +1,1338 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Scott Shawcroft for Adafruit Industries + * 2020 Taku Fukada +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../ulab.h" +#include "../../ulab_tools.h" +#include "numerical.h" + +enum NUMERICAL_FUNCTION_TYPE { + NUMERICAL_ALL, + NUMERICAL_ANY, + NUMERICAL_ARGMAX, + NUMERICAL_ARGMIN, + NUMERICAL_MAX, + NUMERICAL_MEAN, + NUMERICAL_MIN, + NUMERICAL_STD, + NUMERICAL_SUM, +}; + +//| """Numerical and Statistical functions +//| +//| Most of these functions take an "axis" argument, which indicates whether to +//| operate over the flattened array (None), or a particular axis (integer).""" +//| +//| from ulab import _ArrayLike +//| + +static void numerical_reduce_axes(ndarray_obj_t *ndarray, int8_t axis, size_t *shape, int32_t *strides) { + // removes the values corresponding to a single axis from the shape and strides array + uint8_t index = ULAB_MAX_DIMS - ndarray->ndim + axis; + if((ndarray->ndim == 1) && (axis == 0)) { + index = 0; + shape[ULAB_MAX_DIMS - 1] = 1; + return; + } + for(uint8_t i = ULAB_MAX_DIMS - 1; i > 0; i--) { + if(i > index) { + shape[i] = ndarray->shape[i]; + strides[i] = ndarray->strides[i]; + } else { + shape[i] = ndarray->shape[i-1]; + strides[i] = ndarray->strides[i-1]; + } + } +} + +#if ULAB_NUMPY_HAS_ALL | ULAB_NUMPY_HAS_ANY +static mp_obj_t numerical_all_any(mp_obj_t oin, mp_obj_t axis, uint8_t optype) { + bool anytype = optype == NUMERICAL_ALL ? 1 : 0; + if(mp_obj_is_type(oin, &ulab_ndarray_type)) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(oin); + uint8_t *array = (uint8_t *)ndarray->array; + if(ndarray->len == 0) { // return immediately with empty arrays + if(optype == NUMERICAL_ALL) { + return mp_const_true; + } else { + return mp_const_false; + } + } + // always get a float, so that we don't have to resolve the dtype later + mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype); + ndarray_obj_t *results = NULL; + uint8_t *rarray = NULL; + shape_strides _shape_strides = tools_reduce_axes(ndarray, axis); + if(axis != mp_const_none) { + results = ndarray_new_dense_ndarray(_shape_strides.ndim, _shape_strides.shape, NDARRAY_BOOL); + rarray = results->array; + if(optype == NUMERICAL_ALL) { + memset(rarray, 1, results->len); + } + } + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + if(axis == mp_const_none) { + do { + mp_float_t value = func(array); + if((value != MICROPY_FLOAT_CONST(0.0)) & !anytype) { + // optype = NUMERICAL_ANY + return mp_const_true; + } else if((value == MICROPY_FLOAT_CONST(0.0)) & anytype) { + // optype == NUMERICAL_ALL + return mp_const_false; + } + array += _shape_strides.strides[0]; + l++; + } while(l < _shape_strides.shape[0]); + } else { // a scalar axis keyword was supplied + do { + mp_float_t value = func(array); + if((value != MICROPY_FLOAT_CONST(0.0)) & !anytype) { + // optype == NUMERICAL_ANY + *rarray = 1; + // since we are breaking out of the loop, move the pointer forward + array += _shape_strides.strides[0] * (_shape_strides.shape[0] - l); + break; + } else if((value == MICROPY_FLOAT_CONST(0.0)) & anytype) { + // optype == NUMERICAL_ALL + *rarray = 0; + // since we are breaking out of the loop, move the pointer forward + array += _shape_strides.strides[0] * (_shape_strides.shape[0] - l); + break; + } + array += _shape_strides.strides[0]; + l++; + } while(l < _shape_strides.shape[0]); + } + #if ULAB_MAX_DIMS > 1 + rarray += _shape_strides.increment; + array -= _shape_strides.strides[0] * _shape_strides.shape[0]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 1]; + k++; + } while(k < _shape_strides.shape[ULAB_MAX_DIMS - 1]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= _shape_strides.strides[ULAB_MAX_DIMS - 1] * _shape_strides.shape[ULAB_MAX_DIMS - 1]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 2]; + j++; + } while(j < _shape_strides.shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= _shape_strides.strides[ULAB_MAX_DIMS - 2] * _shape_strides.shape[ULAB_MAX_DIMS - 2]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 3]; + i++; + } while(i < _shape_strides.shape[ULAB_MAX_DIMS - 3]) + #endif + return results; + } else if(mp_obj_is_int(oin) || mp_obj_is_float(oin)) { + return mp_obj_is_true(oin) ? mp_const_true : mp_const_false; + } else { + mp_obj_iter_buf_t iter_buf; + mp_obj_t item, iterable = mp_getiter(oin, &iter_buf); + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + if(!mp_obj_is_true(item) & !anytype) { + return mp_const_false; + } else if(mp_obj_is_true(item) & anytype) { + return mp_const_true; + } + } + } + return anytype ? mp_const_true : mp_const_false; +} +#endif + +#if ULAB_NUMPY_HAS_SUM | ULAB_NUMPY_HAS_MEAN | ULAB_NUMPY_HAS_STD +static mp_obj_t numerical_sum_mean_std_iterable(mp_obj_t oin, uint8_t optype, size_t ddof) { + mp_float_t value = MICROPY_FLOAT_CONST(0.0); + mp_float_t M = MICROPY_FLOAT_CONST(0.0); + mp_float_t m = MICROPY_FLOAT_CONST(0.0); + mp_float_t S = MICROPY_FLOAT_CONST(0.0); + mp_float_t s = MICROPY_FLOAT_CONST(0.0); + size_t count = 0; + mp_obj_iter_buf_t iter_buf; + mp_obj_t item, iterable = mp_getiter(oin, &iter_buf); + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + value = mp_obj_get_float(item); + m = M + (value - M) / (count + 1); + s = S + (value - M) * (value - m); + M = m; + S = s; + count++; + } + if(optype == NUMERICAL_SUM) { + return mp_obj_new_float(m * count); + } else if(optype == NUMERICAL_MEAN) { + return count > 0 ? mp_obj_new_float(m) : mp_obj_new_float(MICROPY_FLOAT_CONST(0.0)); + } else { // this should be the case of the standard deviation + return count > ddof ? mp_obj_new_float(MICROPY_FLOAT_C_FUN(sqrt)(s / (count - ddof))) : mp_obj_new_float(MICROPY_FLOAT_CONST(0.0)); + } +} + +static mp_obj_t numerical_sum_mean_std_ndarray(ndarray_obj_t *ndarray, mp_obj_t axis, uint8_t optype, size_t ddof) { + uint8_t *array = (uint8_t *)ndarray->array; + shape_strides _shape_strides = tools_reduce_axes(ndarray, axis); + + if(axis == mp_const_none) { + // work with the flattened array + if((optype == NUMERICAL_STD) && (ddof > ndarray->len)) { + // if there are too many degrees of freedom, there is no point in calculating anything + return mp_obj_new_float(MICROPY_FLOAT_CONST(0.0)); + } + mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype); + mp_float_t M =MICROPY_FLOAT_CONST(0.0); + mp_float_t m = MICROPY_FLOAT_CONST(0.0); + mp_float_t S = MICROPY_FLOAT_CONST(0.0); + mp_float_t s = MICROPY_FLOAT_CONST(0.0); + size_t count = 0; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + count++; + mp_float_t value = func(array); + m = M + (value - M) / (mp_float_t)count; + if(optype == NUMERICAL_STD) { + s = S + (value - M) * (value - m); + S = s; + } + M = m; + array += _shape_strides.strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < _shape_strides.shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + array -= _shape_strides.strides[ULAB_MAX_DIMS - 1] * _shape_strides.shape[ULAB_MAX_DIMS - 1]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < _shape_strides.shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= _shape_strides.strides[ULAB_MAX_DIMS - 2] * _shape_strides.shape[ULAB_MAX_DIMS - 2]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < _shape_strides.shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= _shape_strides.strides[ULAB_MAX_DIMS - 3] * _shape_strides.shape[ULAB_MAX_DIMS - 3]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < _shape_strides.shape[ULAB_MAX_DIMS - 4]); + #endif + if(optype == NUMERICAL_SUM) { + // numpy returns an integer for integer input types + if(ndarray->dtype == NDARRAY_FLOAT) { + return mp_obj_new_float(M * ndarray->len); + } else { + return mp_obj_new_int((int32_t)(M * ndarray->len)); + } + } else if(optype == NUMERICAL_MEAN) { + return mp_obj_new_float(M); + } else { // this must be the case of the standard deviation + // we have already made certain that ddof < ndarray->len holds + return mp_obj_new_float(MICROPY_FLOAT_C_FUN(sqrt)(S / (ndarray->len - ddof))); + } + } else { + ndarray_obj_t *results = NULL; + uint8_t *rarray = NULL; + mp_float_t *farray = NULL; + if(optype == NUMERICAL_SUM) { + results = ndarray_new_dense_ndarray(_shape_strides.ndim, _shape_strides.shape, ndarray->dtype); + rarray = (uint8_t *)results->array; + // TODO: numpy promotes the output to the highest integer type + if(ndarray->dtype == NDARRAY_UINT8) { + RUN_SUM(uint8_t, array, results, rarray, _shape_strides); + } else if(ndarray->dtype == NDARRAY_INT8) { + RUN_SUM(int8_t, array, results, rarray, _shape_strides); + } else if(ndarray->dtype == NDARRAY_UINT16) { + RUN_SUM(uint16_t, array, results, rarray, _shape_strides); + } else if(ndarray->dtype == NDARRAY_INT16) { + RUN_SUM(int16_t, array, results, rarray, _shape_strides); + } else { + // for floats, the sum might be inaccurate with the naive summation + // call mean, and multiply with the number of samples + farray = (mp_float_t *)results->array; + RUN_MEAN_STD(mp_float_t, array, farray, _shape_strides, MICROPY_FLOAT_CONST(0.0), 0); + mp_float_t norm = (mp_float_t)_shape_strides.shape[0]; + // re-wind the array here + farray = (mp_float_t *)results->array; + for(size_t i=0; i < results->len; i++) { + *farray++ *= norm; + } + } + } else { + bool isStd = optype == NUMERICAL_STD ? 1 : 0; + results = ndarray_new_dense_ndarray(_shape_strides.ndim, _shape_strides.shape, NDARRAY_FLOAT); + farray = (mp_float_t *)results->array; + // we can return the 0 array here, if the degrees of freedom is larger than the length of the axis + if((optype == NUMERICAL_STD) && (_shape_strides.shape[0] <= ddof)) { + return MP_OBJ_FROM_PTR(results); + } + mp_float_t div = optype == NUMERICAL_STD ? (mp_float_t)(_shape_strides.shape[0] - ddof) : MICROPY_FLOAT_CONST(0.0); + if(ndarray->dtype == NDARRAY_UINT8) { + RUN_MEAN_STD(uint8_t, array, farray, _shape_strides, div, isStd); + } else if(ndarray->dtype == NDARRAY_INT8) { + RUN_MEAN_STD(int8_t, array, farray, _shape_strides, div, isStd); + } else if(ndarray->dtype == NDARRAY_UINT16) { + RUN_MEAN_STD(uint16_t, array, farray, _shape_strides, div, isStd); + } else if(ndarray->dtype == NDARRAY_INT16) { + RUN_MEAN_STD(int16_t, array, farray, _shape_strides, div, isStd); + } else { + RUN_MEAN_STD(mp_float_t, array, farray, _shape_strides, div, isStd); + } + } + if(results->ndim == 0) { // return a scalar here + return mp_binary_get_val_array(results->dtype, results->array, 0); + } + return MP_OBJ_FROM_PTR(results); + } + return mp_const_none; +} +#endif + +#if ULAB_NUMPY_HAS_ARGMINMAX +static mp_obj_t numerical_argmin_argmax_iterable(mp_obj_t oin, uint8_t optype) { + if(MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(oin)) == 0) { + mp_raise_ValueError(translate("attempt to get argmin/argmax of an empty sequence")); + } + size_t idx = 0, best_idx = 0; + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(oin, &iter_buf); + mp_obj_t item; + uint8_t op = 0; // argmin, min + if((optype == NUMERICAL_ARGMAX) || (optype == NUMERICAL_MAX)) op = 1; + item = mp_iternext(iterable); + mp_obj_t best_obj = item; + mp_float_t value, best_value = mp_obj_get_float(item); + value = best_value; + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + idx++; + value = mp_obj_get_float(item); + if((op == 0) && (value < best_value)) { + best_obj = item; + best_idx = idx; + best_value = value; + } else if((op == 1) && (value > best_value)) { + best_obj = item; + best_idx = idx; + best_value = value; + } + } + if((optype == NUMERICAL_ARGMIN) || (optype == NUMERICAL_ARGMAX)) { + return MP_OBJ_NEW_SMALL_INT(best_idx); + } else { + return best_obj; + } +} + +static mp_obj_t numerical_argmin_argmax_ndarray(ndarray_obj_t *ndarray, mp_obj_t axis, uint8_t optype) { + // TODO: treat the flattened array + if(ndarray->len == 0) { + mp_raise_ValueError(translate("attempt to get (arg)min/(arg)max of empty sequence")); + } + + if(axis == mp_const_none) { + // work with the flattened array + mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype); + uint8_t *array = (uint8_t *)ndarray->array; + mp_float_t best_value = func(array); + mp_float_t value; + size_t index = 0, best_index = 0; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + value = func(array); + if((optype == NUMERICAL_ARGMAX) || (optype == NUMERICAL_MAX)) { + if(best_value < value) { + best_value = value; + best_index = index; + } + } else { + if(best_value > value) { + best_value = value; + best_index = index; + } + } + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + l++; + index++; + } while(l < ndarray->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * ndarray->shape[ULAB_MAX_DIMS-1]; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < ndarray->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= ndarray->strides[ULAB_MAX_DIMS - 2] * ndarray->shape[ULAB_MAX_DIMS-2]; + array += ndarray->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < ndarray->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= ndarray->strides[ULAB_MAX_DIMS - 3] * ndarray->shape[ULAB_MAX_DIMS-3]; + array += ndarray->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < ndarray->shape[ULAB_MAX_DIMS - 4]); + #endif + + if((optype == NUMERICAL_ARGMIN) || (optype == NUMERICAL_ARGMAX)) { + return mp_obj_new_int(best_index); + } else { + if(ndarray->dtype == NDARRAY_FLOAT) { + return mp_obj_new_float(best_value); + } else { + return MP_OBJ_NEW_SMALL_INT((int32_t)best_value); + } + } + } else { + int8_t ax = mp_obj_get_int(axis); + if(ax < 0) ax += ndarray->ndim; + if((ax < 0) || (ax > ndarray->ndim - 1)) { + mp_raise_ValueError(translate("axis is out of bounds")); + } + + uint8_t *array = (uint8_t *)ndarray->array; + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + memset(strides, 0, sizeof(uint32_t)*ULAB_MAX_DIMS); + numerical_reduce_axes(ndarray, ax, shape, strides); + uint8_t index = ULAB_MAX_DIMS - ndarray->ndim + ax; + + ndarray_obj_t *results = NULL; + + if((optype == NUMERICAL_ARGMIN) || (optype == NUMERICAL_ARGMAX)) { + results = ndarray_new_dense_ndarray(MAX(1, ndarray->ndim-1), shape, NDARRAY_INT16); + } else { + results = ndarray_new_dense_ndarray(MAX(1, ndarray->ndim-1), shape, ndarray->dtype); + } + + uint8_t *rarray = (uint8_t *)results->array; + + if(ndarray->dtype == NDARRAY_UINT8) { + RUN_ARGMIN(ndarray, uint8_t, array, results, rarray, shape, strides, index, optype); + } else if(ndarray->dtype == NDARRAY_INT8) { + RUN_ARGMIN(ndarray, int8_t, array, results, rarray, shape, strides, index, optype); + } else if(ndarray->dtype == NDARRAY_UINT16) { + RUN_ARGMIN(ndarray, uint16_t, array, results, rarray, shape, strides, index, optype); + } else if(ndarray->dtype == NDARRAY_INT16) { + RUN_ARGMIN(ndarray, int16_t, array, results, rarray, shape, strides, index, optype); + } else { + RUN_ARGMIN(ndarray, mp_float_t, array, results, rarray, shape, strides, index, optype); + } + if(results->len == 1) { + return mp_binary_get_val_array(results->dtype, results->array, 0); + } + return MP_OBJ_FROM_PTR(results); + } + return mp_const_none; +} +#endif + +static mp_obj_t numerical_function(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args, uint8_t optype) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none} } , + { MP_QSTR_axis, MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t oin = args[0].u_obj; + mp_obj_t axis = args[1].u_obj; + if((axis != mp_const_none) && (!mp_obj_is_int(axis))) { + mp_raise_TypeError(translate("axis must be None, or an integer")); + } + + if((optype == NUMERICAL_ALL) || (optype == NUMERICAL_ANY)) { + return numerical_all_any(oin, axis, optype); + } + if(mp_obj_is_type(oin, &mp_type_tuple) || mp_obj_is_type(oin, &mp_type_list) || + mp_obj_is_type(oin, &mp_type_range)) { + switch(optype) { + case NUMERICAL_MIN: + case NUMERICAL_ARGMIN: + case NUMERICAL_MAX: + case NUMERICAL_ARGMAX: + return numerical_argmin_argmax_iterable(oin, optype); + case NUMERICAL_SUM: + case NUMERICAL_MEAN: + return numerical_sum_mean_std_iterable(oin, optype, 0); + default: // we should never reach this point, but whatever + return mp_const_none; + } + } else if(mp_obj_is_type(oin, &ulab_ndarray_type)) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(oin); + switch(optype) { + case NUMERICAL_MIN: + case NUMERICAL_MAX: + case NUMERICAL_ARGMIN: + case NUMERICAL_ARGMAX: + return numerical_argmin_argmax_ndarray(ndarray, axis, optype); + case NUMERICAL_SUM: + case NUMERICAL_MEAN: + return numerical_sum_mean_std_ndarray(ndarray, axis, optype, 0); + default: + mp_raise_NotImplementedError(translate("operation is not implemented on ndarrays")); + } + } else { + mp_raise_TypeError(translate("input must be tuple, list, range, or ndarray")); + } + return mp_const_none; +} + +#if ULAB_NUMPY_HAS_SORT | NDARRAY_HAS_SORT +static mp_obj_t numerical_sort_helper(mp_obj_t oin, mp_obj_t axis, uint8_t inplace) { + if(!mp_obj_is_type(oin, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("sort argument must be an ndarray")); + } + + ndarray_obj_t *ndarray; + if(inplace == 1) { + ndarray = MP_OBJ_TO_PTR(oin); + } else { + ndarray = ndarray_copy_view(MP_OBJ_TO_PTR(oin)); + } + + int8_t ax = 0; + if(axis == mp_const_none) { + // flatten the array + for(uint8_t i=0; i < ULAB_MAX_DIMS - 1; i++) { + ndarray->shape[i] = 0; + ndarray->strides[i] = 0; + } + ndarray->shape[ULAB_MAX_DIMS - 1] = ndarray->len; + ndarray->strides[ULAB_MAX_DIMS - 1] = ndarray->itemsize; + ndarray->ndim = 1; + } else { + ax = mp_obj_get_int(axis); + if(ax < 0) ax += ndarray->ndim; + if((ax < 0) || (ax > ndarray->ndim - 1)) { + mp_raise_ValueError(translate("index out of range")); + } + } + + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + memset(strides, 0, sizeof(uint32_t)*ULAB_MAX_DIMS); + numerical_reduce_axes(ndarray, ax, shape, strides); + ax = ULAB_MAX_DIMS - ndarray->ndim + ax; + // we work with the typed array, so re-scale the stride + int32_t increment = ndarray->strides[ax] / ndarray->itemsize; + + uint8_t *array = (uint8_t *)ndarray->array; + if((ndarray->dtype == NDARRAY_UINT8) || (ndarray->dtype == NDARRAY_INT8)) { + HEAPSORT(ndarray, uint8_t, array, shape, strides, ax, increment, ndarray->shape[ax]); + } else if((ndarray->dtype == NDARRAY_INT16) || (ndarray->dtype == NDARRAY_INT16)) { + HEAPSORT(ndarray, uint16_t, array, shape, strides, ax, increment, ndarray->shape[ax]); + } else { + HEAPSORT(ndarray, mp_float_t, array, shape, strides, ax, increment, ndarray->shape[ax]); + } + if(inplace == 1) { + return mp_const_none; + } else { + return MP_OBJ_FROM_PTR(ndarray); + } +} +#endif /* ULAB_NUMERICAL_HAS_SORT | NDARRAY_HAS_SORT */ + +#if ULAB_NUMPY_HAS_ALL +mp_obj_t numerical_all(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return numerical_function(n_args, pos_args, kw_args, NUMERICAL_ALL); +} +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_all_obj, 1, numerical_all); +#endif + +#if ULAB_NUMPY_HAS_ANY +mp_obj_t numerical_any(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return numerical_function(n_args, pos_args, kw_args, NUMERICAL_ANY); +} +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_any_obj, 1, numerical_any); +#endif + +#if ULAB_NUMPY_HAS_ARGMINMAX +//| def argmax(array: _ArrayLike, *, axis: Optional[int] = None) -> int: +//| """Return the index of the maximum element of the 1D array""" +//| ... +//| + +mp_obj_t numerical_argmax(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return numerical_function(n_args, pos_args, kw_args, NUMERICAL_ARGMAX); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_argmax_obj, 1, numerical_argmax); + +//| def argmin(array: _ArrayLike, *, axis: Optional[int] = None) -> int: +//| """Return the index of the minimum element of the 1D array""" +//| ... +//| + +static mp_obj_t numerical_argmin(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return numerical_function(n_args, pos_args, kw_args, NUMERICAL_ARGMIN); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_argmin_obj, 1, numerical_argmin); +#endif + +#if ULAB_NUMPY_HAS_ARGSORT +//| def argsort(array: ulab.ndarray, *, axis: int = -1) -> ulab.ndarray: +//| """Returns an array which gives indices into the input array from least to greatest.""" +//| ... +//| + +mp_obj_t numerical_argsort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("argsort argument must be an ndarray")); + } + + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + if(args[1].u_obj == mp_const_none) { + // bail out, though dense arrays could still be sorted + mp_raise_NotImplementedError(translate("argsort is not implemented for flattened arrays")); + } + // Since we are returning an NDARRAY_UINT16 array, bail out, + // if the axis is longer than what we can hold + for(uint8_t i=0; i < ULAB_MAX_DIMS; i++) { + if(ndarray->shape[i] > 65535) { + mp_raise_ValueError(translate("axis too long")); + } + } + int8_t ax = mp_obj_get_int(args[1].u_obj); + if(ax < 0) ax += ndarray->ndim; + if((ax < 0) || (ax > ndarray->ndim - 1)) { + mp_raise_ValueError(translate("index out of range")); + } + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + memset(strides, 0, sizeof(uint32_t)*ULAB_MAX_DIMS); + numerical_reduce_axes(ndarray, ax, shape, strides); + + // We could return an NDARRAY_UINT8 array, if all lengths are shorter than 256 + ndarray_obj_t *indices = ndarray_new_ndarray(ndarray->ndim, ndarray->shape, NULL, NDARRAY_UINT16); + int32_t *istrides = m_new(int32_t, ULAB_MAX_DIMS); + memset(istrides, 0, sizeof(uint32_t)*ULAB_MAX_DIMS); + numerical_reduce_axes(indices, ax, shape, istrides); + for(uint8_t i=0; i < ULAB_MAX_DIMS; i++) { + istrides[i] /= sizeof(uint16_t); + } + + ax = ULAB_MAX_DIMS - ndarray->ndim + ax; + // we work with the typed array, so re-scale the stride + int32_t increment = ndarray->strides[ax] / ndarray->itemsize; + uint16_t iincrement = indices->strides[ax] / sizeof(uint16_t); + + uint8_t *array = (uint8_t *)ndarray->array; + uint16_t *iarray = (uint16_t *)indices->array; + + // fill in the index values + #if ULAB_MAX_DIMS > 3 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t k = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t l = 0; + do { + #endif + uint16_t m = 0; + do { + *iarray = m++; + iarray += iincrement; + } while(m < indices->shape[ax]); + #if ULAB_MAX_DIMS > 1 + iarray -= iincrement * indices->shape[ax]; + iarray += istrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < shape[ULAB_MAX_DIMS - 1]); + iarray -= istrides[ULAB_MAX_DIMS - 1] * shape[ULAB_MAX_DIMS - 1]; + iarray += istrides[ULAB_MAX_DIMS - 2]; + #endif + #if ULAB_MAX_DIMS > 2 + k++; + } while(k < shape[ULAB_MAX_DIMS - 2]); + iarray -= istrides[ULAB_MAX_DIMS - 2] * shape[ULAB_MAX_DIMS - 2]; + iarray += istrides[ULAB_MAX_DIMS - 3]; + #endif + #if ULAB_MAX_DIMS > 3 + j++; + } while(j < shape[ULAB_MAX_DIMS - 3]); + #endif + // reset the array + iarray = indices->array; + + if((ndarray->dtype == NDARRAY_UINT8) || (ndarray->dtype == NDARRAY_INT8)) { + HEAP_ARGSORT(ndarray, uint8_t, array, shape, strides, ax, increment, ndarray->shape[ax], iarray, istrides, iincrement); + } else if((ndarray->dtype == NDARRAY_UINT16) || (ndarray->dtype == NDARRAY_INT16)) { + HEAP_ARGSORT(ndarray, uint16_t, array, shape, strides, ax, increment, ndarray->shape[ax], iarray, istrides, iincrement); + } else { + HEAP_ARGSORT(ndarray, mp_float_t, array, shape, strides, ax, increment, ndarray->shape[ax], iarray, istrides, iincrement); + } + return MP_OBJ_FROM_PTR(indices); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_argsort_obj, 1, numerical_argsort); +#endif + +#if ULAB_NUMPY_HAS_CROSS +//| def cross(a: ulab.ndarray, b: ulab.ndarray) -> ulab.ndarray: +//| """Return the cross product of two vectors of length 3""" +//| ... +//| + +static mp_obj_t numerical_cross(mp_obj_t _a, mp_obj_t _b) { + if (!mp_obj_is_type(_a, &ulab_ndarray_type) || !mp_obj_is_type(_b, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("arguments must be ndarrays")); + } + ndarray_obj_t *a = MP_OBJ_TO_PTR(_a); + ndarray_obj_t *b = MP_OBJ_TO_PTR(_b); + if((a->ndim != 1) || (b->ndim != 1) || (a->len != b->len) || (a->len != 3)) { + mp_raise_ValueError(translate("cross is defined for 1D arrays of length 3")); + } + + mp_float_t *results = m_new(mp_float_t, 3); + results[0] = ndarray_get_float_index(a->array, a->dtype, 1) * ndarray_get_float_index(b->array, b->dtype, 2); + results[0] -= ndarray_get_float_index(a->array, a->dtype, 2) * ndarray_get_float_index(b->array, b->dtype, 1); + results[1] = -ndarray_get_float_index(a->array, a->dtype, 0) * ndarray_get_float_index(b->array, b->dtype, 2); + results[1] += ndarray_get_float_index(a->array, a->dtype, 2) * ndarray_get_float_index(b->array, b->dtype, 0); + results[2] = ndarray_get_float_index(a->array, a->dtype, 0) * ndarray_get_float_index(b->array, b->dtype, 1); + results[2] -= ndarray_get_float_index(a->array, a->dtype, 1) * ndarray_get_float_index(b->array, b->dtype, 0); + + /* The upcasting happens here with the rules + + - if one of the operarands is a float, the result is always float + - operation on identical types preserves type + + uint8 + int8 => int16 + uint8 + int16 => int16 + uint8 + uint16 => uint16 + int8 + int16 => int16 + int8 + uint16 => uint16 + uint16 + int16 => float + + */ + + uint8_t dtype = NDARRAY_FLOAT; + if(a->dtype == b->dtype) { + dtype = a->dtype; + } else if(((a->dtype == NDARRAY_UINT8) && (b->dtype == NDARRAY_INT8)) || ((a->dtype == NDARRAY_INT8) && (b->dtype == NDARRAY_UINT8))) { + dtype = NDARRAY_INT16; + } else if(((a->dtype == NDARRAY_UINT8) && (b->dtype == NDARRAY_INT16)) || ((a->dtype == NDARRAY_INT16) && (b->dtype == NDARRAY_UINT8))) { + dtype = NDARRAY_INT16; + } else if(((a->dtype == NDARRAY_UINT8) && (b->dtype == NDARRAY_UINT16)) || ((a->dtype == NDARRAY_UINT16) && (b->dtype == NDARRAY_UINT8))) { + dtype = NDARRAY_UINT16; + } else if(((a->dtype == NDARRAY_INT8) && (b->dtype == NDARRAY_INT16)) || ((a->dtype == NDARRAY_INT16) && (b->dtype == NDARRAY_INT8))) { + dtype = NDARRAY_INT16; + } else if(((a->dtype == NDARRAY_INT8) && (b->dtype == NDARRAY_UINT16)) || ((a->dtype == NDARRAY_UINT16) && (b->dtype == NDARRAY_INT8))) { + dtype = NDARRAY_UINT16; + } + + ndarray_obj_t *ndarray = ndarray_new_linear_array(3, dtype); + if(dtype == NDARRAY_UINT8) { + uint8_t *array = (uint8_t *)ndarray->array; + for(uint8_t i=0; i < 3; i++) array[i] = (uint8_t)results[i]; + } else if(dtype == NDARRAY_INT8) { + int8_t *array = (int8_t *)ndarray->array; + for(uint8_t i=0; i < 3; i++) array[i] = (int8_t)results[i]; + } else if(dtype == NDARRAY_UINT16) { + uint16_t *array = (uint16_t *)ndarray->array; + for(uint8_t i=0; i < 3; i++) array[i] = (uint16_t)results[i]; + } else if(dtype == NDARRAY_INT16) { + int16_t *array = (int16_t *)ndarray->array; + for(uint8_t i=0; i < 3; i++) array[i] = (int16_t)results[i]; + } else { + mp_float_t *array = (mp_float_t *)ndarray->array; + for(uint8_t i=0; i < 3; i++) array[i] = results[i]; + } + m_del(mp_float_t, results, 3); + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_2(numerical_cross_obj, numerical_cross); + +#endif /* ULAB_NUMERICAL_HAS_CROSS */ + +#if ULAB_NUMPY_HAS_DIFF +//| def diff(array: ulab.ndarray, *, n: int = 1, axis: int = -1) -> ulab.ndarray: +//| """Return the numerical derivative of successive elements of the array, as +//| an array. axis=None is not supported.""" +//| ... +//| + +mp_obj_t numerical_diff(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_n, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1 } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1 } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("diff argument must be an ndarray")); + } + + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + int8_t ax = args[2].u_int; + if(ax < 0) ax += ndarray->ndim; + + if((ax < 0) || (ax > ndarray->ndim - 1)) { + mp_raise_ValueError(translate("index out of range")); + } + + if((args[1].u_int < 0) || (args[1].u_int > 9)) { + mp_raise_ValueError(translate("differentiation order out of range")); + } + uint8_t N = (uint8_t)args[1].u_int; + uint8_t index = ULAB_MAX_DIMS - ndarray->ndim + ax; + if(N > ndarray->shape[index]) { + mp_raise_ValueError(translate("differentiation order out of range")); + } + + int8_t *stencil = m_new(int8_t, N+1); + stencil[0] = 1; + for(uint8_t i=1; i < N+1; i++) { + stencil[i] = -stencil[i-1]*(N-i+1)/i; + } + + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); + for(uint8_t i=0; i < ULAB_MAX_DIMS; i++) { + shape[i] = ndarray->shape[i]; + if(i == index) { + shape[i] -= N; + } + } + uint8_t *array = (uint8_t *)ndarray->array; + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndarray->ndim, shape, ndarray->dtype); + uint8_t *rarray = (uint8_t *)results->array; + + memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + memset(strides, 0, sizeof(int32_t)*ULAB_MAX_DIMS); + numerical_reduce_axes(ndarray, ax, shape, strides); + + if(ndarray->dtype == NDARRAY_UINT8) { + RUN_DIFF(ndarray, uint8_t, array, results, rarray, shape, strides, index, stencil, N); + } else if(ndarray->dtype == NDARRAY_INT8) { + RUN_DIFF(ndarray, int8_t, array, results, rarray, shape, strides, index, stencil, N); + } else if(ndarray->dtype == NDARRAY_UINT16) { + RUN_DIFF(ndarray, uint16_t, array, results, rarray, shape, strides, index, stencil, N); + } else if(ndarray->dtype == NDARRAY_INT16) { + RUN_DIFF(ndarray, int16_t, array, results, rarray, shape, strides, index, stencil, N); + } else { + RUN_DIFF(ndarray, mp_float_t, array, results, rarray, shape, strides, index, stencil, N); + } + m_del(int8_t, stencil, N+1); + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(int32_t, strides, ULAB_MAX_DIMS); + return MP_OBJ_FROM_PTR(results); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_diff_obj, 1, numerical_diff); +#endif + +#if ULAB_NUMPY_HAS_FLIP +//| def flip(array: ulab.ndarray, *, axis: Optional[int] = None) -> ulab.ndarray: +//| """Returns a new array that reverses the order of the elements along the +//| given axis, or along all axes if axis is None.""" +//| ... +//| + +mp_obj_t numerical_flip(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("flip argument must be an ndarray")); + } + + ndarray_obj_t *results = NULL; + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + if(args[1].u_obj == mp_const_none) { // flip the flattened array + results = ndarray_new_linear_array(ndarray->len, ndarray->dtype); + ndarray_copy_array(ndarray, results); + uint8_t *rarray = (uint8_t *)results->array; + rarray += (results->len - 1) * results->itemsize; + results->array = rarray; + results->strides[ULAB_MAX_DIMS - 1] = -results->strides[ULAB_MAX_DIMS - 1]; + } else if(mp_obj_is_int(args[1].u_obj)){ + int8_t ax = mp_obj_get_int(args[1].u_obj); + if(ax < 0) ax += ndarray->ndim; + if((ax < 0) || (ax > ndarray->ndim - 1)) { + mp_raise_ValueError(translate("index out of range")); + } + ax = ULAB_MAX_DIMS - ndarray->ndim + ax; + int32_t offset = (ndarray->shape[ax] - 1) * ndarray->strides[ax]; + results = ndarray_new_view(ndarray, ndarray->ndim, ndarray->shape, ndarray->strides, offset); + results->strides[ax] = -results->strides[ax]; + } else { + mp_raise_TypeError(translate("wrong axis index")); + } + return results; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_flip_obj, 1, numerical_flip); +#endif + +#if ULAB_NUMPY_HAS_MINMAX +//| def max(array: _ArrayLike, *, axis: Optional[int] = None) -> float: +//| """Return the maximum element of the 1D array""" +//| ... +//| + +mp_obj_t numerical_max(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return numerical_function(n_args, pos_args, kw_args, NUMERICAL_MAX); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_max_obj, 1, numerical_max); +#endif + +#if ULAB_NUMPY_HAS_MEAN +//| def mean(array: _ArrayLike, *, axis: Optional[int] = None) -> float: +//| """Return the mean element of the 1D array, as a number if axis is None, otherwise as an array.""" +//| ... +//| + +mp_obj_t numerical_mean(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return numerical_function(n_args, pos_args, kw_args, NUMERICAL_MEAN); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_mean_obj, 1, numerical_mean); +#endif + +#if ULAB_NUMPY_HAS_MEDIAN +//| def median(array: ulab.ndarray, *, axis: int = -1) -> ulab.ndarray: +//| """Find the median value in an array along the given axis, or along all axes if axis is None.""" +//| ... +//| + +mp_obj_t numerical_median(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("median argument must be an ndarray")); + } + + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + if(ndarray->len == 0) { + return mp_obj_new_float(MICROPY_FLOAT_C_FUN(nan)("")); + } + + ndarray = numerical_sort_helper(args[0].u_obj, args[1].u_obj, 0); + + if((args[1].u_obj == mp_const_none) || (ndarray->ndim == 1)) { + // at this point, the array holding the sorted values should be flat + uint8_t *array = (uint8_t *)ndarray->array; + size_t len = ndarray->len; + array += (len >> 1) * ndarray->itemsize; + mp_float_t median = ndarray_get_float_value(array, ndarray->dtype); + if(!(len & 0x01)) { // len is an even number + array -= ndarray->itemsize; + median += ndarray_get_float_value(array, ndarray->dtype); + median *= MICROPY_FLOAT_CONST(0.5); + } + return mp_obj_new_float(median); + } else { + int8_t ax = mp_obj_get_int(args[1].u_obj); + if(ax < 0) ax += ndarray->ndim; + // here we can save the exception, because if the axis is out of range, + // then numerical_sort_helper has already taken care of the issue + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + memset(strides, 0, sizeof(uint32_t)*ULAB_MAX_DIMS); + numerical_reduce_axes(ndarray, ax, shape, strides); + ax = ULAB_MAX_DIMS - ndarray->ndim + ax; + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndarray->ndim-1, shape, NDARRAY_FLOAT); + mp_float_t *rarray = (mp_float_t *)results->array; + + uint8_t *array = (uint8_t *)ndarray->array; + + size_t len = ndarray->shape[ax]; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + size_t k = 0; + do { + array += ndarray->strides[ax] * (len >> 1); + mp_float_t median = ndarray_get_float_value(array, ndarray->dtype); + if(!(len & 0x01)) { // len is an even number + array -= ndarray->strides[ax]; + median += ndarray_get_float_value(array, ndarray->dtype); + median *= MICROPY_FLOAT_CONST(0.5); + array += ndarray->strides[ax]; + } + array -= ndarray->strides[ax] * (len >> 1); + array += strides[ULAB_MAX_DIMS - 1]; + *rarray = median; + rarray++; + k++; + } while(k < shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 2 + array -= strides[ULAB_MAX_DIMS - 1] * shape[ULAB_MAX_DIMS - 1]; + array += strides[ULAB_MAX_DIMS - 2]; + j++; + } while(j < shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= strides[ULAB_MAX_DIMS - 2] * shape[ULAB_MAX_DIMS-2]; + array += strides[ULAB_MAX_DIMS - 3]; + i++; + } while(i < shape[ULAB_MAX_DIMS - 3]); + #endif + + return MP_OBJ_FROM_PTR(results); + } + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_median_obj, 1, numerical_median); +#endif + +#if ULAB_NUMPY_HAS_MINMAX +//| def min(array: _ArrayLike, *, axis: Optional[int] = None) -> float: +//| """Return the minimum element of the 1D array""" +//| ... +//| + +mp_obj_t numerical_min(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return numerical_function(n_args, pos_args, kw_args, NUMERICAL_MIN); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_min_obj, 1, numerical_min); +#endif + +#if ULAB_NUMPY_HAS_ROLL +//| def roll(array: ulab.ndarray, distance: int, *, axis: Optional[int] = None) -> None: +//| """Shift the content of a vector by the positions given as the second +//| argument. If the ``axis`` keyword is supplied, the shift is applied to +//| the given axis. The array is modified in place.""" +//| ... +//| + +mp_obj_t numerical_roll(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("roll argument must be an ndarray")); + } + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + uint8_t *array = ndarray->array; + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndarray->ndim, ndarray->shape, ndarray->dtype); + + int32_t shift = mp_obj_get_int(args[1].u_obj); + int32_t _shift = shift < 0 ? -shift : shift; + + size_t counter; + uint8_t *rarray = (uint8_t *)results->array; + + if(args[2].u_obj == mp_const_none) { // roll the flattened array + _shift = _shift % results->len; + if(shift > 0) { // shift to the right + rarray += _shift * results->itemsize; + counter = results->len - _shift; + } else { // shift to the left + rarray += (results->len - _shift) * results->itemsize; + counter = _shift; + } + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + memcpy(rarray, array, ndarray->itemsize); + rarray += results->itemsize; + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + l++; + if(--counter == 0) { + rarray = results->array; + } + } while(l < ndarray->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * ndarray->shape[ULAB_MAX_DIMS-1]; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < ndarray->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= ndarray->strides[ULAB_MAX_DIMS - 2] * ndarray->shape[ULAB_MAX_DIMS-2]; + array += ndarray->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < ndarray->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= ndarray->strides[ULAB_MAX_DIMS - 3] * ndarray->shape[ULAB_MAX_DIMS-3]; + array += ndarray->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < ndarray->shape[ULAB_MAX_DIMS - 4]); + #endif + } else if(mp_obj_is_int(args[2].u_obj)){ + int8_t ax = mp_obj_get_int(args[2].u_obj); + if(ax < 0) ax += ndarray->ndim; + if((ax < 0) || (ax > ndarray->ndim - 1)) { + mp_raise_ValueError(translate("index out of range")); + } + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + memset(strides, 0, sizeof(int32_t)*ULAB_MAX_DIMS); + numerical_reduce_axes(ndarray, ax, shape, strides); + + size_t *rshape = m_new(size_t, ULAB_MAX_DIMS); + memset(rshape, 0, sizeof(size_t)*ULAB_MAX_DIMS); + int32_t *rstrides = m_new(int32_t, ULAB_MAX_DIMS); + memset(rstrides, 0, sizeof(int32_t)*ULAB_MAX_DIMS); + numerical_reduce_axes(results, ax, rshape, rstrides); + + ax = ULAB_MAX_DIMS - ndarray->ndim + ax; + uint8_t *_rarray; + _shift = _shift % results->shape[ax]; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + _rarray = rarray; + if(shift < 0) { + rarray += (results->shape[ax] - _shift) * results->strides[ax]; + counter = _shift; + } else { + rarray += _shift * results->strides[ax]; + counter = results->shape[ax] - _shift; + } + do { + memcpy(rarray, array, ndarray->itemsize); + array += ndarray->strides[ax]; + rarray += results->strides[ax]; + if(--counter == 0) { + rarray = _rarray; + } + l++; + } while(l < ndarray->shape[ax]); + #if ULAB_MAX_DIMS > 1 + rarray = _rarray; + rarray += rstrides[ULAB_MAX_DIMS - 1]; + array -= ndarray->strides[ax] * ndarray->shape[ax]; + array += strides[ULAB_MAX_DIMS - 1]; + k++; + } while(k < shape[ULAB_MAX_DIMS - 1]); + #endif + #if ULAB_MAX_DIMS > 2 + rarray -= rstrides[ULAB_MAX_DIMS - 1] * rshape[ULAB_MAX_DIMS-1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + array -= strides[ULAB_MAX_DIMS - 1] * shape[ULAB_MAX_DIMS-1]; + array += strides[ULAB_MAX_DIMS - 2]; + j++; + } while(j < shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 3 + rarray -= rstrides[ULAB_MAX_DIMS - 2] * rshape[ULAB_MAX_DIMS-2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + array -= strides[ULAB_MAX_DIMS - 2] * shape[ULAB_MAX_DIMS-2]; + array += strides[ULAB_MAX_DIMS - 3]; + i++; + } while(i < shape[ULAB_MAX_DIMS - 3]); + #endif + } else { + mp_raise_TypeError(translate("wrong axis index")); + } + return results; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_roll_obj, 2, numerical_roll); +#endif + +#if ULAB_NUMPY_HAS_SORT +//| def sort(array: ulab.ndarray, *, axis: int = -1) -> ulab.ndarray: +//| """Sort the array along the given axis, or along all axes if axis is None. +//| The array is modified in place.""" +//| ... +//| + +mp_obj_t numerical_sort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + return numerical_sort_helper(args[0].u_obj, args[1].u_obj, 0); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_sort_obj, 1, numerical_sort); +#endif + +#if NDARRAY_HAS_SORT +// method of an ndarray +static mp_obj_t numerical_sort_inplace(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_int = -1 } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + return numerical_sort_helper(args[0].u_obj, args[1].u_obj, 1); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_sort_inplace_obj, 1, numerical_sort_inplace); +#endif /* NDARRAY_HAS_SORT */ + +#if ULAB_NUMPY_HAS_STD +//| def std(array: _ArrayLike, *, axis: Optional[int] = None, ddof: int = 0) -> float: +//| """Return the standard deviation of the array, as a number if axis is None, otherwise as an array.""" +//| ... +//| + +mp_obj_t numerical_std(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } } , + { MP_QSTR_axis, MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_ddof, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t oin = args[0].u_obj; + mp_obj_t axis = args[1].u_obj; + size_t ddof = args[2].u_int; + if((axis != mp_const_none) && (mp_obj_get_int(axis) != 0) && (mp_obj_get_int(axis) != 1)) { + // this seems to pass with False, and True... + mp_raise_ValueError(translate("axis must be None, or an integer")); + } + if(mp_obj_is_type(oin, &mp_type_tuple) || mp_obj_is_type(oin, &mp_type_list) || mp_obj_is_type(oin, &mp_type_range)) { + return numerical_sum_mean_std_iterable(oin, NUMERICAL_STD, ddof); + } else if(mp_obj_is_type(oin, &ulab_ndarray_type)) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(oin); + return numerical_sum_mean_std_ndarray(ndarray, axis, NUMERICAL_STD, ddof); + } else { + mp_raise_TypeError(translate("input must be tuple, list, range, or ndarray")); + } + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_std_obj, 1, numerical_std); +#endif + +#if ULAB_NUMPY_HAS_SUM +//| def sum(array: _ArrayLike, *, axis: Optional[int] = None) -> Union[float, int, ulab.ndarray]: +//| """Return the sum of the array, as a number if axis is None, otherwise as an array.""" +//| ... +//| + +mp_obj_t numerical_sum(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return numerical_function(n_args, pos_args, kw_args, NUMERICAL_SUM); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_sum_obj, 1, numerical_sum); +#endif diff --git a/python/port/mod/ulab/numpy/numerical/numerical.h b/python/port/mod/ulab/numpy/numerical/numerical.h new file mode 100644 index 00000000000..ef7b95d74c3 --- /dev/null +++ b/python/port/mod/ulab/numpy/numerical/numerical.h @@ -0,0 +1,652 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#ifndef _NUMERICAL_ +#define _NUMERICAL_ + +#include "../../ulab.h" +#include "../../ndarray.h" + +// TODO: implement cumsum + +#define RUN_ARGMIN1(ndarray, type, array, results, rarray, index, op)\ +({\ + uint16_t best_index = 0;\ + type best_value = *((type *)(array));\ + if(((op) == NUMERICAL_MAX) || ((op) == NUMERICAL_ARGMAX)) {\ + for(uint16_t i=0; i < (ndarray)->shape[(index)]; i++) {\ + if(*((type *)(array)) > best_value) {\ + best_index = i;\ + best_value = *((type *)(array));\ + }\ + (array) += (ndarray)->strides[(index)];\ + }\ + } else {\ + for(uint16_t i=0; i < (ndarray)->shape[(index)]; i++) {\ + if(*((type *)(array)) < best_value) {\ + best_index = i;\ + best_value = *((type *)(array));\ + }\ + (array) += (ndarray)->strides[(index)];\ + }\ + }\ + if(((op) == NUMERICAL_ARGMAX) || ((op) == NUMERICAL_ARGMIN)) {\ + memcpy((rarray), &best_index, (results)->itemsize);\ + } else {\ + memcpy((rarray), &best_value, (results)->itemsize);\ + }\ + (rarray) += (results)->itemsize;\ +}) + +#define RUN_SUM1(type, array, results, rarray, ss)\ +({\ + type sum = 0;\ + for(size_t i=0; i < (ss).shape[0]; i++) {\ + sum += *((type *)(array));\ + (array) += (ss).strides[0];\ + }\ + memcpy((rarray), &sum, (results)->itemsize);\ + (rarray) += (results)->itemsize;\ +}) + +// The mean could be calculated by simply dividing the sum by +// the number of elements, but that method is numerically unstable +#define RUN_MEAN1(type, array, rarray, ss)\ +({\ + mp_float_t M = 0.0;\ + for(size_t i=0; i < (ss).shape[0]; i++) {\ + mp_float_t value = (mp_float_t)(*(type *)(array));\ + M = M + (value - M) / (mp_float_t)(i+1);\ + (array) += (ss).strides[0];\ + }\ + *(rarray)++ = M;\ +}) + +// Instead of the straightforward implementation of the definition, +// we take the numerically stable Welford algorithm here +// https://www.johndcook.com/blog/2008/09/26/comparing-three-methods-of-computing-standard-deviation/ +#define RUN_STD1(type, array, rarray, ss, div)\ +({\ + mp_float_t M = 0.0, m = 0.0, S = 0.0;\ + for(size_t i=0; i < (ss).shape[0]; i++) {\ + mp_float_t value = (mp_float_t)(*(type *)(array));\ + m = M + (value - M) / (mp_float_t)(i+1);\ + S = S + (value - M) * (value - m);\ + M = m;\ + (array) += (ss).strides[0];\ + }\ + *(rarray)++ = MICROPY_FLOAT_C_FUN(sqrt)(S / (div));\ +}) + +#define RUN_MEAN_STD1(type, array, rarray, ss, div, isStd)\ +({\ + mp_float_t M = 0.0, m = 0.0, S = 0.0;\ + for(size_t i=0; i < (ss).shape[0]; i++) {\ + mp_float_t value = (mp_float_t)(*(type *)(array));\ + m = M + (value - M) / (mp_float_t)(i+1);\ + if(isStd) {\ + S += (value - M) * (value - m);\ + }\ + M = m;\ + (array) += (ss).strides[0];\ + }\ + *(rarray)++ = isStd ? MICROPY_FLOAT_C_FUN(sqrt)(S / (div)) : M;\ +}) + +#define RUN_DIFF1(ndarray, type, array, results, rarray, index, stencil, N)\ +({\ + for(size_t i=0; i < (results)->shape[ULAB_MAX_DIMS - 1]; i++) {\ + type sum = 0;\ + uint8_t *source = (array);\ + for(uint8_t d=0; d < (N)+1; d++) {\ + sum -= (stencil)[d] * *((type *)source);\ + source += (ndarray)->strides[(index)];\ + }\ + (array) += (ndarray)->strides[ULAB_MAX_DIMS - 1];\ + *(type *)(rarray) = sum;\ + (rarray) += (results)->itemsize;\ + }\ +}) + +#define HEAPSORT1(type, array, increment, N)\ +({\ + type *_array = (type *)array;\ + type tmp;\ + size_t c, q = (N), p, r = (N) >> 1;\ + for (;;) {\ + if (r > 0) {\ + tmp = _array[(--r)*(increment)];\ + } else {\ + q--;\ + if(q == 0) {\ + break;\ + }\ + tmp = _array[q*(increment)];\ + _array[q*(increment)] = _array[0];\ + }\ + p = r;\ + c = r + r + 1;\ + while (c < q) {\ + if((c + 1 < q) && (_array[(c+1)*(increment)] > _array[c*(increment)])) {\ + c++;\ + }\ + if(_array[c*(increment)] > tmp) {\ + _array[p*(increment)] = _array[c*(increment)];\ + p = c;\ + c = p + p + 1;\ + } else {\ + break;\ + }\ + }\ + _array[p*(increment)] = tmp;\ + }\ +}) + +#define HEAP_ARGSORT1(type, array, increment, N, iarray, iincrement)\ +({\ + type *_array = (type *)array;\ + type tmp;\ + uint16_t itmp, c, q = (N), p, r = (N) >> 1;\ + for (;;) {\ + if (r > 0) {\ + r--;\ + itmp = (iarray)[r*(iincrement)];\ + tmp = _array[itmp*(increment)];\ + } else {\ + q--;\ + if(q == 0) {\ + break;\ + }\ + itmp = (iarray)[q*(iincrement)];\ + tmp = _array[itmp*(increment)];\ + (iarray)[q*(iincrement)] = (iarray)[0];\ + }\ + p = r;\ + c = r + r + 1;\ + while (c < q) {\ + if((c + 1 < q) && (_array[(iarray)[(c+1)*(iincrement)]*(increment)] > _array[(iarray)[c*(iincrement)]*(increment)])) {\ + c++;\ + }\ + if(_array[(iarray)[c*(iincrement)]*(increment)] > tmp) {\ + (iarray)[p*(iincrement)] = (iarray)[c*(iincrement)];\ + p = c;\ + c = p + p + 1;\ + } else {\ + break;\ + }\ + }\ + (iarray)[p*(iincrement)] = itmp;\ + }\ +}) + +#if ULAB_MAX_DIMS == 1 +#define RUN_SUM(type, array, results, rarray, ss) do {\ + RUN_SUM1(type, (array), (results), (rarray), (ss));\ +} while(0) + +#define RUN_MEAN(type, array, rarray, ss) do {\ + RUN_MEAN1(type, (array), (rarray), (ss));\ +} while(0) + +#define RUN_STD(type, array, rarray, ss, div) do {\ + RUN_STD1(type, (array), (results), (rarray), (ss), (div));\ +} while(0) + +#define RUN_MEAN_STD(type, array, rarray, ss, div, isStd) do {\ + RUN_MEAN_STD1(type, (array), (results), (rarray), (ss), (div), (isStd));\ +} while(0) + +#define RUN_ARGMIN(ndarray, type, array, results, rarray, shape, strides, index, op) do {\ + RUN_ARGMIN1((ndarray), type, (array), (results), (rarray), (index), (op));\ +} while(0) + +#define RUN_DIFF(ndarray, type, array, results, rarray, shape, strides, index, stencil, N) do {\ + RUN_DIFF1((ndarray), type, (array), (results), (rarray), (index), (stencil), (N));\ +} while(0) + +#define HEAPSORT(ndarray, type, array, shape, strides, index, increment, N) do {\ + HEAPSORT1(type, (array), (increment), (N));\ +} while(0) + +#define HEAP_ARGSORT(ndarray, type, array, shape, strides, index, increment, N, iarray, istrides, iincrement) do {\ + HEAP_ARGSORT1(type, (array), (increment), (N), (iarray), (iincrement));\ +} while(0) + +#endif + +#if ULAB_MAX_DIMS == 2 +#define RUN_SUM(type, array, results, rarray, ss) do {\ + size_t l = 0;\ + do {\ + RUN_SUM1(type, (array), (results), (rarray), (ss));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ +} while(0) + +#define RUN_MEAN(type, array, rarray, ss) do {\ + size_t l = 0;\ + do {\ + RUN_MEAN1(type, (array), (rarray), (ss));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ +} while(0) + +#define RUN_STD(type, array, rarray, ss, div) do {\ + size_t l = 0;\ + do {\ + RUN_STD1(type, (array), (rarray), (ss), (div));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ +} while(0) + +#define RUN_MEAN_STD(type, array, rarray, ss, div, isStd) do {\ + size_t l = 0;\ + do {\ + RUN_MEAN_STD1(type, (array), (rarray), (ss), (div), (isStd));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ +} while(0) + + +#define RUN_ARGMIN(ndarray, type, array, results, rarray, shape, strides, index, op) do {\ + size_t l = 0;\ + do {\ + RUN_ARGMIN1((ndarray), type, (array), (results), (rarray), (index), (op));\ + (array) -= (ndarray)->strides[(index)] * (ndarray)->shape[(index)];\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ +} while(0) + +#define RUN_DIFF(ndarray, type, array, results, rarray, shape, strides, index, stencil, N) do {\ + size_t l = 0;\ + do {\ + RUN_DIFF1((ndarray), type, (array), (results), (rarray), (index), (stencil), (N));\ + (array) -= (ndarray)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (array) += (ndarray)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#define HEAPSORT(ndarray, type, array, shape, strides, index, increment, N) do {\ + size_t l = 0;\ + do {\ + HEAPSORT1(type, (array), (increment), (N));\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ +} while(0) + +#define HEAP_ARGSORT(ndarray, type, array, shape, strides, index, increment, N, iarray, istrides, iincrement) do {\ + size_t l = 0;\ + do {\ + HEAP_ARGSORT1(type, (array), (increment), (N), (iarray), (iincrement));\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + (iarray) += (istrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ +} while(0) + +#endif + +#if ULAB_MAX_DIMS == 3 +#define RUN_SUM(type, array, results, rarray, ss) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_SUM1(type, (array), (results), (rarray), (ss));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 1] * (ss).shape[ULAB_MAX_DIMS - 1];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (ss).shape[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#define RUN_MEAN(type, array, rarray, ss) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_MEAN1(type, (array), (rarray), (ss));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 1] * (ss).shape[ULAB_MAX_DIMS - 1];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (ss).shape[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#define RUN_STD(type, array, rarray, ss, div) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_STD1(type, (array), (rarray), (ss), (div));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 1] * (ss).shape[ULAB_MAX_DIMS - 1];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (ss).shape[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#define RUN_MEAN_STD(type, array, rarray, ss, div, isStd) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_MEAN_STD1(type, (array), (rarray), (ss), (div), (isStd));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 1] * (ss).shape[ULAB_MAX_DIMS - 1];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (ss).shape[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#define RUN_ARGMIN(ndarray, type, array, results, rarray, shape, strides, index, op) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_ARGMIN1((ndarray), type, (array), (results), (rarray), (index), (op));\ + (array) -= (ndarray)->strides[(index)] * (ndarray)->shape[(index)];\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (array) += (strides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#define RUN_DIFF(ndarray, type, array, results, rarray, shape, strides, index, stencil, N) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_DIFF1((ndarray), type, (array), (results), (rarray), (index), (stencil), (N));\ + (array) -= (ndarray)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (array) += (ndarray)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 2]);\ + (array) -= (ndarray)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (array) += (ndarray)->strides[ULAB_MAX_DIMS - 3];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 3];\ + k++;\ + } while(k < (shape)[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#define HEAPSORT(ndarray, type, array, shape, strides, index, increment, N) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + HEAPSORT1(type, (array), (increment), (N));\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (array) += (strides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#define HEAP_ARGSORT(ndarray, type, array, shape, strides, index, increment, N, iarray, istrides, iincrement) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + HEAP_ARGSORT1(type, (array), (increment), (N), (iarray), (iincrement));\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + (iarray) += (istrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ + (iarray) -= (istrides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (iarray) += (istrides)[ULAB_MAX_DIMS - 2];\ + (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (array) += (strides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#endif + +#if ULAB_MAX_DIMS == 4 +#define RUN_SUM(type, array, results, rarray, shape, strides, index) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_SUM1(type, (array), (results), (rarray), (ss));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 1] * (ss).shape[ULAB_MAX_DIMS - 1];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (ss).shape[ULAB_MAX_DIMS - 2]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 2] * (ss).shape[ULAB_MAX_DIMS - 2];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (ss).shape[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#define RUN_MEAN(type, array, rarray, ss) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_MEAN1(type, (array), (rarray), (ss));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 1] * (ss).shape[ULAB_MAX_DIMS - 1];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (ss).shape[ULAB_MAX_DIMS - 2]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 2] * (ss).shape[ULAB_MAX_DIMS - 2];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (ss).shape[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#define RUN_STD(type, array, rarray, ss, div) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_STD1(type, (array), (rarray), (ss), (div));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 1] * (ss).shape[ULAB_MAX_DIMS - 1];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (ss).shape[ULAB_MAX_DIMS - 2]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 2] * (ss).shape[ULAB_MAX_DIMS - 2];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (ss).shape[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#define RUN_MEAN_STD(type, array, rarray, ss, div, isStd) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_MEAN_STD1(type, (array), (rarray), (ss), (div), (isStd));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 1] * (ss).shape[ULAB_MAX_DIMS - 1];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (ss).shape[ULAB_MAX_DIMS - 2]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 2] * (ss).shape[ULAB_MAX_DIMS - 2];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (ss).shape[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#define RUN_ARGMIN(ndarray, type, array, results, rarray, shape, strides, index, op) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_ARGMIN1((ndarray), type, (array), (results), (rarray), (index), (op));\ + (array) -= (ndarray)->strides[(index)] * (ndarray)->shape[(index)];\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (array) += (strides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2];\ + (array) += (strides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (shape)[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#define RUN_DIFF(ndarray, type, array, results, rarray, shape, strides, index, stencil, N) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_DIFF1((ndarray), type, (array), (results), (rarray), (index), (stencil), (N));\ + (array) -= (ndarray)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (array) += (ndarray)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 2]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2];\ + (array) += (strides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 3];\ + k++;\ + } while(k < (shape)[ULAB_MAX_DIMS - 3]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 3] * (shape)[ULAB_MAX_DIMS-3];\ + (array) += (strides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 4];\ + j++;\ + } while(j < (shape)[ULAB_MAX_DIMS - 4]);\ +} while(0) + +#define HEAPSORT(ndarray, type, array, shape, strides, index, increment, N) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + HEAPSORT1(type, (array), (increment), (N));\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (array) += (strides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2];\ + (array) += (strides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (shape)[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#define HEAP_ARGSORT(ndarray, type, array, shape, strides, index, increment, N, iarray, istrides, iincrement) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + HEAP_ARGSORT1(type, (array), (increment), (N), (iarray), (iincrement));\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + (iarray) += (istrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ + (iarray) -= (istrides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (iarray) += (istrides)[ULAB_MAX_DIMS - 2];\ + (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (array) += (strides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ + (iarray) -= (istrides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2];\ + (iarray) += (istrides)[ULAB_MAX_DIMS - 3];\ + (array) -= (strides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2];\ + (array) += (strides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (shape)[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#endif + +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_all_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_any_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_argmax_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_argmin_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_argsort_obj); +MP_DECLARE_CONST_FUN_OBJ_2(numerical_cross_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_diff_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_flip_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_max_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_mean_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_median_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_min_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_roll_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_std_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_sum_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_sort_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_sort_inplace_obj); + +#endif diff --git a/python/port/mod/ulab/numpy/numpy.c b/python/port/mod/ulab/numpy/numpy.c new file mode 100644 index 00000000000..8d8f3ab6bda --- /dev/null +++ b/python/port/mod/ulab/numpy/numpy.c @@ -0,0 +1,336 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020 Scott Shawcroft for Adafruit Industries + * 2020-2021 Zoltán Vörös + * 2020 Taku Fukada +*/ + +#include +#include +#include + +#include "numpy.h" +#include "../ulab_create.h" +#include "approx/approx.h" +#include "compare/compare.h" +#include "fft/fft.h" +#include "filter/filter.h" +#include "linalg/linalg.h" +#include "numerical/numerical.h" +#include "stats/stats.h" +#include "transform/transform.h" +#include "poly/poly.h" +#include "vector/vector.h" + +//| """Compatibility layer for numpy""" +//| + +//| class ndarray: ... + +// math constants +#if ULAB_NUMPY_HAS_E +#if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C +#define ulab_const_float_e MP_ROM_PTR((mp_obj_t)(((0x402df854 & ~3) | 2) + 0x80800000)) +#elif MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D +#define ulab_const_float_e {((mp_obj_t)((uint64_t)0x4005bf0a8b145769 + 0x8004000000000000))} +#else +mp_obj_float_t ulab_const_float_e_obj = {{&mp_type_float}, MP_E}; +#define ulab_const_float_e MP_ROM_PTR(&ulab_const_float_e_obj) +#endif +#endif + +#if ULAB_NUMPY_HAS_INF +#if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C +#define numpy_const_float_inf MP_ROM_PTR((mp_obj_t)(0x7f800002 + 0x80800000)) +#elif MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D +#define numpy_const_float_inf {((mp_obj_t)((uint64_t)0x7ff0000000000000 + 0x8004000000000000))} +#else +mp_obj_float_t numpy_const_float_inf_obj = {{&mp_type_float}, (mp_float_t)INFINITY}; +#define numpy_const_float_inf MP_ROM_PTR(&numpy_const_float_inf_obj) +#endif +#endif + +#if ULAB_NUMPY_HAS_NAN +#if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C +#define numpy_const_float_nan MP_ROM_PTR((mp_obj_t)(0x7fc00002 + 0x80800000)) +#elif MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D +#define numpy_const_float_nan {((mp_obj_t)((uint64_t)0x7ff8000000000000 + 0x8004000000000000))} +#else +mp_obj_float_t numpy_const_float_nan_obj = {{&mp_type_float}, (mp_float_t)NAN}; +#define numpy_const_float_nan MP_ROM_PTR(&numpy_const_float_nan_obj) +#endif +#endif + +#if ULAB_NUMPY_HAS_PI +#if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C +#define ulab_const_float_pi MP_ROM_PTR((mp_obj_t)(((0x40490fdb & ~3) | 2) + 0x80800000)) +#elif MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D +#define ulab_const_float_pi {((mp_obj_t)((uint64_t)0x400921fb54442d18 + 0x8004000000000000))} +#else +mp_obj_float_t ulab_const_float_pi_obj = {{&mp_type_float}, MP_PI}; +#define ulab_const_float_pi MP_ROM_PTR(&ulab_const_float_pi_obj) +#endif +#endif + +static const mp_rom_map_elem_t ulab_numpy_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_numpy) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_ndarray), (mp_obj_t)&ulab_ndarray_type }, + { MP_OBJ_NEW_QSTR(MP_QSTR_array), MP_ROM_PTR(&ndarray_array_constructor_obj) }, + #if ULAB_NUMPY_HAS_FROMBUFFER + { MP_ROM_QSTR(MP_QSTR_frombuffer), MP_ROM_PTR(&create_frombuffer_obj) }, + #endif + // math constants + #if ULAB_NUMPY_HAS_E + { MP_ROM_QSTR(MP_QSTR_e), ulab_const_float_e }, + #endif + #if ULAB_NUMPY_HAS_INF + { MP_ROM_QSTR(MP_QSTR_inf), numpy_const_float_inf }, + #endif + #if ULAB_NUMPY_HAS_NAN + { MP_ROM_QSTR(MP_QSTR_nan), numpy_const_float_nan }, + #endif + #if ULAB_NUMPY_HAS_PI + { MP_ROM_QSTR(MP_QSTR_pi), ulab_const_float_pi }, + #endif + // class constants, always included + { MP_ROM_QSTR(MP_QSTR_bool), MP_ROM_INT(NDARRAY_BOOL) }, + { MP_ROM_QSTR(MP_QSTR_uint8), MP_ROM_INT(NDARRAY_UINT8) }, + { MP_ROM_QSTR(MP_QSTR_int8), MP_ROM_INT(NDARRAY_INT8) }, + { MP_ROM_QSTR(MP_QSTR_uint16), MP_ROM_INT(NDARRAY_UINT16) }, + { MP_ROM_QSTR(MP_QSTR_int16), MP_ROM_INT(NDARRAY_INT16) }, + { MP_ROM_QSTR(MP_QSTR_float), MP_ROM_INT(NDARRAY_FLOAT) }, + // modules of numpy + #if ULAB_NUMPY_HAS_FFT_MODULE + { MP_ROM_QSTR(MP_QSTR_fft), MP_ROM_PTR(&ulab_fft_module) }, + #endif + #if ULAB_NUMPY_HAS_LINALG_MODULE + { MP_ROM_QSTR(MP_QSTR_linalg), MP_ROM_PTR(&ulab_linalg_module) }, + #endif + #if ULAB_HAS_PRINTOPTIONS + { MP_ROM_QSTR(MP_QSTR_set_printoptions), (mp_obj_t)&ndarray_set_printoptions_obj }, + { MP_ROM_QSTR(MP_QSTR_get_printoptions), (mp_obj_t)&ndarray_get_printoptions_obj }, + #endif + #if ULAB_NUMPY_HAS_NDINFO + { MP_ROM_QSTR(MP_QSTR_ndinfo), (mp_obj_t)&ndarray_info_obj }, + #endif + #if ULAB_NUMPY_HAS_ARANGE + { MP_ROM_QSTR(MP_QSTR_arange), (mp_obj_t)&create_arange_obj }, + #endif + #if ULAB_NUMPY_HAS_CONCATENATE + { MP_ROM_QSTR(MP_QSTR_concatenate), (mp_obj_t)&create_concatenate_obj }, + #endif + #if ULAB_NUMPY_HAS_DIAG + { MP_ROM_QSTR(MP_QSTR_diag), (mp_obj_t)&create_diag_obj }, + #endif + #if ULAB_NUMPY_HAS_EMPTY + { MP_ROM_QSTR(MP_QSTR_empty), (mp_obj_t)&create_zeros_obj }, + #endif + #if ULAB_MAX_DIMS > 1 + #if ULAB_NUMPY_HAS_EYE + { MP_ROM_QSTR(MP_QSTR_eye), (mp_obj_t)&create_eye_obj }, + #endif + #endif /* ULAB_MAX_DIMS */ + // functions of the approx sub-module + #if ULAB_NUMPY_HAS_INTERP + { MP_OBJ_NEW_QSTR(MP_QSTR_interp), (mp_obj_t)&approx_interp_obj }, + #endif + #if ULAB_NUMPY_HAS_TRAPZ + { MP_OBJ_NEW_QSTR(MP_QSTR_trapz), (mp_obj_t)&approx_trapz_obj }, + #endif + // functions of the create sub-module + #if ULAB_NUMPY_HAS_FULL + { MP_ROM_QSTR(MP_QSTR_full), (mp_obj_t)&create_full_obj }, + #endif + #if ULAB_NUMPY_HAS_LINSPACE + { MP_ROM_QSTR(MP_QSTR_linspace), (mp_obj_t)&create_linspace_obj }, + #endif + #if ULAB_NUMPY_HAS_LOGSPACE + { MP_ROM_QSTR(MP_QSTR_logspace), (mp_obj_t)&create_logspace_obj }, + #endif + #if ULAB_NUMPY_HAS_ONES + { MP_ROM_QSTR(MP_QSTR_ones), (mp_obj_t)&create_ones_obj }, + #endif + #if ULAB_NUMPY_HAS_ZEROS + { MP_ROM_QSTR(MP_QSTR_zeros), (mp_obj_t)&create_zeros_obj }, + #endif + // functions of the compare sub-module + #if ULAB_NUMPY_HAS_CLIP + { MP_OBJ_NEW_QSTR(MP_QSTR_clip), (mp_obj_t)&compare_clip_obj }, + #endif + #if ULAB_NUMPY_HAS_EQUAL + { MP_OBJ_NEW_QSTR(MP_QSTR_equal), (mp_obj_t)&compare_equal_obj }, + #endif + #if ULAB_NUMPY_HAS_NOTEQUAL + { MP_OBJ_NEW_QSTR(MP_QSTR_not_equal), (mp_obj_t)&compare_not_equal_obj }, + #endif + #if ULAB_NUMPY_HAS_ISFINITE + { MP_OBJ_NEW_QSTR(MP_QSTR_isfinite), (mp_obj_t)&compare_isfinite_obj }, + #endif + #if ULAB_NUMPY_HAS_ISINF + { MP_OBJ_NEW_QSTR(MP_QSTR_isinf), (mp_obj_t)&compare_isinf_obj }, + #endif + #if ULAB_NUMPY_HAS_MAXIMUM + { MP_OBJ_NEW_QSTR(MP_QSTR_maximum), (mp_obj_t)&compare_maximum_obj }, + #endif + #if ULAB_NUMPY_HAS_MINIMUM + { MP_OBJ_NEW_QSTR(MP_QSTR_minimum), (mp_obj_t)&compare_minimum_obj }, + #endif + #if ULAB_NUMPY_HAS_WHERE + { MP_OBJ_NEW_QSTR(MP_QSTR_where), (mp_obj_t)&compare_where_obj }, + #endif + // functions of the filter sub-module + #if ULAB_NUMPY_HAS_CONVOLVE + { MP_OBJ_NEW_QSTR(MP_QSTR_convolve), (mp_obj_t)&filter_convolve_obj }, + #endif + // functions of the numerical sub-module + #if ULAB_NUMPY_HAS_ALL + { MP_OBJ_NEW_QSTR(MP_QSTR_all), (mp_obj_t)&numerical_all_obj }, + #endif + #if ULAB_NUMPY_HAS_ANY + { MP_OBJ_NEW_QSTR(MP_QSTR_any), (mp_obj_t)&numerical_any_obj }, + #endif + #if ULAB_NUMPY_HAS_ARGMINMAX + { MP_OBJ_NEW_QSTR(MP_QSTR_argmax), (mp_obj_t)&numerical_argmax_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_argmin), (mp_obj_t)&numerical_argmin_obj }, + #endif + #if ULAB_NUMPY_HAS_ARGSORT + { MP_OBJ_NEW_QSTR(MP_QSTR_argsort), (mp_obj_t)&numerical_argsort_obj }, + #endif + #if ULAB_NUMPY_HAS_CROSS + { MP_OBJ_NEW_QSTR(MP_QSTR_cross), (mp_obj_t)&numerical_cross_obj }, + #endif + #if ULAB_NUMPY_HAS_DIFF + { MP_OBJ_NEW_QSTR(MP_QSTR_diff), (mp_obj_t)&numerical_diff_obj }, + #endif + #if ULAB_NUMPY_HAS_DOT + { MP_OBJ_NEW_QSTR(MP_QSTR_dot), (mp_obj_t)&transform_dot_obj }, + #endif + #if ULAB_NUMPY_HAS_TRACE + { MP_ROM_QSTR(MP_QSTR_trace), (mp_obj_t)&stats_trace_obj }, + #endif + #if ULAB_NUMPY_HAS_FLIP + { MP_OBJ_NEW_QSTR(MP_QSTR_flip), (mp_obj_t)&numerical_flip_obj }, + #endif + #if ULAB_NUMPY_HAS_MINMAX + { MP_OBJ_NEW_QSTR(MP_QSTR_max), (mp_obj_t)&numerical_max_obj }, + #endif + #if ULAB_NUMPY_HAS_MEAN + { MP_OBJ_NEW_QSTR(MP_QSTR_mean), (mp_obj_t)&numerical_mean_obj }, + #endif + #if ULAB_NUMPY_HAS_MEDIAN + { MP_OBJ_NEW_QSTR(MP_QSTR_median), (mp_obj_t)&numerical_median_obj }, + #endif + #if ULAB_NUMPY_HAS_MINMAX + { MP_OBJ_NEW_QSTR(MP_QSTR_min), (mp_obj_t)&numerical_min_obj }, + #endif + #if ULAB_NUMPY_HAS_ROLL + { MP_OBJ_NEW_QSTR(MP_QSTR_roll), (mp_obj_t)&numerical_roll_obj }, + #endif + #if ULAB_NUMPY_HAS_SORT + { MP_OBJ_NEW_QSTR(MP_QSTR_sort), (mp_obj_t)&numerical_sort_obj }, + #endif + #if ULAB_NUMPY_HAS_STD + { MP_OBJ_NEW_QSTR(MP_QSTR_std), (mp_obj_t)&numerical_std_obj }, + #endif + #if ULAB_NUMPY_HAS_SUM + { MP_OBJ_NEW_QSTR(MP_QSTR_sum), (mp_obj_t)&numerical_sum_obj }, + #endif + // functions of the poly sub-module + #if ULAB_NUMPY_HAS_POLYFIT + { MP_OBJ_NEW_QSTR(MP_QSTR_polyfit), (mp_obj_t)&poly_polyfit_obj }, + #endif + #if ULAB_NUMPY_HAS_POLYVAL + { MP_OBJ_NEW_QSTR(MP_QSTR_polyval), (mp_obj_t)&poly_polyval_obj }, + #endif + // functions of the vector sub-module + #if ULAB_NUMPY_HAS_ACOS + { MP_OBJ_NEW_QSTR(MP_QSTR_acos), (mp_obj_t)&vectorise_acos_obj }, + #endif + #if ULAB_NUMPY_HAS_ACOSH + { MP_OBJ_NEW_QSTR(MP_QSTR_acosh), (mp_obj_t)&vectorise_acosh_obj }, + #endif + #if ULAB_NUMPY_HAS_ARCTAN2 + { MP_OBJ_NEW_QSTR(MP_QSTR_arctan2), (mp_obj_t)&vectorise_arctan2_obj }, + #endif + #if ULAB_NUMPY_HAS_AROUND + { MP_OBJ_NEW_QSTR(MP_QSTR_around), (mp_obj_t)&vectorise_around_obj }, + #endif + #if ULAB_NUMPY_HAS_ASIN + { MP_OBJ_NEW_QSTR(MP_QSTR_asin), (mp_obj_t)&vectorise_asin_obj }, + #endif + #if ULAB_NUMPY_HAS_ASINH + { MP_OBJ_NEW_QSTR(MP_QSTR_asinh), (mp_obj_t)&vectorise_asinh_obj }, + #endif + #if ULAB_NUMPY_HAS_ATAN + { MP_OBJ_NEW_QSTR(MP_QSTR_atan), (mp_obj_t)&vectorise_atan_obj }, + #endif + #if ULAB_NUMPY_HAS_ATANH + { MP_OBJ_NEW_QSTR(MP_QSTR_atanh), (mp_obj_t)&vectorise_atanh_obj }, + #endif + #if ULAB_NUMPY_HAS_CEIL + { MP_OBJ_NEW_QSTR(MP_QSTR_ceil), (mp_obj_t)&vectorise_ceil_obj }, + #endif + #if ULAB_NUMPY_HAS_COS + { MP_OBJ_NEW_QSTR(MP_QSTR_cos), (mp_obj_t)&vectorise_cos_obj }, + #endif + #if ULAB_NUMPY_HAS_COSH + { MP_OBJ_NEW_QSTR(MP_QSTR_cosh), (mp_obj_t)&vectorise_cosh_obj }, + #endif + #if ULAB_NUMPY_HAS_DEGREES + { MP_OBJ_NEW_QSTR(MP_QSTR_degrees), (mp_obj_t)&vectorise_degrees_obj }, + #endif + #if ULAB_NUMPY_HAS_EXP + { MP_OBJ_NEW_QSTR(MP_QSTR_exp), (mp_obj_t)&vectorise_exp_obj }, + #endif + #if ULAB_NUMPY_HAS_EXPM1 + { MP_OBJ_NEW_QSTR(MP_QSTR_expm1), (mp_obj_t)&vectorise_expm1_obj }, + #endif + #if ULAB_NUMPY_HAS_FLOOR + { MP_OBJ_NEW_QSTR(MP_QSTR_floor), (mp_obj_t)&vectorise_floor_obj }, + #endif + #if ULAB_NUMPY_HAS_LOG + { MP_OBJ_NEW_QSTR(MP_QSTR_log), (mp_obj_t)&vectorise_log_obj }, + #endif + #if ULAB_NUMPY_HAS_LOG10 + { MP_OBJ_NEW_QSTR(MP_QSTR_log10), (mp_obj_t)&vectorise_log10_obj }, + #endif + #if ULAB_NUMPY_HAS_LOG2 + { MP_OBJ_NEW_QSTR(MP_QSTR_log2), (mp_obj_t)&vectorise_log2_obj }, + #endif + #if ULAB_NUMPY_HAS_RADIANS + { MP_OBJ_NEW_QSTR(MP_QSTR_radians), (mp_obj_t)&vectorise_radians_obj }, + #endif + #if ULAB_NUMPY_HAS_SIN + { MP_OBJ_NEW_QSTR(MP_QSTR_sin), (mp_obj_t)&vectorise_sin_obj }, + #endif + #if ULAB_NUMPY_HAS_SINH + { MP_OBJ_NEW_QSTR(MP_QSTR_sinh), (mp_obj_t)&vectorise_sinh_obj }, + #endif + #if ULAB_NUMPY_HAS_SQRT + { MP_OBJ_NEW_QSTR(MP_QSTR_sqrt), (mp_obj_t)&vectorise_sqrt_obj }, + #endif + #if ULAB_NUMPY_HAS_TAN + { MP_OBJ_NEW_QSTR(MP_QSTR_tan), (mp_obj_t)&vectorise_tan_obj }, + #endif + #if ULAB_NUMPY_HAS_TANH + { MP_OBJ_NEW_QSTR(MP_QSTR_tanh), (mp_obj_t)&vectorise_tanh_obj }, + #endif + #if ULAB_NUMPY_HAS_VECTORIZE + { MP_OBJ_NEW_QSTR(MP_QSTR_vectorize), (mp_obj_t)&vectorise_vectorize_obj }, + #endif + +}; + +static MP_DEFINE_CONST_DICT(mp_module_ulab_numpy_globals, ulab_numpy_globals_table); + +mp_obj_module_t ulab_numpy_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_numpy_globals, +}; diff --git a/python/port/mod/ulab/numpy/numpy.h b/python/port/mod/ulab/numpy/numpy.h new file mode 100644 index 00000000000..cccebde180a --- /dev/null +++ b/python/port/mod/ulab/numpy/numpy.h @@ -0,0 +1,21 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös + * +*/ + +#ifndef _NUMPY_ +#define _NUMPY_ + +#include "../ulab.h" +#include "../ndarray.h" + +extern mp_obj_module_t ulab_numpy_module; + +#endif /* _NUMPY_ */ diff --git a/python/port/mod/ulab/numpy/poly/poly.c b/python/port/mod/ulab/numpy/poly/poly.c new file mode 100644 index 00000000000..c9a07655ff9 --- /dev/null +++ b/python/port/mod/ulab/numpy/poly/poly.c @@ -0,0 +1,232 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Jeff Epler for Adafruit Industries + * 2020 Scott Shawcroft for Adafruit Industries + * 2020 Taku Fukada +*/ + +#include +#include +#include + +#include "../../ulab.h" +#include "../linalg/linalg_tools.h" +#include "../../ulab_tools.h" +#include "poly.h" + +#if ULAB_NUMPY_HAS_POLYFIT + +mp_obj_t poly_polyfit(size_t n_args, const mp_obj_t *args) { + if(!ndarray_object_is_array_like(args[0])) { + mp_raise_ValueError(translate("input data must be an iterable")); + } + size_t lenx = 0, leny = 0; + uint8_t deg = 0; + mp_float_t *x, *XT, *y, *prod; + + if(n_args == 2) { // only the y values are supplied + // TODO: this is actually not enough: the first argument can very well be a matrix, + // in which case we are between the rock and a hard place + leny = (size_t)mp_obj_get_int(mp_obj_len_maybe(args[0])); + deg = (uint8_t)mp_obj_get_int(args[1]); + if(leny < deg) { + mp_raise_ValueError(translate("more degrees of freedom than data points")); + } + lenx = leny; + x = m_new(mp_float_t, lenx); // assume uniformly spaced data points + for(size_t i=0; i < lenx; i++) { + x[i] = i; + } + y = m_new(mp_float_t, leny); + fill_array_iterable(y, args[0]); + } else /* n_args == 3 */ { + if(!ndarray_object_is_array_like(args[1])) { + mp_raise_ValueError(translate("input data must be an iterable")); + } + lenx = (size_t)mp_obj_get_int(mp_obj_len_maybe(args[0])); + leny = (size_t)mp_obj_get_int(mp_obj_len_maybe(args[1])); + if(lenx != leny) { + mp_raise_ValueError(translate("input vectors must be of equal length")); + } + deg = (uint8_t)mp_obj_get_int(args[2]); + if(leny < deg) { + mp_raise_ValueError(translate("more degrees of freedom than data points")); + } + x = m_new(mp_float_t, lenx); + fill_array_iterable(x, args[0]); + y = m_new(mp_float_t, leny); + fill_array_iterable(y, args[1]); + } + + // one could probably express X as a function of XT, + // and thereby save RAM, because X is used only in the product + XT = m_new(mp_float_t, (deg+1)*leny); // XT is a matrix of shape (deg+1, len) (rows, columns) + for(size_t i=0; i < leny; i++) { // column index + XT[i+0*lenx] = 1.0; // top row + for(uint8_t j=1; j < deg+1; j++) { // row index + XT[i+j*leny] = XT[i+(j-1)*leny]*x[i]; + } + } + + prod = m_new(mp_float_t, (deg+1)*(deg+1)); // the product matrix is of shape (deg+1, deg+1) + mp_float_t sum; + for(uint8_t i=0; i < deg+1; i++) { // column index + for(uint8_t j=0; j < deg+1; j++) { // row index + sum = 0.0; + for(size_t k=0; k < lenx; k++) { + // (j, k) * (k, i) + // Note that the second matrix is simply the transpose of the first: + // X(k, i) = XT(i, k) = XT[k*lenx+i] + sum += XT[j*lenx+k]*XT[i*lenx+k]; // X[k*(deg+1)+i]; + } + prod[j*(deg+1)+i] = sum; + } + } + if(!linalg_invert_matrix(prod, deg+1)) { + // Although X was a Vandermonde matrix, whose inverse is guaranteed to exist, + // we bail out here, if prod couldn't be inverted: if the values in x are not all + // distinct, prod is singular + m_del(mp_float_t, XT, (deg+1)*lenx); + m_del(mp_float_t, x, lenx); + m_del(mp_float_t, y, lenx); + m_del(mp_float_t, prod, (deg+1)*(deg+1)); + mp_raise_ValueError(translate("could not invert Vandermonde matrix")); + } + // at this point, we have the inverse of X^T * X + // y is a column vector; x is free now, we can use it for storing intermediate values + for(uint8_t i=0; i < deg+1; i++) { // row index + sum = 0.0; + for(size_t j=0; j < lenx; j++) { // column index + sum += XT[i*lenx+j]*y[j]; + } + x[i] = sum; + } + // XT is no longer needed + m_del(mp_float_t, XT, (deg+1)*leny); + + ndarray_obj_t *beta = ndarray_new_linear_array(deg+1, NDARRAY_FLOAT); + mp_float_t *betav = (mp_float_t *)beta->array; + // x[0..(deg+1)] contains now the product X^T * y; we can get rid of y + m_del(float, y, leny); + + // now, we calculate beta, i.e., we apply prod = (X^T * X)^(-1) on x = X^T * y; x is a column vector now + for(uint8_t i=0; i < deg+1; i++) { + sum = 0.0; + for(uint8_t j=0; j < deg+1; j++) { + sum += prod[i*(deg+1)+j]*x[j]; + } + betav[i] = sum; + } + m_del(mp_float_t, x, lenx); + m_del(mp_float_t, prod, (deg+1)*(deg+1)); + for(uint8_t i=0; i < (deg+1)/2; i++) { + // We have to reverse the array, for the leading coefficient comes first. + SWAP(mp_float_t, betav[i], betav[deg-i]); + } + return MP_OBJ_FROM_PTR(beta); +} + +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(poly_polyfit_obj, 2, 3, poly_polyfit); +#endif + +#if ULAB_NUMPY_HAS_POLYVAL + +mp_obj_t poly_polyval(mp_obj_t o_p, mp_obj_t o_x) { + if(!ndarray_object_is_array_like(o_p) || !ndarray_object_is_array_like(o_x)) { + mp_raise_TypeError(translate("inputs are not iterable")); + } + // p had better be a one-dimensional standard iterable + uint8_t plen = mp_obj_get_int(mp_obj_len_maybe(o_p)); + mp_float_t *p = m_new(mp_float_t, plen); + mp_obj_iter_buf_t p_buf; + mp_obj_t p_item, p_iterable = mp_getiter(o_p, &p_buf); + uint8_t i = 0; + while((p_item = mp_iternext(p_iterable)) != MP_OBJ_STOP_ITERATION) { + p[i] = mp_obj_get_float(p_item); + i++; + } + + // polynomials are going to be of type float, except, when both + // the coefficients and the independent variable are integers + ndarray_obj_t *ndarray; + if(mp_obj_is_type(o_x, &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(o_x); + uint8_t *sarray = (uint8_t *)source->array; + ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_FLOAT); + mp_float_t *array = (mp_float_t *)ndarray->array; + + mp_float_t (*func)(void *) = ndarray_get_float_function(source->dtype); + + // TODO: these loops are really nothing, but the re-impplementation of + // ITERATE_VECTOR from vectorise.c. We could pass a function pointer here + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t y = p[0]; + mp_float_t _x = func(sarray); + for(uint8_t m=0; m < plen-1; m++) { + y *= _x; + y += p[m+1]; + } + *array++ = y; + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif + } else { + // o_x had better be a one-dimensional standard iterable + ndarray = ndarray_new_linear_array(mp_obj_get_int(mp_obj_len_maybe(o_x)), NDARRAY_FLOAT); + mp_float_t *array = (mp_float_t *)ndarray->array; + mp_obj_iter_buf_t x_buf; + mp_obj_t x_item, x_iterable = mp_getiter(o_x, &x_buf); + while ((x_item = mp_iternext(x_iterable)) != MP_OBJ_STOP_ITERATION) { + mp_float_t _x = mp_obj_get_float(x_item); + mp_float_t y = p[0]; + for(uint8_t j=0; j < plen-1; j++) { + y *= _x; + y += p[j+1]; + } + *array++ = y; + } + } + m_del(mp_float_t, p, plen); + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_2(poly_polyval_obj, poly_polyval); +#endif diff --git a/python/port/mod/ulab/numpy/poly/poly.h b/python/port/mod/ulab/numpy/poly/poly.h new file mode 100644 index 00000000000..cf66ab193e8 --- /dev/null +++ b/python/port/mod/ulab/numpy/poly/poly.h @@ -0,0 +1,21 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#ifndef _POLY_ +#define _POLY_ + +#include "../../ulab.h" +#include "../../ndarray.h" + +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(poly_polyfit_obj); +MP_DECLARE_CONST_FUN_OBJ_2(poly_polyval_obj); + +#endif diff --git a/python/port/mod/ulab/numpy/stats/stats.c b/python/port/mod/ulab/numpy/stats/stats.c new file mode 100644 index 00000000000..9172b234065 --- /dev/null +++ b/python/port/mod/ulab/numpy/stats/stats.c @@ -0,0 +1,52 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Scott Shawcroft for Adafruit Industries + * 2020 Roberto Colistete Jr. + * 2020 Taku Fukada + * +*/ + +#include +#include +#include +#include +#include +#include + +#include "../../ulab.h" +#include "../../ulab_tools.h" +#include "stats.h" + +#if ULAB_MAX_DIMS > 1 +#if ULAB_NUMPY_HAS_TRACE + +//| def trace(m: ulab.numpy.ndarray) -> float: +//| """ +//| :param m: a square matrix +//| +//| Compute the trace of the matrix, the sum of its diagonal elements.""" +//| ... +//| + +static mp_obj_t stats_trace(mp_obj_t oin) { + ndarray_obj_t *ndarray = tools_object_is_square(oin); + mp_float_t trace = 0.0; + for(size_t i=0; i < ndarray->shape[ULAB_MAX_DIMS - 1]; i++) { + int32_t pos = i * (ndarray->strides[ULAB_MAX_DIMS - 1] + ndarray->strides[ULAB_MAX_DIMS - 2]); + trace += ndarray_get_float_index(ndarray->array, ndarray->dtype, pos/ndarray->itemsize); + } + if(ndarray->dtype == NDARRAY_FLOAT) { + return mp_obj_new_float(trace); + } + return mp_obj_new_int_from_float(trace); +} + +MP_DEFINE_CONST_FUN_OBJ_1(stats_trace_obj, stats_trace); +#endif +#endif diff --git a/python/port/mod/ulab/numpy/stats/stats.h b/python/port/mod/ulab/numpy/stats/stats.h new file mode 100644 index 00000000000..e5fab5f9ff8 --- /dev/null +++ b/python/port/mod/ulab/numpy/stats/stats.h @@ -0,0 +1,20 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#ifndef _STATS_ +#define _STATS_ + +#include "../../ulab.h" +#include "../../ndarray.h" + +MP_DECLARE_CONST_FUN_OBJ_1(stats_trace_obj); + +#endif diff --git a/python/port/mod/ulab/numpy/transform/transform.c b/python/port/mod/ulab/numpy/transform/transform.c new file mode 100644 index 00000000000..71473f5c6f6 --- /dev/null +++ b/python/port/mod/ulab/numpy/transform/transform.c @@ -0,0 +1,89 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * +*/ + +#include +#include +#include +#include +#include +#include + +#include "../../ulab.h" +#include "../../ulab_tools.h" +#include "transform.h" + + +#if ULAB_NUMPY_HAS_DOT +//| def dot(m1: ulab.numpy.ndarray, m2: ulab.numpy.ndarray) -> Union[ulab.numpy.ndarray, float]: +//| """ +//| :param ~ulab.numpy.ndarray m1: a matrix, or a vector +//| :param ~ulab.numpy.ndarray m2: a matrix, or a vector +//| +//| Computes the product of two matrices, or two vectors. In the letter case, the inner product is returned.""" +//| ... +//| + +mp_obj_t transform_dot(mp_obj_t _m1, mp_obj_t _m2) { + // TODO: should the results be upcast? + // This implements 2D operations only! + if(!mp_obj_is_type(_m1, &ulab_ndarray_type) || !mp_obj_is_type(_m2, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("arguments must be ndarrays")); + } + ndarray_obj_t *m1 = MP_OBJ_TO_PTR(_m1); + ndarray_obj_t *m2 = MP_OBJ_TO_PTR(_m2); + uint8_t *array1 = (uint8_t *)m1->array; + uint8_t *array2 = (uint8_t *)m2->array; + + mp_float_t (*func1)(void *) = ndarray_get_float_function(m1->dtype); + mp_float_t (*func2)(void *) = ndarray_get_float_function(m2->dtype); + + if(m1->shape[ULAB_MAX_DIMS - 1] != m2->shape[ULAB_MAX_DIMS - m2->ndim]) { + mp_raise_ValueError(translate("dimensions do not match")); + } + uint8_t ndim = MIN(m1->ndim, m2->ndim); + size_t shape1 = m1->ndim == 2 ? m1->shape[ULAB_MAX_DIMS - m1->ndim] : 1; + size_t shape2 = m2->ndim == 2 ? m2->shape[ULAB_MAX_DIMS - 1] : 1; + + size_t *shape = NULL; + if(ndim == 2) { // matrix times matrix -> matrix + shape = ndarray_shape_vector(0, 0, shape1, shape2); + } else { // matrix times vector -> vector, vector times vector -> vector (size 1) + shape = ndarray_shape_vector(0, 0, 0, shape1); + } + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + mp_float_t *rarray = (mp_float_t *)results->array; + + for(size_t i=0; i < shape1; i++) { // rows of m1 + for(size_t j=0; j < shape2; j++) { // columns of m2 + mp_float_t dot = 0.0; + for(size_t k=0; k < m1->shape[ULAB_MAX_DIMS - 1]; k++) { + // (i, k) * (k, j) + dot += func1(array1) * func2(array2); + array1 += m1->strides[ULAB_MAX_DIMS - 1]; + array2 += m2->strides[ULAB_MAX_DIMS - m2->ndim]; + } + *rarray++ = dot; + array1 -= m1->strides[ULAB_MAX_DIMS - 1] * m1->shape[ULAB_MAX_DIMS - 1]; + array2 -= m2->strides[ULAB_MAX_DIMS - m2->ndim] * m2->shape[ULAB_MAX_DIMS - m2->ndim]; + array2 += m2->strides[ULAB_MAX_DIMS - 1]; + } + array1 += m1->strides[ULAB_MAX_DIMS - m1->ndim]; + array2 = m2->array; + } + if((m1->ndim * m2->ndim) == 1) { // return a scalar, if product of two vectors + return mp_obj_new_float(*(--rarray)); + } else { + return MP_OBJ_FROM_PTR(results); + } +} + +MP_DEFINE_CONST_FUN_OBJ_2(transform_dot_obj, transform_dot); +#endif diff --git a/python/port/mod/ulab/numpy/transform/transform.h b/python/port/mod/ulab/numpy/transform/transform.h new file mode 100644 index 00000000000..ba4194e3bf9 --- /dev/null +++ b/python/port/mod/ulab/numpy/transform/transform.h @@ -0,0 +1,28 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * +*/ + +#ifndef _TRANSFORM_ +#define _TRANSFORM_ + +#include +#include +#include +#include +#include +#include + +#include "../../ulab.h" +#include "../../ulab_tools.h" +#include "transform.h" + +MP_DECLARE_CONST_FUN_OBJ_2(transform_dot_obj); + +#endif diff --git a/python/port/mod/ulab/numpy/vector/vector.c b/python/port/mod/ulab/numpy/vector/vector.c new file mode 100644 index 00000000000..bc8326c6d19 --- /dev/null +++ b/python/port/mod/ulab/numpy/vector/vector.c @@ -0,0 +1,635 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Jeff Epler for Adafruit Industries + * 2020 Scott Shawcroft for Adafruit Industries + * 2020 Taku Fukada +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "../../ulab.h" +#include "../../ulab_tools.h" +#include "vector.h" + +//| """Element-by-element functions +//| +//| These functions can operate on numbers, 1-D iterables, and arrays of 1 to 4 dimensions by +//| applying the function to every element in the array. This is typically +//| much more efficient than expressing the same operation as a Python loop.""" +//| +//| from ulab import _DType, _ArrayLike +//| + +static mp_obj_t vectorise_generic_vector(mp_obj_t o_in, mp_float_t (*f)(mp_float_t)) { + // Return a single value, if o_in is not iterable + if(mp_obj_is_float(o_in) || mp_obj_is_int(o_in)) { + return mp_obj_new_float(f(mp_obj_get_float(o_in))); + } + ndarray_obj_t *ndarray = NULL; + if(mp_obj_is_type(o_in, &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(o_in); + uint8_t *sarray = (uint8_t *)source->array; + ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_FLOAT); + mp_float_t *array = (mp_float_t *)ndarray->array; + + #if ULAB_VECTORISE_USES_FUN_POINTER + + mp_float_t (*func)(void *) = ndarray_get_float_function(source->dtype); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t value = func(sarray); + *array++ = f(value); + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + #else + if(source->dtype == NDARRAY_UINT8) { + ITERATE_VECTOR(uint8_t, array, source, sarray); + } else if(source->dtype == NDARRAY_INT8) { + ITERATE_VECTOR(int8_t, array, source, sarray); + } else if(source->dtype == NDARRAY_UINT16) { + ITERATE_VECTOR(uint16_t, array, source, sarray); + } else if(source->dtype == NDARRAY_INT16) { + ITERATE_VECTOR(int16_t, array, source, sarray); + } else { + ITERATE_VECTOR(mp_float_t, array, source, sarray); + } + #endif /* ULAB_VECTORISE_USES_FUN_POINTER */ + } else { + ndarray = ndarray_from_mp_obj(o_in, 0); + mp_float_t *array = (mp_float_t *)ndarray->array; + for(size_t i = 0; i < ndarray->len; i++) { + *array = f(*array); + array++; + } + } + return MP_OBJ_FROM_PTR(ndarray); +} + +#if ULAB_NUMPY_HAS_ACOS +//| def acos(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the inverse cosine function""" +//| ... +//| + +MATH_FUN_1(acos, acos); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_acos_obj, vectorise_acos); +#endif + +#if ULAB_NUMPY_HAS_ACOSH +//| def acosh(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the inverse hyperbolic cosine function""" +//| ... +//| + +MATH_FUN_1(acosh, acosh); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_acosh_obj, vectorise_acosh); +#endif + +#if ULAB_NUMPY_HAS_ASIN +//| def asin(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the inverse sine function""" +//| ... +//| + +MATH_FUN_1(asin, asin); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_asin_obj, vectorise_asin); +#endif + +#if ULAB_NUMPY_HAS_ASINH +//| def asinh(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the inverse hyperbolic sine function""" +//| ... +//| + +MATH_FUN_1(asinh, asinh); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_asinh_obj, vectorise_asinh); +#endif + +#if ULAB_NUMPY_HAS_AROUND +//| def around(a: _ArrayLike, *, decimals: int = 0) -> ulab.ndarray: +//| """Returns a new float array in which each element is rounded to +//| ``decimals`` places.""" +//| ... +//| + +mp_obj_t vectorise_around(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} }, + { MP_QSTR_decimals, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0 } } + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("first argument must be an ndarray")); + } + int8_t n = args[1].u_int; + mp_float_t mul = MICROPY_FLOAT_C_FUN(pow)(10.0, n); + ndarray_obj_t *source = MP_OBJ_TO_PTR(args[0].u_obj); + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_FLOAT); + mp_float_t *narray = (mp_float_t *)ndarray->array; + uint8_t *sarray = (uint8_t *)source->array; + + mp_float_t (*func)(void *) = ndarray_get_float_function(source->dtype); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t f = func(sarray); + *narray++ = MICROPY_FLOAT_C_FUN(round)(f * mul) / mul; + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(vectorise_around_obj, 1, vectorise_around); +#endif + +#if ULAB_NUMPY_HAS_ATAN +//| def atan(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the inverse tangent function; the return values are in the +//| range [-pi/2,pi/2].""" +//| ... +//| + +MATH_FUN_1(atan, atan); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_atan_obj, vectorise_atan); +#endif + +#if ULAB_NUMPY_HAS_ARCTAN2 +//| def arctan2(ya: _ArrayLike, xa: _ArrayLike) -> ulab.ndarray: +//| """Computes the inverse tangent function of y/x; the return values are in +//| the range [-pi, pi].""" +//| ... +//| + +mp_obj_t vectorise_arctan2(mp_obj_t y, mp_obj_t x) { + ndarray_obj_t *ndarray_x = ndarray_from_mp_obj(x, 0); + ndarray_obj_t *ndarray_y = ndarray_from_mp_obj(y, 0); + + uint8_t ndim = 0; + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + int32_t *xstrides = m_new(int32_t, ULAB_MAX_DIMS); + int32_t *ystrides = m_new(int32_t, ULAB_MAX_DIMS); + if(!ndarray_can_broadcast(ndarray_x, ndarray_y, &ndim, shape, xstrides, ystrides)) { + mp_raise_ValueError(translate("operands could not be broadcast together")); + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(int32_t, xstrides, ULAB_MAX_DIMS); + m_del(int32_t, ystrides, ULAB_MAX_DIMS); + } + + uint8_t *xarray = (uint8_t *)ndarray_x->array; + uint8_t *yarray = (uint8_t *)ndarray_y->array; + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + mp_float_t *rarray = (mp_float_t *)results->array; + + mp_float_t (*funcx)(void *) = ndarray_get_float_function(ndarray_x->dtype); + mp_float_t (*funcy)(void *) = ndarray_get_float_function(ndarray_y->dtype); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t _x = funcx(xarray); + mp_float_t _y = funcy(yarray); + *rarray++ = MICROPY_FLOAT_C_FUN(atan2)(_y, _x); + xarray += xstrides[ULAB_MAX_DIMS - 1]; + yarray += ystrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < results->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + xarray -= xstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + xarray += xstrides[ULAB_MAX_DIMS - 2]; + yarray -= ystrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + yarray += ystrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < results->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + xarray -= xstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + xarray += xstrides[ULAB_MAX_DIMS - 3]; + yarray -= ystrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + yarray += ystrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < results->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + xarray -= xstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + xarray += xstrides[ULAB_MAX_DIMS - 4]; + yarray -= ystrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + yarray += ystrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < results->shape[ULAB_MAX_DIMS - 4]); + #endif + + return MP_OBJ_FROM_PTR(results); +} + +MP_DEFINE_CONST_FUN_OBJ_2(vectorise_arctan2_obj, vectorise_arctan2); +#endif /* ULAB_VECTORISE_HAS_ARCTAN2 */ + +#if ULAB_NUMPY_HAS_ATANH +//| def atanh(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the inverse hyperbolic tangent function""" +//| ... +//| + +MATH_FUN_1(atanh, atanh); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_atanh_obj, vectorise_atanh); +#endif + +#if ULAB_NUMPY_HAS_CEIL +//| def ceil(a: _ArrayLike) -> ulab.ndarray: +//| """Rounds numbers up to the next whole number""" +//| ... +//| + +MATH_FUN_1(ceil, ceil); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_ceil_obj, vectorise_ceil); +#endif + +#if ULAB_NUMPY_HAS_COS +//| def cos(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the cosine function""" +//| ... +//| + +MATH_FUN_1(cos, cos); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_cos_obj, vectorise_cos); +#endif + +#if ULAB_NUMPY_HAS_COSH +//| def cosh(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the hyperbolic cosine function""" +//| ... +//| + +MATH_FUN_1(cosh, cosh); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_cosh_obj, vectorise_cosh); +#endif + +#if ULAB_NUMPY_HAS_DEGREES +//| def degrees(a: _ArrayLike) -> ulab.ndarray: +//| """Converts angles from radians to degrees""" +//| ... +//| + +static mp_float_t vectorise_degrees_(mp_float_t value) { + return value * MICROPY_FLOAT_CONST(180.0) / MP_PI; +} + +static mp_obj_t vectorise_degrees(mp_obj_t x_obj) { + return vectorise_generic_vector(x_obj, vectorise_degrees_); +} + +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_degrees_obj, vectorise_degrees); +#endif + +#if ULAB_SCIPY_SPECIAL_HAS_ERF +//| def erf(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the error function, which has applications in statistics""" +//| ... +//| + +MATH_FUN_1(erf, erf); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_erf_obj, vectorise_erf); +#endif + +#if ULAB_SCIPY_SPECIAL_HAS_ERFC +//| def erfc(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the complementary error function, which has applications in statistics""" +//| ... +//| + +MATH_FUN_1(erfc, erfc); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_erfc_obj, vectorise_erfc); +#endif + +#if ULAB_NUMPY_HAS_EXP +//| def exp(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the exponent function.""" +//| ... +//| + +MATH_FUN_1(exp, exp); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_exp_obj, vectorise_exp); +#endif + +#if ULAB_NUMPY_HAS_EXPM1 +//| def expm1(a: _ArrayLike) -> ulab.ndarray: +//| """Computes $e^x-1$. In certain applications, using this function preserves numeric accuracy better than the `exp` function.""" +//| ... +//| + +MATH_FUN_1(expm1, expm1); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_expm1_obj, vectorise_expm1); +#endif + +#if ULAB_NUMPY_HAS_FLOOR +//| def floor(a: _ArrayLike) -> ulab.ndarray: +//| """Rounds numbers up to the next whole number""" +//| ... +//| + +MATH_FUN_1(floor, floor); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_floor_obj, vectorise_floor); +#endif + +#if ULAB_SCIPY_SPECIAL_HAS_GAMMA +//| def gamma(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the gamma function""" +//| ... +//| + +MATH_FUN_1(gamma, tgamma); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_gamma_obj, vectorise_gamma); +#endif + +#if ULAB_SCIPY_SPECIAL_HAS_GAMMALN +//| def lgamma(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the natural log of the gamma function""" +//| ... +//| + +MATH_FUN_1(lgamma, lgamma); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_lgamma_obj, vectorise_lgamma); +#endif + +#if ULAB_NUMPY_HAS_LOG +//| def log(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the natural log""" +//| ... +//| + +MATH_FUN_1(log, log); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log_obj, vectorise_log); +#endif + +#if ULAB_NUMPY_HAS_LOG10 +//| def log10(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the log base 10""" +//| ... +//| + +MATH_FUN_1(log10, log10); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log10_obj, vectorise_log10); +#endif + +#if ULAB_NUMPY_HAS_LOG2 +//| def log2(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the log base 2""" +//| ... +//| + +MATH_FUN_1(log2, log2); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log2_obj, vectorise_log2); +#endif + +#if ULAB_NUMPY_HAS_RADIANS +//| def radians(a: _ArrayLike) -> ulab.ndarray: +//| """Converts angles from degrees to radians""" +//| ... +//| + +static mp_float_t vectorise_radians_(mp_float_t value) { + return value * MP_PI / MICROPY_FLOAT_CONST(180.0); +} + +static mp_obj_t vectorise_radians(mp_obj_t x_obj) { + return vectorise_generic_vector(x_obj, vectorise_radians_); +} + +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_radians_obj, vectorise_radians); +#endif + +#if ULAB_NUMPY_HAS_SIN +//| def sin(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the sine function""" +//| ... +//| + +MATH_FUN_1(sin, sin); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_sin_obj, vectorise_sin); +#endif + +#if ULAB_NUMPY_HAS_SINH +//| def sinh(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the hyperbolic sine""" +//| ... +//| + +MATH_FUN_1(sinh, sinh); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_sinh_obj, vectorise_sinh); +#endif + +#if ULAB_NUMPY_HAS_SQRT +//| def sqrt(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the square root""" +//| ... +//| + +MATH_FUN_1(sqrt, sqrt); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_sqrt_obj, vectorise_sqrt); +#endif + +#if ULAB_NUMPY_HAS_TAN +//| def tan(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the tangent""" +//| ... +//| + +MATH_FUN_1(tan, tan); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_tan_obj, vectorise_tan); +#endif + +#if ULAB_NUMPY_HAS_TANH +//| def tanh(a: _ArrayLike) -> ulab.ndarray: +//| """Computes the hyperbolic tangent""" +//| ... + +MATH_FUN_1(tanh, tanh); +MP_DEFINE_CONST_FUN_OBJ_1(vectorise_tanh_obj, vectorise_tanh); +#endif + +#if ULAB_NUMPY_HAS_VECTORIZE +static mp_obj_t vectorise_vectorized_function_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void) n_args; + (void) n_kw; + vectorized_function_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t avalue[1]; + mp_obj_t fvalue; + if(mp_obj_is_type(args[0], &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(args[0]); + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, self->otypes); + for(size_t i=0; i < source->len; i++) { + avalue[0] = mp_binary_get_val_array(source->dtype, source->array, i); + fvalue = self->type->call(self->fun, 1, 0, avalue); + ndarray_set_value(self->otypes, ndarray->array, i, fvalue); + } + return MP_OBJ_FROM_PTR(ndarray); + } else if(mp_obj_is_type(args[0], &mp_type_tuple) || mp_obj_is_type(args[0], &mp_type_list) || + mp_obj_is_type(args[0], &mp_type_range)) { // i.e., the input is a generic iterable + size_t len = (size_t)mp_obj_get_int(mp_obj_len_maybe(args[0])); + ndarray_obj_t *ndarray = ndarray_new_linear_array(len, self->otypes); + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(args[0], &iter_buf); + size_t i=0; + while ((avalue[0] = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + fvalue = self->type->call(self->fun, 1, 0, avalue); + ndarray_set_value(self->otypes, ndarray->array, i, fvalue); + i++; + } + return MP_OBJ_FROM_PTR(ndarray); + } else if(mp_obj_is_int(args[0]) || mp_obj_is_float(args[0])) { + ndarray_obj_t *ndarray = ndarray_new_linear_array(1, self->otypes); + fvalue = self->type->call(self->fun, 1, 0, args); + ndarray_set_value(self->otypes, ndarray->array, 0, fvalue); + return MP_OBJ_FROM_PTR(ndarray); + } else { + mp_raise_ValueError(translate("wrong input type")); + } + return mp_const_none; +} + +const mp_obj_type_t vectorise_function_type = { + { &mp_type_type }, + .name = MP_QSTR_, + .call = vectorise_vectorized_function_call, +}; + +//| def vectorize( +//| f: Union[Callable[[int], float], Callable[[float], float]], +//| *, +//| otypes: Optional[_DType] = None +//| ) -> Callable[[_ArrayLike], ulab.ndarray]: +//| """ +//| :param callable f: The function to wrap +//| :param otypes: List of array types that may be returned by the function. None is interpreted to mean the return value is float. +//| +//| Wrap a Python function ``f`` so that it can be applied to arrays. +//| The callable must return only values of the types specified by ``otypes``, or the result is undefined.""" +//| ... +//| + +static mp_obj_t vectorise_vectorize(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} }, + { MP_QSTR_otypes, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} } + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + const mp_obj_type_t *type = mp_obj_get_type(args[0].u_obj); + if(type->call == NULL) { + mp_raise_TypeError(translate("first argument must be a callable")); + } + mp_obj_t _otypes = args[1].u_obj; + uint8_t otypes = NDARRAY_FLOAT; + if(_otypes == mp_const_none) { + // TODO: is this what numpy does? + otypes = NDARRAY_FLOAT; + } else if(mp_obj_is_int(_otypes)) { + otypes = mp_obj_get_int(_otypes); + if(otypes != NDARRAY_FLOAT && otypes != NDARRAY_UINT8 && otypes != NDARRAY_INT8 && + otypes != NDARRAY_UINT16 && otypes != NDARRAY_INT16) { + mp_raise_ValueError(translate("wrong output type")); + } + } + else { + mp_raise_ValueError(translate("wrong output type")); + } + vectorized_function_obj_t *function = m_new_obj(vectorized_function_obj_t); + function->base.type = &vectorise_function_type; + function->otypes = otypes; + function->fun = args[0].u_obj; + function->type = type; + return MP_OBJ_FROM_PTR(function); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(vectorise_vectorize_obj, 1, vectorise_vectorize); +#endif diff --git a/python/port/mod/ulab/numpy/vector/vector.h b/python/port/mod/ulab/numpy/vector/vector.h new file mode 100644 index 00000000000..6b00a221b9c --- /dev/null +++ b/python/port/mod/ulab/numpy/vector/vector.h @@ -0,0 +1,156 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#ifndef _VECTOR_ +#define _VECTOR_ + +#include "../../ulab.h" +#include "../../ndarray.h" + +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_acos_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_acosh_obj); +MP_DECLARE_CONST_FUN_OBJ_2(vectorise_arctan2_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(vectorise_around_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_asin_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_asinh_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_atan_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_atanh_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_ceil_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_cos_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_cosh_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_degrees_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_erf_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_erfc_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_exp_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_expm1_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_floor_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_gamma_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_lgamma_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_log_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_log10_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_log2_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_radians_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_sin_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_sinh_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_sqrt_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_tan_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vectorise_tanh_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(vectorise_vectorize_obj); + +typedef struct _vectorized_function_obj_t { + mp_obj_base_t base; + uint8_t otypes; + mp_obj_t fun; + const mp_obj_type_t *type; +} vectorized_function_obj_t; + +#if ULAB_HAS_FUNCTION_ITERATOR +#define ITERATE_VECTOR(type, array, source, sarray)\ +({\ + size_t *scoords = ndarray_new_coords((source)->ndim);\ + for(size_t i=0; i < (source)->len/(source)->shape[ULAB_MAX_DIMS -1]; i++) {\ + for(size_t l=0; l < (source)->shape[ULAB_MAX_DIMS - 1]; l++) {\ + *(array)++ = f(*((type *)(sarray)));\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 1];\ + }\ + ndarray_rewind_array((source)->ndim, sarray, (source)->shape, (source)->strides, scoords);\ + }\ +}) + +#else + +#if ULAB_MAX_DIMS == 4 +#define ITERATE_VECTOR(type, array, source, sarray) do {\ + size_t i=0;\ + do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *(array)++ = f(*((type *)(sarray)));\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (source)->shape[ULAB_MAX_DIMS-1]);\ + (sarray) -= (source)->strides[ULAB_MAX_DIMS - 1] * (source)->shape[ULAB_MAX_DIMS-1];\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (source)->shape[ULAB_MAX_DIMS-2]);\ + (sarray) -= (source)->strides[ULAB_MAX_DIMS - 2] * (source)->shape[ULAB_MAX_DIMS-2];\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (source)->shape[ULAB_MAX_DIMS-3]);\ + (sarray) -= (source)->strides[ULAB_MAX_DIMS - 3] * (source)->shape[ULAB_MAX_DIMS-3];\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (source)->shape[ULAB_MAX_DIMS-4]);\ +} while(0) +#endif /* ULAB_MAX_DIMS == 4 */ + +#if ULAB_MAX_DIMS == 3 +#define ITERATE_VECTOR(type, array, source, sarray) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *(array)++ = f(*((type *)(sarray)));\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (source)->shape[ULAB_MAX_DIMS-1]);\ + (sarray) -= (source)->strides[ULAB_MAX_DIMS - 1] * (source)->shape[ULAB_MAX_DIMS-1];\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (source)->shape[ULAB_MAX_DIMS-2]);\ + (sarray) -= (source)->strides[ULAB_MAX_DIMS - 2] * (source)->shape[ULAB_MAX_DIMS-2];\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (source)->shape[ULAB_MAX_DIMS-3]);\ +} while(0) +#endif /* ULAB_MAX_DIMS == 3 */ + +#if ULAB_MAX_DIMS == 2 +#define ITERATE_VECTOR(type, array, source, sarray) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *(array)++ = f(*((type *)(sarray)));\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (source)->shape[ULAB_MAX_DIMS-1]);\ + (sarray) -= (source)->strides[ULAB_MAX_DIMS - 1] * (source)->shape[ULAB_MAX_DIMS-1];\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (source)->shape[ULAB_MAX_DIMS-2]);\ +} while(0) +#endif /* ULAB_MAX_DIMS == 2 */ + +#if ULAB_MAX_DIMS == 1 +#define ITERATE_VECTOR(type, array, source, sarray) do {\ + size_t l = 0;\ + do {\ + *(array)++ = f(*((type *)(sarray)));\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (source)->shape[ULAB_MAX_DIMS-1]);\ +} while(0) +#endif /* ULAB_MAX_DIMS == 1 */ +#endif /* ULAB_HAS_FUNCTION_ITERATOR */ + +#define MATH_FUN_1(py_name, c_name) \ + static mp_obj_t vectorise_ ## py_name(mp_obj_t x_obj) { \ + return vectorise_generic_vector(x_obj, MICROPY_FLOAT_C_FUN(c_name)); \ +} + +#endif /* _VECTOR_ */ diff --git a/python/port/mod/ulab/scipy/linalg/linalg.c b/python/port/mod/ulab/scipy/linalg/linalg.c new file mode 100644 index 00000000000..0f1b933af0e --- /dev/null +++ b/python/port/mod/ulab/scipy/linalg/linalg.c @@ -0,0 +1,279 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Vikas Udupa + * +*/ + +#include +#include +#include +#include +#include +#include + +#include "../../ulab.h" +#include "../../ulab_tools.h" +#include "linalg.h" + +#if ULAB_SCIPY_HAS_LINALG_MODULE +//| +//| import ulab.scipy +//| +//| """Linear algebra functions""" +//| + +#if ULAB_MAX_DIMS > 1 + +#define TOLERANCE 0.0000001 + +//| def solve_triangular(A: ulab.numpy.ndarray, b: ulab.numpy.ndarray, lower: bool) -> ulab.numpy.ndarray: +//| """ +//| :param ~ulab.numpy.ndarray A: a matrix +//| :param ~ulab.numpy.ndarray b: a vector +//| :param ~bool lower: if true, use only data contained in lower triangle of A, else use upper triangle of A +//| :return: solution to the system A x = b. Shape of return matches b +//| :raises TypeError: if A and b are not of type ndarray and are not dense +//| :raises ValueError: if A is a singular matrix +//| +//| Solve the equation A x = b for x, assuming A is a triangular matrix""" +//| ... +//| + +static mp_obj_t solve_triangular(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + + size_t i, j; + + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none} } , + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none} } , + { MP_QSTR_lower, MP_ARG_OBJ, { .u_rom_obj = mp_const_false } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type) || !mp_obj_is_type(args[1].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("first two arguments must be ndarrays")); + } + + ndarray_obj_t *A = MP_OBJ_TO_PTR(args[0].u_obj); + ndarray_obj_t *b = MP_OBJ_TO_PTR(args[1].u_obj); + + if(!ndarray_is_dense(A) || !ndarray_is_dense(b)) { + mp_raise_TypeError(translate("input must be a dense ndarray")); + } + + size_t A_rows = A->shape[ULAB_MAX_DIMS - 2]; + size_t A_cols = A->shape[ULAB_MAX_DIMS - 1]; + + uint8_t *A_arr = (uint8_t *)A->array; + uint8_t *b_arr = (uint8_t *)b->array; + + mp_float_t (*get_A_ele)(void *) = ndarray_get_float_function(A->dtype); + mp_float_t (*get_b_ele)(void *) = ndarray_get_float_function(b->dtype); + + uint8_t *temp_A = A_arr; + + // check if input matrix A is singular + for (i = 0; i < A_rows; i++) { + if (MICROPY_FLOAT_C_FUN(fabs)(get_A_ele(A_arr)) < TOLERANCE) + mp_raise_ValueError(translate("input matrix is singular")); + A_arr += A->strides[ULAB_MAX_DIMS - 2]; + A_arr += A->strides[ULAB_MAX_DIMS - 1]; + } + + A_arr = temp_A; + + ndarray_obj_t *x = ndarray_new_dense_ndarray(b->ndim, b->shape, NDARRAY_FLOAT); + mp_float_t *x_arr = (mp_float_t *)x->array; + + if (mp_obj_is_true(args[2].u_obj)) { + // Solve the lower triangular matrix by iterating each row of A. + // Start by finding the first unknown using the first row. + // On finding this unknown, find the second unknown using the second row. + // Continue the same till the last unknown is found using the last row. + + for (i = 0; i < A_rows; i++) { + mp_float_t sum = 0.0; + for (j = 0; j < i; j++) { + sum += (get_A_ele(A_arr) * (*x_arr++)); + A_arr += A->strides[ULAB_MAX_DIMS - 1]; + } + + sum = (get_b_ele(b_arr) - sum) / (get_A_ele(A_arr)); + *x_arr = sum; + + x_arr -= j; + A_arr -= A->strides[ULAB_MAX_DIMS - 1] * j; + A_arr += A->strides[ULAB_MAX_DIMS - 2]; + b_arr += b->strides[ULAB_MAX_DIMS - 1]; + } + } else { + // Solve the upper triangular matrix by iterating each row of A. + // Start by finding the last unknown using the last row. + // On finding this unknown, find the last-but-one unknown using the last-but-one row. + // Continue the same till the first unknown is found using the first row. + + A_arr += (A->strides[ULAB_MAX_DIMS - 2] * A_rows); + b_arr += (b->strides[ULAB_MAX_DIMS - 1] * A_cols); + x_arr += A_cols; + + for (i = A_rows - 1; i < A_rows; i--) { + mp_float_t sum = 0.0; + for (j = i + 1; j < A_cols; j++) { + sum += (get_A_ele(A_arr) * (*x_arr++)); + A_arr += A->strides[ULAB_MAX_DIMS - 1]; + } + + x_arr -= (j - i); + A_arr -= (A->strides[ULAB_MAX_DIMS - 1] * (j - i)); + b_arr -= b->strides[ULAB_MAX_DIMS - 1]; + + sum = (get_b_ele(b_arr) - sum) / get_A_ele(A_arr); + *x_arr = sum; + + A_arr -= A->strides[ULAB_MAX_DIMS - 2]; + } + } + + return MP_OBJ_FROM_PTR(x); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(linalg_solve_triangular_obj, 2, solve_triangular); + +//| def cho_solve(L: ulab.numpy.ndarray, b: ulab.numpy.ndarray) -> ulab.numpy.ndarray: +//| """ +//| :param ~ulab.numpy.ndarray L: the lower triangular, Cholesky factorization of A +//| :param ~ulab.numpy.ndarray b: right-hand-side vector b +//| :return: solution to the system A x = b. Shape of return matches b +//| :raises TypeError: if L and b are not of type ndarray and are not dense +//| +//| Solve the linear equations A x = b, given the Cholesky factorization of A as input""" +//| ... +//| + +static mp_obj_t cho_solve(mp_obj_t _L, mp_obj_t _b) { + + if(!mp_obj_is_type(_L, &ulab_ndarray_type) || !mp_obj_is_type(_b, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("first two arguments must be ndarrays")); + } + + ndarray_obj_t *L = MP_OBJ_TO_PTR(_L); + ndarray_obj_t *b = MP_OBJ_TO_PTR(_b); + + if(!ndarray_is_dense(L) || !ndarray_is_dense(b)) { + mp_raise_TypeError(translate("input must be a dense ndarray")); + } + + mp_float_t (*get_L_ele)(void *) = ndarray_get_float_function(L->dtype); + mp_float_t (*get_b_ele)(void *) = ndarray_get_float_function(b->dtype); + void (*set_L_ele)(void *, mp_float_t) = ndarray_set_float_function(L->dtype); + + size_t L_rows = L->shape[ULAB_MAX_DIMS - 2]; + size_t L_cols = L->shape[ULAB_MAX_DIMS - 1]; + + // Obtain transpose of the input matrix L in L_t + size_t L_t_shape[ULAB_MAX_DIMS]; + size_t L_t_rows = L_t_shape[ULAB_MAX_DIMS - 2] = L_cols; + size_t L_t_cols = L_t_shape[ULAB_MAX_DIMS - 1] = L_rows; + ndarray_obj_t *L_t = ndarray_new_dense_ndarray(L->ndim, L_t_shape, L->dtype); + + uint8_t *L_arr = (uint8_t *)L->array; + uint8_t *L_t_arr = (uint8_t *)L_t->array; + uint8_t *b_arr = (uint8_t *)b->array; + + size_t i, j; + + uint8_t *L_ptr = L_arr; + uint8_t *L_t_ptr = L_t_arr; + for (i = 0; i < L_rows; i++) { + for (j = 0; j < L_cols; j++) { + set_L_ele(L_t_ptr, get_L_ele(L_ptr)); + L_t_ptr += L_t->strides[ULAB_MAX_DIMS - 2]; + L_ptr += L->strides[ULAB_MAX_DIMS - 1]; + } + + L_t_ptr -= j * L_t->strides[ULAB_MAX_DIMS - 2]; + L_t_ptr += L_t->strides[ULAB_MAX_DIMS - 1]; + L_ptr -= j * L->strides[ULAB_MAX_DIMS - 1]; + L_ptr += L->strides[ULAB_MAX_DIMS - 2]; + } + + ndarray_obj_t *x = ndarray_new_dense_ndarray(b->ndim, b->shape, NDARRAY_FLOAT); + mp_float_t *x_arr = (mp_float_t *)x->array; + + ndarray_obj_t *y = ndarray_new_dense_ndarray(b->ndim, b->shape, NDARRAY_FLOAT); + mp_float_t *y_arr = (mp_float_t *)y->array; + + // solve L y = b to obtain y, where L_t x = y + for (i = 0; i < L_rows; i++) { + mp_float_t sum = 0.0; + for (j = 0; j < i; j++) { + sum += (get_L_ele(L_arr) * (*y_arr++)); + L_arr += L->strides[ULAB_MAX_DIMS - 1]; + } + + sum = (get_b_ele(b_arr) - sum) / (get_L_ele(L_arr)); + *y_arr = sum; + + y_arr -= j; + L_arr -= L->strides[ULAB_MAX_DIMS - 1] * j; + L_arr += L->strides[ULAB_MAX_DIMS - 2]; + b_arr += b->strides[ULAB_MAX_DIMS - 1]; + } + + // using y, solve L_t x = y to obtain x + L_t_arr += (L_t->strides[ULAB_MAX_DIMS - 2] * L_t_rows); + y_arr += L_t_cols; + x_arr += L_t_cols; + + for (i = L_t_rows - 1; i < L_t_rows; i--) { + mp_float_t sum = 0.0; + for (j = i + 1; j < L_t_cols; j++) { + sum += (get_L_ele(L_t_arr) * (*x_arr++)); + L_t_arr += L_t->strides[ULAB_MAX_DIMS - 1]; + } + + x_arr -= (j - i); + L_t_arr -= (L_t->strides[ULAB_MAX_DIMS - 1] * (j - i)); + y_arr--; + + sum = ((*y_arr) - sum) / get_L_ele(L_t_arr); + *x_arr = sum; + + L_t_arr -= L_t->strides[ULAB_MAX_DIMS - 2]; + } + + return MP_OBJ_FROM_PTR(x); +} + +MP_DEFINE_CONST_FUN_OBJ_2(linalg_cho_solve_obj, cho_solve); + +#endif + +static const mp_rom_map_elem_t ulab_scipy_linalg_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_linalg) }, + #if ULAB_MAX_DIMS > 1 + #if ULAB_SCIPY_LINALG_HAS_SOLVE_TRIANGULAR + { MP_ROM_QSTR(MP_QSTR_solve_triangular), (mp_obj_t)&linalg_solve_triangular_obj }, + #endif + #if ULAB_SCIPY_LINALG_HAS_CHO_SOLVE + { MP_ROM_QSTR(MP_QSTR_cho_solve), (mp_obj_t)&linalg_cho_solve_obj }, + #endif + #endif +}; + +static MP_DEFINE_CONST_DICT(mp_module_ulab_scipy_linalg_globals, ulab_scipy_linalg_globals_table); + +mp_obj_module_t ulab_scipy_linalg_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_scipy_linalg_globals, +}; + +#endif diff --git a/python/port/mod/ulab/scipy/linalg/linalg.h b/python/port/mod/ulab/scipy/linalg/linalg.h new file mode 100644 index 00000000000..7396affd6af --- /dev/null +++ b/python/port/mod/ulab/scipy/linalg/linalg.h @@ -0,0 +1,21 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Vikas Udupa + * +*/ + +#ifndef _SCIPY_LINALG_ +#define _SCIPY_LINALG_ + +extern mp_obj_module_t ulab_scipy_linalg_module; + +MP_DECLARE_CONST_FUN_OBJ_KW(linalg_solve_triangular_obj); +MP_DECLARE_CONST_FUN_OBJ_2(linalg_cho_solve_obj); + +#endif /* _SCIPY_LINALG_ */ diff --git a/python/port/mod/ulab/scipy/optimize/optimize.c b/python/port/mod/ulab/scipy/optimize/optimize.c new file mode 100644 index 00000000000..9218e6936d9 --- /dev/null +++ b/python/port/mod/ulab/scipy/optimize/optimize.c @@ -0,0 +1,414 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020 Scott Shawcroft for Adafruit Industries + * 2020-2021 Zoltán Vörös + * 2020 Taku Fukada +*/ + +#include +#include +#include +#include + +#include "../../ndarray.h" +#include "../../ulab.h" +#include "../../ulab_tools.h" +#include "optimize.h" + +const mp_obj_float_t xtolerance = {{&mp_type_float}, MICROPY_FLOAT_CONST(2.4e-7)}; +const mp_obj_float_t rtolerance = {{&mp_type_float}, MICROPY_FLOAT_CONST(0.0)}; + +static mp_float_t optimize_python_call(const mp_obj_type_t *type, mp_obj_t fun, mp_float_t x, mp_obj_t *fargs, uint8_t nparams) { + // Helper function for calculating the value of f(x, a, b, c, ...), + // where f is defined in python. Takes a float, returns a float. + // The array of mp_obj_t type must be supplied, as must the number of parameters (a, b, c...) in nparams + fargs[0] = mp_obj_new_float(x); + return mp_obj_get_float(type->call(fun, nparams+1, 0, fargs)); +} + +#if ULAB_SCIPY_OPTIMIZE_HAS_BISECT +//| def bisect( +//| fun: Callable[[float], float], +//| a: float, +//| b: float, +//| *, +//| xtol: float = 2.4e-7, +//| maxiter: int = 100 +//| ) -> float: +//| """ +//| :param callable f: The function to bisect +//| :param float a: The left side of the interval +//| :param float b: The right side of the interval +//| :param float xtol: The tolerance value +//| :param float maxiter: The maximum number of iterations to perform +//| +//| Find a solution (zero) of the function ``f(x)`` on the interval +//| (``a``..``b``) using the bisection method. The result is accurate to within +//| ``xtol`` unless more than ``maxiter`` steps are required.""" +//| ... +//| + +STATIC mp_obj_t optimize_bisect(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + // Simple bisection routine + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_xtol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&xtolerance)} }, + { MP_QSTR_maxiter, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 100} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t fun = args[0].u_obj; + const mp_obj_type_t *type = mp_obj_get_type(fun); + if(type->call == NULL) { + mp_raise_TypeError(translate("first argument must be a function")); + } + mp_float_t xtol = mp_obj_get_float(args[3].u_obj); + mp_obj_t *fargs = m_new(mp_obj_t, 1); + mp_float_t left, right; + mp_float_t x_mid; + mp_float_t a = mp_obj_get_float(args[1].u_obj); + mp_float_t b = mp_obj_get_float(args[2].u_obj); + left = optimize_python_call(type, fun, a, fargs, 0); + right = optimize_python_call(type, fun, b, fargs, 0); + if(left * right > 0) { + mp_raise_ValueError(translate("function has the same sign at the ends of interval")); + } + mp_float_t rtb = left < MICROPY_FLOAT_CONST(0.0) ? a : b; + mp_float_t dx = left < MICROPY_FLOAT_CONST(0.0) ? b - a : a - b; + if(args[4].u_int < 0) { + mp_raise_ValueError(translate("maxiter should be > 0")); + } + for(uint16_t i=0; i < args[4].u_int; i++) { + dx *= MICROPY_FLOAT_CONST(0.5); + x_mid = rtb + dx; + if(optimize_python_call(type, fun, x_mid, fargs, 0) < MICROPY_FLOAT_CONST(0.0)) { + rtb = x_mid; + } + if(MICROPY_FLOAT_C_FUN(fabs)(dx) < xtol) break; + } + return mp_obj_new_float(rtb); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(optimize_bisect_obj, 3, optimize_bisect); +#endif + +#if ULAB_SCIPY_OPTIMIZE_HAS_FMIN +//| def fmin( +//| fun: Callable[[float], float], +//| x0: float, +//| *, +//| xatol: float = 2.4e-7, +//| fatol: float = 2.4e-7, +//| maxiter: int = 200 +//| ) -> float: +//| """ +//| :param callable f: The function to bisect +//| :param float x0: The initial x value +//| :param float xatol: The absolute tolerance value +//| :param float fatol: The relative tolerance value +//| +//| Find a minimum of the function ``f(x)`` using the downhill simplex method. +//| The located ``x`` is within ``fxtol`` of the actual minimum, and ``f(x)`` +//| is within ``fatol`` of the actual minimum unless more than ``maxiter`` +//| steps are requried.""" +//| ... +//| + +STATIC mp_obj_t optimize_fmin(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + // downhill simplex method in 1D + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_xatol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&xtolerance)} }, + { MP_QSTR_fatol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&xtolerance)} }, + { MP_QSTR_maxiter, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 200} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t fun = args[0].u_obj; + const mp_obj_type_t *type = mp_obj_get_type(fun); + if(type->call == NULL) { + mp_raise_TypeError(translate("first argument must be a function")); + } + + // parameters controlling convergence conditions + mp_float_t xatol = mp_obj_get_float(args[2].u_obj); + mp_float_t fatol = mp_obj_get_float(args[3].u_obj); + if(args[4].u_int <= 0) { + mp_raise_ValueError(translate("maxiter must be > 0")); + } + uint16_t maxiter = (uint16_t)args[4].u_int; + + mp_float_t x0 = mp_obj_get_float(args[1].u_obj); + mp_float_t x1 = MICROPY_FLOAT_C_FUN(fabs)(x0) > OPTIMIZE_EPSILON ? (MICROPY_FLOAT_CONST(1.0) + OPTIMIZE_NONZDELTA) * x0 : OPTIMIZE_ZDELTA; + mp_obj_t *fargs = m_new(mp_obj_t, 1); + mp_float_t f0 = optimize_python_call(type, fun, x0, fargs, 0); + mp_float_t f1 = optimize_python_call(type, fun, x1, fargs, 0); + if(f1 < f0) { + SWAP(mp_float_t, x0, x1); + SWAP(mp_float_t, f0, f1); + } + for(uint16_t i=0; i < maxiter; i++) { + uint8_t shrink = 0; + f0 = optimize_python_call(type, fun, x0, fargs, 0); + f1 = optimize_python_call(type, fun, x1, fargs, 0); + + // reflection + mp_float_t xr = (MICROPY_FLOAT_CONST(1.0) + OPTIMIZE_ALPHA) * x0 - OPTIMIZE_ALPHA * x1; + mp_float_t fr = optimize_python_call(type, fun, xr, fargs, 0); + if(fr < f0) { // expansion + mp_float_t xe = (1 + OPTIMIZE_ALPHA * OPTIMIZE_BETA) * x0 - OPTIMIZE_ALPHA * OPTIMIZE_BETA * x1; + mp_float_t fe = optimize_python_call(type, fun, xe, fargs, 0); + if(fe < fr) { + x1 = xe; + f1 = fe; + } else { + x1 = xr; + f1 = fr; + } + } else { + if(fr < f1) { // contraction + mp_float_t xc = (1 + OPTIMIZE_GAMMA * OPTIMIZE_ALPHA) * x0 - OPTIMIZE_GAMMA * OPTIMIZE_ALPHA * x1; + mp_float_t fc = optimize_python_call(type, fun, xc, fargs, 0); + if(fc < fr) { + x1 = xc; + f1 = fc; + } else { + shrink = 1; + } + } else { // inside contraction + mp_float_t xc = (MICROPY_FLOAT_CONST(1.0) - OPTIMIZE_GAMMA) * x0 + OPTIMIZE_GAMMA * x1; + mp_float_t fc = optimize_python_call(type, fun, xc, fargs, 0); + if(fc < f1) { + x1 = xc; + f1 = fc; + } else { + shrink = 1; + } + } + if(shrink == 1) { + x1 = x0 + OPTIMIZE_DELTA * (x1 - x0); + f1 = optimize_python_call(type, fun, x1, fargs, 0); + } + if((MICROPY_FLOAT_C_FUN(fabs)(f1 - f0) < fatol) || + (MICROPY_FLOAT_C_FUN(fabs)(x1 - x0) < xatol)) { + break; + } + if(f1 < f0) { + SWAP(mp_float_t, x0, x1); + SWAP(mp_float_t, f0, f1); + } + } + } + return mp_obj_new_float(x0); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(optimize_fmin_obj, 2, optimize_fmin); +#endif + +#if ULAB_SCIPY_OPTIMIZE_HAS_CURVE_FIT +static void optimize_jacobi(const mp_obj_type_t *type, mp_obj_t fun, mp_float_t *x, mp_float_t *y, uint16_t len, mp_float_t *params, uint8_t nparams, mp_float_t *jacobi, mp_float_t *grad) { + /* Calculates the Jacobian and the gradient of the cost function + * + * The entries in the Jacobian are + * J(m, n) = de_m/da_n, + * + * where + * + * e_m = (f(x_m, a1, a2, ...) - y_m)/sigma_m is the error at x_m, + * + * and + * + * a1, a2, ..., a_n are the free parameters + */ + mp_obj_t *fargs0 = m_new(mp_obj_t, lenp+1); + mp_obj_t *fargs1 = m_new(mp_obj_t, lenp+1); + for(uint8_t p=0; p < nparams; p++) { + fargs0[p+1] = mp_obj_new_float(params[p]); + fargs1[p+1] = mp_obj_new_float(params[p]); + } + for(uint8_t p=0; p < nparams; p++) { + mp_float_t da = params[p] != MICROPY_FLOAT_CONST(0.0) ? (MICROPY_FLOAT_CONST(1.0) + APPROX_NONZDELTA) * params[p] : APPROX_ZDELTA; + fargs1[p+1] = mp_obj_new_float(params[p] + da); + grad[p] = MICROPY_FLOAT_CONST(0.0); + for(uint16_t i=0; i < len; i++) { + mp_float_t f0 = optimize_python_call(type, fun, x[i], fargs0, nparams); + mp_float_t f1 = optimize_python_call(type, fun, x[i], fargs1, nparams); + jacobi[i*nparamp+p] = (f1 - f0) / da; + grad[p] += (f0 - y[i]) * jacobi[i*nparamp+p]; + } + fargs1[p+1] = fargs0[p+1]; // set back to the original value + } +} + +static void optimize_delta(mp_float_t *jacobi, mp_float_t *grad, uint16_t len, uint8_t nparams, mp_float_t lambda) { + // +} + +mp_obj_t optimize_curve_fit(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + // Levenberg-Marquardt non-linear fit + // The implementation follows the introductory discussion in Mark Tanstrum's paper, https://arxiv.org/abs/1201.5885 + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_p0, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_xatol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&xtolerance)} }, + { MP_QSTR_fatol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&xtolerance)} }, + { MP_QSTR_maxiter, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t fun = args[0].u_obj; + const mp_obj_type_t *type = mp_obj_get_type(fun); + if(type->call == NULL) { + mp_raise_TypeError(translate("first argument must be a function")); + } + + mp_obj_t x_obj = args[1].u_obj; + mp_obj_t y_obj = args[2].u_obj; + mp_obj_t p0_obj = args[3].u_obj; + if(!ndarray_object_is_array_like(x_obj) || !ndarray_object_is_array_like(y_obj)) { + mp_raise_TypeError(translate("data must be iterable")); + } + if(!ndarray_object_is_nditerable(p0_obj)) { + mp_raise_TypeError(translate("initial values must be iterable")); + } + size_t len = (size_t)mp_obj_get_int(mp_obj_len_maybe(x_obj)); + uint8_t lenp = (uint8_t)mp_obj_get_int(mp_obj_len_maybe(p0_obj)); + if(len != (uint16_t)mp_obj_get_int(mp_obj_len_maybe(y_obj))) { + mp_raise_ValueError(translate("data must be of equal length")); + } + + mp_float_t *x = m_new(mp_float_t, len); + fill_array_iterable(x, x_obj); + mp_float_t *y = m_new(mp_float_t, len); + fill_array_iterable(y, y_obj); + mp_float_t *p0 = m_new(mp_float_t, lenp); + fill_array_iterable(p0, p0_obj); + mp_float_t *grad = m_new(mp_float_t, len); + mp_float_t *jacobi = m_new(mp_float_t, len*len); + mp_obj_t *fargs = m_new(mp_obj_t, lenp+1); + + m_del(mp_float_t, p0, lenp); + // parameters controlling convergence conditions + //mp_float_t xatol = mp_obj_get_float(args[2].u_obj); + //mp_float_t fatol = mp_obj_get_float(args[3].u_obj); + + // this has finite binary representation; we will multiply/divide by 4 + //mp_float_t lambda = 0.0078125; + + //linalg_invert_matrix(mp_float_t *data, size_t N) + + m_del(mp_float_t, x, len); + m_del(mp_float_t, y, len); + m_del(mp_float_t, grad, len); + m_del(mp_float_t, jacobi, len*len); + m_del(mp_obj_t, fargs, lenp+1); + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(optimize_curve_fit_obj, 2, optimize_curve_fit); +#endif + +#if ULAB_SCIPY_OPTIMIZE_HAS_NEWTON +//| def newton( +//| fun: Callable[[float], float], +//| x0: float, +//| *, +//| xtol: float = 2.4e-7, +//| rtol: float = 0.0, +//| maxiter: int = 50 +//| ) -> float: +//| """ +//| :param callable f: The function to bisect +//| :param float x0: The initial x value +//| :param float xtol: The absolute tolerance value +//| :param float rtol: The relative tolerance value +//| :param float maxiter: The maximum number of iterations to perform +//| +//| Find a solution (zero) of the function ``f(x)`` using Newton's Method. +//| The result is accurate to within ``xtol * rtol * |f(x)|`` unless more than +//| ``maxiter`` steps are requried.""" +//| ... +//| + +static mp_obj_t optimize_newton(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + // this is actually the secant method, as the first derivative of the function + // is not accepted as an argument. The function whose root we want to solve for + // must depend on a single variable without parameters, i.e., f(x) + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_tol, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_PTR(&xtolerance) } }, + { MP_QSTR_rtol, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_PTR(&rtolerance) } }, + { MP_QSTR_maxiter, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 50 } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t fun = args[0].u_obj; + const mp_obj_type_t *type = mp_obj_get_type(fun); + if(type->call == NULL) { + mp_raise_TypeError(translate("first argument must be a function")); + } + mp_float_t x = mp_obj_get_float(args[1].u_obj); + mp_float_t tol = mp_obj_get_float(args[2].u_obj); + mp_float_t rtol = mp_obj_get_float(args[3].u_obj); + mp_float_t dx, df, fx; + dx = x > MICROPY_FLOAT_CONST(0.0) ? OPTIMIZE_EPS * x : -OPTIMIZE_EPS * x; + mp_obj_t *fargs = m_new(mp_obj_t, 1); + if(args[4].u_int <= 0) { + mp_raise_ValueError(translate("maxiter must be > 0")); + } + for(uint16_t i=0; i < args[4].u_int; i++) { + fx = optimize_python_call(type, fun, x, fargs, 0); + df = (optimize_python_call(type, fun, x + dx, fargs, 0) - fx) / dx; + dx = fx / df; + x -= dx; + if(MICROPY_FLOAT_C_FUN(fabs)(dx) < (tol + rtol * MICROPY_FLOAT_C_FUN(fabs)(x))) break; + } + return mp_obj_new_float(x); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(optimize_newton_obj, 2, optimize_newton); +#endif + +static const mp_rom_map_elem_t ulab_scipy_optimize_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_optimize) }, + #if ULAB_SCIPY_OPTIMIZE_HAS_BISECT + { MP_OBJ_NEW_QSTR(MP_QSTR_bisect), (mp_obj_t)&optimize_bisect_obj }, + #endif + #if ULAB_SCIPY_OPTIMIZE_HAS_CURVE_FIT + { MP_OBJ_NEW_QSTR(MP_QSTR_curve_fit), (mp_obj_t)&optimize_curve_fit_obj }, + #endif + #if ULAB_SCIPY_OPTIMIZE_HAS_FMIN + { MP_OBJ_NEW_QSTR(MP_QSTR_fmin), (mp_obj_t)&optimize_fmin_obj }, + #endif + #if ULAB_SCIPY_OPTIMIZE_HAS_NEWTON + { MP_OBJ_NEW_QSTR(MP_QSTR_newton), (mp_obj_t)&optimize_newton_obj }, + #endif +}; + +static MP_DEFINE_CONST_DICT(mp_module_ulab_scipy_optimize_globals, ulab_scipy_optimize_globals_table); + +mp_obj_module_t ulab_scipy_optimize_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_scipy_optimize_globals, +}; diff --git a/python/port/mod/ulab/scipy/optimize/optimize.h b/python/port/mod/ulab/scipy/optimize/optimize.h new file mode 100644 index 00000000000..5d956df49ed --- /dev/null +++ b/python/port/mod/ulab/scipy/optimize/optimize.h @@ -0,0 +1,41 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös + * +*/ + +#ifndef _SCIPY_OPTIMIZE_ +#define _SCIPY_OPTIMIZE_ + +#include "../../ulab_tools.h" + +#ifndef OPTIMIZE_EPSILON +#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT +#define OPTIMIZE_EPSILON MICROPY_FLOAT_CONST(1.2e-7) +#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE +#define OPTIMIZE_EPSILON MICROPY_FLOAT_CONST(2.3e-16) +#endif +#endif + +#define OPTIMIZE_EPS MICROPY_FLOAT_CONST(1.0e-4) +#define OPTIMIZE_NONZDELTA MICROPY_FLOAT_CONST(0.05) +#define OPTIMIZE_ZDELTA MICROPY_FLOAT_CONST(0.00025) +#define OPTIMIZE_ALPHA MICROPY_FLOAT_CONST(1.0) +#define OPTIMIZE_BETA MICROPY_FLOAT_CONST(2.0) +#define OPTIMIZE_GAMMA MICROPY_FLOAT_CONST(0.5) +#define OPTIMIZE_DELTA MICROPY_FLOAT_CONST(0.5) + +extern mp_obj_module_t ulab_scipy_optimize_module; + +MP_DECLARE_CONST_FUN_OBJ_KW(optimize_bisect_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(optimize_curve_fit_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(optimize_fmin_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(optimize_newton_obj); + +#endif /* _SCIPY_OPTIMIZE_ */ diff --git a/python/port/mod/ulab/scipy/scipy.c b/python/port/mod/ulab/scipy/scipy.c new file mode 100644 index 00000000000..3e3a8280f50 --- /dev/null +++ b/python/port/mod/ulab/scipy/scipy.c @@ -0,0 +1,51 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020 Scott Shawcroft for Adafruit Industries + * 2020-2021 Zoltán Vörös + * 2020 Taku Fukada +*/ + +#include +#include + +#include "../ulab.h" +#include "optimize/optimize.h" +#include "signal/signal.h" +#include "special/special.h" +#include "linalg/linalg.h" + +#if ULAB_HAS_SCIPY + +//| """Compatibility layer for scipy""" +//| + +static const mp_rom_map_elem_t ulab_scipy_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_scipy) }, + #if ULAB_SCIPY_HAS_LINALG_MODULE + { MP_ROM_QSTR(MP_QSTR_linalg), MP_ROM_PTR(&ulab_scipy_linalg_module) }, + #endif + #if ULAB_SCIPY_HAS_OPTIMIZE_MODULE + { MP_ROM_QSTR(MP_QSTR_optimize), MP_ROM_PTR(&ulab_scipy_optimize_module) }, + #endif + #if ULAB_SCIPY_HAS_SIGNAL_MODULE + { MP_ROM_QSTR(MP_QSTR_signal), MP_ROM_PTR(&ulab_scipy_signal_module) }, + #endif + #if ULAB_SCIPY_HAS_SPECIAL_MODULE + { MP_ROM_QSTR(MP_QSTR_special), MP_ROM_PTR(&ulab_scipy_special_module) }, + #endif +}; + +static MP_DEFINE_CONST_DICT(mp_module_ulab_scipy_globals, ulab_scipy_globals_table); + +mp_obj_module_t ulab_scipy_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_scipy_globals, +}; +#endif diff --git a/python/port/mod/ulab/scipy/scipy.h b/python/port/mod/ulab/scipy/scipy.h new file mode 100644 index 00000000000..ff287a5a15f --- /dev/null +++ b/python/port/mod/ulab/scipy/scipy.h @@ -0,0 +1,21 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös + * +*/ + +#ifndef _SCIPY_ +#define _SCIPY_ + +#include "../ulab.h" +#include "../ndarray.h" + +extern mp_obj_module_t ulab_scipy_module; + +#endif /* _SCIPY_ */ diff --git a/python/port/mod/ulab/scipy/signal/signal.c b/python/port/mod/ulab/scipy/signal/signal.c new file mode 100644 index 00000000000..0dbafd2c181 --- /dev/null +++ b/python/port/mod/ulab/scipy/signal/signal.c @@ -0,0 +1,153 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020 Scott Shawcroft for Adafruit Industries + * 2020-2021 Zoltán Vörös + * 2020 Taku Fukada +*/ + +#include +#include +#include + +#include "../../ulab.h" +#include "../../ndarray.h" +#include "../../numpy/fft/fft_tools.h" + +#if ULAB_SCIPY_SIGNAL_HAS_SPECTROGRAM +//| def spectrogram(r: ulab.ndarray) -> ulab.ndarray: +//| """ +//| :param ulab.ndarray r: A 1-dimension array of values whose size is a power of 2 +//| +//| Computes the spectrum of the input signal. This is the absolute value of the (complex-valued) fft of the signal. +//| This function is similar to scipy's ``scipy.signal.spectrogram``.""" +//| ... +//| + +mp_obj_t signal_spectrogram(size_t n_args, const mp_obj_t *args) { + if(n_args == 2) { + return fft_fft_ifft_spectrogram(n_args, args[0], args[1], FFT_SPECTROGRAM); + } else { + return fft_fft_ifft_spectrogram(n_args, args[0], mp_const_none, FFT_SPECTROGRAM); + } +} + +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(signal_spectrogram_obj, 1, 2, signal_spectrogram); +#endif /* ULAB_SCIPY_SIGNAL_HAS_SPECTROGRAM */ + +#if ULAB_SCIPY_SIGNAL_HAS_SOSFILT +static void signal_sosfilt_array(mp_float_t *x, const mp_float_t *coeffs, mp_float_t *zf, const size_t len) { + for(size_t i=0; i < len; i++) { + mp_float_t xn = *x; + *x = coeffs[0] * xn + zf[0]; + zf[0] = zf[1] + coeffs[1] * xn - coeffs[4] * *x; + zf[1] = coeffs[2] * xn - coeffs[5] * *x; + x++; + } + x -= len; +} + +mp_obj_t signal_sosfilt(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_sos, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + { MP_QSTR_zi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!ndarray_object_is_array_like(args[0].u_obj) || !ndarray_object_is_array_like(args[1].u_obj)) { + mp_raise_TypeError(translate("sosfilt requires iterable arguments")); + } + size_t lenx = (size_t)mp_obj_get_int(mp_obj_len_maybe(args[1].u_obj)); + ndarray_obj_t *y = ndarray_new_linear_array(lenx, NDARRAY_FLOAT); + mp_float_t *yarray = (mp_float_t *)y->array; + mp_float_t coeffs[6]; + if(mp_obj_is_type(args[1].u_obj, &ulab_ndarray_type)) { + ndarray_obj_t *inarray = MP_OBJ_TO_PTR(args[1].u_obj); + #if ULAB_MAX_DIMS > 1 + if(inarray->ndim > 1) { + mp_raise_ValueError(translate("input must be one-dimensional")); + } + #endif + uint8_t *iarray = (uint8_t *)inarray->array; + for(size_t i=0; i < lenx; i++) { + *yarray++ = ndarray_get_float_value(iarray, inarray->dtype); + iarray += inarray->strides[ULAB_MAX_DIMS - 1]; + } + yarray -= lenx; + } else { + fill_array_iterable(yarray, args[1].u_obj); + } + + mp_obj_iter_buf_t iter_buf; + mp_obj_t item, iterable = mp_getiter(args[0].u_obj, &iter_buf); + size_t lensos = (size_t)mp_obj_get_int(mp_obj_len_maybe(args[0].u_obj)); + + size_t *shape = ndarray_shape_vector(0, 0, lensos, 2); + ndarray_obj_t *zf = ndarray_new_dense_ndarray(2, shape, NDARRAY_FLOAT); + mp_float_t *zf_array = (mp_float_t *)zf->array; + + if(args[2].u_obj != mp_const_none) { + if(!mp_obj_is_type(args[2].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("zi must be an ndarray")); + } else { + ndarray_obj_t *zi = MP_OBJ_TO_PTR(args[2].u_obj); + if((zi->shape[ULAB_MAX_DIMS - 1] != lensos) || (zi->shape[ULAB_MAX_DIMS - 1] != 2)) { + mp_raise_ValueError(translate("zi must be of shape (n_section, 2)")); + } + if(zi->dtype != NDARRAY_FLOAT) { + mp_raise_ValueError(translate("zi must be of float type")); + } + // TODO: this won't work with sparse arrays + memcpy(zf_array, zi->array, 2*lensos*sizeof(mp_float_t)); + } + } + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + if(mp_obj_get_int(mp_obj_len_maybe(item)) != 6) { + mp_raise_ValueError(translate("sos array must be of shape (n_section, 6)")); + } else { + fill_array_iterable(coeffs, item); + if(coeffs[3] != MICROPY_FLOAT_CONST(1.0)) { + mp_raise_ValueError(translate("sos[:, 3] should be all ones")); + } + signal_sosfilt_array(yarray, coeffs, zf_array, lenx); + zf_array += 2; + } + } + if(args[2].u_obj == mp_const_none) { + return MP_OBJ_FROM_PTR(y); + } else { + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + tuple->items[0] = MP_OBJ_FROM_PTR(y); + tuple->items[1] = MP_OBJ_FROM_PTR(zf); + return tuple; + } +} + +MP_DEFINE_CONST_FUN_OBJ_KW(signal_sosfilt_obj, 2, signal_sosfilt); +#endif /* ULAB_SCIPY_SIGNAL_HAS_SOSFILT */ + +static const mp_rom_map_elem_t ulab_scipy_signal_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_signal) }, + #if ULAB_SCIPY_SIGNAL_HAS_SPECTROGRAM + { MP_OBJ_NEW_QSTR(MP_QSTR_spectrogram), (mp_obj_t)&signal_spectrogram_obj }, + #endif + #if ULAB_SCIPY_SIGNAL_HAS_SOSFILT + { MP_OBJ_NEW_QSTR(MP_QSTR_sosfilt), (mp_obj_t)&signal_sosfilt_obj }, + #endif +}; + +static MP_DEFINE_CONST_DICT(mp_module_ulab_scipy_signal_globals, ulab_scipy_signal_globals_table); + +mp_obj_module_t ulab_scipy_signal_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_scipy_signal_globals, +}; diff --git a/python/port/mod/ulab/scipy/signal/signal.h b/python/port/mod/ulab/scipy/signal/signal.h new file mode 100644 index 00000000000..d33220e6299 --- /dev/null +++ b/python/port/mod/ulab/scipy/signal/signal.h @@ -0,0 +1,24 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös + * +*/ + +#ifndef _SCIPY_SIGNAL_ +#define _SCIPY_SIGNAL_ + +#include "../../ulab.h" +#include "../../ndarray.h" + +extern mp_obj_module_t ulab_scipy_signal_module; + +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(signal_spectrogram_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(signal_sosfilt_obj); + +#endif /* _SCIPY_SIGNAL_ */ diff --git a/python/port/mod/ulab/scipy/special/special.c b/python/port/mod/ulab/scipy/special/special.c new file mode 100644 index 00000000000..bd4cf87c467 --- /dev/null +++ b/python/port/mod/ulab/scipy/special/special.c @@ -0,0 +1,42 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020 Scott Shawcroft for Adafruit Industries + * 2020-2021 Zoltán Vörös + * 2020 Taku Fukada +*/ + +#include +#include + +#include "../../ulab.h" +#include "../../numpy/vector/vector.h" + +static const mp_rom_map_elem_t ulab_scipy_special_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_special) }, + #if ULAB_SCIPY_SPECIAL_HAS_ERF + { MP_OBJ_NEW_QSTR(MP_QSTR_erf), (mp_obj_t)&vectorise_erf_obj }, + #endif + #if ULAB_SCIPY_SPECIAL_HAS_ERFC + { MP_OBJ_NEW_QSTR(MP_QSTR_erfc), (mp_obj_t)&vectorise_erfc_obj }, + #endif + #if ULAB_SCIPY_SPECIAL_HAS_GAMMA + { MP_OBJ_NEW_QSTR(MP_QSTR_gamma), (mp_obj_t)&vectorise_gamma_obj }, + #endif + #if ULAB_SCIPY_SPECIAL_HAS_GAMMALN + { MP_OBJ_NEW_QSTR(MP_QSTR_gammaln), (mp_obj_t)&vectorise_lgamma_obj }, + #endif +}; + +static MP_DEFINE_CONST_DICT(mp_module_ulab_scipy_special_globals, ulab_scipy_special_globals_table); + +mp_obj_module_t ulab_scipy_special_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_scipy_special_globals, +}; diff --git a/python/port/mod/ulab/scipy/special/special.h b/python/port/mod/ulab/scipy/special/special.h new file mode 100644 index 00000000000..ca0bac58dd1 --- /dev/null +++ b/python/port/mod/ulab/scipy/special/special.h @@ -0,0 +1,21 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös + * +*/ + +#ifndef _SCIPY_SPECIAL_ +#define _SCIPY_SPECIAL_ + +#include "../../ulab.h" +#include "../../ndarray.h" + +extern mp_obj_module_t ulab_scipy_special_module; + +#endif /* _SCIPY_SPECIAL_ */ diff --git a/python/port/mod/ulab/ulab.c b/python/port/mod/ulab/ulab.c new file mode 100644 index 00000000000..d7a563c855f --- /dev/null +++ b/python/port/mod/ulab/ulab.c @@ -0,0 +1,162 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Jeff Epler for Adafruit Industries +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ulab.h" +#include "ulab_create.h" +#include "ndarray.h" +#include "ndarray_properties.h" + +#include "numpy/numpy.h" +#include "scipy/scipy.h" +#include "numpy/fft/fft.h" +#include "numpy/linalg/linalg.h" +// TODO: we should get rid of this; array.sort depends on it +#include "numpy/numerical/numerical.h" + +#include "user/user.h" +#include "utils/utils.h" + +#define ULAB_VERSION 3.1.0 +#define xstr(s) str(s) +#define str(s) #s +#define ULAB_VERSION_STRING xstr(ULAB_VERSION) xstr(-) xstr(ULAB_MAX_DIMS) xstr(D) + +STATIC MP_DEFINE_STR_OBJ(ulab_version_obj, ULAB_VERSION_STRING); + + +STATIC const mp_rom_map_elem_t ulab_ndarray_locals_dict_table[] = { + #if ULAB_MAX_DIMS > 1 + #if NDARRAY_HAS_RESHAPE + { MP_ROM_QSTR(MP_QSTR_reshape), MP_ROM_PTR(&ndarray_reshape_obj) }, + #endif + #if NDARRAY_HAS_TRANSPOSE + { MP_ROM_QSTR(MP_QSTR_transpose), MP_ROM_PTR(&ndarray_transpose_obj) }, + #endif + #endif + #if NDARRAY_HAS_BYTESWAP + { MP_ROM_QSTR(MP_QSTR_byteswap), MP_ROM_PTR(&ndarray_byteswap_obj) }, + #endif + #if NDARRAY_HAS_COPY + { MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&ndarray_copy_obj) }, + #endif + #if NDARRAY_HAS_FLATTEN + { MP_ROM_QSTR(MP_QSTR_flatten), MP_ROM_PTR(&ndarray_flatten_obj) }, + #endif + #if NDARRAY_HAS_TOBYTES + { MP_ROM_QSTR(MP_QSTR_tobytes), MP_ROM_PTR(&ndarray_tobytes_obj) }, + #endif + #if NDARRAY_HAS_SORT + { MP_ROM_QSTR(MP_QSTR_sort), MP_ROM_PTR(&numerical_sort_inplace_obj) }, + #endif + #ifdef CIRCUITPY + #if NDARRAY_HAS_DTYPE + { MP_ROM_QSTR(MP_QSTR_dtype), MP_ROM_PTR(&ndarray_dtype_obj) }, + #endif + #if NDARRAY_HAS_ITEMSIZE + { MP_ROM_QSTR(MP_QSTR_itemsize), MP_ROM_PTR(&ndarray_itemsize_obj) }, + #endif + #if NDARRAY_HAS_SHAPE + { MP_ROM_QSTR(MP_QSTR_shape), MP_ROM_PTR(&ndarray_shape_obj) }, + #endif + #if NDARRAY_HAS_SIZE + { MP_ROM_QSTR(MP_QSTR_size), MP_ROM_PTR(&ndarray_size_obj) }, + #endif + #if NDARRAY_HAS_STRIDES + { MP_ROM_QSTR(MP_QSTR_strides), MP_ROM_PTR(&ndarray_strides_obj) }, + #endif + #endif /* CIRCUITPY */ +}; + +STATIC MP_DEFINE_CONST_DICT(ulab_ndarray_locals_dict, ulab_ndarray_locals_dict_table); + +const mp_obj_type_t ulab_ndarray_type = { + { &mp_type_type }, + #if defined(MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE) && defined(MP_TYPE_FLAG_EQ_HAS_NEQ_TEST) + .flags = MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE | MP_TYPE_FLAG_EQ_HAS_NEQ_TEST, + #endif + .name = MP_QSTR_ndarray, + .print = ndarray_print, + .make_new = ndarray_make_new, + #if NDARRAY_IS_SLICEABLE + .subscr = ndarray_subscr, + #endif + #if NDARRAY_IS_ITERABLE + .getiter = ndarray_getiter, + #endif + #if NDARRAY_HAS_UNARY_OPS + .unary_op = ndarray_unary_op, + #endif + #if NDARRAY_HAS_BINARY_OPS + .binary_op = ndarray_binary_op, + #endif + #ifndef CIRCUITPY + .attr = ndarray_properties_attr, + #endif + .buffer_p = { .get_buffer = ndarray_get_buffer, }, + .locals_dict = (mp_obj_dict_t*)&ulab_ndarray_locals_dict, +}; + +#if ULAB_HAS_DTYPE_OBJECT +const mp_obj_type_t ulab_dtype_type = { + { &mp_type_type }, + .name = MP_QSTR_dtype, + .print = ndarray_dtype_print, + .make_new = ndarray_dtype_make_new, +}; +#endif + +STATIC const mp_map_elem_t ulab_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_ulab) }, + { MP_ROM_QSTR(MP_QSTR___version__), MP_ROM_PTR(&ulab_version_obj) }, + #if ULAB_HAS_DTYPE_OBJECT + { MP_OBJ_NEW_QSTR(MP_QSTR_dtype), (mp_obj_t)&ulab_dtype_type }, + #else + #if NDARRAY_HAS_DTYPE + { MP_OBJ_NEW_QSTR(MP_QSTR_dtype), (mp_obj_t)&ndarray_dtype_obj }, + #endif /* NDARRAY_HAS_DTYPE */ + #endif /* ULAB_HAS_DTYPE_OBJECT */ + { MP_ROM_QSTR(MP_QSTR_numpy), MP_ROM_PTR(&ulab_numpy_module) }, + #if ULAB_HAS_SCIPY + { MP_ROM_QSTR(MP_QSTR_scipy), MP_ROM_PTR(&ulab_scipy_module) }, + #endif + #if ULAB_HAS_USER_MODULE + { MP_ROM_QSTR(MP_QSTR_user), MP_ROM_PTR(&ulab_user_module) }, + #endif + #if ULAB_HAS_UTILS_MODULE + { MP_ROM_QSTR(MP_QSTR_utils), MP_ROM_PTR(&ulab_utils_module) }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT ( + mp_module_ulab_globals, + ulab_globals_table +); + +#ifdef OPENMV +const struct _mp_obj_module_t ulab_user_cmodule = { +#else +const mp_obj_module_t ulab_user_cmodule = { +#endif + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_ulab, ulab_user_cmodule, MODULE_ULAB_ENABLED); diff --git a/python/port/mod/ulab/ulab.h b/python/port/mod/ulab/ulab.h new file mode 100644 index 00000000000..e38310020c0 --- /dev/null +++ b/python/port/mod/ulab/ulab.h @@ -0,0 +1,658 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#ifndef __ULAB__ +#define __ULAB__ + + + +// The pre-processor constants in this file determine how ulab behaves: +// +// - how many dimensions ulab can handle +// - which functions are included in the compiled firmware +// - whether the python syntax is numpy-like, or modular +// - whether arrays can be sliced and iterated over +// - which binary/unary operators are supported +// +// A considerable amount of flash space can be saved by removing (setting +// the corresponding constants to 0) the unnecessary functions and features. + +// Values defined here can be overridden by your own config file as +// make -DULAB_CONFIG_FILE="my_ulab_config.h" +#if defined(ULAB_CONFIG_FILE) +#include ULAB_CONFIG_FILE +#endif + + +// Determines, whether scipy is defined in ulab. The sub-modules and functions +// of scipy have to be defined separately +#ifndef ULAB_HAS_SCIPY +#define ULAB_HAS_SCIPY (0) +#endif + +// The maximum number of dimensions the firmware should be able to support +// Possible values lie between 1, and 4, inclusive +#define ULAB_MAX_DIMS 2 + +// By setting this constant to 1, iteration over array dimensions will be implemented +// as a function (ndarray_rewind_array), instead of writing out the loops in macros +// This reduces firmware size at the expense of speed +#define ULAB_HAS_FUNCTION_ITERATOR (0) + +// If NDARRAY_IS_ITERABLE is 1, the ndarray object defines its own iterator function +// This option saves approx. 250 bytes of flash space +#ifndef NDARRAY_IS_ITERABLE +#define NDARRAY_IS_ITERABLE (1) +#endif + +// Slicing can be switched off by setting this variable to 0 +#ifndef NDARRAY_IS_SLICEABLE +#define NDARRAY_IS_SLICEABLE (1) +#endif + +// The default threshold for pretty printing. These variables can be overwritten +// at run-time via the set_printoptions() function +#ifndef ULAB_HAS_PRINTOPTIONS +#define ULAB_HAS_PRINTOPTIONS (1) +#endif +#define NDARRAY_PRINT_THRESHOLD 10 +#define NDARRAY_PRINT_EDGEITEMS 3 + +// determines, whether the dtype is an object, or simply a character +// the object implementation is numpythonic, but requires more space +#ifndef ULAB_HAS_DTYPE_OBJECT +#define ULAB_HAS_DTYPE_OBJECT (0) +#endif + +// the ndarray binary operators +#ifndef NDARRAY_HAS_BINARY_OPS +#define NDARRAY_HAS_BINARY_OPS (1) +#endif + +// Firmware size can be reduced at the expense of speed by using function +// pointers in iterations. For each operator, he function pointer saves around +// 2 kB in the two-dimensional case, and around 4 kB in the four-dimensional case. + +#ifndef NDARRAY_BINARY_USES_FUN_POINTER +#define NDARRAY_BINARY_USES_FUN_POINTER (0) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_ADD +#define NDARRAY_HAS_BINARY_OP_ADD (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_EQUAL +#define NDARRAY_HAS_BINARY_OP_EQUAL (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_LESS +#define NDARRAY_HAS_BINARY_OP_LESS (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_LESS_EQUAL +#define NDARRAY_HAS_BINARY_OP_LESS_EQUAL (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_MORE +#define NDARRAY_HAS_BINARY_OP_MORE (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_MORE_EQUAL +#define NDARRAY_HAS_BINARY_OP_MORE_EQUAL (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_MULTIPLY +#define NDARRAY_HAS_BINARY_OP_MULTIPLY (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_NOT_EQUAL +#define NDARRAY_HAS_BINARY_OP_NOT_EQUAL (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_POWER +#define NDARRAY_HAS_BINARY_OP_POWER (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_SUBTRACT +#define NDARRAY_HAS_BINARY_OP_SUBTRACT (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_TRUE_DIVIDE +#define NDARRAY_HAS_BINARY_OP_TRUE_DIVIDE (1) +#endif + +#ifndef NDARRAY_HAS_INPLACE_OPS +#define NDARRAY_HAS_INPLACE_OPS (1) +#endif + +#ifndef NDARRAY_HAS_INPLACE_ADD +#define NDARRAY_HAS_INPLACE_ADD (1) +#endif + +#ifndef NDARRAY_HAS_INPLACE_MULTIPLY +#define NDARRAY_HAS_INPLACE_MULTIPLY (1) +#endif + +#ifndef NDARRAY_HAS_INPLACE_POWER +#define NDARRAY_HAS_INPLACE_POWER (1) +#endif + +#ifndef NDARRAY_HAS_INPLACE_SUBTRACT +#define NDARRAY_HAS_INPLACE_SUBTRACT (1) +#endif + +#ifndef NDARRAY_HAS_INPLACE_TRUE_DIVIDE +#define NDARRAY_HAS_INPLACE_TRUE_DIVIDE (1) +#endif + +// the ndarray unary operators +#ifndef NDARRAY_HAS_UNARY_OPS +#define NDARRAY_HAS_UNARY_OPS (1) +#endif + +#ifndef NDARRAY_HAS_UNARY_OP_ABS +#define NDARRAY_HAS_UNARY_OP_ABS (1) +#endif + +#ifndef NDARRAY_HAS_UNARY_OP_INVERT +#define NDARRAY_HAS_UNARY_OP_INVERT (1) +#endif + +#ifndef NDARRAY_HAS_UNARY_OP_LEN +#define NDARRAY_HAS_UNARY_OP_LEN (1) +#endif + +#ifndef NDARRAY_HAS_UNARY_OP_NEGATIVE +#define NDARRAY_HAS_UNARY_OP_NEGATIVE (1) +#endif + +#ifndef NDARRAY_HAS_UNARY_OP_POSITIVE +#define NDARRAY_HAS_UNARY_OP_POSITIVE (1) +#endif + + +// determines, which ndarray methods are available +#ifndef NDARRAY_HAS_BYTESWAP +#define NDARRAY_HAS_BYTESWAP (1) +#endif + +#ifndef NDARRAY_HAS_COPY +#define NDARRAY_HAS_COPY (1) +#endif + +#ifndef NDARRAY_HAS_DTYPE +#define NDARRAY_HAS_DTYPE (1) +#endif + +#ifndef NDARRAY_HAS_FLATTEN +#define NDARRAY_HAS_FLATTEN (1) +#endif + +#ifndef NDARRAY_HAS_ITEMSIZE +#define NDARRAY_HAS_ITEMSIZE (1) +#endif + +#ifndef NDARRAY_HAS_RESHAPE +#define NDARRAY_HAS_RESHAPE (1) +#endif + +#ifndef NDARRAY_HAS_SHAPE +#define NDARRAY_HAS_SHAPE (1) +#endif + +#ifndef NDARRAY_HAS_SIZE +#define NDARRAY_HAS_SIZE (1) +#endif + +#ifndef NDARRAY_HAS_SORT +#define NDARRAY_HAS_SORT (1) +#endif + +#ifndef NDARRAY_HAS_STRIDES +#define NDARRAY_HAS_STRIDES (1) +#endif + +#ifndef NDARRAY_HAS_TOBYTES +#define NDARRAY_HAS_TOBYTES (0) +#endif + +#ifndef NDARRAY_HAS_TRANSPOSE +#define NDARRAY_HAS_TRANSPOSE (1) +#endif + +// Firmware size can be reduced at the expense of speed by using a function +// pointer in iterations. Setting ULAB_VECTORISE_USES_FUNCPOINTER to 1 saves +// around 800 bytes in the four-dimensional case, and around 200 in two dimensions. +#ifndef ULAB_VECTORISE_USES_FUN_POINTER +#define ULAB_VECTORISE_USES_FUN_POINTER (1) +#endif + +// determines, whether e is defined in ulab.numpy itself +#ifndef ULAB_NUMPY_HAS_E +#define ULAB_NUMPY_HAS_E (1) +#endif + +// ulab defines infinite as a class constant in ulab.numpy +#ifndef ULAB_NUMPY_HAS_INF +#define ULAB_NUMPY_HAS_INF (1) +#endif + +// ulab defines NaN as a class constant in ulab.numpy +#ifndef ULAB_NUMPY_HAS_NAN +#define ULAB_NUMPY_HAS_NAN (1) +#endif + +// determines, whether pi is defined in ulab.numpy itself +#ifndef ULAB_NUMPY_HAS_PI +#define ULAB_NUMPY_HAS_PI (1) +#endif + +// determines, whether the ndinfo function is available +#ifndef ULAB_NUMPY_HAS_NDINFO +#define ULAB_NUMPY_HAS_NDINFO (1) +#endif + +// frombuffer adds 600 bytes to the firmware +#ifndef ULAB_NUMPY_HAS_FROMBUFFER +#define ULAB_NUMPY_HAS_FROMBUFFER (1) +#endif + +// functions that create an array +#ifndef ULAB_NUMPY_HAS_ARANGE +#define ULAB_NUMPY_HAS_ARANGE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_CONCATENATE +#define ULAB_NUMPY_HAS_CONCATENATE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_DIAG +#define ULAB_NUMPY_HAS_DIAG (1) +#endif + +#ifndef ULAB_NUMPY_HAS_EMPTY +#define ULAB_NUMPY_HAS_EMPTY (1) +#endif + +#ifndef ULAB_NUMPY_HAS_EYE +#define ULAB_NUMPY_HAS_EYE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_FULL +#define ULAB_NUMPY_HAS_FULL (1) +#endif + +#ifndef ULAB_NUMPY_HAS_LINSPACE +#define ULAB_NUMPY_HAS_LINSPACE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_LOGSPACE +#define ULAB_NUMPY_HAS_LOGSPACE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ONES +#define ULAB_NUMPY_HAS_ONES (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ZEROS +#define ULAB_NUMPY_HAS_ZEROS (1) +#endif + +// functions that compare arrays +#ifndef ULAB_NUMPY_HAS_CLIP +#define ULAB_NUMPY_HAS_CLIP (1) +#endif + +#ifndef ULAB_NUMPY_HAS_EQUAL +#define ULAB_NUMPY_HAS_EQUAL (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ISFINITE +#define ULAB_NUMPY_HAS_ISFINITE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ISINF +#define ULAB_NUMPY_HAS_ISINF (1) +#endif + +#ifndef ULAB_NUMPY_HAS_MAXIMUM +#define ULAB_NUMPY_HAS_MAXIMUM (1) +#endif + +#ifndef ULAB_NUMPY_HAS_MINIMUM +#define ULAB_NUMPY_HAS_MINIMUM (1) +#endif + +#ifndef ULAB_NUMPY_HAS_NOTEQUAL +#define ULAB_NUMPY_HAS_NOTEQUAL (1) +#endif + +#ifndef ULAB_NUMPY_HAS_WHERE +#define ULAB_NUMPY_HAS_WHERE (1) +#endif + +// the linalg module; functions of the linalg module still have +// to be defined separately +#ifndef ULAB_NUMPY_HAS_LINALG_MODULE +#define ULAB_NUMPY_HAS_LINALG_MODULE (1) +#endif + +#ifndef ULAB_LINALG_HAS_CHOLESKY +#define ULAB_LINALG_HAS_CHOLESKY (1) +#endif + +#ifndef ULAB_LINALG_HAS_DET +#define ULAB_LINALG_HAS_DET (1) +#endif + +#ifndef ULAB_LINALG_HAS_EIG +#define ULAB_LINALG_HAS_EIG (1) +#endif + +#ifndef ULAB_LINALG_HAS_INV +#define ULAB_LINALG_HAS_INV (1) +#endif + +#ifndef ULAB_LINALG_HAS_NORM +#define ULAB_LINALG_HAS_NORM (1) +#endif + +// the FFT module; functions of the fft module still have +// to be defined separately +#ifndef ULAB_NUMPY_HAS_FFT_MODULE +#define ULAB_NUMPY_HAS_FFT_MODULE (1) +#endif + +#ifndef ULAB_FFT_HAS_FFT +#define ULAB_FFT_HAS_FFT (1) +#endif + +#ifndef ULAB_FFT_HAS_IFFT +#define ULAB_FFT_HAS_IFFT (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ALL +#define ULAB_NUMPY_HAS_ALL (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ANY +#define ULAB_NUMPY_HAS_ANY (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ARGMINMAX +#define ULAB_NUMPY_HAS_ARGMINMAX (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ARGSORT +#define ULAB_NUMPY_HAS_ARGSORT (1) +#endif + +#ifndef ULAB_NUMPY_HAS_CONVOLVE +#define ULAB_NUMPY_HAS_CONVOLVE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_CROSS +#define ULAB_NUMPY_HAS_CROSS (1) +#endif + +#ifndef ULAB_NUMPY_HAS_DIFF +#define ULAB_NUMPY_HAS_DIFF (1) +#endif + +#ifndef ULAB_NUMPY_HAS_DOT +#define ULAB_NUMPY_HAS_DOT (1) +#endif + +#ifndef ULAB_NUMPY_HAS_FLIP +#define ULAB_NUMPY_HAS_FLIP (1) +#endif + +#ifndef ULAB_NUMPY_HAS_INTERP +#define ULAB_NUMPY_HAS_INTERP (1) +#endif + +#ifndef ULAB_NUMPY_HAS_MEAN +#define ULAB_NUMPY_HAS_MEAN (1) +#endif + +#ifndef ULAB_NUMPY_HAS_MEDIAN +#define ULAB_NUMPY_HAS_MEDIAN (1) +#endif + +#ifndef ULAB_NUMPY_HAS_MINMAX +#define ULAB_NUMPY_HAS_MINMAX (1) +#endif + +#ifndef ULAB_NUMPY_HAS_POLYFIT +#define ULAB_NUMPY_HAS_POLYFIT (1) +#endif + +#ifndef ULAB_NUMPY_HAS_POLYVAL +#define ULAB_NUMPY_HAS_POLYVAL (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ROLL +#define ULAB_NUMPY_HAS_ROLL (1) +#endif + +#ifndef ULAB_NUMPY_HAS_SORT +#define ULAB_NUMPY_HAS_SORT (1) +#endif + +#ifndef ULAB_NUMPY_HAS_STD +#define ULAB_NUMPY_HAS_STD (1) +#endif + +#ifndef ULAB_NUMPY_HAS_SUM +#define ULAB_NUMPY_HAS_SUM (1) +#endif + +#ifndef ULAB_NUMPY_HAS_TRACE +#define ULAB_NUMPY_HAS_TRACE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_TRAPZ +#define ULAB_NUMPY_HAS_TRAPZ (1) +#endif + +// vectorised versions of the functions of the math python module, with +// the exception of the functions listed in scipy.special +#ifndef ULAB_NUMPY_HAS_ACOS +#define ULAB_NUMPY_HAS_ACOS (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ACOSH +#define ULAB_NUMPY_HAS_ACOSH (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ARCTAN2 +#define ULAB_NUMPY_HAS_ARCTAN2 (1) +#endif + +#ifndef ULAB_NUMPY_HAS_AROUND +#define ULAB_NUMPY_HAS_AROUND (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ASIN +#define ULAB_NUMPY_HAS_ASIN (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ASINH +#define ULAB_NUMPY_HAS_ASINH (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ATAN +#define ULAB_NUMPY_HAS_ATAN (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ATANH +#define ULAB_NUMPY_HAS_ATANH (1) +#endif + +#ifndef ULAB_NUMPY_HAS_CEIL +#define ULAB_NUMPY_HAS_CEIL (1) +#endif + +#ifndef ULAB_NUMPY_HAS_COS +#define ULAB_NUMPY_HAS_COS (1) +#endif + +#ifndef ULAB_NUMPY_HAS_COSH +#define ULAB_NUMPY_HAS_COSH (1) +#endif + +#ifndef ULAB_NUMPY_HAS_DEGREES +#define ULAB_NUMPY_HAS_DEGREES (1) +#endif + +#ifndef ULAB_NUMPY_HAS_EXP +#define ULAB_NUMPY_HAS_EXP (1) +#endif + +#ifndef ULAB_NUMPY_HAS_EXPM1 +#define ULAB_NUMPY_HAS_EXPM1 (1) +#endif + +#ifndef ULAB_NUMPY_HAS_FLOOR +#define ULAB_NUMPY_HAS_FLOOR (1) +#endif + +#ifndef ULAB_NUMPY_HAS_LOG +#define ULAB_NUMPY_HAS_LOG (1) +#endif + +#ifndef ULAB_NUMPY_HAS_LOG10 +#define ULAB_NUMPY_HAS_LOG10 (1) +#endif + +#ifndef ULAB_NUMPY_HAS_LOG2 +#define ULAB_NUMPY_HAS_LOG2 (1) +#endif + +#ifndef ULAB_NUMPY_HAS_RADIANS +#define ULAB_NUMPY_HAS_RADIANS (1) +#endif + +#ifndef ULAB_NUMPY_HAS_SIN +#define ULAB_NUMPY_HAS_SIN (1) +#endif + +#ifndef ULAB_NUMPY_HAS_SINH +#define ULAB_NUMPY_HAS_SINH (1) +#endif + +#ifndef ULAB_NUMPY_HAS_SQRT +#define ULAB_NUMPY_HAS_SQRT (1) +#endif + +#ifndef ULAB_NUMPY_HAS_TAN +#define ULAB_NUMPY_HAS_TAN (1) +#endif + +#ifndef ULAB_NUMPY_HAS_TANH +#define ULAB_NUMPY_HAS_TANH (1) +#endif + +#ifndef ULAB_NUMPY_HAS_VECTORIZE +#define ULAB_NUMPY_HAS_VECTORIZE (1) +#endif + +#ifndef ULAB_SCIPY_HAS_LINALG_MODULE +#define ULAB_SCIPY_HAS_LINALG_MODULE (1) +#endif + +#ifndef ULAB_SCIPY_LINALG_HAS_CHO_SOLVE +#define ULAB_SCIPY_LINALG_HAS_CHO_SOLVE (1) +#endif + +#ifndef ULAB_SCIPY_LINALG_HAS_SOLVE_TRIANGULAR +#define ULAB_SCIPY_LINALG_HAS_SOLVE_TRIANGULAR (1) +#endif + +#ifndef ULAB_SCIPY_HAS_SIGNAL_MODULE +#define ULAB_SCIPY_HAS_SIGNAL_MODULE (1) +#endif + +#ifndef ULAB_SCIPY_SIGNAL_HAS_SPECTROGRAM +#define ULAB_SCIPY_SIGNAL_HAS_SPECTROGRAM (1) +#endif + +#ifndef ULAB_SCIPY_SIGNAL_HAS_SOSFILT +#define ULAB_SCIPY_SIGNAL_HAS_SOSFILT (1) +#endif + +#ifndef ULAB_SCIPY_HAS_OPTIMIZE_MODULE +#define ULAB_SCIPY_HAS_OPTIMIZE_MODULE (1) +#endif + +#ifndef ULAB_SCIPY_OPTIMIZE_HAS_BISECT +#define ULAB_SCIPY_OPTIMIZE_HAS_BISECT (1) +#endif + +#ifndef ULAB_SCIPY_OPTIMIZE_HAS_CURVE_FIT +#define ULAB_SCIPY_OPTIMIZE_HAS_CURVE_FIT (0) // not fully implemented +#endif + +#ifndef ULAB_SCIPY_OPTIMIZE_HAS_FMIN +#define ULAB_SCIPY_OPTIMIZE_HAS_FMIN (1) +#endif + +#ifndef ULAB_SCIPY_OPTIMIZE_HAS_NEWTON +#define ULAB_SCIPY_OPTIMIZE_HAS_NEWTON (1) +#endif + +#ifndef ULAB_SCIPY_HAS_SPECIAL_MODULE +#define ULAB_SCIPY_HAS_SPECIAL_MODULE (1) +#endif + +#ifndef ULAB_SCIPY_SPECIAL_HAS_ERF +#define ULAB_SCIPY_SPECIAL_HAS_ERF (1) +#endif + +#ifndef ULAB_SCIPY_SPECIAL_HAS_ERFC +#define ULAB_SCIPY_SPECIAL_HAS_ERFC (1) +#endif + +#ifndef ULAB_SCIPY_SPECIAL_HAS_GAMMA +#define ULAB_SCIPY_SPECIAL_HAS_GAMMA (1) +#endif + +#ifndef ULAB_SCIPY_SPECIAL_HAS_GAMMALN +#define ULAB_SCIPY_SPECIAL_HAS_GAMMALN (1) +#endif + +// user-defined module; source of the module and +// its sub-modules should be placed in code/user/ +#ifndef ULAB_HAS_USER_MODULE +#define ULAB_HAS_USER_MODULE (0) +#endif + +#ifndef ULAB_HAS_UTILS_MODULE +#define ULAB_HAS_UTILS_MODULE (1) +#endif + +#ifndef ULAB_UTILS_HAS_FROM_INT16_BUFFER +#define ULAB_UTILS_HAS_FROM_INT16_BUFFER (1) +#endif + +#ifndef ULAB_UTILS_HAS_FROM_UINT16_BUFFER +#define ULAB_UTILS_HAS_FROM_UINT16_BUFFER (1) +#endif + +#ifndef ULAB_UTILS_HAS_FROM_INT32_BUFFER +#define ULAB_UTILS_HAS_FROM_INT32_BUFFER (1) +#endif + +#ifndef ULAB_UTILS_HAS_FROM_UINT32_BUFFER +#define ULAB_UTILS_HAS_FROM_UINT32_BUFFER (1) +#endif + +#endif diff --git a/python/port/mod/ulab/ulab_create.c b/python/port/mod/ulab/ulab_create.c new file mode 100644 index 00000000000..2cb8f23252f --- /dev/null +++ b/python/port/mod/ulab/ulab_create.c @@ -0,0 +1,706 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2019-2021 Zoltán Vörös + * 2020 Taku Fukada +*/ + +#include +#include +#include +#include +#include +#include + +#include "ulab.h" +#include "ulab_create.h" + +#if ULAB_NUMPY_HAS_ONES | ULAB_NUMPY_HAS_ZEROS | ULAB_NUMPY_HAS_FULL | ULAB_NUMPY_HAS_EMPTY +static mp_obj_t create_zeros_ones_full(mp_obj_t oshape, uint8_t dtype, mp_obj_t value) { + if(!mp_obj_is_int(oshape) && !mp_obj_is_type(oshape, &mp_type_tuple) && !mp_obj_is_type(oshape, &mp_type_list)) { + mp_raise_TypeError(translate("input argument must be an integer, a tuple, or a list")); + } + ndarray_obj_t *ndarray = NULL; + if(mp_obj_is_int(oshape)) { + size_t n = mp_obj_get_int(oshape); + ndarray = ndarray_new_linear_array(n, dtype); + } else if(mp_obj_is_type(oshape, &mp_type_tuple) || mp_obj_is_type(oshape, &mp_type_list)) { + uint8_t len = (uint8_t)mp_obj_get_int(mp_obj_len_maybe(oshape)); + if(len > ULAB_MAX_DIMS) { + mp_raise_TypeError(translate("too many dimensions")); + } + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + memset(shape, 0, ULAB_MAX_DIMS * sizeof(size_t)); + size_t i = 0; + mp_obj_iter_buf_t iter_buf; + mp_obj_t item, iterable = mp_getiter(oshape, &iter_buf); + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION){ + shape[ULAB_MAX_DIMS - len + i] = (size_t)mp_obj_get_int(item); + i++; + } + ndarray = ndarray_new_dense_ndarray(len, shape, dtype); + } + if(value != mp_const_none) { + if(dtype == NDARRAY_BOOL) { + dtype = NDARRAY_UINT8; + if(mp_obj_is_true(value)) { + value = mp_obj_new_int(1); + } else { + value = mp_obj_new_int(0); + } + } + for(size_t i=0; i < ndarray->len; i++) { + ndarray_set_value(dtype, ndarray->array, i, value); + } + } + // if zeros calls the function, we don't have to do anything + return MP_OBJ_FROM_PTR(ndarray); +} +#endif + +#if ULAB_NUMPY_HAS_ARANGE | ULAB_NUMPY_HAS_LINSPACE +static ndarray_obj_t *create_linspace_arange(mp_float_t start, mp_float_t step, size_t len, uint8_t dtype) { + mp_float_t value = start; + + ndarray_obj_t *ndarray = ndarray_new_linear_array(len, dtype); + if(ndarray->boolean == NDARRAY_BOOLEAN) { + uint8_t *array = (uint8_t *)ndarray->array; + for(size_t i=0; i < len; i++, value += step) { + *array++ = value == MICROPY_FLOAT_CONST(0.0) ? 0 : 1; + } + } else if(dtype == NDARRAY_UINT8) { + ARANGE_LOOP(uint8_t, ndarray, len, step); + } else if(dtype == NDARRAY_INT8) { + ARANGE_LOOP(int8_t, ndarray, len, step); + } else if(dtype == NDARRAY_UINT16) { + ARANGE_LOOP(uint16_t, ndarray, len, step); + } else if(dtype == NDARRAY_INT16) { + ARANGE_LOOP(int16_t, ndarray, len, step); + } else { + ARANGE_LOOP(mp_float_t, ndarray, len, step); + } + return ndarray; +} +#endif + +#if ULAB_NUMPY_HAS_ARANGE +//| @overload +//| def arange(stop: _float, step: _float = 1, *, dtype: _DType = ulab.float) -> ulab.ndarray: ... +//| @overload +//| def arange(start: _float, stop: _float, step: _float = 1, *, dtype: _DType = ulab.float) -> ulab.ndarray: +//| """ +//| .. param: start +//| First value in the array, optional, defaults to 0 +//| .. param: stop +//| Final value in the array +//| .. param: step +//| Difference between consecutive elements, optional, defaults to 1.0 +//| .. param: dtype +//| Type of values in the array +//| +//| Return a new 1-D array with elements ranging from ``start`` to ``stop``, with step size ``step``.""" +//| ... +//| + +mp_obj_t create_arange(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + uint8_t dtype = NDARRAY_FLOAT; + mp_float_t start, stop, step; + if(n_args == 1) { + start = 0.0; + stop = mp_obj_get_float(args[0].u_obj); + step = 1.0; + if(mp_obj_is_int(args[0].u_obj)) dtype = NDARRAY_INT16; + } else if(n_args == 2) { + start = mp_obj_get_float(args[0].u_obj); + stop = mp_obj_get_float(args[1].u_obj); + step = 1.0; + if(mp_obj_is_int(args[0].u_obj) && mp_obj_is_int(args[1].u_obj)) dtype = NDARRAY_INT16; + } else if(n_args == 3) { + start = mp_obj_get_float(args[0].u_obj); + stop = mp_obj_get_float(args[1].u_obj); + step = mp_obj_get_float(args[2].u_obj); + if(mp_obj_is_int(args[0].u_obj) && mp_obj_is_int(args[1].u_obj) && mp_obj_is_int(args[2].u_obj)) dtype = NDARRAY_INT16; + } else { + mp_raise_TypeError(translate("wrong number of arguments")); + } + if((MICROPY_FLOAT_C_FUN(fabs)(stop) > 32768) || (MICROPY_FLOAT_C_FUN(fabs)(start) > 32768) || (MICROPY_FLOAT_C_FUN(fabs)(step) > 32768)) { + dtype = NDARRAY_FLOAT; + } + if(args[3].u_obj != mp_const_none) { + dtype = (uint8_t)mp_obj_get_int(args[3].u_obj); + } + ndarray_obj_t *ndarray; + if((stop - start)/step < 0) { + ndarray = ndarray_new_linear_array(0, dtype); + } else { + size_t len = (size_t)(MICROPY_FLOAT_C_FUN(ceil)((stop - start)/step)); + ndarray = create_linspace_arange(start, step, len, dtype); + } + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_arange_obj, 1, create_arange); +#endif + +#if ULAB_NUMPY_HAS_CONCATENATE +//| def concatenate(arrays: Tuple[ulab.ndarray], *, axis: int = 0) -> ulab.ndarray: +//| """ +//| .. param: arrays +//| tuple of ndarrays +//| .. param: axis +//| axis along which the arrays will be joined +//| +//| Join a sequence of arrays along an existing axis.""" +//| ... +//| + +mp_obj_t create_concatenate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 0 } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_type(args[0].u_obj, &mp_type_tuple)) { + mp_raise_TypeError(translate("first argument must be a tuple of ndarrays")); + } + int8_t axis = (int8_t)args[1].u_int; + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); + mp_obj_tuple_t *ndarrays = MP_OBJ_TO_PTR(args[0].u_obj); + + // first check, whether the arrays are compatible + ndarray_obj_t *_ndarray = MP_OBJ_TO_PTR(ndarrays->items[0]); + uint8_t dtype = _ndarray->dtype; + uint8_t ndim = _ndarray->ndim; + if(axis < 0) { + axis += ndim; + } + if((axis < 0) || (axis >= ndim)) { + mp_raise_ValueError(translate("wrong axis specified")); + } + // shift axis + axis = ULAB_MAX_DIMS - ndim + axis; + for(uint8_t j=0; j < ULAB_MAX_DIMS; j++) { + shape[j] = _ndarray->shape[j]; + } + + for(uint8_t i=1; i < ndarrays->len; i++) { + _ndarray = MP_OBJ_TO_PTR(ndarrays->items[i]); + // check, whether the arrays are compatible + if((dtype != _ndarray->dtype) || (ndim != _ndarray->ndim)) { + mp_raise_ValueError(translate("input arrays are not compatible")); + } + for(uint8_t j=0; j < ULAB_MAX_DIMS; j++) { + if(j == axis) { + shape[j] += _ndarray->shape[j]; + } else { + if(shape[j] != _ndarray->shape[j]) { + mp_raise_ValueError(translate("input arrays are not compatible")); + } + } + } + } + + ndarray_obj_t *target = ndarray_new_dense_ndarray(ndim, shape, dtype); + uint8_t *tpos = (uint8_t *)target->array; + uint8_t *tarray; + + for(uint8_t p=0; p < ndarrays->len; p++) { + // reset the pointer along the axis + ndarray_obj_t *source = MP_OBJ_TO_PTR(ndarrays->items[p]); + uint8_t *sarray = (uint8_t *)source->array; + tarray = tpos; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + memcpy(tarray, sarray, source->itemsize); + tarray += target->strides[ULAB_MAX_DIMS - 1]; + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + tarray -= target->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + tarray += target->strides[ULAB_MAX_DIMS - 2]; + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + tarray -= target->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + tarray += target->strides[ULAB_MAX_DIMS - 3]; + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + tarray -= target->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + tarray += target->strides[ULAB_MAX_DIMS - 4]; + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif + if(p < ndarrays->len - 1) { + tpos += target->strides[axis] * source->shape[axis]; + } + } + return MP_OBJ_FROM_PTR(target); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_concatenate_obj, 1, create_concatenate); +#endif + +#if ULAB_NUMPY_HAS_DIAG +//| def diag(a: ulab.ndarray, *, k: int = 0) -> ulab.ndarray: +//| """ +//| .. param: a +//| an ndarray +//| .. param: k +//| Offset of the diagonal from the main diagonal. Can be positive or negative. +//| +//| Return specified diagonals.""" +//| ... +//| +mp_obj_t create_diag(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_k, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 0 } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("input must be an ndarray")); + } + ndarray_obj_t *source = MP_OBJ_TO_PTR(args[0].u_obj); + if(source->ndim == 1) { // return a rank-2 tensor with the prescribed diagonal + ndarray_obj_t *target = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, source->len, source->len), source->dtype); + uint8_t *sarray = (uint8_t *)source->array; + uint8_t *tarray = (uint8_t *)target->array; + for(size_t i=0; i < source->len; i++) { + memcpy(tarray, sarray, source->itemsize); + sarray += source->strides[ULAB_MAX_DIMS - 1]; + tarray += (source->len + 1) * target->itemsize; + } + return MP_OBJ_FROM_PTR(target); + } + if(source->ndim > 2) { + mp_raise_TypeError(translate("input must be a tensor of rank 2")); + } + int32_t k = args[1].u_int; + size_t len = 0; + uint8_t *sarray = (uint8_t *)source->array; + if(k < 0) { // move the pointer "vertically" + if(-k < (int32_t)source->shape[ULAB_MAX_DIMS - 2]) { + sarray -= k * source->strides[ULAB_MAX_DIMS - 2]; + len = MIN(source->shape[ULAB_MAX_DIMS - 2] + k, source->shape[ULAB_MAX_DIMS - 1]); + } + } else { // move the pointer "horizontally" + if(k < (int32_t)source->shape[ULAB_MAX_DIMS - 1]) { + sarray += k * source->strides[ULAB_MAX_DIMS - 1]; + len = MIN(source->shape[ULAB_MAX_DIMS - 1] - k, source->shape[ULAB_MAX_DIMS - 2]); + } + } + + if(len == 0) { + mp_raise_ValueError(translate("offset is too large")); + } + + ndarray_obj_t *target = ndarray_new_linear_array(len, source->dtype); + uint8_t *tarray = (uint8_t *)target->array; + + for(size_t i=0; i < len; i++) { + memcpy(tarray, sarray, source->itemsize); + sarray += source->strides[ULAB_MAX_DIMS - 2]; + sarray += source->strides[ULAB_MAX_DIMS - 1]; + tarray += source->itemsize; + } + return MP_OBJ_FROM_PTR(target); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_diag_obj, 1, create_diag); +#endif /* ULAB_NUMPY_HAS_DIAG */ + +#if ULAB_MAX_DIMS > 1 +#if ULAB_NUMPY_HAS_EYE +//| def eye(size: int, *, M: Optional[int] = None, k: int = 0, dtype: _DType = ulab.float) -> ulab.ndarray: +//| """Return a new square array of size, with the diagonal elements set to 1 +//| and the other elements set to 0.""" +//| ... +//| + +mp_obj_t create_eye(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_INT, { .u_int = 0 } }, + { MP_QSTR_M, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_k, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 0 } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + size_t n = args[0].u_int, m; + size_t k = args[2].u_int > 0 ? (size_t)args[2].u_int : (size_t)(-args[2].u_int); + uint8_t dtype = args[3].u_int; + if(args[1].u_rom_obj == mp_const_none) { + m = n; + } else { + m = mp_obj_get_int(args[1].u_rom_obj); + } + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, n, m), dtype); + if(dtype == NDARRAY_BOOL) { + dtype = NDARRAY_UINT8; + } + mp_obj_t one = mp_obj_new_int(1); + size_t i = 0; + if((args[2].u_int >= 0)) { + while(k < m) { + ndarray_set_value(dtype, ndarray->array, i*m+k, one); + k++; + i++; + } + } else { + while(k < n) { + ndarray_set_value(dtype, ndarray->array, k*m+i, one); + k++; + i++; + } + } + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_eye_obj, 1, create_eye); +#endif /* ULAB_NUMPY_HAS_EYE */ +#endif /* ULAB_MAX_DIMS > 1 */ + +#if ULAB_NUMPY_HAS_FULL +//| def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[_float, _bool], *, dtype: _DType = ulab.float) -> ulab.ndarray: +//| """ +//| .. param: shape +//| Shape of the array, either an integer (for a 1-D array) or a tuple of integers (for tensors of higher rank) +//| .. param: fill_value +//| scalar, the value with which the array is filled +//| .. param: dtype +//| Type of values in the array +//| +//| Return a new array of the given shape with all elements set to 0.""" +//| ... +//| + +mp_obj_t create_full(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t dtype = args[2].u_int; + + return create_zeros_ones_full(args[0].u_obj, dtype, args[1].u_obj); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_full_obj, 0, create_full); +#endif + + +#if ULAB_NUMPY_HAS_LINSPACE +//| def linspace( +//| start: _float, +//| stop: _float, +//| *, +//| dtype: _DType = ulab.float, +//| num: int = 50, +//| endpoint: _bool = True, +//| retstep: _bool = False +//| ) -> ulab.ndarray: +//| """ +//| .. param: start +//| First value in the array +//| .. param: stop +//| Final value in the array +//| .. param int: num +//| Count of values in the array. +//| .. param: dtype +//| Type of values in the array +//| .. param bool: endpoint +//| Whether the ``stop`` value is included. Note that even when +//| endpoint=True, the exact ``stop`` value may not be included due to the +//| inaccuracy of floating point arithmetic. +// .. param bool: retstep, +//| If True, return (`samples`, `step`), where `step` is the spacing between samples. +//| +//| Return a new 1-D array with ``num`` elements ranging from ``start`` to ``stop`` linearly.""" +//| ... +//| + +mp_obj_t create_linspace(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_num, MP_ARG_INT, { .u_int = 50 } }, + { MP_QSTR_endpoint, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_true } }, + { MP_QSTR_retstep, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_false } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(args[2].u_int < 2) { + mp_raise_ValueError(translate("number of points must be at least 2")); + } + size_t len = (size_t)args[2].u_int; + mp_float_t start, step; + start = mp_obj_get_float(args[0].u_obj); + uint8_t typecode = args[5].u_int; + if(args[3].u_obj == mp_const_true) step = (mp_obj_get_float(args[1].u_obj)-start)/(len-1); + else step = (mp_obj_get_float(args[1].u_obj)-start)/len; + ndarray_obj_t *ndarray = create_linspace_arange(start, step, len, typecode); + if(args[4].u_obj == mp_const_false) { + return MP_OBJ_FROM_PTR(ndarray); + } else { + mp_obj_t tuple[2]; + tuple[0] = ndarray; + tuple[1] = mp_obj_new_float(step); + return mp_obj_new_tuple(2, tuple); + } +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_linspace_obj, 2, create_linspace); +#endif + +#if ULAB_NUMPY_HAS_LOGSPACE +//| def logspace( +//| start: _float, +//| stop: _float, +//| *, +//| dtype: _DType = ulab.float, +//| num: int = 50, +//| endpoint: _bool = True, +//| base: _float = 10.0 +//| ) -> ulab.ndarray: +//| """ +//| .. param: start +//| First value in the array +//| .. param: stop +//| Final value in the array +//| .. param int: num +//| Count of values in the array. Defaults to 50. +//| .. param: base +//| The base of the log space. The step size between the elements in +//| ``ln(samples) / ln(base)`` (or ``log_base(samples)``) is uniform. Defaults to 10.0. +//| .. param: dtype +//| Type of values in the array +//| .. param bool: endpoint +//| Whether the ``stop`` value is included. Note that even when +//| endpoint=True, the exact ``stop`` value may not be included due to the +//| inaccuracy of floating point arithmetic. Defaults to True. +//| +//| Return a new 1-D array with ``num`` evenly spaced elements on a log scale. +//| The sequence starts at ``base ** start``, and ends with ``base ** stop``.""" +//| ... +//| + +const mp_obj_float_t create_float_const_ten = {{&mp_type_float}, MICROPY_FLOAT_CONST(10.0)}; + +mp_obj_t create_logspace(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_num, MP_ARG_INT, { .u_int = 50 } }, + { MP_QSTR_base, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_PTR(&create_float_const_ten) } }, + { MP_QSTR_endpoint, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_true } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(args[2].u_int < 2) { + mp_raise_ValueError(translate("number of points must be at least 2")); + } + size_t len = (size_t)args[2].u_int; + mp_float_t start, step, quotient; + start = mp_obj_get_float(args[0].u_obj); + uint8_t dtype = args[5].u_int; + mp_float_t base = mp_obj_get_float(args[3].u_obj); + if(args[4].u_obj == mp_const_true) step = (mp_obj_get_float(args[1].u_obj) - start)/(len - 1); + else step = (mp_obj_get_float(args[1].u_obj) - start) / len; + quotient = MICROPY_FLOAT_C_FUN(pow)(base, step); + ndarray_obj_t *ndarray = ndarray_new_linear_array(len, dtype); + + mp_float_t value = MICROPY_FLOAT_C_FUN(pow)(base, start); + if(ndarray->dtype == NDARRAY_UINT8) { + uint8_t *array = (uint8_t *)ndarray->array; + if(ndarray->boolean) { + memset(array, 1, len); + } else { + for(size_t i=0; i < len; i++, value *= quotient) *array++ = (uint8_t)value; + } + } else if(ndarray->dtype == NDARRAY_INT8) { + int8_t *array = (int8_t *)ndarray->array; + for(size_t i=0; i < len; i++, value *= quotient) *array++ = (int8_t)value; + } else if(ndarray->dtype == NDARRAY_UINT16) { + uint16_t *array = (uint16_t *)ndarray->array; + for(size_t i=0; i < len; i++, value *= quotient) *array++ = (uint16_t)value; + } else if(ndarray->dtype == NDARRAY_INT16) { + int16_t *array = (int16_t *)ndarray->array; + for(size_t i=0; i < len; i++, value *= quotient) *array++ = (int16_t)value; + } else { + mp_float_t *array = (mp_float_t *)ndarray->array; + for(size_t i=0; i < len; i++, value *= quotient) *array++ = value; + } + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_logspace_obj, 2, create_logspace); +#endif + +#if ULAB_NUMPY_HAS_ONES +//| def ones(shape: Union[int, Tuple[int, ...]], *, dtype: _DType = ulab.float) -> ulab.ndarray: +//| """ +//| .. param: shape +//| Shape of the array, either an integer (for a 1-D array) or a tuple of 2 integers (for a 2-D array) +//| .. param: dtype +//| Type of values in the array +//| +//| Return a new array of the given shape with all elements set to 1.""" +//| ... +//| + +mp_obj_t create_ones(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t dtype = args[1].u_int; + mp_obj_t one = mp_obj_new_int(1); + return create_zeros_ones_full(args[0].u_obj, dtype, one); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_ones_obj, 0, create_ones); +#endif + +#if ULAB_NUMPY_HAS_ZEROS +//| def zeros(shape: Union[int, Tuple[int, ...]], *, dtype: _DType = ulab.float) -> ulab.ndarray: +//| """ +//| .. param: shape +//| Shape of the array, either an integer (for a 1-D array) or a tuple of 2 integers (for a 2-D array) +//| .. param: dtype +//| Type of values in the array +//| +//| Return a new array of the given shape with all elements set to 0.""" +//| ... +//| + +mp_obj_t create_zeros(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t dtype = args[1].u_int; + return create_zeros_ones_full(args[0].u_obj, dtype, mp_const_none); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_zeros_obj, 0, create_zeros); +#endif + +#if ULAB_NUMPY_HAS_FROMBUFFER +mp_obj_t create_frombuffer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_INT(NDARRAY_FLOAT) } }, + { MP_QSTR_count, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_INT(-1) } }, + { MP_QSTR_offset, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_INT(0) } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t dtype = mp_obj_get_int(args[1].u_obj); + size_t offset = mp_obj_get_int(args[3].u_obj); + + mp_buffer_info_t bufinfo; + if(mp_get_buffer(args[0].u_obj, &bufinfo, MP_BUFFER_READ)) { + size_t sz = 1; + if(dtype != NDARRAY_BOOL) { // mp_binary_get_size doesn't work with Booleans + sz = mp_binary_get_size('@', dtype, NULL); + } + if(bufinfo.len < offset) { + mp_raise_ValueError(translate("offset must be non-negative and no greater than buffer length")); + } + size_t len = (bufinfo.len - offset) / sz; + if((len * sz) != (bufinfo.len - offset)) { + mp_raise_ValueError(translate("buffer size must be a multiple of element size")); + } + if(mp_obj_get_int(args[2].u_obj) > 0) { + size_t count = mp_obj_get_int(args[2].u_obj); + if(len < count) { + mp_raise_ValueError(translate("buffer is smaller than requested size")); + } else { + len = count; + } + } + ndarray_obj_t *ndarray = m_new_obj(ndarray_obj_t); + ndarray->base.type = &ulab_ndarray_type; + ndarray->dtype = dtype == NDARRAY_BOOL ? NDARRAY_UINT8 : dtype; + ndarray->boolean = dtype == NDARRAY_BOOL ? NDARRAY_BOOLEAN : NDARRAY_NUMERIC; + ndarray->ndim = 1; + ndarray->len = len; + ndarray->itemsize = sz; + ndarray->shape[ULAB_MAX_DIMS - 1] = len; + ndarray->strides[ULAB_MAX_DIMS - 1] = sz; + + uint8_t *buffer = bufinfo.buf; + ndarray->array = buffer + offset; + return MP_OBJ_FROM_PTR(ndarray); + } + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_frombuffer_obj, 1, create_frombuffer); +#endif diff --git a/python/port/mod/ulab/ulab_create.h b/python/port/mod/ulab/ulab_create.h new file mode 100644 index 00000000000..9aefc0b2b31 --- /dev/null +++ b/python/port/mod/ulab/ulab_create.h @@ -0,0 +1,78 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2019-2021 Zoltán Vörös +*/ + +#ifndef _CREATE_ +#define _CREATE_ + +#include "ulab.h" +#include "ndarray.h" + +#if ULAB_NUMPY_HAS_ARANGE +mp_obj_t create_arange(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_arange_obj); +#endif + +#if ULAB_NUMPY_HAS_CONCATENATE +mp_obj_t create_concatenate(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_concatenate_obj); +#endif + +#if ULAB_NUMPY_HAS_DIAG +mp_obj_t create_diag(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_diag_obj); +#endif + +#if ULAB_MAX_DIMS > 1 +#if ULAB_NUMPY_HAS_EYE +mp_obj_t create_eye(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_eye_obj); +#endif +#endif + +#if ULAB_NUMPY_HAS_FULL +mp_obj_t create_full(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_full_obj); +#endif + +#if ULAB_NUMPY_HAS_LINSPACE +mp_obj_t create_linspace(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_linspace_obj); +#endif + +#if ULAB_NUMPY_HAS_LOGSPACE +mp_obj_t create_logspace(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_logspace_obj); +#endif + +#if ULAB_NUMPY_HAS_ONES +mp_obj_t create_ones(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_ones_obj); +#endif + +#if ULAB_NUMPY_HAS_ZEROS +mp_obj_t create_zeros(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_zeros_obj); +#endif + +#if ULAB_NUMPY_HAS_FROMBUFFER +mp_obj_t create_frombuffer(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_frombuffer_obj); +#endif + +#define ARANGE_LOOP(type_, ndarray, len, step) \ +({\ + type_ *array = (type_ *)(ndarray)->array;\ + for (size_t i = 0; i < (len); i++, (value) += (step)) {\ + *array++ = (type_)value;\ + }\ +}) + +#endif diff --git a/python/port/mod/ulab/ulab_tools.c b/python/port/mod/ulab/ulab_tools.c new file mode 100644 index 00000000000..dd93787bab8 --- /dev/null +++ b/python/port/mod/ulab/ulab_tools.c @@ -0,0 +1,233 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös + */ + + +#include +#include + +#include "ulab.h" +#include "ndarray.h" +#include "ulab_tools.h" + +// The following five functions return a float from a void type +// The value in question is supposed to be located at the head of the pointer + +mp_float_t ndarray_get_float_uint8(void *data) { + // Returns a float value from an uint8_t type + return (mp_float_t)(*(uint8_t *)data); +} + +mp_float_t ndarray_get_float_int8(void *data) { + // Returns a float value from an int8_t type + return (mp_float_t)(*(int8_t *)data); +} + +mp_float_t ndarray_get_float_uint16(void *data) { + // Returns a float value from an uint16_t type + return (mp_float_t)(*(uint16_t *)data); +} + +mp_float_t ndarray_get_float_int16(void *data) { + // Returns a float value from an int16_t type + return (mp_float_t)(*(int16_t *)data); +} + + +mp_float_t ndarray_get_float_float(void *data) { + // Returns a float value from an mp_float_t type + return *((mp_float_t *)data); +} + +// returns a single function pointer, depending on the dtype +void *ndarray_get_float_function(uint8_t dtype) { + if(dtype == NDARRAY_UINT8) { + return ndarray_get_float_uint8; + } else if(dtype == NDARRAY_INT8) { + return ndarray_get_float_int8; + } else if(dtype == NDARRAY_UINT16) { + return ndarray_get_float_uint16; + } else if(dtype == NDARRAY_INT16) { + return ndarray_get_float_int16; + } else { + return ndarray_get_float_float; + } +} + +mp_float_t ndarray_get_float_index(void *data, uint8_t dtype, size_t index) { + // returns a single float value from an array located at index + if(dtype == NDARRAY_UINT8) { + return (mp_float_t)((uint8_t *)data)[index]; + } else if(dtype == NDARRAY_INT8) { + return (mp_float_t)((int8_t *)data)[index]; + } else if(dtype == NDARRAY_UINT16) { + return (mp_float_t)((uint16_t *)data)[index]; + } else if(dtype == NDARRAY_INT16) { + return (mp_float_t)((int16_t *)data)[index]; + } else { + return (mp_float_t)((mp_float_t *)data)[index]; + } +} + +mp_float_t ndarray_get_float_value(void *data, uint8_t dtype) { + // Returns a float value from an arbitrary data type + // The value in question is supposed to be located at the head of the pointer + if(dtype == NDARRAY_UINT8) { + return (mp_float_t)(*(uint8_t *)data); + } else if(dtype == NDARRAY_INT8) { + return (mp_float_t)(*(int8_t *)data); + } else if(dtype == NDARRAY_UINT16) { + return (mp_float_t)(*(uint16_t *)data); + } else if(dtype == NDARRAY_INT16) { + return (mp_float_t)(*(int16_t *)data); + } else { + return *((mp_float_t *)data); + } +} + +#if NDARRAY_BINARY_USES_FUN_POINTER | ULAB_NUMPY_HAS_WHERE +uint8_t ndarray_upcast_dtype(uint8_t ldtype, uint8_t rdtype) { + // returns a single character that corresponds to the broadcasting rules + // - if one of the operarands is a float, the result is always float + // - operation on identical types preserves type + // + // uint8 + int8 => int16 + // uint8 + int16 => int16 + // uint8 + uint16 => uint16 + // int8 + int16 => int16 + // int8 + uint16 => uint16 + // uint16 + int16 => float + + if(ldtype == rdtype) { + // if the two dtypes are equal, the result is also of that type + return ldtype; + } else if(((ldtype == NDARRAY_UINT8) && (rdtype == NDARRAY_INT8)) || + ((ldtype == NDARRAY_INT8) && (rdtype == NDARRAY_UINT8)) || + ((ldtype == NDARRAY_UINT8) && (rdtype == NDARRAY_INT16)) || + ((ldtype == NDARRAY_INT16) && (rdtype == NDARRAY_UINT8)) || + ((ldtype == NDARRAY_INT8) && (rdtype == NDARRAY_INT16)) || + ((ldtype == NDARRAY_INT16) && (rdtype == NDARRAY_INT8))) { + return NDARRAY_INT16; + } else if(((ldtype == NDARRAY_UINT8) && (rdtype == NDARRAY_UINT16)) || + ((ldtype == NDARRAY_UINT16) && (rdtype == NDARRAY_UINT8)) || + ((ldtype == NDARRAY_INT8) && (rdtype == NDARRAY_UINT16)) || + ((ldtype == NDARRAY_UINT16) && (rdtype == NDARRAY_INT8))) { + return NDARRAY_UINT16; + } + return NDARRAY_FLOAT; +} + +// The following five functions are the inverse of the ndarray_get_... functions, +// and write a floating point datum into a void pointer + +void ndarray_set_float_uint8(void *data, mp_float_t datum) { + *((uint8_t *)data) = (uint8_t)datum; +} + +void ndarray_set_float_int8(void *data, mp_float_t datum) { + *((int8_t *)data) = (int8_t)datum; +} + +void ndarray_set_float_uint16(void *data, mp_float_t datum) { + *((uint16_t *)data) = (uint16_t)datum; +} + +void ndarray_set_float_int16(void *data, mp_float_t datum) { + *((int16_t *)data) = (int16_t)datum; +} + +void ndarray_set_float_float(void *data, mp_float_t datum) { + *((mp_float_t *)data) = datum; +} + +// returns a single function pointer, depending on the dtype +void *ndarray_set_float_function(uint8_t dtype) { + if(dtype == NDARRAY_UINT8) { + return ndarray_set_float_uint8; + } else if(dtype == NDARRAY_INT8) { + return ndarray_set_float_int8; + } else if(dtype == NDARRAY_UINT16) { + return ndarray_set_float_uint16; + } else if(dtype == NDARRAY_INT16) { + return ndarray_set_float_int16; + } else { + return ndarray_set_float_float; + } +} +#endif /* NDARRAY_BINARY_USES_FUN_POINTER */ + +shape_strides tools_reduce_axes(ndarray_obj_t *ndarray, mp_obj_t axis) { + // TODO: replace numerical_reduce_axes with this function, wherever applicable + // This function should be used, whenever a tensor is contracted; + // The shape and strides at `axis` are moved to the zeroth position, + // everything else is aligned to the right + if(!mp_obj_is_int(axis) & (axis != mp_const_none)) { + mp_raise_TypeError(translate("axis must be None, or an integer")); + } + shape_strides _shape_strides; + + size_t *shape = m_new(size_t, ULAB_MAX_DIMS + 1); + _shape_strides.shape = shape; + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS + 1); + _shape_strides.strides = strides; + + _shape_strides.increment = 0; + // this is the contracted dimension (won't be overwritten for axis == None) + _shape_strides.ndim = 0; + + memcpy(_shape_strides.shape, ndarray->shape, sizeof(size_t) * ULAB_MAX_DIMS); + memcpy(_shape_strides.strides, ndarray->strides, sizeof(int32_t) * ULAB_MAX_DIMS); + + if(axis == mp_const_none) { + return _shape_strides; + } + + uint8_t index = ULAB_MAX_DIMS - 1; // value of index for axis == mp_const_none (won't be overwritten) + + if(axis != mp_const_none) { // i.e., axis is an integer + int8_t ax = mp_obj_get_int(axis); + if(ax < 0) ax += ndarray->ndim; + if((ax < 0) || (ax > ndarray->ndim - 1)) { + mp_raise_ValueError(translate("index out of range")); + } + index = ULAB_MAX_DIMS - ndarray->ndim + ax; + _shape_strides.ndim = ndarray->ndim - 1; + } + + // move the value stored at index to the leftmost position, and align everything else to the right + _shape_strides.shape[0] = ndarray->shape[index]; + _shape_strides.strides[0] = ndarray->strides[index]; + for(uint8_t i = 0; i < index; i++) { + // entries to the right of index must be shifted by one position to the left + _shape_strides.shape[i + 1] = ndarray->shape[i]; + _shape_strides.strides[i + 1] = ndarray->strides[i]; + } + + if(_shape_strides.ndim != 0) { + _shape_strides.increment = 1; + } + + return _shape_strides; +} + + +#if ULAB_MAX_DIMS > 1 +ndarray_obj_t *tools_object_is_square(mp_obj_t obj) { + // Returns an ndarray, if the object is a square ndarray, + // raises the appropriate exception otherwise + if(!mp_obj_is_type(obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("size is defined for ndarrays only")); + } + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(obj); + if((ndarray->shape[ULAB_MAX_DIMS - 1] != ndarray->shape[ULAB_MAX_DIMS - 2]) || (ndarray->ndim != 2)) { + mp_raise_ValueError(translate("input must be square matrix")); + } + return ndarray; +} +#endif diff --git a/python/port/mod/ulab/ulab_tools.h b/python/port/mod/ulab/ulab_tools.h new file mode 100644 index 00000000000..378e4f0ca62 --- /dev/null +++ b/python/port/mod/ulab/ulab_tools.h @@ -0,0 +1,37 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + +#ifndef _TOOLS_ +#define _TOOLS_ + +#include "ndarray.h" + +#define SWAP(t, a, b) { t tmp = a; a = b; b = tmp; } + +typedef struct _shape_strides_t { + uint8_t increment; + uint8_t ndim; + size_t *shape; + int32_t *strides; +} shape_strides; + +mp_float_t ndarray_get_float_uint8(void *); +mp_float_t ndarray_get_float_int8(void *); +mp_float_t ndarray_get_float_uint16(void *); +mp_float_t ndarray_get_float_int16(void *); +mp_float_t ndarray_get_float_float(void *); +void *ndarray_get_float_function(uint8_t ); + +uint8_t ndarray_upcast_dtype(uint8_t , uint8_t ); +void *ndarray_set_float_function(uint8_t ); + +shape_strides tools_reduce_axes(ndarray_obj_t *, mp_obj_t ); +ndarray_obj_t *tools_object_is_square(mp_obj_t ); +#endif diff --git a/python/port/mod/ulab/user/user.c b/python/port/mod/ulab/user/user.c new file mode 100644 index 00000000000..be7c6d0a04b --- /dev/null +++ b/python/port/mod/ulab/user/user.c @@ -0,0 +1,95 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + +#include +#include +#include +#include +#include +#include +#include "user.h" + +#if ULAB_HAS_USER_MODULE + +//| """This module should hold arbitrary user-defined functions.""" +//| + +static mp_obj_t user_square(mp_obj_t arg) { + // the function takes a single dense ndarray, and calculates the + // element-wise square of its entries + + // raise a TypeError exception, if the input is not an ndarray + if(!mp_obj_is_type(arg, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("input must be an ndarray")); + } + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(arg); + + // make sure that the input is a dense array + if(!ndarray_is_dense(ndarray)) { + mp_raise_TypeError(translate("input must be a dense ndarray")); + } + + // if the input is a dense array, create `results` with the same number of + // dimensions, shape, and dtype + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndarray->ndim, ndarray->shape, ndarray->dtype); + + // since in a dense array the iteration over the elements is trivial, we + // can cast the data arrays ndarray->array and results->array to the actual type + if(ndarray->dtype == NDARRAY_UINT8) { + uint8_t *array = (uint8_t *)ndarray->array; + uint8_t *rarray = (uint8_t *)results->array; + for(size_t i=0; i < ndarray->len; i++, array++) { + *rarray++ = (*array) * (*array); + } + } else if(ndarray->dtype == NDARRAY_INT8) { + int8_t *array = (int8_t *)ndarray->array; + int8_t *rarray = (int8_t *)results->array; + for(size_t i=0; i < ndarray->len; i++, array++) { + *rarray++ = (*array) * (*array); + } + } else if(ndarray->dtype == NDARRAY_UINT16) { + uint16_t *array = (uint16_t *)ndarray->array; + uint16_t *rarray = (uint16_t *)results->array; + for(size_t i=0; i < ndarray->len; i++, array++) { + *rarray++ = (*array) * (*array); + } + } else if(ndarray->dtype == NDARRAY_INT16) { + int16_t *array = (int16_t *)ndarray->array; + int16_t *rarray = (int16_t *)results->array; + for(size_t i=0; i < ndarray->len; i++, array++) { + *rarray++ = (*array) * (*array); + } + } else { // if we end up here, the dtype is NDARRAY_FLOAT + mp_float_t *array = (mp_float_t *)ndarray->array; + mp_float_t *rarray = (mp_float_t *)results->array; + for(size_t i=0; i < ndarray->len; i++, array++) { + *rarray++ = (*array) * (*array); + } + } + // at the end, return a micrppython object + return MP_OBJ_FROM_PTR(results); +} + +MP_DEFINE_CONST_FUN_OBJ_1(user_square_obj, user_square); + +static const mp_rom_map_elem_t ulab_user_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_user) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_square), (mp_obj_t)&user_square_obj }, +}; + +static MP_DEFINE_CONST_DICT(mp_module_ulab_user_globals, ulab_user_globals_table); + +mp_obj_module_t ulab_user_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_user_globals, +}; + +#endif diff --git a/python/port/mod/ulab/user/user.h b/python/port/mod/ulab/user/user.h new file mode 100644 index 00000000000..4ec366af1f6 --- /dev/null +++ b/python/port/mod/ulab/user/user.h @@ -0,0 +1,20 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + +#ifndef _USER_ +#define _USER_ + +#include "../ulab.h" +#include "../ndarray.h" + +extern mp_obj_module_t ulab_user_module; + +#endif diff --git a/python/port/mod/ulab/utils/utils.c b/python/port/mod/ulab/utils/utils.c new file mode 100644 index 00000000000..6ff787b3b42 --- /dev/null +++ b/python/port/mod/ulab/utils/utils.c @@ -0,0 +1,215 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + +#include +#include +#include +#include +#include +#include +#include "utils.h" + +#if ULAB_HAS_UTILS_MODULE + +enum UTILS_BUFFER_TYPE { + UTILS_INT16_BUFFER, + UTILS_UINT16_BUFFER, + UTILS_INT32_BUFFER, + UTILS_UINT32_BUFFER, +}; + +#if ULAB_UTILS_HAS_FROM_INT16_BUFFER | ULAB_UTILS_HAS_FROM_UINT16_BUFFER | ULAB_UTILS_HAS_FROM_INT32_BUFFER | ULAB_UTILS_HAS_FROM_UINT32_BUFFER +static mp_obj_t utils_from_intbuffer_helper(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args, uint8_t buffer_type) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } } , + { MP_QSTR_count, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_INT(-1) } }, + { MP_QSTR_offset, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_INT(0) } }, + { MP_QSTR_out, MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_byteswap, MP_ARG_OBJ, { .u_rom_obj = mp_const_false } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + ndarray_obj_t *ndarray = NULL; + + if(args[3].u_obj != mp_const_none) { + ndarray = MP_OBJ_TO_PTR(args[3].u_obj); + if((ndarray->dtype != NDARRAY_FLOAT) || !ndarray_is_dense(ndarray)) { + mp_raise_TypeError(translate("out must be a float dense array")); + } + } + + size_t offset = mp_obj_get_int(args[2].u_obj); + + mp_buffer_info_t bufinfo; + if(mp_get_buffer(args[0].u_obj, &bufinfo, MP_BUFFER_READ)) { + if(bufinfo.len < offset) { + mp_raise_ValueError(translate("offset is too large")); + } + uint8_t sz = sizeof(int16_t); + #if ULAB_UTILS_HAS_FROM_INT32_BUFFER | ULAB_UTILS_HAS_FROM_UINT32_BUFFER + if((buffer_type == UTILS_INT32_BUFFER) || (buffer_type == UTILS_UINT32_BUFFER)) { + sz = sizeof(int32_t); + } + #endif + + size_t len = (bufinfo.len - offset) / sz; + if((len * sz) != (bufinfo.len - offset)) { + mp_raise_ValueError(translate("buffer size must be a multiple of element size")); + } + if(mp_obj_get_int(args[1].u_obj) > 0) { + size_t count = mp_obj_get_int(args[1].u_obj); + if(len < count) { + mp_raise_ValueError(translate("buffer is smaller than requested size")); + } else { + len = count; + } + } + if(args[3].u_obj == mp_const_none) { + ndarray = ndarray_new_linear_array(len, NDARRAY_FLOAT); + } else { + if(ndarray->len < len) { + mp_raise_ValueError(translate("out array is too small")); + } + } + uint8_t *buffer = bufinfo.buf; + + mp_float_t *array = (mp_float_t *)ndarray->array; + if(args[4].u_obj == mp_const_true) { + // swap the bytes before conversion + uint8_t *tmpbuff = m_new(uint8_t, sz); + #if ULAB_UTILS_HAS_FROM_INT16_BUFFER | ULAB_UTILS_HAS_FROM_UINT16_BUFFER + if((buffer_type == UTILS_INT16_BUFFER) || (buffer_type == UTILS_UINT16_BUFFER)) { + for(size_t i = 0; i < len; i++) { + tmpbuff += sz; + for(uint8_t j = 0; j < sz; j++) { + memcpy(--tmpbuff, buffer++, 1); + } + if(buffer_type == UTILS_INT16_BUFFER) { + *array++ = (mp_float_t)(*(int16_t *)tmpbuff); + } else { + *array++ = (mp_float_t)(*(uint16_t *)tmpbuff); + } + } + } + #endif + #if ULAB_UTILS_HAS_FROM_INT32_BUFFER | ULAB_UTILS_HAS_FROM_UINT32_BUFFER + if((buffer_type == UTILS_INT32_BUFFER) || (buffer_type == UTILS_UINT32_BUFFER)) { + for(size_t i = 0; i < len; i++) { + tmpbuff += sz; + for(uint8_t j = 0; j < sz; j++) { + memcpy(--tmpbuff, buffer++, 1); + } + if(buffer_type == UTILS_INT32_BUFFER) { + *array++ = (mp_float_t)(*(int32_t *)tmpbuff); + } else { + *array++ = (mp_float_t)(*(uint32_t *)tmpbuff); + } + } + } + #endif + } else { + #if ULAB_UTILS_HAS_FROM_INT16_BUFFER + if(buffer_type == UTILS_INT16_BUFFER) { + for(size_t i = 0; i < len; i++) { + *array++ = (mp_float_t)(*(int16_t *)buffer); + buffer += sz; + } + } + #endif + #if ULAB_UTILS_HAS_FROM_UINT16_BUFFER + if(buffer_type == UTILS_UINT16_BUFFER) { + for(size_t i = 0; i < len; i++) { + *array++ = (mp_float_t)(*(uint16_t *)buffer); + buffer += sz; + } + } + #endif + #if ULAB_UTILS_HAS_FROM_INT32_BUFFER + if(buffer_type == UTILS_INT32_BUFFER) { + for(size_t i = 0; i < len; i++) { + *array++ = (mp_float_t)(*(int32_t *)buffer); + buffer += sz; + } + } + #endif + #if ULAB_UTILS_HAS_FROM_UINT32_BUFFER + if(buffer_type == UTILS_UINT32_BUFFER) { + for(size_t i = 0; i < len; i++) { + *array++ = (mp_float_t)(*(uint32_t *)buffer); + buffer += sz; + } + } + #endif + } + return MP_OBJ_FROM_PTR(ndarray); + } + return mp_const_none; +} + +#ifdef ULAB_UTILS_HAS_FROM_INT16_BUFFER +static mp_obj_t utils_from_int16_buffer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return utils_from_intbuffer_helper(n_args, pos_args, kw_args, UTILS_INT16_BUFFER); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(utils_from_int16_buffer_obj, 1, utils_from_int16_buffer); +#endif + +#ifdef ULAB_UTILS_HAS_FROM_UINT16_BUFFER +static mp_obj_t utils_from_uint16_buffer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return utils_from_intbuffer_helper(n_args, pos_args, kw_args, UTILS_UINT16_BUFFER); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(utils_from_uint16_buffer_obj, 1, utils_from_uint16_buffer); +#endif + +#ifdef ULAB_UTILS_HAS_FROM_INT32_BUFFER +static mp_obj_t utils_from_int32_buffer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return utils_from_intbuffer_helper(n_args, pos_args, kw_args, UTILS_INT32_BUFFER); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(utils_from_int32_buffer_obj, 1, utils_from_int32_buffer); +#endif + +#ifdef ULAB_UTILS_HAS_FROM_UINT32_BUFFER +static mp_obj_t utils_from_uint32_buffer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return utils_from_intbuffer_helper(n_args, pos_args, kw_args, UTILS_UINT32_BUFFER); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(utils_from_uint32_buffer_obj, 1, utils_from_uint32_buffer); +#endif + +#endif + +static const mp_rom_map_elem_t ulab_utils_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_utils) }, + #if ULAB_UTILS_HAS_FROM_INT16_BUFFER + { MP_OBJ_NEW_QSTR(MP_QSTR_from_int16_buffer), (mp_obj_t)&utils_from_int16_buffer_obj }, + #endif + #if ULAB_UTILS_HAS_FROM_UINT16_BUFFER + { MP_OBJ_NEW_QSTR(MP_QSTR_from_uint16_buffer), (mp_obj_t)&utils_from_uint16_buffer_obj }, + #endif + #if ULAB_UTILS_HAS_FROM_INT32_BUFFER + { MP_OBJ_NEW_QSTR(MP_QSTR_from_int32_buffer), (mp_obj_t)&utils_from_int32_buffer_obj }, + #endif + #if ULAB_UTILS_HAS_FROM_UINT32_BUFFER + { MP_OBJ_NEW_QSTR(MP_QSTR_from_uint32_buffer), (mp_obj_t)&utils_from_uint32_buffer_obj }, + #endif +}; + +static MP_DEFINE_CONST_DICT(mp_module_ulab_utils_globals, ulab_utils_globals_table); + +mp_obj_module_t ulab_utils_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_utils_globals, +}; + +#endif diff --git a/python/port/mod/ulab/utils/utils.h b/python/port/mod/ulab/utils/utils.h new file mode 100644 index 00000000000..3b258bf2431 --- /dev/null +++ b/python/port/mod/ulab/utils/utils.h @@ -0,0 +1,19 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + +#ifndef _UTILS_ +#define _UTILS_ + +#include "../ulab.h" +#include "../ndarray.h" + +extern mp_obj_module_t ulab_utils_module; + +#endif diff --git a/python/port/mpconfigport.h b/python/port/mpconfigport.h index fa3d71da6a4..07a6e3e993d 100644 --- a/python/port/mpconfigport.h +++ b/python/port/mpconfigport.h @@ -136,6 +136,7 @@ extern const struct _mp_obj_module_t modpyplot_module; extern const struct _mp_obj_module_t modtime_module; extern const struct _mp_obj_module_t modos_module; extern const struct _mp_obj_module_t modturtle_module; +extern const struct _mp_obj_module_t ulab_user_cmodule; #define MICROPY_PORT_BUILTIN_MODULES \ { MP_ROM_QSTR(MP_QSTR_ion), MP_ROM_PTR(&modion_module) }, \ @@ -145,6 +146,7 @@ extern const struct _mp_obj_module_t modturtle_module; { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&modtime_module) }, \ { MP_ROM_QSTR(MP_QSTR_os), MP_ROM_PTR(&modos_module) }, \ { MP_ROM_QSTR(MP_QSTR_turtle), MP_ROM_PTR(&modturtle_module) }, \ + { MP_ROM_QSTR(MP_QSTR_ulab), MP_ROM_PTR(&ulab_user_cmodule) }, \ // Enable setjmp in debug mode. This is to avoid some optimizations done // specifically for x86_64 using inline assembly, which makes the debug binary diff --git a/python/port/port.cpp b/python/port/port.cpp index 1f83a01dd0c..e90b9de0b1e 100644 --- a/python/port/port.cpp +++ b/python/port/port.cpp @@ -61,6 +61,7 @@ extern "C" { #include "mphalport.h" #include "mod/turtle/modturtle.h" #include "mod/matplotlib/pyplot/modpyplot.h" +#include "mod/ulab/ulab.h" } #include From 837fcd9bcc491e491f8aa392cd5c09d47e1927b9 Mon Sep 17 00:00:00 2001 From: ArtichOwO Date: Wed, 23 Jun 2021 23:08:46 +0200 Subject: [PATCH 015/355] [MPY/MOD/ULAB] Added N100 compatibility N100: - doesn't have Scipy; - uses function pointers --- python/port/mod/ulab/ulab.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/python/port/mod/ulab/ulab.h b/python/port/mod/ulab/ulab.h index e38310020c0..647672d8bd3 100644 --- a/python/port/mod/ulab/ulab.h +++ b/python/port/mod/ulab/ulab.h @@ -35,7 +35,11 @@ // Determines, whether scipy is defined in ulab. The sub-modules and functions // of scipy have to be defined separately #ifndef ULAB_HAS_SCIPY +#if defined(DEVICE_N0100) #define ULAB_HAS_SCIPY (0) +#else +#define ULAB_HAS_SCIPY (1) +#endif #endif // The maximum number of dimensions the firmware should be able to support @@ -82,8 +86,12 @@ // 2 kB in the two-dimensional case, and around 4 kB in the four-dimensional case. #ifndef NDARRAY_BINARY_USES_FUN_POINTER +#if defined(DEVICE_N0100) +#define NDARRAY_BINARY_USES_FUN_POINTER (1) +#else #define NDARRAY_BINARY_USES_FUN_POINTER (0) #endif +#endif #ifndef NDARRAY_HAS_BINARY_OP_ADD #define NDARRAY_HAS_BINARY_OP_ADD (1) From 649f48919eef5cc89fd3df0df5f2f01e9ff9fdf5 Mon Sep 17 00:00:00 2001 From: Laury Date: Tue, 6 Jul 2021 14:54:55 +0200 Subject: [PATCH 016/355] [home] Added support for a wallpaper in a special format (.obm) --- apps/home/Makefile | 1 + apps/home/app_cell.cpp | 9 ++- apps/home/app_cell.h | 4 +- apps/home/controller.cpp | 27 ++++++- apps/home/controller.h | 5 +- .../selectable_table_view_with_background.cpp | 16 ++++ .../selectable_table_view_with_background.h | 25 ++++++ escher/Makefile | 2 + escher/include/escher.h | 2 + escher/include/escher/background_view.h | 20 +++++ escher/include/escher/icon_view.h | 20 +++++ escher/include/escher/scroll_view.h | 29 +++---- escher/src/background_view.cpp | 65 +++++++++++++++ escher/src/icon_view.cpp | 79 +++++++++++++++++++ escher/src/scroll_view.cpp | 6 +- 15 files changed, 286 insertions(+), 24 deletions(-) create mode 100644 apps/home/selectable_table_view_with_background.cpp create mode 100644 apps/home/selectable_table_view_with_background.h create mode 100644 escher/include/escher/background_view.h create mode 100644 escher/include/escher/icon_view.h create mode 100644 escher/src/background_view.cpp create mode 100644 escher/src/icon_view.cpp diff --git a/apps/home/Makefile b/apps/home/Makefile index c76c4580862..880302a5262 100644 --- a/apps/home/Makefile +++ b/apps/home/Makefile @@ -3,6 +3,7 @@ app_home_src = $(addprefix apps/home/,\ app_cell.cpp \ apps_layout.py \ controller.cpp \ + selectable_table_view_with_background.cpp \ ) apps_src += $(app_home_src) diff --git a/apps/home/app_cell.cpp b/apps/home/app_cell.cpp index a6e5a155478..777bc14f612 100644 --- a/apps/home/app_cell.cpp +++ b/apps/home/app_cell.cpp @@ -8,6 +8,7 @@ namespace Home { AppCell::AppCell() : HighlightCell(), m_nameView(KDFont::SmallFont, (I18n::Message)0, 0.5f, 0.5f, Palette::HomeCellText, Palette::HomeCellBackground), + m_backgroundView(nullptr), m_visible(true), m_external_app(false) { } @@ -15,8 +16,8 @@ AppCell::AppCell() : void AppCell::drawRect(KDContext * ctx, KDRect rect) const { KDSize nameSize = m_nameView.minimalSizeForOptimalDisplay(); - ctx->fillRect(KDRect(0, bounds().height()-nameSize.height() - 2*k_nameHeightMargin, bounds().width(), nameSize.height()+2*k_nameHeightMargin), Palette::HomeBackground); -} + m_backgroundView->drawRect(ctx, KDRect(0, bounds().height()-nameSize.height() - 2*k_nameHeightMargin, bounds().width(), nameSize.height()+2*k_nameHeightMargin)); + } int AppCell::numberOfSubviews() const { return m_visible ? 2 : 0; @@ -70,6 +71,10 @@ void AppCell::setVisible(bool visible) { } } +void AppCell::setBackgroundView(const BackgroundView * backgroundView) { + m_backgroundView = backgroundView; +} + void AppCell::reloadCell() { m_nameView.setTextColor(isHighlighted() ? (m_external_app ? Palette::HomeCellTextExternalActive : Palette::HomeCellTextActive) : (m_external_app ? Palette::HomeCellTextExternal : Palette::HomeCellText)); m_nameView.setBackgroundColor(isHighlighted() ? Palette::HomeCellBackgroundActive : Palette::HomeCellBackground); diff --git a/apps/home/app_cell.h b/apps/home/app_cell.h index 89c22e00848..f9ff029bd28 100644 --- a/apps/home/app_cell.h +++ b/apps/home/app_cell.h @@ -15,6 +15,7 @@ class AppCell : public HighlightCell { void layoutSubviews(bool force = false) override; void setVisible(bool visible); + void setBackgroundView(const BackgroundView * backgroundView); void reloadCell() override; void setAppDescriptor(::App::Descriptor * appDescriptor); void setExtAppDescriptor(const char* name, const Image* icon); @@ -25,8 +26,9 @@ class AppCell : public HighlightCell { static constexpr KDCoordinate k_iconHeight = 56; static constexpr KDCoordinate k_nameWidthMargin = 4; static constexpr KDCoordinate k_nameHeightMargin = 1; - ImageView m_iconView; + IconView m_iconView; MessageTextView m_nameView; + const BackgroundView * m_backgroundView; bool m_visible; bool m_external_app; }; diff --git a/apps/home/controller.cpp b/apps/home/controller.cpp index 2724bd172e3..df725f10fdc 100644 --- a/apps/home/controller.cpp +++ b/apps/home/controller.cpp @@ -18,11 +18,11 @@ extern "C" { namespace Home { Controller::ContentView::ContentView(Controller * controller, SelectableTableViewDataSource * selectionDataSource) : - m_selectableTableView(controller, controller, selectionDataSource, controller) + m_selectableTableView(controller, controller, &m_backgroundView, selectionDataSource, controller), + m_backgroundView() { m_selectableTableView.setVerticalCellOverlap(0); m_selectableTableView.setMargins(0, k_sideMargin, k_bottomMargin, k_sideMargin); - m_selectableTableView.setBackgroundColor(Palette::HomeBackground); static_cast(m_selectableTableView.decorator())->verticalBar()->setMargin(k_indicatorMargin); } @@ -31,7 +31,7 @@ SelectableTableView * Controller::ContentView::selectableTableView() { } void Controller::ContentView::drawRect(KDContext * ctx, KDRect rect) const { - ctx->fillRect(bounds(), Palette::HomeBackground); + m_selectableTableView.drawRect(ctx, rect); } void Controller::ContentView::reloadBottomRow(SimpleTableViewDataSource * dataSource, int numberOfIcons, int numberOfColumns) { @@ -45,6 +45,10 @@ void Controller::ContentView::reloadBottomRow(SimpleTableViewDataSource * dataSo } } +BackgroundView * Controller::ContentView::backgroundView() { + return &m_backgroundView; +} + int Controller::ContentView::numberOfSubviews() const { return 1; } @@ -56,6 +60,8 @@ View * Controller::ContentView::subviewAtIndex(int index) { void Controller::ContentView::layoutSubviews(bool force) { m_selectableTableView.setFrame(bounds(), force); + m_backgroundView.setFrame(KDRect(0, Metric::TitleBarHeight, Ion::Display::Width, Ion::Display::Height-Metric::TitleBarHeight), force); + m_backgroundView.updateDataValidity(); } Controller::Controller(Responder * parentResponder, SelectableTableViewDataSource * selectionDataSource, ::App * app) : @@ -63,6 +69,21 @@ Controller::Controller(Responder * parentResponder, SelectableTableViewDataSourc m_view(this, selectionDataSource) { m_app = app; + for (int i = 0; i < k_maxNumberOfCells; i++) { + m_cells[i].setBackgroundView(m_view.backgroundView()); + } + + m_view.backgroundView()->setDefaultColor(Palette::HomeBackground); + + +#ifdef HOME_DISPLAY_EXTERNALS + int index = External::Archive::indexFromName("wallpaper.obm"); + if(index > -1) { + External::Archive::File image; + External::Archive::fileAtIndex(index, image); + m_view.backgroundView()->setBackgroundImage(image.data); + } +#endif } bool Controller::handleEvent(Ion::Events::Event event) { diff --git a/apps/home/controller.h b/apps/home/controller.h index 90a5604a3e8..3301fac78fb 100644 --- a/apps/home/controller.h +++ b/apps/home/controller.h @@ -2,6 +2,7 @@ #define HOME_CONTROLLER_H #include +#include "selectable_table_view_with_background.h" #include "app_cell.h" namespace Home { @@ -35,11 +36,13 @@ class Controller : public ViewController, public SimpleTableViewDataSource, publ SelectableTableView * selectableTableView(); void drawRect(KDContext * ctx, KDRect rect) const override; void reloadBottomRow(SimpleTableViewDataSource * dataSource, int numberOfIcons, int numberOfColumns); + BackgroundView * backgroundView(); private: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; void layoutSubviews(bool force = false) override; - SelectableTableView m_selectableTableView; + SelectableTableViewWithBackground m_selectableTableView; + BackgroundView m_backgroundView; }; static constexpr KDCoordinate k_sideMargin = 4; static constexpr KDCoordinate k_bottomMargin = 14; diff --git a/apps/home/selectable_table_view_with_background.cpp b/apps/home/selectable_table_view_with_background.cpp new file mode 100644 index 00000000000..66854589f13 --- /dev/null +++ b/apps/home/selectable_table_view_with_background.cpp @@ -0,0 +1,16 @@ +#include "selectable_table_view_with_background.h" + +SelectableTableViewWithBackground::SelectableTableViewWithBackground(Responder * parentResponder, TableViewDataSource * dataSource, BackgroundView * backgroundView, SelectableTableViewDataSource * selectionDataSource, SelectableTableViewDelegate * delegate) : + SelectableTableView(parentResponder, dataSource, selectionDataSource, delegate), + m_backgroundInnerView(this, backgroundView) +{ + +} + +void SelectableTableViewWithBackground::BackgroundInnerView::drawRect(KDContext * ctx, KDRect rect) const { + m_backgroundView->drawRect(ctx, rect); +} + +void SelectableTableViewWithBackground::BackgroundInnerView::setBackgroundView(const uint8_t * data) { + m_backgroundView->setBackgroundImage(data); +} \ No newline at end of file diff --git a/apps/home/selectable_table_view_with_background.h b/apps/home/selectable_table_view_with_background.h new file mode 100644 index 00000000000..b722cf20b77 --- /dev/null +++ b/apps/home/selectable_table_view_with_background.h @@ -0,0 +1,25 @@ +#ifndef ESCHER_SELECTABLE_TABLE_VIEW_WITH_BACKGROUND_H +#define ESCHER_SELECTABLE_TABLE_VIEW_WITH_BACKGROUND_H + +#include +#include + +class SelectableTableViewWithBackground : public SelectableTableView { +public: + SelectableTableViewWithBackground(Responder * parentResponder, TableViewDataSource * dataSource, BackgroundView * backgroundView, + SelectableTableViewDataSource * selectionDataSource = nullptr, SelectableTableViewDelegate * delegate = nullptr); + View * subviewAtIndex(int index) override { return (index == 0) ? &m_backgroundInnerView : decorator()->indicatorAtIndex(index); } +protected: + virtual InnerView * getInnerView() { return &m_backgroundInnerView; } + class BackgroundInnerView : public ScrollView::InnerView { + public: + BackgroundInnerView(ScrollView * scrollView, BackgroundView * backgroundView): InnerView(scrollView), m_backgroundView(backgroundView) {} + void drawRect(KDContext * ctx, KDRect rect) const override; + void setBackgroundView(const uint8_t * data); + private: + BackgroundView * m_backgroundView; + }; + BackgroundInnerView m_backgroundInnerView; +}; + +#endif diff --git a/escher/Makefile b/escher/Makefile index 5a4a0fe870e..a8bb8491cc2 100644 --- a/escher/Makefile +++ b/escher/Makefile @@ -22,6 +22,7 @@ endif escher_src += $(addprefix escher/src/,\ alternate_empty_view_controller.cpp \ app.cpp \ + background_view.cpp \ bank_view_controller.cpp \ bordered.cpp \ buffer_text_view.cpp \ @@ -46,6 +47,7 @@ escher_src += $(addprefix escher/src/,\ expression_view.cpp \ highlight_cell.cpp \ gauge_view.cpp \ + icon_view.cpp \ image_view.cpp \ input_event_handler.cpp \ invocation.cpp \ diff --git a/escher/include/escher.h b/escher/include/escher.h index e07ce1abbf3..62ceb7e1afc 100644 --- a/escher/include/escher.h +++ b/escher/include/escher.h @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include #include #include #include diff --git a/escher/include/escher/background_view.h b/escher/include/escher/background_view.h new file mode 100644 index 00000000000..c7db13c69a9 --- /dev/null +++ b/escher/include/escher/background_view.h @@ -0,0 +1,20 @@ +#ifndef ESCHER_BACKGROUND_VIEW_H +#define ESCHER_BACKGROUND_VIEW_H + +#include +#include + +class BackgroundView : public View { +public: + BackgroundView(); + void drawRect(KDContext * ctx, KDRect rect) const override; + void setBackgroundImage(const uint8_t * data); + void setDefaultColor(KDColor defaultColor); + void updateDataValidity(); +private: + const uint8_t * m_data; + bool m_isDataValid; + KDColor m_defaultColor; +}; + +#endif \ No newline at end of file diff --git a/escher/include/escher/icon_view.h b/escher/include/escher/icon_view.h new file mode 100644 index 00000000000..4f03a140a92 --- /dev/null +++ b/escher/include/escher/icon_view.h @@ -0,0 +1,20 @@ +#ifndef ESCHER_ICON_VIEW_H +#define ESCHER_ICON_VIEW_H + +#include +#include + +class IconView : public View { +//Unlike the ImageView class, IconView displays an image with rounded corners +public: + IconView(); + void setImage(const Image * image); + void setImage(const uint8_t *data, size_t dataLength); + void drawRect(KDContext * ctx, KDRect rect) const override; +private: + const Image * m_image; + const uint8_t * m_data; + size_t m_dataLength; +}; + +#endif diff --git a/escher/include/escher/scroll_view.h b/escher/include/escher/scroll_view.h index 34225273e61..8a27610595f 100644 --- a/escher/include/escher/scroll_view.h +++ b/escher/include/escher/scroll_view.h @@ -96,6 +96,19 @@ class ScrollView : public View { void scrollToContentPoint(KDPoint p, bool allowOverscroll = false); void scrollToContentRect(KDRect rect, bool allowOverscroll = false); // Minimal scrolling to make this rect visible protected: + class InnerView : public View { + public: + InnerView(ScrollView * scrollView) : View(), m_scrollView(scrollView) {} + void drawRect(KDContext * ctx, KDRect rect) const override; + private: + int numberOfSubviews() const override { return 1; } + View * subviewAtIndex(int index) override { + assert(index == 0); + return m_scrollView->m_contentView; + } + const ScrollView * m_scrollView; + }; + KDCoordinate maxContentWidthDisplayableWithoutScrolling() const { return m_frame.width() - m_leftMargin - m_rightMargin; } @@ -105,35 +118,23 @@ class ScrollView : public View { KDRect visibleContentRect(); void layoutSubviews(bool force = false) override; virtual KDSize contentSize() const { return m_contentView->minimalSizeForOptimalDisplay(); } + virtual InnerView * getInnerView() { return &m_innerView; } #if ESCHER_VIEW_LOGGING const char * className() const override; void logAttributes(std::ostream &os) const override; #endif View * m_contentView; + InnerView m_innerView; private: ScrollViewDataSource * m_dataSource; int numberOfSubviews() const override { return 1 + const_cast(this)->decorator()->numberOfIndicators(); } View * subviewAtIndex(int index) override { return (index == 0) ? &m_innerView : decorator()->indicatorAtIndex(index); } - class InnerView : public View { - public: - InnerView(ScrollView * scrollView) : View(), m_scrollView(scrollView) {} - void drawRect(KDContext * ctx, KDRect rect) const override; - private: - int numberOfSubviews() const override { return 1; } - View * subviewAtIndex(int index) override { - assert(index == 0); - return m_scrollView->m_contentView; - } - const ScrollView * m_scrollView; - }; - KDCoordinate m_topMargin; KDCoordinate m_rightMargin; KDCoordinate m_bottomMargin; KDCoordinate m_leftMargin; - InnerView m_innerView; Decorator::Type m_decoratorType; union Decorators { public: diff --git a/escher/src/background_view.cpp b/escher/src/background_view.cpp new file mode 100644 index 00000000000..3711185c080 --- /dev/null +++ b/escher/src/background_view.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include //AND THIS +#include "apps/home/controller.h" +#ifdef HOME_DISPLAY_EXTERNALS +#include "apps/external/external_icon.h" +#include "apps/external/archive.h" +#include +#endif + +//To store the Omega backgrounds, we use a specific file in the "OmegaBitMap" format. +//Here is its header +struct OBMHeader +{ + uint32_t signature; //Normally it is 32145 + int32_t width; + int32_t height; + const KDColor image_data; +}; + +BackgroundView::BackgroundView(): + m_data(nullptr), + m_isDataValid(false), + m_defaultColor(Palette::BackgroundHard) +{ + +} + +void BackgroundView::setBackgroundImage(const uint8_t * data) { + m_data = data; + updateDataValidity(); + markRectAsDirty(bounds()); +} + +void BackgroundView::setDefaultColor(KDColor defaultColor) { + m_defaultColor = defaultColor; +} + +void BackgroundView::drawRect(KDContext * ctx, KDRect rect) const { + if(!m_isDataValid) { + ctx->fillRect(rect, m_defaultColor); + return; + } + + OBMHeader* h = (OBMHeader*)m_data; + + int yrectToImage = ctx->origin().y() - m_frame.y(); + int xrectToImage = ctx->origin().x() - m_frame.x(); + + for (int line = rect.y(); line <= rect.bottom(); line++) { + int offset = ((line + yrectToImage) * h->width) + (rect.x() + xrectToImage); + ctx->fillRectWithPixels(KDRect(rect.x(), line, rect.width(), 1), &(h->image_data) + offset, nullptr); + } +} + +void BackgroundView::updateDataValidity() { + if(m_data == nullptr || KDIonContext::sharedContext()->gammaEnabled) { + m_isDataValid = false; + return; + } + + OBMHeader* h = (OBMHeader*)m_data; + m_isDataValid = h->signature==466512775 && h->height==m_frame.height() && h->width==m_frame.width(); +} \ No newline at end of file diff --git a/escher/src/icon_view.cpp b/escher/src/icon_view.cpp new file mode 100644 index 00000000000..0637d62c67d --- /dev/null +++ b/escher/src/icon_view.cpp @@ -0,0 +1,79 @@ +#include +extern "C" { +#include +} +#include +#include + +IconView::IconView() : + View(), + m_image(nullptr) +{ +} + +constexpr static int iconBufferSize = 3080; +// Icon file is 55 x 56 = 3080 + +void IconView::drawRect(KDContext * ctx, KDRect rect) const { + const uint8_t* data; + size_t size; + + if (m_image != nullptr) { + assert(bounds().width() == m_image->width()); + assert(bounds().height() == m_image->height()); + assert(bounds().width() == 55); + assert(bounds().height() == 56); + + data = m_image->compressedPixelData(); + size = m_image->compressedPixelDataSize(); + } else if (m_data != nullptr) { + data = m_data; + size = m_dataLength; + } else { + return; + } + + KDColor pixelBuffer[iconBufferSize]; + assert(Ion::stackSafe()); // That's a VERY big buffer we're allocating on the stack + + Ion::decompress( + data, + reinterpret_cast(pixelBuffer), + size, + iconBufferSize * sizeof(KDColor) + ); + + //We push the first 6 lines of the image so that they are truncated on the sides + ctx->fillRectWithPixels(KDRect(6, 0, m_frame.width()-12, 1),pixelBuffer+6, nullptr); + ctx->fillRectWithPixels(KDRect(4, 1, m_frame.width()-8, 1),pixelBuffer+6+55, nullptr); + ctx->fillRectWithPixels(KDRect(3, 2, m_frame.width()-6, 1),pixelBuffer+3+(2*55), nullptr); + ctx->fillRectWithPixels(KDRect(2, 3, m_frame.width()-4, 1),pixelBuffer+2+(3*55), nullptr); + ctx->fillRectWithPixels(KDRect(1, 4, m_frame.width()-2, 1),pixelBuffer+1+(4*55), nullptr); + ctx->fillRectWithPixels(KDRect(1, 5, m_frame.width()-2, 1),pixelBuffer+1+(5*55), nullptr); + + //Then we push the rectangular part of the image + ctx->fillRectWithPixels(KDRect(0, 6, m_frame.width(), 44),pixelBuffer+(6*55), nullptr); + + //Finaly we push the last 5 lines of the image so that they are truncated on the sides + ctx->fillRectWithPixels(KDRect(1, 50, m_frame.width()-2, 1),pixelBuffer+1+(50*55), nullptr); + ctx->fillRectWithPixels(KDRect(1, 51, m_frame.width()-2, 1),pixelBuffer+1+(51*55), nullptr); + ctx->fillRectWithPixels(KDRect(2, 52, m_frame.width()-4, 1),pixelBuffer+2+(52*55), nullptr); + ctx->fillRectWithPixels(KDRect(3, 53, m_frame.width()-6, 1),pixelBuffer+3+(53*55), nullptr); + ctx->fillRectWithPixels(KDRect(4, 54, m_frame.width()-8, 1),pixelBuffer+4+(54*55), nullptr); + ctx->fillRectWithPixels(KDRect(6, 55, m_frame.width()-12, 1),pixelBuffer+6+(55*55), nullptr); +} + +void IconView::setImage(const uint8_t *data, size_t dataLength) { + if (data != m_data && dataLength != m_dataLength) { + m_data = data; + m_dataLength = dataLength; + markRectAsDirty(bounds()); + } +} + +void IconView::setImage(const Image * image) { + if (image != m_image) { + m_image = image; + markRectAsDirty(bounds()); + } +} diff --git a/escher/src/scroll_view.cpp b/escher/src/scroll_view.cpp index 9cb783d99b8..a653ae4cf92 100644 --- a/escher/src/scroll_view.cpp +++ b/escher/src/scroll_view.cpp @@ -9,12 +9,12 @@ extern "C" { ScrollView::ScrollView(View * contentView, ScrollViewDataSource * dataSource) : View(), m_contentView(contentView), + m_innerView(this), m_dataSource(dataSource), m_topMargin(0), m_rightMargin(0), m_bottomMargin(0), m_leftMargin(0), - m_innerView(this), m_decorators(), m_backgroundColor(Palette::BackgroundApps) { @@ -24,12 +24,12 @@ ScrollView::ScrollView(View * contentView, ScrollViewDataSource * dataSource) : ScrollView::ScrollView(ScrollView&& other) : m_contentView(other.m_contentView), + m_innerView(this), m_dataSource(other.m_dataSource), m_topMargin(other.m_topMargin), m_rightMargin(other.m_rightMargin), m_bottomMargin(other.m_bottomMargin), m_leftMargin(other.m_leftMargin), - m_innerView(this), m_backgroundColor(other.m_backgroundColor) { setDecoratorType(other.m_decoratorType); @@ -144,7 +144,7 @@ void ScrollView::layoutSubviews(bool force) { if (!r2.isEmpty()) { markRectAsDirty(r2); } - m_innerView.setFrame(innerFrame, force); + getInnerView()->setFrame(innerFrame, force); KDPoint absoluteOffset = contentOffset().opposite().translatedBy(KDPoint(m_leftMargin - innerFrame.x(), m_topMargin - innerFrame.y())); KDRect contentFrame = KDRect(absoluteOffset, contentSize()); m_contentView->setFrame(contentFrame, force); From dc8a1d48d9e29fd2fc87ddd8075f6d7047a1aadb Mon Sep 17 00:00:00 2001 From: Maxime FRIESS Date: Tue, 6 Jul 2021 16:23:28 +0200 Subject: [PATCH 017/355] [escher] Remove unnecessary include --- escher/include/escher/background_view.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/escher/include/escher/background_view.h b/escher/include/escher/background_view.h index c7db13c69a9..697d7f27d72 100644 --- a/escher/include/escher/background_view.h +++ b/escher/include/escher/background_view.h @@ -2,7 +2,6 @@ #define ESCHER_BACKGROUND_VIEW_H #include -#include class BackgroundView : public View { public: @@ -17,4 +16,4 @@ class BackgroundView : public View { KDColor m_defaultColor; }; -#endif \ No newline at end of file +#endif From 6fcab3952f85f9c858463815873454a529155a8e Mon Sep 17 00:00:00 2001 From: Laury Date: Thu, 11 Mar 2021 19:47:22 +0100 Subject: [PATCH 018/355] [python] Add some useful functions in kandinsky --- apps/code/catalog.de.i18n | 4 + apps/code/catalog.en.i18n | 4 + apps/code/catalog.es.i18n | 4 + apps/code/catalog.fr.i18n | 4 + apps/code/catalog.it.i18n | 4 + apps/code/catalog.nl.i18n | 4 + apps/code/catalog.pt.i18n | 4 + apps/code/catalog.universal.i18n | 4 + apps/code/python_toolbox.cpp | 10 ++- kandinsky/Makefile | 2 + kandinsky/include/kandinsky/context.h | 8 ++ kandinsky/src/context_circle.cpp | 49 ++++++++++++ kandinsky/src/context_polygon.cpp | 68 +++++++++++++++++ python/port/genhdr/qstrdefs.in.h | 4 + python/port/mod/kandinsky/modkandinsky.cpp | 74 +++++++++++++++++++ python/port/mod/kandinsky/modkandinsky.h | 4 + .../port/mod/kandinsky/modkandinsky_table.c | 8 ++ 17 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 kandinsky/src/context_circle.cpp create mode 100644 kandinsky/src/context_polygon.cpp diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index ab998c81bc6..5da1a10aa60 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -45,6 +45,8 @@ PythonCosh = "Hyperbolic cosine" PythonCount = "Count the occurrences of x" PythonDegrees = "Convert x from radians to degrees" PythonDivMod = "Quotient and remainder" +PythonDrawCircle = "Draw a circle" +PythonDrawLine = "Draw a line" PythonDrawString = "Display a text from pixel (x,y)" PythonErf = "Error function" PythonErfc = "Complementary error function" @@ -52,6 +54,8 @@ PythonEval = "Return the evaluated expression" PythonExp = "Exponential function" PythonExpm1 = "Compute exp(x)-1" PythonFabs = "Absolute value" +PythonFillCircle = "Fill a circle" +PythonFillPolygon = "Fill a polygon" PythonFillRect = "Fill a rectangle at pixel (x,y)" PythonFloat = "Convert x to a float" PythonFloor = "Floor" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index eaffc09a7b9..279c77ab48f 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -45,6 +45,8 @@ PythonCosh = "Hyperbolic cosine" PythonCount = "Count the occurrences of x" PythonDegrees = "Convert x from radians to degrees" PythonDivMod = "Quotient and remainder" +PythonDrawCircle = "Draw a circle" +PythonDrawLine = "Draw a line" PythonDrawString = "Display a text from pixel (x,y)" PythonErf = "Error function" PythonErfc = "Complementary error function" @@ -52,6 +54,8 @@ PythonEval = "Return the evaluated expression" PythonExp = "Exponential function" PythonExpm1 = "Compute exp(x)-1" PythonFabs = "Absolute value" +PythonFillCircle = "Fill a circle" +PythonFillPolygon = "Fill a polygon" PythonFillRect = "Fill a rectangle at pixel (x,y)" PythonFloat = "Convert x to a float" PythonFloor = "Floor" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index eaffc09a7b9..279c77ab48f 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -45,6 +45,8 @@ PythonCosh = "Hyperbolic cosine" PythonCount = "Count the occurrences of x" PythonDegrees = "Convert x from radians to degrees" PythonDivMod = "Quotient and remainder" +PythonDrawCircle = "Draw a circle" +PythonDrawLine = "Draw a line" PythonDrawString = "Display a text from pixel (x,y)" PythonErf = "Error function" PythonErfc = "Complementary error function" @@ -52,6 +54,8 @@ PythonEval = "Return the evaluated expression" PythonExp = "Exponential function" PythonExpm1 = "Compute exp(x)-1" PythonFabs = "Absolute value" +PythonFillCircle = "Fill a circle" +PythonFillPolygon = "Fill a polygon" PythonFillRect = "Fill a rectangle at pixel (x,y)" PythonFloat = "Convert x to a float" PythonFloor = "Floor" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index f98473ed823..2b288652b05 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -45,6 +45,8 @@ PythonCosh = "Cosinus hyperbolique" PythonCount = "Compte les occurrences de x" PythonDegrees = "Conversion de radians en degrés" PythonDivMod = "Quotient et reste" +PythonDrawCircle = "Trace un cercle" +PythonDrawLine = "Trace une ligne" PythonDrawString = "Affiche un texte au pixel (x,y)" PythonErf = "Fonction d'erreur" PythonErfc = "Fonction d'erreur complémentaire" @@ -52,6 +54,8 @@ PythonEval = "Evalue l'expression en argument " PythonExp = "Fonction exponentielle" PythonExpm1 = "Calcul de exp(x)-1" PythonFabs = "Valeur absolue" +PythonFillCircle = "Remplit un cercle" +PythonFillPolygon = "Remplit un polygone" PythonFillRect = "Remplit un rectangle" PythonFloat = "Conversion en flottant" PythonFloor = "Partie entière" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index 51223cbdcfb..a9a32222d90 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -45,6 +45,8 @@ PythonCosh = "Coseno iperbolico" PythonCount = "Conta le ricorrenze di x" PythonDegrees = "Conversione di radianti in gradi" PythonDivMod = "Quoziente e resto" +PythonDrawCircle = "Disegnare un cerchio" +PythonDrawLine = "Disegna una linea" PythonDrawString = "Visualizza il testo dal pixel x,y" PythonErf = "Funzione d'errore" PythonErfc = "Funzione d'errore complementare" @@ -52,6 +54,8 @@ PythonEval = "Valuta l'espressione nell'argomento " PythonExp = "Funzione esponenziale" PythonExpm1 = "Calcola exp(x)-1" PythonFabs = "Valore assoluto" +PythonFillCircle = "Riempire un cerchio" +PythonFillPolygon = "Riempire un poligono" PythonFillRect = "Riempie un rettangolo" PythonFloat = "Conversione in flottanti" PythonFloor = "Parte intera" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index 84be6987829..95fab0586f0 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -45,6 +45,8 @@ PythonCosh = "Cosinus hyperbolicus" PythonCount = "Tel voorkomen van x" PythonDegrees = "Zet x om van radialen naar graden" PythonDivMod = "Quotiënt en rest" +PythonDrawCircle = "Teken een cirkel" +PythonDrawLine = "Teken een lijn" PythonDrawString = "Geef een tekst weer van pixel (x,y)" PythonErf = "Error functie" PythonErfc = "Complementaire error functie" @@ -52,6 +54,8 @@ PythonEval = "Geef de geëvalueerde uitdrukking" PythonExp = "Exponentiële functie" PythonExpm1 = "Bereken exp(x)-1" PythonFabs = "Absolute waarde" +PythonFillCircle = "Vul een cirkel" +PythonFillPolygon = "Vul een veelhoek" PythonFillRect = "Vul een rechthoek bij pixel (x,y)" PythonFloat = "Zet x om in een float" PythonFloor = "Vloer" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index 4f50cadaab4..8820cfc432d 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -45,6 +45,8 @@ PythonCosh = "Cosseno hiperbólico" PythonCount = "Contar as ocorrências de x" PythonDegrees = "Converter x de radianos para graus" PythonDivMod = "Quociente e resto" +PythonDrawCircle = "Desenha um círculo" +PythonDrawLine = "Desenhe uma linha" PythonDrawString = "Mostrar o texto do pixel (x,y)" PythonErf = "Função erro" PythonErfc = "Função erro complementar" @@ -52,6 +54,8 @@ PythonEval = "Devolve a expressão avaliada" PythonExp = "Função exponencial" PythonExpm1 = "Calcular exp(x)-1" PythonFabs = "Valor absoluto" +PythonFillCircle = "Preencher um círculo" +PythonFillPolygon = "Preencher um polígono" PythonFillRect = "Preencher um retângulo em (x,y)" PythonFloat = "Converter x num flutuante" PythonFloor = "Parte inteira" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index ff0cfed7a3f..d950bf9ff1d 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -50,6 +50,8 @@ PythonCommandCount = "list.count(x)" PythonCommandCountWithoutArg = ".count(\x11)" PythonCommandDegrees = "degrees(x)" PythonCommandDivMod = "divmod(a,b)" +PythonCommandDrawCircle = "draw_circle(x,y,r,color)" +PythonCommandDrawLine = "draw_line(x1,y1,x2,y2,color)" PythonCommandDrawString = "draw_string(\"text\",x,y)" PythonCommandConstantE = "e" PythonCommandErf = "erf(x)" @@ -59,6 +61,8 @@ PythonCommandExp = "exp(x)" PythonCommandExpComplex = "exp(z)" PythonCommandExpm1 = "expm1(x)" PythonCommandFabs = "fabs(x)" +PythonCommandFillCircle = "fill_circle(x,y,r,color)" +PythonCommandFillPolygon = "fill_polygon([(x1,y1),...],color)" PythonCommandFillRect = "fill_rect(x,y,width,height,color)" PythonCommandFloat = "float(x)" PythonCommandFloor = "floor(x)" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index bcc0bef7b57..a5801bec28f 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -192,7 +192,11 @@ const ToolboxMessageTree KandinskyModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSetPixel, I18n::Message::PythonSetPixel), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColor, I18n::Message::PythonColor), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawString, I18n::Message::PythonDrawString), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillRect, I18n::Message::PythonFillRect) + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawLine, I18n::Message::PythonDrawLine), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawCircle, I18n::Message::PythonDrawCircle), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillRect, I18n::Message::PythonFillRect), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillCircle, I18n::Message::PythonFillCircle), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillPolygon, I18n::Message::PythonFillPolygon) }; const ToolboxMessageTree IonModuleChildren[] = { @@ -305,6 +309,8 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandCosh, I18n::Message::PythonCosh), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDegrees, I18n::Message::PythonDegrees), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDivMod, I18n::Message::PythonDivMod), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawCircle, I18n::Message::PythonDrawCircle), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawLine, I18n::Message::PythonDrawLine), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawString, I18n::Message::PythonDrawString), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandConstantE, I18n::Message::PythonConstantE, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandErf, I18n::Message::PythonErf), @@ -313,6 +319,8 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandExp, I18n::Message::PythonExp), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandExpm1, I18n::Message::PythonExpm1), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFabs, I18n::Message::PythonFabs), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillCircle, I18n::Message::PythonFillCircle), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillPolygon, I18n::Message::PythonFillPolygon), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillRect, I18n::Message::PythonFillRect), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFloat, I18n::Message::PythonFloat), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFloor, I18n::Message::PythonFloor), diff --git a/kandinsky/Makefile b/kandinsky/Makefile index 1e1ad24ff45..38776bfd459 100644 --- a/kandinsky/Makefile +++ b/kandinsky/Makefile @@ -2,8 +2,10 @@ SFLAGS += -Ikandinsky/include kandinsky_src += $(addprefix kandinsky/src/,\ color.cpp \ + context_circle.cpp \ context_line.cpp \ context_pixel.cpp \ + context_polygon.cpp \ context_rect.cpp \ context_text.cpp \ font.cpp \ diff --git a/kandinsky/include/kandinsky/context.h b/kandinsky/include/kandinsky/context.h index fbcc6bf4f38..846e4c10827 100644 --- a/kandinsky/include/kandinsky/context.h +++ b/kandinsky/include/kandinsky/context.h @@ -23,11 +23,19 @@ class KDContext { // Line. Not anti-aliased. void drawLine(KDPoint p1, KDPoint p2, KDColor c); + // Circle + void drawCircle(KDPoint c, KDCoordinate r, KDColor color); + void fillCircle(KDPoint c, KDCoordinate r, KDColor color); + // Rect void fillRect(KDRect rect, KDColor color); void fillRectWithPixels(KDRect rect, const KDColor * pixels, KDColor * workingBuffer); void blendRectWithMask(KDRect rect, KDColor color, const uint8_t * mask, KDColor * workingBuffer); void strokeRect(KDRect rect, KDColor color); + + //Polygon + static const int k_polygonMaxNumberOfPoints = 32; + void fillPolygon(KDCoordinate pointsX[], KDCoordinate pointsY[], int numberOfPoints, KDColor color); protected: KDContext(KDPoint origin, KDRect clippingRect) : m_origin(origin), diff --git a/kandinsky/src/context_circle.cpp b/kandinsky/src/context_circle.cpp new file mode 100644 index 00000000000..4e18c2b56b7 --- /dev/null +++ b/kandinsky/src/context_circle.cpp @@ -0,0 +1,49 @@ +#include +#include +#include + +void KDContext::drawCircle(KDPoint c, KDCoordinate r, KDColor color) { + + //The used algorithm is the Bresenham's arc tracing algorithm + + KDCoordinate x, y, m; + x = 0; + y = r; + m = 5 - 4*r; + + while(x <= y) { + setPixel(c.translatedBy(KDPoint(x,y)), color); + setPixel(c.translatedBy(KDPoint(y,x)), color); + setPixel(c.translatedBy(KDPoint(-x,y)), color); + setPixel(c.translatedBy(KDPoint(-y,x)), color); + setPixel(c.translatedBy(KDPoint(x,-y)), color); + setPixel(c.translatedBy(KDPoint(y,-x)), color); + setPixel(c.translatedBy(KDPoint(-x,-y)), color); + setPixel(c.translatedBy(KDPoint(-y,-x)), color); + + if(m > 0) { + y = y - 1 ; + m = m - 8*y ; + } + + x = x + 1 ; + m = m + 8*x + 4 ; + } +} + +void KDContext::fillCircle(KDPoint c, KDCoordinate r, KDColor color) { + KDCoordinate left = c.x()-r; + KDCoordinate right = c.x()+r; + if(left < 0) { + left = 0; + } + if(right > m_clippingRect.width()) { + right = m_clippingRect.width(); + } + + for(KDCoordinate x=left; x<=right; x++) { + KDCoordinate semiHeight = sqrt((r*r)-((c.x()-x)*(c.x()-x))); + + fillRect(KDRect(x, c.y()-semiHeight, 1, semiHeight * 2 ), color); + } +} diff --git a/kandinsky/src/context_polygon.cpp b/kandinsky/src/context_polygon.cpp new file mode 100644 index 00000000000..8c95ababbe2 --- /dev/null +++ b/kandinsky/src/context_polygon.cpp @@ -0,0 +1,68 @@ +#include +#include +#include + +void KDContext::fillPolygon(KDCoordinate pointsX[], KDCoordinate pointsY[], int numberOfPoints, KDColor color) +{ + //The used algorithm is the scan-line algorithm + + KDCoordinate top = KDCOORDINATE_MAX; + KDCoordinate bottom = KDCOORDINATE_MIN; + KDCoordinate right = m_clippingRect.width(); + KDCoordinate left = 0; + + for (int i = 0; i < numberOfPoints; i++) { + if (pointsY[i] < top) { + top = pointsY[i]; + } + if (pointsY[i] > bottom) { + bottom = pointsY[i]; + } + } + + if (top < 0) { + top = 0; + } + if (bottom > m_clippingRect.height()) { + bottom = m_clippingRect.height(); + } + + + for (int y=top; y<=bottom; y++) { + + int switches=0; + KDCoordinate switchesX[KDContext::k_polygonMaxNumberOfPoints]; + int lastPointIndex = numberOfPoints-1; + + for (int i=0; i=y) || (pointsY[lastPointIndex]< y && pointsY[i]>=y)) { + switchesX[switches++] = (int) round(pointsX[i]+1.0*(y-pointsY[i])/(pointsY[lastPointIndex]-pointsY[i])*(pointsX[lastPointIndex]-pointsX[i])); + } + lastPointIndex=i; + } + + //Sorting switches by a bubble sort + int i=0; + while (iswitchesX[i+1]) { + KDCoordinate temp = switchesX[i]; + switchesX[i]=switchesX[i+1]; + switchesX[i+1]=temp; + if (i) i--; + } + else { + i++; + } + } + + for (i=0; i=right) { + break; + } + + if (switchesX[i+1]>left) { + fillRect( KDRect( switchesX[ i ] , y, switchesX[ i+1 ] - switchesX[ i ], 1 ), color ) ; + } + } + } +} diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index fa1aa21b717..06ea392ca61 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -373,8 +373,12 @@ Q(KEY_EXE) // Kandinsky QSTRs Q(kandinsky) Q(color) +Q(draw_line) Q(draw_string) +Q(draw_circle) Q(fill_rect) +Q(fill_circle) +Q(fill_polygon) Q(get_pixel) Q(set_pixel) diff --git a/python/port/mod/kandinsky/modkandinsky.cpp b/python/port/mod/kandinsky/modkandinsky.cpp index f6f797bc7f0..a1d93bcc775 100644 --- a/python/port/mod/kandinsky/modkandinsky.cpp +++ b/python/port/mod/kandinsky/modkandinsky.cpp @@ -65,6 +65,34 @@ mp_obj_t modkandinsky_draw_string(size_t n_args, const mp_obj_t * args) { return mp_const_none; } +mp_obj_t modkandinsky_draw_line(size_t n_args, const mp_obj_t * args) { + mp_int_t x1 = mp_obj_get_int(args[0]); + mp_int_t y1 = mp_obj_get_int(args[1]); + mp_int_t x2 = mp_obj_get_int(args[2]); + mp_int_t y2 = mp_obj_get_int(args[3]); + KDPoint p1 = KDPoint(x1, y1); + KDPoint p2 = KDPoint(x2, y2); + KDColor color = MicroPython::Color::Parse(args[4]); + MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); + KDIonContext::sharedContext()->drawLine(p1, p2, color); + return mp_const_none; +} + +mp_obj_t modkandinsky_draw_circle(size_t n_args, const mp_obj_t * args) { + mp_int_t cx = mp_obj_get_int(args[0]); + mp_int_t cy = mp_obj_get_int(args[1]); + mp_int_t r = mp_obj_get_int(args[2]); + if(r<0) + { + r = -r; + } + KDPoint center = KDPoint(cx, cy); + KDColor color = MicroPython::Color::Parse(args[3]); + MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); + KDIonContext::sharedContext()->drawCircle(center, r, color); + return mp_const_none; +} + mp_obj_t modkandinsky_fill_rect(size_t n_args, const mp_obj_t * args) { mp_int_t x = mp_obj_get_int(args[0]); mp_int_t y = mp_obj_get_int(args[1]); @@ -84,3 +112,49 @@ mp_obj_t modkandinsky_fill_rect(size_t n_args, const mp_obj_t * args) { KDIonContext::sharedContext()->fillRect(rect, color); return mp_const_none; } + +mp_obj_t modkandinsky_fill_circle(size_t n_args, const mp_obj_t * args) { + mp_int_t cx = mp_obj_get_int(args[0]); + mp_int_t cy = mp_obj_get_int(args[1]); + mp_int_t r = mp_obj_get_int(args[2]); + if(r<0) + { + r = -r; + } + KDPoint center = KDPoint(cx, cy); + KDColor color = MicroPython::Color::Parse(args[3]); + MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); + KDIonContext::sharedContext()->fillCircle(center, r, color); + return mp_const_none; +} + +mp_obj_t modkandinsky_fill_polygon(size_t n_args, const mp_obj_t * args) { + KDCoordinate pointsX[KDContext::k_polygonMaxNumberOfPoints]; + KDCoordinate pointsY[KDContext::k_polygonMaxNumberOfPoints]; + + size_t itemLength; + mp_obj_t * items; + + mp_obj_get_array(args[0], &itemLength, &items); + + if (itemLength < 3) { + mp_raise_ValueError("polygon must have at least 3 points"); + } + else if (itemLength > KDContext::k_polygonMaxNumberOfPoints) { + mp_raise_ValueError("polygon is defined by too many points"); + } + + for(unsigned int i=0; idisplaySandbox(); + KDIonContext::sharedContext()->fillPolygon(pointsX, pointsY, itemLength, color); + return mp_const_none; +} \ No newline at end of file diff --git a/python/port/mod/kandinsky/modkandinsky.h b/python/port/mod/kandinsky/modkandinsky.h index 95f0366aa13..12252a81e78 100644 --- a/python/port/mod/kandinsky/modkandinsky.h +++ b/python/port/mod/kandinsky/modkandinsky.h @@ -4,4 +4,8 @@ mp_obj_t modkandinsky_color(size_t n_args, const mp_obj_t *args); mp_obj_t modkandinsky_get_pixel(mp_obj_t x, mp_obj_t y); mp_obj_t modkandinsky_set_pixel(mp_obj_t x, mp_obj_t y, mp_obj_t color); mp_obj_t modkandinsky_draw_string(size_t n_args, const mp_obj_t *args); +mp_obj_t modkandinsky_draw_line(size_t n_args, const mp_obj_t *args); +mp_obj_t modkandinsky_draw_circle(size_t n_args, const mp_obj_t *args); mp_obj_t modkandinsky_fill_rect(size_t n_args, const mp_obj_t *args); +mp_obj_t modkandinsky_fill_circle(size_t n_args, const mp_obj_t *args); +mp_obj_t modkandinsky_fill_polygon(size_t n_args, const mp_obj_t *args); \ No newline at end of file diff --git a/python/port/mod/kandinsky/modkandinsky_table.c b/python/port/mod/kandinsky/modkandinsky_table.c index ff916f915eb..862ad99e355 100644 --- a/python/port/mod/kandinsky/modkandinsky_table.c +++ b/python/port/mod/kandinsky/modkandinsky_table.c @@ -4,7 +4,11 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_color_obj, 1, 3, modkand STATIC MP_DEFINE_CONST_FUN_OBJ_2(modkandinsky_get_pixel_obj, modkandinsky_get_pixel); STATIC MP_DEFINE_CONST_FUN_OBJ_3(modkandinsky_set_pixel_obj, modkandinsky_set_pixel); STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_draw_string_obj, 3, 5, modkandinsky_draw_string); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_draw_line_obj, 5, 5, modkandinsky_draw_line); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_draw_circle_obj, 4, 4, modkandinsky_draw_circle); STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_fill_rect_obj, 5, 5, modkandinsky_fill_rect); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_fill_circle_obj, 4, 4, modkandinsky_fill_circle); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_fill_polygon_obj, 2, 2, modkandinsky_fill_polygon); STATIC const mp_rom_map_elem_t modkandinsky_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_kandinsky) }, @@ -12,7 +16,11 @@ STATIC const mp_rom_map_elem_t modkandinsky_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_get_pixel), (mp_obj_t)&modkandinsky_get_pixel_obj }, { MP_ROM_QSTR(MP_QSTR_set_pixel), (mp_obj_t)&modkandinsky_set_pixel_obj }, { MP_ROM_QSTR(MP_QSTR_draw_string), (mp_obj_t)&modkandinsky_draw_string_obj }, + { MP_ROM_QSTR(MP_QSTR_draw_line), (mp_obj_t)&modkandinsky_draw_line_obj }, + { MP_ROM_QSTR(MP_QSTR_draw_circle), (mp_obj_t)&modkandinsky_draw_circle_obj }, { MP_ROM_QSTR(MP_QSTR_fill_rect), (mp_obj_t)&modkandinsky_fill_rect_obj }, + { MP_ROM_QSTR(MP_QSTR_fill_circle), (mp_obj_t)&modkandinsky_fill_circle_obj }, + { MP_ROM_QSTR(MP_QSTR_fill_polygon), (mp_obj_t)&modkandinsky_fill_polygon_obj }, }; STATIC MP_DEFINE_CONST_DICT(modkandinsky_module_globals, modkandinsky_module_globals_table); From eb02fb5baa5a7d54543bdbf4f26a66ad5e3358b4 Mon Sep 17 00:00:00 2001 From: ArtichOwO Date: Wed, 14 Jul 2021 19:52:32 +0200 Subject: [PATCH 019/355] [ion/mac] Added missing source --- ion/src/simulator/macos/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/ion/src/simulator/macos/Makefile b/ion/src/simulator/macos/Makefile index a640a657187..cdc8015d63c 100644 --- a/ion/src/simulator/macos/Makefile +++ b/ion/src/simulator/macos/Makefile @@ -15,6 +15,7 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ collect_registers.cpp \ haptics.cpp \ journal.cpp \ + store_script.cpp \ ) ifeq ($(EPSILON_TELEMETRY),1) From fb4fc4862d74ef88868e15470772bb0761627b94 Mon Sep 17 00:00:00 2001 From: Laury Date: Tue, 31 Aug 2021 15:48:11 +0200 Subject: [PATCH 020/355] [python] Enable operator overload --- python/port/genhdr/qstrdefs.in.h | 44 ++++++++++++++++++++++++++++++++ python/src/py/mpconfig.h | 10 ++++---- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index 7d65a07c2f3..20dec6a7ac3 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -49,6 +49,7 @@ Q(GeneratorExit) Q(ImportError) Q(IndentationError) Q(IndexError) +Q(keepends) Q(KeyError) Q(KeyboardInterrupt) Q(LookupError) @@ -67,47 +68,88 @@ Q(UnicodeError) Q(ValueError) Q(ZeroDivisionError) Q(_0x0a_) +Q(__abs__) Q(__add__) +Q(__and__) Q(__bool__) Q(__build_class__) Q(__call__) Q(__class__) Q(__contains__) Q(__delitem__) +Q(__divmod__) Q(__enter__) Q(__eq__) Q(__exit__) +Q(__floordiv__) Q(__ge__) Q(__getattr__) Q(__getitem__) Q(__gt__) Q(__hash__) Q(__iadd__) +Q(__iand__) +Q(__ifloordiv__) +Q(__ilshift__) +Q(__imatmul__) +Q(__imod__) Q(__import__) +Q(__imul__) Q(__init__) Q(__int__) +Q(__invert__) +Q(__ior__) +Q(__ipow__) +Q(__irshift__) Q(__isub__) Q(__iter__) +Q(__itruediv__) +Q(__ixor__) Q(__le__) Q(__len__) +Q(__lshift__) Q(__lt__) Q(__main__) +Q(__matmul__) +Q(__mod__) Q(__module__) +Q(__mul__) Q(__name__) #if __EMSCRIPTEN__ Q(__ne__) #endif +Q(__neg__) Q(__new__) Q(__next__) +Q(__or__) Q(__path__) +Q(__pos__) +Q(__pow__) Q(__qualname__) +Q(__radd__) +Q(__rand__) Q(__repl_print__) Q(__repr__) Q(__reversed__) +Q(__rfloordiv__) +Q(__rlshift__) +Q(__rmatmul__) +Q(__rmod__) +Q(__rmul__) +Q(__ror__) +Q(__rpow__) +Q(__rrshift__) +Q(__rshift__) +Q(__rsub__) +Q(__rtruediv__) +Q(__rxor__) Q(__setitem__) Q(__str__) Q(__sub__) +Q(__sub__) Q(__traceback__) +Q(__truediv__) +Q(__xor__) Q(_brace_open__colon__hash_b_brace_close_) Q(_lt_dictcomp_gt_) Q(_lt_genexpr_gt_) @@ -141,6 +183,7 @@ Q(bytecode) Q(bytes) Q(callable) Q(ceil) +Q(center) Q(choice) Q(chr) Q(classmethod) @@ -293,6 +336,7 @@ Q(slice) Q(sort) Q(sorted) Q(split) +Q(splitlines) Q(sqrt) Q(start) Q(startswith) diff --git a/python/src/py/mpconfig.h b/python/src/py/mpconfig.h index 1e786f753b6..7c213c6688b 100644 --- a/python/src/py/mpconfig.h +++ b/python/src/py/mpconfig.h @@ -830,7 +830,7 @@ typedef double mp_float_t; // Whether str.center() method provided #ifndef MICROPY_PY_BUILTINS_STR_CENTER -#define MICROPY_PY_BUILTINS_STR_CENTER (0) +#define MICROPY_PY_BUILTINS_STR_CENTER (1) #endif // Whether str.count() method provided @@ -850,7 +850,7 @@ typedef double mp_float_t; // Whether str.splitlines() method provided #ifndef MICROPY_PY_BUILTINS_STR_SPLITLINES -#define MICROPY_PY_BUILTINS_STR_SPLITLINES (0) +#define MICROPY_PY_BUILTINS_STR_SPLITLINES (1) #endif // Whether to support bytearray object @@ -934,20 +934,20 @@ typedef double mp_float_t; // "Reverse" methods are controlled by // MICROPY_PY_REVERSE_SPECIAL_METHODS below. #ifndef MICROPY_PY_ALL_SPECIAL_METHODS -#define MICROPY_PY_ALL_SPECIAL_METHODS (0) +#define MICROPY_PY_ALL_SPECIAL_METHODS (1) #endif // Whether to support all inplace arithmetic operarion methods // (__imul__, etc.) #ifndef MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS -#define MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS (0) +#define MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS (1) #endif // Whether to support reverse arithmetic operarion methods // (__radd__, etc.). Additionally gated by // MICROPY_PY_ALL_SPECIAL_METHODS. #ifndef MICROPY_PY_REVERSE_SPECIAL_METHODS -#define MICROPY_PY_REVERSE_SPECIAL_METHODS (0) +#define MICROPY_PY_REVERSE_SPECIAL_METHODS (1) #endif // Whether to support compile function From 210a83b04d78ee7bd51d22ec90934457f566f9ff Mon Sep 17 00:00:00 2001 From: Laury Date: Tue, 31 Aug 2021 16:17:36 +0200 Subject: [PATCH 021/355] [python/kandinsky] Added get_palette() function --- apps/code/catalog.de.i18n | 61 ++++++++++--------- apps/code/catalog.en.i18n | 1 + apps/code/catalog.es.i18n | 1 + apps/code/catalog.fr.i18n | 1 + apps/code/catalog.hu.i18n | 1 + apps/code/catalog.it.i18n | 1 + apps/code/catalog.nl.i18n | 1 + apps/code/catalog.pt.i18n | 1 + apps/code/catalog.universal.i18n | 1 + apps/code/python_toolbox.cpp | 3 +- python/port/genhdr/qstrdefs.in.h | 7 +++ python/port/mod/kandinsky/modkandinsky.cpp | 13 ++++ python/port/mod/kandinsky/modkandinsky.h | 1 + .../port/mod/kandinsky/modkandinsky_table.c | 2 + 14 files changed, 64 insertions(+), 31 deletions(-) diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index b002d902569..1721b0feaf4 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -63,6 +63,7 @@ PythonFloor = "Floor" PythonFmod = "a modulo b" PythonFrExp = "Mantissa and exponent of x: (m,e)" PythonGamma = "Gamma function" +PythonGetPalette = "Themenpalette erhalten" PythonGetPixel = "Return pixel (x,y) color" PythonGetrandbits = "Integer with k random bits" PythonGrid = "Toggle the visibility of the grid" @@ -101,36 +102,36 @@ PythonMathFunction = "math module function prefix" PythonMatplotlibPyplotFunction = "matplotlib.pyplot module prefix" PythonMax = "Maximum" PythonMin = "Minimum" -PythonModf = "Fractional and integer parts of x" -PythonMonotonic = "Value of a monotonic clock" -PythonOct = "Convert integer to octal" -PythonPhase = "Phase of z" -PythonPlot = "Plot y versus x as lines" -PythonPolar = "z in polar coordinates" -PythonPop = "Remove and return the last item" -PythonPower = "x raised to the power y" -PythonPrint = "Print object" -PythonRadians = "Convert x from degrees to radians" -PythonRandint = "Random integer in [a,b]" -PythonRandom = "Floating point number in [0,1[" -PythonRandomFunction = "random module function prefix" -PythonRandrange = "Random number in range(start,stop)" -PythonRangeStartStop = "List from start to stop-1" -PythonRangeStop = "List from 0 to stop-1" -PythonRect = "Convert to cartesian coordinates" -PythonRemove = "Remove the first occurrence of x" -PythonReverse = "Reverse the elements of the list" -PythonRound = "Round to n digits" -PythonScatter = "Draw a scatter plot of y versus x" -PythonSeed = "Initialize random number generator" -PythonSetPixel = "Color pixel (x,y)" -PythonShow = "Display the figure" -PythonSin = "Sine" -PythonSinh = "Hyperbolic sine" -PythonSleep = "Suspend the execution for t seconds" -PythonSort = "Sort the list" -PythonSqrt = "Wurzel" -PythonSum = "Sum the items of a list" +PythonModf = "Bruch- und Ganzzahl-Anteile von x" +PythonMonotonic = "Wert einer monotonen Uhr" +PythonOct = "Ganzzahl in Oktal umwandeln" +PythonPhase = "Phase von z" +PythonPlot = "Plotten von y gegen x als Linien" +PythonPolar = "z in Polarkoordinaten" +PythonPop = "Letztes Element abnehmen" +PythonPower = "x erhöht mit der Potenz y" +PythonPrint = "Objekt drucken" +PythonRadians = "x von Grad in Bogenmaß umrechnen" +PythonRandint = "Zufällige Ganzzahl in [a,b]" +PythonRandom = "Fließkommazahl in [0,1]" +PythonRandomFunction = "Random-Modul Funktionspräfix" +PythonRandrange = "Zufallszahl im Bereich(start,stop)" +PythonRangeStartStop = "Liste von Start bis Stop-1" +PythonRangeStop = "Liste von 0 bis Stop-1" +PythonRect = "In kartesische Koordinaten" +PythonRemove = "Entferne das erste Vorkommen von x" +PythonReverse = "Kehrt die Elemente der Liste um" +PythonRound = "Runden auf n Stellen" +PythonScatter = "Streudiagramm von y gg. x zeichnen" +PythonSeed = "Zufallszahlengenerator initiieren" +PythonSetPixel = "Pixel (x,y) einfärben" +PythonShow = "Figur anzeigen" +PythonSin = "Sinus" +PythonSinh = "Hyperbolischer Sinus" +PythonSleep = "Ausführung aussetzen für t Sekunden" +PythonSort = "Die Liste sortieren" +PythonSqrt = "Quadratwurzel" +PythonSum = "Summe der Elemente einer Liste" PythonTan = "Tangens" PythonTanh = "Hyperbolic tangent" PythonText = "Display a text at (x,y) coordinates" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index 458ec1eeb35..1a826ecf0fd 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -62,6 +62,7 @@ PythonFloor = "Floor" PythonFmod = "a modulo b" PythonFrExp = "Mantissa and exponent of x: (m,e)" PythonGamma = "Gamma function" +PythonGetPalette = "Get theme palette" PythonGetPixel = "Return pixel (x,y) color" PythonGetrandbits = "Integer with k random bits" PythonGrid = "Toggle the visibility of the grid" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index 4ac5de2871a..ff6de6a865e 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -62,6 +62,7 @@ PythonFloor = "Floor" PythonFmod = "a modulo b" PythonFrExp = "Mantissa and exponent of x: (m,e)" PythonGamma = "Gamma function" +PythonGetPalette = "Get theme palette" PythonGetPixel = "Return pixel (x,y) color" PythonGetrandbits = "Integer with k random bits" PythonGrid = "Toggle the visibility of the grid" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index e3c3c22b879..05725e9c7a4 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -62,6 +62,7 @@ PythonFloor = "Partie entière" PythonFmod = "a modulo b" PythonFrExp = "Mantisse et exposant de x : (m,e)" PythonGamma = "Fonction gamma" +PythonGetPalette = "Obtient la palette du thème" PythonGetPixel = "Renvoie la couleur du pixel (x,y)" PythonGetrandbits = "Nombre aléatoire sur k bits" PythonGrid = "Affiche ou masque la grille" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index 2749d3789d9..a9349ac9cb9 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -62,6 +62,7 @@ PythonFloor = "Egész része" PythonFmod = "a modulo b" PythonFrExp = "X mantissája és kiállítója" PythonGamma = "Gamma funkció" +PythonGetPalette = "Téma paletta beszerzése" PythonGetPixel = "Visszatéríti (x,y) színét" PythonGetrandbits = "Váletlenszám visszatérítése k biten" PythonGrid = "Rács megjelenítése/elrejtése" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index e4fdd0f26ef..9cd02959ab5 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -62,6 +62,7 @@ PythonFloor = "Parte intera" PythonFmod = "a modulo b" PythonFrExp = "Mantissa ed esponente di x : (m,e)" PythonGamma = "Funzione gamma" +PythonGetPalette = "Ottieni la tavolozza del tema" PythonGetPixel = "Restituisce colore del pixel(x,y)" PythonGetrandbits = "Numero aleatorio con k bit" PythonGrid = "Attiva la visibilità della griglia" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index 8a8d4265b97..31b2f1d60f3 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -62,6 +62,7 @@ PythonFloor = "Vloer" PythonFmod = "a modulo b" PythonFrExp = "Mantisse en exponent van x: (m,e)" PythonGamma = "Gammafunctie" +PythonGetPalette = "Thema palet krijgen" PythonGetPixel = "Geef pixel (x,y) kleur (rgb)" PythonGetrandbits = "Integer met k willekeurige bits" PythonGrid = "Verander zichtbaarheid raster" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index f432b28ceb6..a06882d6a04 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -62,6 +62,7 @@ PythonFloor = "Parte inteira" PythonFmod = "a módulo b" PythonFrExp = "Coeficiente e expoente de x: (m, e)" PythonGamma = "Função gama" +PythonGetPalette = "Obter paleta temática" PythonGetPixel = "Devolve a cor do pixel (x,y)" PythonGetrandbits = "Número inteiro aleatório com k bits" PythonGrid = "Alterar visibilidade da grelha" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index e6fa758a643..1b5b3684b58 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -69,6 +69,7 @@ PythonCommandFloor = "floor(x)" PythonCommandFmod = "fmod(a,b)" PythonCommandFrExp = "frexp(x)" PythonCommandGamma = "gamma(x)" +PythonCommandGetPalette = "get_palette()" PythonCommandGetPixel = "get_pixel(x,y)" PythonCommandGetrandbits = "getrandbits(k)" PythonCommandGrid = "grid()" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 0036acdb358..c11a0963211 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -197,7 +197,8 @@ const ToolboxMessageTree KandinskyModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawCircle, I18n::Message::PythonDrawCircle), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillRect, I18n::Message::PythonFillRect), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillCircle, I18n::Message::PythonFillCircle), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillPolygon, I18n::Message::PythonFillPolygon) + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillPolygon, I18n::Message::PythonFillPolygon), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetPalette, I18n::Message::PythonGetPalette) }; const ToolboxMessageTree IonModuleChildren[] = { diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index 20dec6a7ac3..65478063fd4 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -429,6 +429,13 @@ Q(large_font) Q(small_font) Q(wait_vblank) Q(get_keys) +Q(get_palette) + +Q(PrimaryText) +Q(SecondaryText) +Q(AccentText) +Q(Toolbar) +Q(HomeBackground) // Keys QSTRs Q(left) diff --git a/python/port/mod/kandinsky/modkandinsky.cpp b/python/port/mod/kandinsky/modkandinsky.cpp index ff55f3f1595..29218376c27 100644 --- a/python/port/mod/kandinsky/modkandinsky.cpp +++ b/python/port/mod/kandinsky/modkandinsky.cpp @@ -2,9 +2,11 @@ extern "C" { #include "modkandinsky.h" #include } +#include #include #include #include "port.h" +#include static mp_obj_t TupleForKDColor(KDColor c) { @@ -245,3 +247,14 @@ mp_obj_t modkandinsky_get_keys() { return result; } + +mp_obj_t modkandinsky_get_palette() { + mp_obj_t modkandinsky_palette_table = mp_obj_new_dict(0); + mp_obj_dict_store(modkandinsky_palette_table, MP_ROM_QSTR(MP_QSTR_PrimaryText), TupleForKDColor(Palette::PrimaryText)); + mp_obj_dict_store(modkandinsky_palette_table, MP_ROM_QSTR(MP_QSTR_SecondaryText), TupleForKDColor(Palette::SecondaryText)); + mp_obj_dict_store(modkandinsky_palette_table, MP_ROM_QSTR(MP_QSTR_AccentText), TupleForKDColor(Palette::AccentText)); + mp_obj_dict_store(modkandinsky_palette_table, MP_ROM_QSTR(MP_QSTR_Toolbar), TupleForKDColor(Palette::Toolbar)); + mp_obj_dict_store(modkandinsky_palette_table, MP_ROM_QSTR(MP_QSTR_HomeBackground), TupleForKDColor(Palette::HomeBackground)); + + return modkandinsky_palette_table; +} diff --git a/python/port/mod/kandinsky/modkandinsky.h b/python/port/mod/kandinsky/modkandinsky.h index 4d4024aef4a..3ce71155adb 100644 --- a/python/port/mod/kandinsky/modkandinsky.h +++ b/python/port/mod/kandinsky/modkandinsky.h @@ -11,3 +11,4 @@ mp_obj_t modkandinsky_fill_circle(size_t n_args, const mp_obj_t *args); mp_obj_t modkandinsky_fill_polygon(size_t n_args, const mp_obj_t *args); mp_obj_t modkandinsky_wait_vblank(); mp_obj_t modkandinsky_get_keys(); +mp_obj_t modkandinsky_get_palette(); diff --git a/python/port/mod/kandinsky/modkandinsky_table.c b/python/port/mod/kandinsky/modkandinsky_table.c index 1cc76c73c7e..ce962e4c1b6 100644 --- a/python/port/mod/kandinsky/modkandinsky_table.c +++ b/python/port/mod/kandinsky/modkandinsky_table.c @@ -11,6 +11,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_fill_circle_obj, 4, 4, m STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_fill_polygon_obj, 2, 2, modkandinsky_fill_polygon); STATIC MP_DEFINE_CONST_FUN_OBJ_0(modkandinsky_wait_vblank_obj, modkandinsky_wait_vblank); STATIC MP_DEFINE_CONST_FUN_OBJ_0(modkandinsky_get_keys_obj, modkandinsky_get_keys); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(modkandinsky_get_palette_obj, modkandinsky_get_palette); STATIC const mp_rom_map_elem_t modkandinsky_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_kandinsky) }, @@ -27,6 +28,7 @@ STATIC const mp_rom_map_elem_t modkandinsky_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_small_font), mp_const_false }, { MP_ROM_QSTR(MP_QSTR_wait_vblank), (mp_obj_t)&modkandinsky_wait_vblank_obj }, { MP_ROM_QSTR(MP_QSTR_get_keys), (mp_obj_t)&modkandinsky_get_keys_obj }, + { MP_ROM_QSTR(MP_QSTR_get_palette), (mp_obj_t)&modkandinsky_get_palette_obj }, }; STATIC MP_DEFINE_CONST_DICT(modkandinsky_module_globals, modkandinsky_module_globals_table); From c0739b0e062cd0c531d0dbade7ccea5ea9d1e9fe Mon Sep 17 00:00:00 2001 From: Laury Date: Tue, 31 Aug 2021 19:23:09 +0200 Subject: [PATCH 022/355] [atomic] updated submodule --- .gitmodules | 2 +- apps/atomic | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 02641c8d556..9725262b56a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/Omega-Numworks/Omega-RPN.git [submodule "apps/atomic"] path = apps/atomic - url = https://github.com/Omega-Numworks/Omega-Atomic.git + url = https://github.com/Lauryy06/atomic diff --git a/apps/atomic b/apps/atomic index 64f2e38ed1e..2a621d6d48e 160000 --- a/apps/atomic +++ b/apps/atomic @@ -1 +1 @@ -Subproject commit 64f2e38ed1e4b8a896b0488039fb866b7015ff3f +Subproject commit 2a621d6d48e98595f86628ce9fc479e8d6e4ac17 From 7b4a94296bd5879e4496d5139c0b93f535117b96 Mon Sep 17 00:00:00 2001 From: Laury Date: Wed, 1 Sep 2021 17:31:51 +0200 Subject: [PATCH 023/355] [calculation][poincare] Revert 'b8544e3' and improve equal simplification --- apps/calculation/app.cpp | 2 +- apps/shared/function_app.cpp | 10 +++++++--- apps/shared/text_field_delegate_app.cpp | 8 +++++++- apps/solver/app.cpp | 6 ------ apps/solver/app.h | 3 --- poincare/include/poincare/equal.h | 2 +- poincare/src/equal.cpp | 16 ++++++---------- 7 files changed, 22 insertions(+), 25 deletions(-) diff --git a/apps/calculation/app.cpp b/apps/calculation/app.cpp index 2c5c97d3c87..ad344c31687 100644 --- a/apps/calculation/app.cpp +++ b/apps/calculation/app.cpp @@ -72,7 +72,7 @@ bool App::isAcceptableExpression(const Poincare::Expression expression) { return false; } } - return !(expression.isUninitialized() || expression.type() == ExpressionNode::Type::Equal); + return !expression.isUninitialized(); } void App::didBecomeActive(Window * window) { diff --git a/apps/shared/function_app.cpp b/apps/shared/function_app.cpp index fbdc4f43aeb..a63ca328430 100644 --- a/apps/shared/function_app.cpp +++ b/apps/shared/function_app.cpp @@ -31,9 +31,13 @@ void FunctionApp::willBecomeInactive() { ::App::willBecomeInactive(); } - -bool FunctionApp::isAcceptableExpression(const Expression exp) { - return TextFieldDelegateApp::isAcceptableExpression(exp) && ExpressionCanBeSerialized(exp, false, Expression(), localContext()); +bool FunctionApp::isAcceptableExpression(const Poincare::Expression expression) { + /* We forbid functions whose type is equal because the input "2+f(3)" would be + * simplify to an expression with an nested equal node which makes no sense. */ + if (!TextFieldDelegateApp::ExpressionCanBeSerialized(expression, false, Expression(), localContext()) || expression.type() == ExpressionNode::Type::Equal) { + return false; + } + return TextFieldDelegateApp::isAcceptableExpression(expression); } } diff --git a/apps/shared/text_field_delegate_app.cpp b/apps/shared/text_field_delegate_app.cpp index e66a874224d..ece157f9bd9 100644 --- a/apps/shared/text_field_delegate_app.cpp +++ b/apps/shared/text_field_delegate_app.cpp @@ -85,7 +85,13 @@ bool TextFieldDelegateApp::isFinishingEvent(Ion::Events::Event event) { } bool TextFieldDelegateApp::isAcceptableExpression(const Expression exp) { - return !(exp.isUninitialized() || exp.type() == ExpressionNode::Type::Store || exp.type() == ExpressionNode::Type::Equal); + if (exp.isUninitialized()) { + return false; + } + if (exp.type() == ExpressionNode::Type::Store) { + return false; + } + return true; } bool TextFieldDelegateApp::ExpressionCanBeSerialized(const Expression expression, bool replaceAns, Expression ansExpression, Context * context) { diff --git a/apps/solver/app.cpp b/apps/solver/app.cpp index 11f96a32ae6..1c88b7df52c 100644 --- a/apps/solver/app.cpp +++ b/apps/solver/app.cpp @@ -68,10 +68,4 @@ void App::willBecomeInactive() { ::App::willBecomeInactive(); } - -bool App::isAcceptableExpression(const Poincare::Expression exp) { - return TextFieldDelegateApp::ExpressionCanBeSerialized(exp, false, Poincare::Expression(), localContext()) - && !(exp.isUninitialized() || exp.type() == Poincare::ExpressionNode::Type::Store); -} - } diff --git a/apps/solver/app.h b/apps/solver/app.h index a4919978e1b..898c3c6b804 100644 --- a/apps/solver/app.h +++ b/apps/solver/app.h @@ -47,9 +47,6 @@ class App : public Shared::ExpressionFieldDelegateApp { void willBecomeInactive() override; TELEMETRY_ID("Solver"); private: - // TextFieldDelegateApp - bool isAcceptableExpression(const Poincare::Expression expression) override; - App(Snapshot * snapshot); SolutionsController m_solutionsController; IntervalController m_intervalController; diff --git a/poincare/include/poincare/equal.h b/poincare/include/poincare/equal.h index fa067652f8e..1f61b2c579b 100644 --- a/poincare/include/poincare/equal.h +++ b/poincare/include/poincare/equal.h @@ -41,7 +41,7 @@ class Equal final : public Expression { // For the equation A = B, create the reduced expression A-B Expression standardEquation(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::ReductionTarget reductionTarget) const; // Expression - Expression shallowReduce(); + Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); }; } diff --git a/poincare/src/equal.cpp b/poincare/src/equal.cpp index 4468a2e32d4..3e8ca67bd27 100644 --- a/poincare/src/equal.cpp +++ b/poincare/src/equal.cpp @@ -23,7 +23,7 @@ extern "C" { namespace Poincare { Expression EqualNode::shallowReduce(ReductionContext reductionContext) { - return Equal(this).shallowReduce(); + return Equal(this).shallowReduce(reductionContext); } Layout EqualNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { @@ -49,19 +49,15 @@ Expression Equal::standardEquation(Context * context, Preferences::ComplexFormat return sub.reduce(ExpressionNode::ReductionContext(context, complexFormat, angleUnit, unitFormat, reductionTarget)); } -Expression Equal::shallowReduce() { - { - Expression e = Expression::defaultShallowReduce(); - if (e.isUndefined()) { - return e; - } - } - if (childAtIndex(0).isIdenticalTo(childAtIndex(1))) { +Expression Equal::shallowReduce(ExpressionNode::ReductionContext reductionContext) { + + Expression e = Equal::Builder(Subtraction::Builder(childAtIndex(0).clone(), childAtIndex(1).clone()).shallowReduce(reductionContext), Rational::Builder(0)); + if (e.childAtIndex(0).isIdenticalTo(e.childAtIndex(1))) { Expression result = Rational::Builder(1); replaceWithInPlace(result); return result; } - return *this; + return e; } } From 0df76f1680ada11199b930d468b7581973a31cb4 Mon Sep 17 00:00:00 2001 From: Laporte <40714786+Laporte12974@users.noreply.github.com> Date: Wed, 1 Sep 2021 17:58:06 +0200 Subject: [PATCH 024/355] [readme] Update readme * Update README.fr.md * Update README.md --- README.fr.md | 74 +++++++++++++++++++++++++++------------------------- README.md | 63 ++++++++++++++++++++++---------------------- 2 files changed, 70 insertions(+), 67 deletions(-) diff --git a/README.fr.md b/README.fr.md index 0eda582df08..4ee47639e10 100644 --- a/README.fr.md +++ b/README.fr.md @@ -1,37 +1,34 @@ -

+

cc by-nc-sa 4.0 - Issues + Issues
- Discord + Discord

> Don't understand french ? speak english ? here's the [english README](./README.md) ! ## À propos -Omega est un fork d'Epsilon, l'OS de Numworks tournant sur les calculatrices du même nom, qui apporte beaucoup de fonctionnalités en plus. Omega est fait pour ceux qui aimeraient ajouter certaines fonctionnalités ayant été rejetées par Numworks à leurs calculatrices (pour des raisons 100% compréhensibles !). [Essayez en ligne](https://getomega.web.app/simulator). +Upsilon est un fork d'Omega, un fork d'Epsilon, l'OS de Numworks tournant sur les calculatrices du même nom, qui apporte beaucoup de fonctionnalités en plus, mais qui fut archivé et fermé pour des raisons légales après un changement de politique de Numworks. Upsilon est fait pour ceux qui aimeraient voir un futur pour les OS créées par les utilisateurs pour Numworks, même après l'arrèt du projet initial. ### Quelques fonctionnalités supplémentaires -- Retour du calcul littéral -- Une application RPN -- Application Externes +- RDes améliorations du module python Kandinsky +- Un support pour fonds d'écrans personnalisés +- Des applications externes - Des thèmes -- Python amélioré (module os, méthode open...) -- Un tableau périodique et toutes les masses molaires des éléments dans la toolbox -- *Ainsi que d'autres à découvrir...* [Changelogs complets](https://github.com/Omega-Numworks/Omega/wiki/Changelog) | [Fonctionnalités princpales & captures d'écran](https://github.com/Omega-Numworks/Omega/wiki/Main-features). +- Des améliorations pour python +- Un tableau périodique légèrement modifié +- L'utilisation du signe "=" +- *Ainsi que tout ce qui a été ajouté sur Omega, et bien plus...* [Changelogs complets d'Omega](https://github.com/Omega-Numworks/Omega/wiki/Changelog) | [Fonctionnalités principales d'Omega & captures d'écran](https://github.com/Omega-Numworks/Omega/wiki/Main-features). ## Installation -### Automatique - -Vous pouvez installer Omega automatiquement depuis [notre site](https://getomega.web.app/) sur la page "installer". - -

Omega Banner Discord

- ### Manuelle +*A l'heure actuelle, seule l'installation manuelle est possible.* + Tout d'abord, suivez **la première étape** [ici](https://www.numworks.com/resources/engineering/software/build/), puis :
@@ -40,15 +37,15 @@ Tout d'abord, suivez **la première étape** [ici](https://www.numworks.com/reso (note : vous pouvez changer `EPSILON_I18N=fr` en `en`, `nl`, `pt`, `it`, `de`, `es` ou `hu`). ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega +git clone --recursive https://github.com/Lauryy06/Upsilon.git +cd Upsilon git checkout omega-master make MODEL=n0100 clean make MODEL=n0100 EPSILON_I18N=fr OMEGA_USERNAME="{Votre nom ici, 15 caractères max}" -j4 make MODEL=n0100 epsilon_flash ``` -Important : N'oubliez pas l'argument `--recursive`, Omega a besoin de sous-modules. +Important : N'oubliez pas l'argument `--recursive`, Upsilon a besoin de sous-modules. Vous pouvez aussi changer le nombre de processus parallèles pendant la compilation en changeant la valeur suivant `-j`.
@@ -57,15 +54,15 @@ Vous pouvez aussi changer le nombre de processus parallèles pendant la compilat Modèle n0110 ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega +git clone --recursive https://github.com/Lauryy06/Upsilon.git +cd Upsilon git checkout omega-master make clean make OMEGA_USERNAME="{Votre nom ici, 15 caractères max}" -j4 make epsilon_flash ``` -Important : N'oubliez pas l'argument `--recursive`, Omega a besoin de sous-modules. +Important : N'oubliez pas l'argument `--recursive`, Upsilon a besoin de sous-modules. Vous pouvez aussi changer le nombre de processus parallèles pendant la compilation en changeant la valeur suivant `-j`. @@ -73,11 +70,11 @@ Vous pouvez aussi changer le nombre de processus parallèles pendant la compilat
Fichiers binaires -Ces fichiers peuvent être utilisés pour distribuer Omega (pour que tout le monde puisse le flasher via [Webdfu_Numworks](https://ti-planet.github.io/webdfu_numworks/)). +Ces fichiers peuvent être utilisés pour distribuer Upsilon (pour que tout le monde puisse le flasher via [Webdfu_Numworks](https://ti-planet.github.io/webdfu_numworks/)). ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega +git clone --recursive https://github.com/Lauryy06/Upsilon.git +cd Upsilon git checkout omega-master make clean make MODEL=n0100 OMEGA_USERNAME="" -j8 @@ -86,7 +83,7 @@ make OMEGA_USERNAME="" -j8 make OMEGA_USERNAME="" binpack -j8 ``` -Important : N'oubliez pas l'argument `--recursive`, Omega a besoin de sous-modules. +Important : N'oubliez pas l'argument `--recursive`, Upsilon a besoin de sous-modules. Vous pouvez aussi changer le nombre de processus parallèles pendant la compilation en changeant la valeur suivant `-j`.
@@ -104,11 +101,11 @@ cd emsdk source emsdk_env.sh ``` -Puis, compilez Omega : +Puis, compilez Upsilon : ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega +git clone --recursive https://github.com/Lauryy06/Upsilon.git +cd Upsilon git checkout omega-master make clean make PLATFORM=simulator TARGET=web OMEGA_USERNAME="{Votre nom ici, 15 caractères max}" -j4 @@ -116,7 +113,7 @@ make PLATFORM=simulator TARGET=web OMEGA_USERNAME="{Votre nom ici, 15 caractère Le simulateur se trouve dans `output/release/simulator/web/simulator.zip` -Important : N'oubliez pas l'argument `--recursive`, Omega a besoin de sous-modules. +Important : N'oubliez pas l'argument `--recursive`, Upsilon a besoin de sous-modules. Vous pouvez aussi changer le nombre de processus parallèles pendant la compilation en changeant la valeur suivant `-j`. @@ -127,8 +124,8 @@ Vous pouvez aussi changer le nombre de processus parallèles pendant la compilat Vous aurez besoin de devkitPro et de devkitARM disponible dans votre `$PATH` (instructions [ici](https://devkitpro.org/wiki/Getting_Started) (en anglais)) ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega +git clone --recursive https://github.com/Lauryy06/Upsilon.git +cd Upsilon git checkout --recursive omega-dev make PLATFORM=simulator TARGET=3ds -j ``` @@ -141,16 +138,18 @@ Vous pouvez ensuite copier epsilon.3dsx sur une carte SD pour l'exécuter depuis -Si vous avez besoin d'aide, n'hésitez pas à rejoindre notre serveur discord : https://discord.gg/X2TWhh9 +Si vous avez besoin d'aide, n'hésitez pas à rejoindre notre serveur discord : https://discord.gg/Q9buEMduXG -

Omega Banner Discord

+

Omega Banner Discord

--- ## Contribution -Pour contribuer, merci de lire le [Wiki](https://github.com/Omega-Numworks/Omega/wiki/Contributing) +Pour contribuer, merci de lire le [Wiki d'Omega](https://github.com/Omega-Numworks/Omega/wiki/Contributing), les mêmes règles s'appliquent ici. + +## Les autres projets -## Nos autres projets +Les anciens projets d'Omega, avant sa fermeture, qui ont été utilisés pour ce projet * [Omega Themes](https://github.com/Omega-Numworks/Omega-Themes) * [Omega Website](https://github.com/Omega-Numworks/Omega-Website) @@ -163,6 +162,8 @@ Pour contribuer, merci de lire le [Wiki](https://github.com/Omega-Numworks/Omega ## À propos d'Epsilon +Upsilon est un fork d'Omega, visant a continuer le projet des OS utilisateurs pour Numworks + Omega est un fork d'Epsilon, un système d'exploitation performant pour calculatrices graphiques. Il inclut huit applications pour les mathématiques de lycée et d'études supérieurs Vous pouvez essayer Epsilon depuis votre navigateur sur le [simulateur en ligne](https://www.numworks.com/simulator/). @@ -175,3 +176,4 @@ NumWorks SAS et Nintendo of America Inc ne sont en aucun cas associés avec ce p * NumWorks Epsilon est disponible sous [Lisense CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). * Omega est disponible sous [Lisense CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). +* Upsilon est disponible sous [Lisense CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). diff --git a/README.md b/README.md index 2620c8f49cf..ceb8bd5bc46 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,33 @@ -

+

cc by-nc-sa 4.0 - Issues + Issues
- Discord + Discord

> Vous ne comprenez pas l'anglais ? vous êtes francophone ? Regardez le [*LISEZ-MOI* français](./README.fr.md) ! ## About -Omega is a fork of Numworks' Epsilon, the OS that runs on their calculator, which brings many features to it. Omega is for the people who want to add features to the calculator, but cannot because they have been rejected by Numworks (for reasons that are 100% understandable!). [Try it online](https://getomega.web.app/simulator). +Upsilon is a fork of Omega, an user-made OS that runs on the Numworks calculator, which brings many features to it, but was discontinued because of a policy change from Numworks. Upsilon is for the people who want to see a future for user-made OSes for Numworks, even after the closure and archiving of Omega. ### Some new features -- Adding symbolic calculation back into the calculator -- An app for RPN +- Enhancements for the Kandinsky python module +- A support for wallpapers - Exernal apps -- A theme engine -- New python features (os module, open method...) -- A periodic table app + all of the molar masses for the elements in the toolbox -- *And much more to discover...* [Complete changelog](https://github.com/Omega-Numworks/Omega/wiki/Changelog) | [Main new features + screenshots](https://github.com/Omega-Numworks/Omega/wiki/Main-features). +- A custom theme +- Operator overload for python +- Improvements for the Periodic table application +- *And everything that has been added to Omega before its termination!* [See Omega's changelog here](https://github.com/Omega-Numworks/Omega/wiki/Changelog) | [Main Omega features + screenshots](https://github.com/Omega-Numworks/Omega/wiki/Main-features). ## Installation -### Automatic - -You can install Omega automatically on our website [here](https://getomega.web.app/) in the "install" page. - -

Omega Banner Discord

- ### Manual +*As of today, only the manual installation is available.* + First of all, follow **step 1** [here](https://www.numworks.com/resources/engineering/software/build/). Then:
@@ -40,8 +36,8 @@ First of all, follow **step 1** [here](https://www.numworks.com/resources/engine (note: you can change the `EPSILON_I18N=en` flag to `fr`, `nl`, `pt`, `it`, `de`, `es` or `hu`). ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega +git clone --recursive https://github.com/Lauryy06/Upsilon.git +cd Upsilon git checkout omega-master make MODEL=n0100 clean make MODEL=n0100 EPSILON_I18N=en OMEGA_USERNAME="{Your name, max 15 characters}" -j4 @@ -57,7 +53,7 @@ Also, you can change the number of processes that run in parallel during the bui Model n0110 ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git +git clone --recursive https://github.com/Lauryy06/Upsilon.git cd Omega git checkout omega-master make clean @@ -73,10 +69,10 @@ Also, you can change the number of processes that run in parallel during the bui
Bin files -These can be used to distribute Omega (so that it can be flashed by anyone with [Webdfu_Numworks](https://ti-planet.github.io/webdfu_numworks/)). +These can be used to distribute Upsilon (so that it can be flashed by anyone with [Webdfu_Numworks](https://ti-planet.github.io/webdfu_numworks/)). ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git +git clone --recursive https://github.com/Lauryy06/Upsilon.git cd Omega git checkout omega-master make clean @@ -86,7 +82,7 @@ make OMEGA_USERNAME="" -j8 make OMEGA_USERNAME="" binpack -j8 ``` -Important: Don't forget the `--recursive` tag, because Omega relies on submodules. +Important: Don't forget the `--recursive` tag, because Upsilon relies on submodules. Also, you can change the number of processes that run in parallel during the build by changing the value of the `-j` flag.
@@ -104,11 +100,11 @@ cd emsdk source emsdk_env.sh ``` -Then, compile Omega : +Then, compile Upsilon : ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega +git clone --recursive https://github.com/Lauryy06/Upsilon.git +cd Upsilon git checkout omega-master make clean make PLATFORM=simulator TARGET=web OMEGA_USERNAME="{Your name, max 15 characters}" -j4 @@ -116,7 +112,7 @@ make PLATFORM=simulator TARGET=web OMEGA_USERNAME="{Your name, max 15 characters The simulator is now in `output/release/simulator/web/simulator.zip` -Important: Don't forget the `--recursive` tag, because Omega relies on submodules. +Important: Don't forget the `--recursive` tag, because Upsilon relies on submodules. Also, you can change the number of processes that run in parallel during the build by changing the value of the `-j` flag.
@@ -127,8 +123,8 @@ Also, you can change the number of processes that run in parallel during the bui You need devkitPro and devkitARM installed and in your path (instructions [here](https://devkitpro.org/wiki/Getting_Started)) ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega +git clone --recursive https://github.com/Lauryy06/Upsilon.git +cd Upsilon git checkout --recursive omega-dev make PLATFORM=simulator TARGET=3ds -j ``` @@ -140,17 +136,19 @@ You can then put epsilon.3dsx on a SD card to run it from the HBC or use 3dslink -If you need help, you can join our Discord server here : https://discord.gg/X2TWhh9 +If you need help, you can join our Discord server here : https://discord.gg/Q9buEMduXG -

Omega Banner Discord

+

Omega Banner Discord

--- ## Contributing -To contribute, please refer to the [Wiki](https://github.com/Omega-Numworks/Omega/wiki/Contributing) +To contribute, please refer to [Omega's Wiki](https://github.com/Omega-Numworks/Omega/wiki/Contributing), the same rules apply here. ## Related repositories +Here are the main links toward Omega's different websites and repositories, that have been used for the creation of Upsilon. + * [Omega Themes](https://github.com/Omega-Numworks/Omega-Themes) * [Omega Website](https://github.com/Omega-Numworks/Omega-Website) * [Omega RPN `APP`](https://github.com/Omega-Numworks/Omega-RPN) @@ -162,6 +160,8 @@ To contribute, please refer to the [Wiki](https://github.com/Omega-Numworks/Omeg ## About Epsilon +Upsilon is a fork of Omega, after the project's discontinuation. + Omega is a fork of Epsilon, a high-performance graphing calculator operating system. It includes eight apps that cover the high school mathematics curriculum. You can try Epsilon straight from your browser in the [online simulator](https://www.numworks.com/simulator/). @@ -174,3 +174,4 @@ NumWorks SAS and Nintendo of America Inc aren't associated in any shape or form * NumWorks Epsilon is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). * Omega is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). +* Upsilon is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). From dd757969f3857312cab188563c231e97d2691e08 Mon Sep 17 00:00:00 2001 From: Lauryy06 <80424145+Lauryy06@users.noreply.github.com> Date: Wed, 1 Sep 2021 18:00:24 +0200 Subject: [PATCH 025/355] [readme] Correct readme.fr --- README.fr.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.fr.md b/README.fr.md index 4ee47639e10..00ab5f10810 100644 --- a/README.fr.md +++ b/README.fr.md @@ -14,13 +14,13 @@ Upsilon est un fork d'Omega, un fork d'Epsilon, l'OS de Numworks tournant sur les calculatrices du même nom, qui apporte beaucoup de fonctionnalités en plus, mais qui fut archivé et fermé pour des raisons légales après un changement de politique de Numworks. Upsilon est fait pour ceux qui aimeraient voir un futur pour les OS créées par les utilisateurs pour Numworks, même après l'arrèt du projet initial. ### Quelques fonctionnalités supplémentaires -- RDes améliorations du module python Kandinsky +- Un module python kandinsky amélioré - Un support pour fonds d'écrans personnalisés - Des applications externes -- Des thèmes -- Des améliorations pour python -- Un tableau périodique légèrement modifié -- L'utilisation du signe "=" +- Un thème Upsilon +- La surcharge des opérateurs en python +- Un tableau périodique légèrement amélioré +- L'utilisation possible du signe "=" dans les calculs - *Ainsi que tout ce qui a été ajouté sur Omega, et bien plus...* [Changelogs complets d'Omega](https://github.com/Omega-Numworks/Omega/wiki/Changelog) | [Fonctionnalités principales d'Omega & captures d'écran](https://github.com/Omega-Numworks/Omega/wiki/Main-features). ## Installation From ebe6a7cc5f2c3c4bcbf1b9790459e4b87e2ab3b1 Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Thu, 2 Sep 2021 17:58:23 +0200 Subject: [PATCH 026/355] Merge pull request #2 (Update some translations) --- apps/home/base.de.i18n | 2 +- apps/home/base.en.i18n | 2 +- apps/home/base.es.i18n | 2 +- apps/home/base.fr.i18n | 2 +- apps/home/base.hu.i18n | 2 +- apps/home/base.it.i18n | 2 +- apps/home/base.nl.i18n | 2 +- apps/home/base.pt.i18n | 2 +- apps/settings/base.de.i18n | 2 +- apps/settings/base.en.i18n | 2 +- apps/settings/base.es.i18n | 2 +- apps/settings/base.fr.i18n | 2 +- apps/settings/base.hu.i18n | 2 +- apps/settings/base.it.i18n | 2 +- apps/settings/base.nl.i18n | 2 +- apps/settings/base.pt.i18n | 2 +- apps/usb/base.de.i18n | 2 +- apps/usb/base.en.i18n | 2 +- apps/usb/base.es.i18n | 2 +- apps/usb/base.fr.i18n | 2 +- apps/usb/base.hu.i18n | 2 +- apps/usb/base.it.i18n | 2 +- apps/usb/base.nl.i18n | 2 +- apps/usb/base.pt.i18n | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/home/base.de.i18n b/apps/home/base.de.i18n index 30ac1c9c256..a0fc91420a0 100644 --- a/apps/home/base.de.i18n +++ b/apps/home/base.de.i18n @@ -1,4 +1,4 @@ Apps = "Anwendungen" -AppsCapital = "OMEGA" +AppsCapital = "UPSILON" ForbidenAppInExamMode1 = "Diese Anwendung ist im" ForbidenAppInExamMode2 = "Prüfungsmodus nicht erlaubt." diff --git a/apps/home/base.en.i18n b/apps/home/base.en.i18n index dde4e21299b..3468cca663c 100644 --- a/apps/home/base.en.i18n +++ b/apps/home/base.en.i18n @@ -1,4 +1,4 @@ Apps = "Applications" -AppsCapital = "OMEGA" +AppsCapital = "UPSILON" ForbidenAppInExamMode1 = "This application is" ForbidenAppInExamMode2 = "forbidden in exam mode" diff --git a/apps/home/base.es.i18n b/apps/home/base.es.i18n index 93e7bdf4914..3c262497cb6 100644 --- a/apps/home/base.es.i18n +++ b/apps/home/base.es.i18n @@ -1,4 +1,4 @@ Apps = "Aplicaciones" -AppsCapital = "OMEGA" +AppsCapital = "UPSILON" ForbidenAppInExamMode1 = "Esta aplicación está prohibida" ForbidenAppInExamMode2 = "en el modo de examen" diff --git a/apps/home/base.fr.i18n b/apps/home/base.fr.i18n index 8280b458409..4b4ab02dd5f 100644 --- a/apps/home/base.fr.i18n +++ b/apps/home/base.fr.i18n @@ -1,4 +1,4 @@ Apps = "Applications" -AppsCapital = "OMEGA" +AppsCapital = "UPSILON" ForbidenAppInExamMode1 = "Cette application n'est" ForbidenAppInExamMode2 = "pas autorisée en mode examen." diff --git a/apps/home/base.hu.i18n b/apps/home/base.hu.i18n index 6b0b188157c..d526193eb1a 100644 --- a/apps/home/base.hu.i18n +++ b/apps/home/base.hu.i18n @@ -1,4 +1,4 @@ Apps = "Alkalmazások" -AppsCapital = "OMEGA" +AppsCapital = "UPSILON" ForbidenAppInExamMode1 = "Ez az alkalmazás" ForbidenAppInExamMode2 = "tilos vizsga módban" diff --git a/apps/home/base.it.i18n b/apps/home/base.it.i18n index 95373c0401a..4bd26ced310 100644 --- a/apps/home/base.it.i18n +++ b/apps/home/base.it.i18n @@ -1,4 +1,4 @@ Apps = "Applicazioni" -AppsCapital = "OMEGA" +AppsCapital = "UPSILON" ForbidenAppInExamMode1 = "Questa applicazione è" ForbidenAppInExamMode2 = "proibita nella modalità d'esame" diff --git a/apps/home/base.nl.i18n b/apps/home/base.nl.i18n index dd6a8991935..496e8d6a3d7 100644 --- a/apps/home/base.nl.i18n +++ b/apps/home/base.nl.i18n @@ -1,4 +1,4 @@ Apps = "Applicaties" -AppsCapital = "OMEGA" +AppsCapital = "UPSILON" ForbidenAppInExamMode1 = "Deze applicatie is" ForbidenAppInExamMode2 = "uitgesloten in examenstand" diff --git a/apps/home/base.pt.i18n b/apps/home/base.pt.i18n index f3efc8ffdee..6c074aa86bb 100644 --- a/apps/home/base.pt.i18n +++ b/apps/home/base.pt.i18n @@ -1,4 +1,4 @@ Apps = "Aplicações" -AppsCapital = "OMEGA" +AppsCapital = "UPSILON" ForbidenAppInExamMode1 = "Esta aplicação é" ForbidenAppInExamMode2 = "proibida no Modo de Exame" diff --git a/apps/settings/base.de.i18n b/apps/settings/base.de.i18n index 5c00526fc3e..7510e33348d 100644 --- a/apps/settings/base.de.i18n +++ b/apps/settings/base.de.i18n @@ -33,7 +33,7 @@ Cartesian = "Kartesisch " Polar = "Polar " Brightness = "Helligkeit" SoftwareVersion = "Epsilon version" -OmegaVersion = "Omega version" +UpsilonVersion = "Upsilon version" Username = "Name" MicroPythonVersion = "µPythonversion" FontSizes = "Python-Schriftgröße" diff --git a/apps/settings/base.en.i18n b/apps/settings/base.en.i18n index b5d07690485..0b4b81ebdbd 100644 --- a/apps/settings/base.en.i18n +++ b/apps/settings/base.en.i18n @@ -33,7 +33,7 @@ Cartesian = "Cartesian " Polar = "Polar " Brightness = "Brightness" SoftwareVersion = "Epsilon version" -OmegaVersion = "Omega version" +UpsilonVersion = "Upsilon version" Username = "Name" MicroPythonVersion = "µPython version" FontSizes = "Python font size" diff --git a/apps/settings/base.es.i18n b/apps/settings/base.es.i18n index 944709570f0..213ef0dc1c7 100644 --- a/apps/settings/base.es.i18n +++ b/apps/settings/base.es.i18n @@ -33,7 +33,7 @@ Cartesian = "Binómica " Polar = "Polar " Brightness = "Brillo" SoftwareVersion = "Versión de Epsilon" -OmegaVersion = "Versión de Omega" +UpsilonVersion = "Versión de Upsilon" Username = "Apellido" MicroPythonVersion = "Version de µPython" FontSizes = "Tipografía Python" diff --git a/apps/settings/base.fr.i18n b/apps/settings/base.fr.i18n index ac76c7dca0d..be2e08daf69 100644 --- a/apps/settings/base.fr.i18n +++ b/apps/settings/base.fr.i18n @@ -33,7 +33,7 @@ Cartesian = "Algébrique " Polar = "Exponentielle " Brightness = "Luminosité" SoftwareVersion = "Version d'Epsilon" -OmegaVersion = "Version d'Omega" +UpsilonVersion = "Version d'Upsilon" Username = "Nom" MicroPythonVersion = "Version de µPython" FontSizes = "Police Python" diff --git a/apps/settings/base.hu.i18n b/apps/settings/base.hu.i18n index 19ed3a67f63..db70d604f8c 100644 --- a/apps/settings/base.hu.i18n +++ b/apps/settings/base.hu.i18n @@ -33,7 +33,7 @@ Cartesian = "Kartéziánus " Polar = "Poláris " Brightness = "Fényerö" SoftwareVersion = "Epsilon verzió" -OmegaVersion = "Omega verzió" +UpsilonVersion = "Upsilon verzió" Username = "Felhasználónév" MicroPythonVersion = "µPython verzió" FontSizes = "Python betü méret" diff --git a/apps/settings/base.it.i18n b/apps/settings/base.it.i18n index 5c4dcc0a47c..20fe834e885 100644 --- a/apps/settings/base.it.i18n +++ b/apps/settings/base.it.i18n @@ -33,7 +33,7 @@ Cartesian = "Algebrico " Polar = "Esponenziale " Brightness = "Luminosità" SoftwareVersion = "Epsilon version" -OmegaVersion = "Omega version" +UpsilonVersion = "Upsilon version" Username = "Name" MicroPythonVersion = "µPython version" FontSizes = "Carattere Python" diff --git a/apps/settings/base.nl.i18n b/apps/settings/base.nl.i18n index 75939d6a877..bf0cc43a8e6 100644 --- a/apps/settings/base.nl.i18n +++ b/apps/settings/base.nl.i18n @@ -33,7 +33,7 @@ Cartesian = "Cartesisch " Polar = "Polair " Brightness = "Helderheid" SoftwareVersion = "Epsilon version" -OmegaVersion = "Omega version" +UpsilonVersion = "Upsilon version" Username = "Name" MicroPythonVersion = "µPython version" FontSizes = "Python lettergrootte" diff --git a/apps/settings/base.pt.i18n b/apps/settings/base.pt.i18n index f33006f4ad6..167abd7c643 100644 --- a/apps/settings/base.pt.i18n +++ b/apps/settings/base.pt.i18n @@ -33,7 +33,7 @@ Cartesian = "Cartesiana " Polar = "Polar " Brightness = "Brilho" SoftwareVersion = "Versão do Epsilon" -OmegaVersion = "Versão do Omega" +UpsilonVersion = "Versão do Upsilon" Username = "Nome" MicroPythonVersion = "Versao do µPython" FontSizes = "Tipografia Python" diff --git a/apps/usb/base.de.i18n b/apps/usb/base.de.i18n index e381f9fb38d..794b4d36bd7 100644 --- a/apps/usb/base.de.i18n +++ b/apps/usb/base.de.i18n @@ -1,7 +1,7 @@ USBConnected = "TASCHENRECHNER IST ANGESCHLOSSEN" ConnectedMessage1 = "Um Daten zu übertragen, verbinden" ConnectedMessage2 = "Sie sich von Ihrem Computer aus mit" -ConnectedMessage3 = "workshop.numworks.com." +ConnectedMessage3 = "getomega.dev/ide." ConnectedMessage4 = "Drücken Sie die Zurück-Taste am" ConnectedMessage5 = "Taschenrechner oder Kabel abziehen," ConnectedMessage6 = "um die Verbindung zu trennen." diff --git a/apps/usb/base.en.i18n b/apps/usb/base.en.i18n index 60eae221433..0eb615fa2c6 100644 --- a/apps/usb/base.en.i18n +++ b/apps/usb/base.en.i18n @@ -1,7 +1,7 @@ USBConnected = "THE CALCULATOR IS CONNECTED" ConnectedMessage1 = "To transfer data, browse" ConnectedMessage2 = "our page from your computer" -ConnectedMessage3 = "workshop.numworks.com" +ConnectedMessage3 = "getomega.dev/ide" ConnectedMessage4 = "Press the BACK key of your" ConnectedMessage5 = "calculator or unplug it to" ConnectedMessage6 = "disconnect it." diff --git a/apps/usb/base.es.i18n b/apps/usb/base.es.i18n index 7fa1e5cd7b1..cf9b3d7b52c 100644 --- a/apps/usb/base.es.i18n +++ b/apps/usb/base.es.i18n @@ -1,7 +1,7 @@ USBConnected = "CALCULADORA CONECTADA" ConnectedMessage1 = "Para transferir datos, visite" ConnectedMessage2 = "nuestra página desde su ordenador" -ConnectedMessage3 = "workshop.numworks.com" +ConnectedMessage3 = "getomega.dev/ide" ConnectedMessage4 = "Pulse el botón RETURN de la" ConnectedMessage5 = "calculadora o desenchúfela para" ConnectedMessage6 = "desconectarla." \ No newline at end of file diff --git a/apps/usb/base.fr.i18n b/apps/usb/base.fr.i18n index aeda714e80e..33762ca546a 100644 --- a/apps/usb/base.fr.i18n +++ b/apps/usb/base.fr.i18n @@ -1,7 +1,7 @@ USBConnected = "LA CALCULATRICE EST CONNECTÉE" ConnectedMessage1 = "Pour transférer des données, connectez-" ConnectedMessage2 = "vous depuis votre ordinateur sur le site" -ConnectedMessage3 = "workshop.numworks.com" +ConnectedMessage3 = "getomega.dev/ide" ConnectedMessage4 = "Appuyez sur la touche RETOUR" ConnectedMessage5 = "de la calculatrice ou débranchez-la" ConnectedMessage6 = "pour la déconnecter." diff --git a/apps/usb/base.hu.i18n b/apps/usb/base.hu.i18n index 3afbc67ab97..a8a3de172a8 100644 --- a/apps/usb/base.hu.i18n +++ b/apps/usb/base.hu.i18n @@ -1,7 +1,7 @@ USBConnected = "A SZÁMOLÓGÉP CSATLAKOZTATVA VAN" ConnectedMessage1 = "" ConnectedMessage2 = "Adat másolásához csatlakozzon" -ConnectedMessage3 = "fel workshop.numworks.com ra." +ConnectedMessage3 = "fel getomega.dev/ide ra." ConnectedMessage4 = "Nyomjon majd a VISSZA gombra" ConnectedMessage5 = "vagy huzza ki a kábelt azért" ConnectedMessage6 = "hogy a másolás véget érjen." diff --git a/apps/usb/base.it.i18n b/apps/usb/base.it.i18n index a0d7339a73a..fdc2be0bd1c 100644 --- a/apps/usb/base.it.i18n +++ b/apps/usb/base.it.i18n @@ -1,7 +1,7 @@ USBConnected = "CALCOLATRICE CONNESSA" ConnectedMessage1 = "Per trasferire dei dati, connettetevi" ConnectedMessage2 = "dal vostro computer sul sito" -ConnectedMessage3 = "workshop.numworks.com" +ConnectedMessage3 = "getomega.dev/ide" ConnectedMessage4 = "Premere sul tasto INDIETRO della" ConnectedMessage5 = "calcolatrice o scollegatela per" ConnectedMessage6 = "disconnetterla." diff --git a/apps/usb/base.nl.i18n b/apps/usb/base.nl.i18n index ed8c78ca347..db29c112760 100644 --- a/apps/usb/base.nl.i18n +++ b/apps/usb/base.nl.i18n @@ -1,7 +1,7 @@ USBConnected = "DE REKENMACHINE IS AANGESLOTEN" ConnectedMessage1 = "Om gegevens op te slaan," ConnectedMessage2 = "ga naar onze webpagina" -ConnectedMessage3 = "workshop.numworks.com" +ConnectedMessage3 = "getomega.dev/ide" ConnectedMessage4 = "Druk op de TERUG toets van je" ConnectedMessage5 = "rekenmachine of verwijder de" ConnectedMessage6 = " kabel om hem los te koppelen." diff --git a/apps/usb/base.pt.i18n b/apps/usb/base.pt.i18n index 5e6c3cea791..408b24db7a4 100644 --- a/apps/usb/base.pt.i18n +++ b/apps/usb/base.pt.i18n @@ -1,7 +1,7 @@ USBConnected = "A CALCULADORA ESTÁ CONECTADA" ConnectedMessage1 = "Para transferir dados, navegue" ConnectedMessage2 = "na nossa página no seu computador" -ConnectedMessage3 = "workshop.numworks.com" +ConnectedMessage3 = "getomega.dev/ide" ConnectedMessage4 = "Pressione o botão RETURN na" ConnectedMessage5 = "calculadora ou desligue-a para a" ConnectedMessage6 = "desconectar." From 74635f8d88191aa21fd30ba4f743eb9c4aad9fc2 Mon Sep 17 00:00:00 2001 From: Laury Date: Fri, 3 Sep 2021 22:57:04 +0200 Subject: [PATCH 027/355] [code/ulab] Added ulab to toolbox --- apps/code/catalog.de.i18n | 98 ++++++++++++++ apps/code/catalog.en.i18n | 98 ++++++++++++++ apps/code/catalog.es.i18n | 98 ++++++++++++++ apps/code/catalog.fr.i18n | 98 ++++++++++++++ apps/code/catalog.hu.i18n | 98 ++++++++++++++ apps/code/catalog.it.i18n | 98 ++++++++++++++ apps/code/catalog.nl.i18n | 98 ++++++++++++++ apps/code/catalog.pt.i18n | 98 ++++++++++++++ apps/code/catalog.universal.i18n | 110 ++++++++++++++++ apps/code/python_toolbox.cpp | 122 ++++++++++++++++++ apps/code/toolbox.universal.i18n | 5 + .../include/escher/nested_menu_controller.h | 2 +- 12 files changed, 1022 insertions(+), 1 deletion(-) diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index 1e2e2e4193e..2c942a56ee6 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -74,6 +74,7 @@ PythonImportKandinsky = "Kandinsky-Modul importieren" PythonImportRandom = "Random-Modul importieren" PythonImportMath = "Math-Modul importieren" PythonImportMatplotlibPyplot = "Matplotlib.pyplot-Modul importieren" +PythonImportNumpy = "Ulab.numpy-Modul importieren" PythonImportOs = "OS-Modul importieren" PythonOsUname = "Informationen über das System holen" PythonOsGetlogin = "Benutzernamen holen" @@ -104,6 +105,103 @@ PythonMax = "Maximum" PythonMin = "Minimum" PythonModf = "Bruch- und Ganzzahl-Anteile von x" PythonMonotonic = "Wert einer monotonen Uhr" +PythonNumpyFunction = "numpy Modul-Präfix" +PythonNumpyFftFunction = "numpy.fft Modul-Präfix" +PythonNumpyLinalgFunction = "numpy.linalg Modul-Präfix" +PythonNumpyArray = "Konvertieren Sie ein Array in ndarray" +PythonNumpyArange = "Erstellen Sie eine Tabelle aus dem Bereich (i)" +PythonNumpyConcatenate = "Verketten Sie a und b" +PythonNumpyDiag = "Extrahiere oder konstruiere ein diagonales Array" +PythonNumpyZeros = "S-förmiges Array gefüllt mit 0" +PythonNumpyOnes = "S-förmiges Array gefüllt mit 1" +PythonNumpyEmpty = "Nicht initialisiertes Array der Form s" +PythonNumpyEye = "Tabelle mit Einsen auf der Diagonale und Nullen an anderer Stelle" +PythonNumpyFull = "S-förmiges Array gefüllt mit v" +PythonNumpyLinspace = "Zahlen, die über ein bestimmtes Intervall verteilt sind" +PythonNumpyLogspace = "Zahlen mit logarithmischem Abstand" +PythonNumpyCopy = "Kopie der Tabelle" +PythonNumpyDtype = "Tischtyp" +PythonNumpyFlat = "Flat-Array-Iterator" +PythonNumpyFlatten = "Abgeflachte Version der Tabelle" +PythonNumpyShape = "Holen Sie sich die Form des Arrays" +PythonNumpyReshape = "Array-Form durch s ersetzen" +PythonNumpySize = "Anzahl der Elemente im Array" +PythonNumpyTranspose = "Transponierte Version der Tabelle" +PythonNumpySortWithArguments = "Sortierte Version der Tabelle" +PythonNumpyNdinfo = "Informationen zu a . drucken" +PythonNumpyAll = "Testen Sie, ob alle Elemente von a trye sind" +PythonNumpyAny = "Teste, ob ein Element von a wahr ist" +PythonNumpyArgmax = "Index des Maximalwertes von a" +PythonNumpyArgmin = "Tiefgestellter Wert des Mindestwertes von a" +PythonNumpyArgsort = "Hinweise, die ein Array sortieren würden" +PythonNumpyClip = "Werte in einem Array ausschneiden" +PythonNumpyConvolve = "Diskrete lineare Faltung von a und b" +PythonNumpyDiff = "Abgeleitet von a" +PythonNumpyInterp = "Linear interpolierte Werte von a" +PythonNumpyDot = "Punktprodukt von a und b" +PythonNumpyCross = "Kreuzprodukt von a und b" +PythonNumpyEqual = "A == Element für Element" +PythonNumpyNot_equal = "A! = Element für Element" +PythonNumpyFlip = "Turnaround-Tabelle" +PythonNumpyIsfinite = "Testen Sie die Endlichkeit Element für Element" +PythonNumpyIsinf = "Teste die Unendlichkeit Element für Element" +PythonNumpyMean = "Durchschnitt d" +PythonNumpyMin = "Maximalwert von a" +PythonNumpyMax = "Mindestwert von a" +PythonNumpyMedian = "Medianwert von a" +PythonNumpyMinimum = "Minimale Array-Elemente pro Element" +PythonNumpyMaximum = "Maximum pro Element von Array-Elementen" +PythonNumpyPolyfit = "Polynomanpassung der kleinsten Quadrate" +PythonNumpyPolyval = "Bewerte ein Polynom bei bestimmten Werten" +PythonNumpyRoll = "Verschiebe den Inhalt von a um n" +PythonNumpySort = "Sortieren nach" +PythonNumpyStd = "Berechnen Sie die Standardabweichung von a" +PythonNumpySum = "Berechnen Sie die Summe von a" +PythonNumpyTrace = "Berechnen Sie die Summe der diagonalen Elemente von a" +PythonNumpyTrapz = "Integrieren Sie mit dem zusammengesetzten Trapezlineal" +PythonNumpyWhere = "Gibt Elemente aus x oder y gemäß c . zurück" +PythonNumpyVectorize = "Vektorisieren Sie die generische Python-Funktion f" +PythonNumpyAcos = "Wenden Sie acos Artikel für Artikel an" +PythonNumpyAcosh = "Wenden Sie acosh Artikel für Artikel an" +PythonNumpyArctan2 = "arctan2 Element für Element anwenden" +PythonNumpyAround = "Um das Element herum auftragen" +PythonNumpyAsin = "Element für Element anwenden" +PythonNumpyAsinh = "Wenden Sie asinh Element für Element an" +PythonNumpyAtan = "Wenden Sie ein Element für Element an" +PythonNumpyAtanh = "Wenden Sie atanh Element für Element an" +PythonNumpyCeil = "Bringen Sie die Decke nach Element an" +PythonNumpyCos = "Wenden Sie cos Element für Element an" +PythonNumpyCosh = "Wenden Sie cosh Element für Element an" +PythonNumpyDegrees = "Grade Element für Element anwenden" +PythonNumpyExp = "Exp pro Artikel anwenden" +PythonNumpyExpm1 = "Wenden Sie expm1 Element für Element an" +PythonNumpyFloor = "Boden nach Element auftragen" +PythonNumpyLog = "Tagebuch nach Artikel anwenden" +PythonNumpyLog10 = "Wenden Sie log10 Element für Element an" +PythonNumpyLog2 = "Wenden Sie log2 Element für Element an" +PythonNumpyRadians = "Wenden Sie Radiant pro Element an" +PythonNumpySin = "Wende Sünde nach Element an" +PythonNumpySinh = "Wenden Sie sinh Element für Element an" +PythonNumpySqrt = "Wenden Sie sqrt Element für Element an" +PythonNumpyTan = "Trage die Bräune nach Element auf" +PythonNumpyTanh = "Tanh pro Artikel auftragen" +PythonNumpyBool = "Bool Typ von numpy" +PythonNumpyFloat = "Float-Typ von numpy" +PythonNumpyUint8 = "Geben Sie uint8 von numpy . ein" +PythonNumpyInt8 = "Geben Sie int8 von numpy . ein" +PythonNumpyUint16 = "Geben Sie uint16 von numpy ein" +PythonNumpyInt16 = "Geben Sie int16 von numpy . ein" +PythonNumpyNan = "Nan-Darstellung von numpy" +PythonNumpyInf = "Inf-Darstellung von numpy" +PythonNumpyE = "2.718281828459045" +PythonNumpyPi = "3.141592653589793" +PythonNumpyFft = "Eindimensionale diskrete Fourier-Transformation" +PythonNumpyIfft = "Eindimensionale inverse diskrete Fourier-Transformation" +PythonNumpyDet = "Determinante von a" +PythonNumpyEig = "Eigenwerte und rechte Eigenvektoren von a" +PythonNumpyCholesky = "Cholesky-Zerlegung" +PythonNumpyInv = "Inverse Matrix a" +PythonNumpyNorm = "Matrix- oder Vektorstandard" PythonOct = "Ganzzahl in Oktal umwandeln" PythonPhase = "Phase von z" PythonPlot = "Plotten von y gegen x als Linien" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index 169955b451c..09de24ccb23 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -74,6 +74,7 @@ PythonImportKandinsky = "Import kandinsky module" PythonImportRandom = "Import random module" PythonImportMath = "Import math module" PythonImportMatplotlibPyplot = "Import matplotlib.pyplot module" +PythonImportNumpy = "Import ulab.numpy module" PythonImportTime = "Import time module" PythonImportTurtle = "Import turtle module" PythonIndex = "Index of the first x occurrence" @@ -98,6 +99,103 @@ PythonMax = "Maximum" PythonMin = "Minimum" PythonModf = "Fractional and integer parts of x" PythonMonotonic = "Value of a monotonic clock" +PythonNumpyFunction = "numpy module prefix" +PythonNumpyFftFunction = "numpy.fft module prefix" +PythonNumpyLinalgFunction = "numpy.linalg module prefix" +PythonNumpyArray = "Convert an array to ndarray" +PythonNumpyArange = "Make a table from the range (i)" +PythonNumpyConcatenate = "Concatenate a and b" +PythonNumpyDiag = "Extract or construct a diagonal array" +PythonNumpyZeros = "S shape array filled with 0" +PythonNumpyOnes = "S shape array filled with 1" +PythonNumpyEmpty = "Uninitialized array of form s" +PythonNumpyEye = "Table with 1s on the diagonal and 0s elsewhere" +PythonNumpyFull = "S shape array filled with v" +PythonNumpyLinspace = "Numbers spaced over a specified interval" +PythonNumpyLogspace = "Numbers spaced on a logarithmic scale" +PythonNumpyCopy = "Copy of table" +PythonNumpyDtype = "Table type" +PythonNumpyFlat = "Flat array iterator" +PythonNumpyFlatten = "Flattened version of the table" +PythonNumpyShape = "Get the shape of the array" +PythonNumpyReshape = "Replace array shape with s" +PythonNumpySize = "Number of elements in the array" +PythonNumpyTranspose = "Transposed version of the table" +PythonNumpySortWithArguments = "Sorted version of the table" +PythonNumpyNdinfo = "Print information about a" +PythonNumpyAll = "Test if all elements of a are trye" +PythonNumpyAny = "Test if an element of a is true" +PythonNumpyArgmax = "Index of the maximum value of a" +PythonNumpyArgmin = "Subscript of the minimum value of a" +PythonNumpyArgsort = "Clues that would sort an array" +PythonNumpyClip = "Cut values in an array" +PythonNumpyConvolve = "Discrete linear convolution of a and b" +PythonNumpyDiff = "Derived from a" +PythonNumpyInterp = "Linearly interpolated values of a" +PythonNumpyDot = "Dot product of a and b" +PythonNumpyCross = "Cross product of a and b" +PythonNumpyEqual = "A == a element by element" +PythonNumpyNot_equal = "A! = A element by element" +PythonNumpyFlip = "Turnaround table" +PythonNumpyIsfinite = "Test the finiteness element by element" +PythonNumpyIsinf = "Test the infinity element by element" +PythonNumpyMean = "Average d" +PythonNumpyMin = "Maximum value of a" +PythonNumpyMax = "Minimum value of a" +PythonNumpyMedian = "Median value of a" +PythonNumpyMinimum = "Minimum array elements per element" +PythonNumpyMaximum = "Maximum per element of array elements" +PythonNumpyPolyfit = "Least squares polynomial fit" +PythonNumpyPolyval = "Evaluate a polynomial at specific values" +PythonNumpyRoll = "Shift the content of a by n" +PythonNumpySort = "Sort to" +PythonNumpyStd = "Calculate the standard deviation of a" +PythonNumpySum = "Calculate the sum of a" +PythonNumpyTrace = "Calculate the sum of the diagonal elements of a" +PythonNumpyTrapz = "Integrate using the composite trapezoidal ruler" +PythonNumpyWhere = "Returns elements chosen from x or y according to c" +PythonNumpyVectorize = "Vectorize the generic python function f" +PythonNumpyAcos = "Apply acos item by item" +PythonNumpyAcosh = "Apply acosh item by item" +PythonNumpyArctan2 = "Apply arctan2 element by element" +PythonNumpyAround = "Apply around the element" +PythonNumpyAsin = "Apply asin element by element" +PythonNumpyAsinh = "Apply asinh element by element" +PythonNumpyAtan = "Apply one item by item" +PythonNumpyAtanh = "Apply atanh element by element" +PythonNumpyCeil = "Apply the ceiling by element" +PythonNumpyCos = "Apply cos element by element" +PythonNumpyCosh = "Apply cosh element by element" +PythonNumpyDegrees = "Apply degrees element by element" +PythonNumpyExp = "Apply exp per item" +PythonNumpyExpm1 = "Apply expm1 element by element" +PythonNumpyFloor = "Apply soil by element" +PythonNumpyLog = "Apply journal by item" +PythonNumpyLog10 = "Apply log10 element by element" +PythonNumpyLog2 = "Apply log2 element by element" +PythonNumpyRadians = "Apply radians per element" +PythonNumpySin = "Apply sin by element" +PythonNumpySinh = "Apply sinh element by element" +PythonNumpySqrt = "Apply sqrt element by element" +PythonNumpyTan = "Apply the tan by element" +PythonNumpyTanh = "Apply tanh per item" +PythonNumpyBool = "Bool type of numpy" +PythonNumpyFloat = "Float type of numpy" +PythonNumpyUint8 = "Type uint8 of numpy" +PythonNumpyInt8 = "Type int8 of numpy" +PythonNumpyUint16 = "Type uint16 from numpy" +PythonNumpyInt16 = "Type int16 of numpy" +PythonNumpyNan = "Nan representation of numpy" +PythonNumpyInf = "Inf representation of numpy" +PythonNumpyE = "2.718281828459045" +PythonNumpyPi = "3.141592653589793" +PythonNumpyFft = "One-dimensional discrete fourier transform" +PythonNumpyIfft = "One-dimensional inverse discrete fourier transform" +PythonNumpyDet = "Determinant of a" +PythonNumpyEig = "Eigenvalues and right eigenvectors of a" +PythonNumpyCholesky = "Cholesky decomposition" +PythonNumpyInv = "Inverse matrix a" +PythonNumpyNorm = "Matrix or vector standard" PythonOct = "Convert integer to octal" PythonPhase = "Phase of z" PythonPlot = "Plot y versus x as lines" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index ef181756a63..48bff282b2c 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -74,6 +74,7 @@ PythonImportKandinsky = "Import kandinsky module" PythonImportRandom = "Import random module" PythonImportMath = "Import math module" PythonImportMatplotlibPyplot = "Import matplotlib.pyplot module" +PythonImportNumpy = "Import ulab.numpy module" PythonImportTime = "Import time module" PythonImportTurtle = "Import turtle module" PythonIndex = "Index of the first x occurrence" @@ -98,6 +99,103 @@ PythonMax = "Maximum" PythonMin = "Minimum" PythonModf = "Fractional and integer parts of x" PythonMonotonic = "Value of a monotonic clock" +PythonNumpyFunction = "numpy module prefix" +PythonNumpyFftFunction = "numpy.fft module prefix" +PythonNumpyLinalgFunction = "numpy.linalg module prefix" +PythonNumpyArray = "Convertir una matriz a ndarray" +PythonNumpyArange = "Haz una tabla de la gama (i)" +PythonNumpyConcatenate = "Concatenar ayb" +PythonNumpyDiag = "Extraer o construir una matriz diagonal" +PythonNumpyZeros = "Matriz en forma de S rellena con 0" +PythonNumpyOnes = "Matriz en forma de S llena con 1" +PythonNumpyEmpty = "Matriz no inicializada de formulario s" +PythonNumpyEye = "Tabla con 1 en la diagonal y 0 en el resto" +PythonNumpyFull = "Matriz en forma de S rellena con v" +PythonNumpyLinspace = "Números espaciados en un intervalo específico" +PythonNumpyLogspace = "Números espaciados en una escala logarítmica" +PythonNumpyCopy = "Copia de la tabla" +PythonNumpyDtype = "Tipo de mesa" +PythonNumpyFlat = "Iterador de matriz plana" +PythonNumpyFlatten = "Versión aplanada de la mesa." +PythonNumpyShape = "Obtén la forma de la matriz" +PythonNumpyReshape = "Reemplazar la forma de la matriz con s" +PythonNumpySize = "Número de elementos en la matriz" +PythonNumpyTranspose = "Versión transpuesta de la tabla" +PythonNumpySortWithArguments = "Versión ordenada de la tabla" +PythonNumpyNdinfo = "Imprimir información sobre un" +PythonNumpyAll = "Prueba si todos los elementos de a son probables" +PythonNumpyAny = "Prueba si un elemento de a es verdadero" +PythonNumpyArgmax = "Índice del valor máximo de un" +PythonNumpyArgmin = "Subíndice del valor mínimo de un" +PythonNumpyArgsort = "Pistas que ordenarían una matriz" +PythonNumpyClip = "Cortar valores en una matriz" +PythonNumpyConvolve = "Convolución lineal discreta de ayb" +PythonNumpyDiff = "Derivado de un" +PythonNumpyInterp = "Valores interpolados linealmente de a" +PythonNumpyDot = "Producto escalar de ayb" +PythonNumpyCross = "Producto cruzado de ayb" +PythonNumpyEqual = "A == un elemento por elemento" +PythonNumpyNot_equal = "A! = Un elemento por elemento" +PythonNumpyFlip = "Tabla de cambio" +PythonNumpyIsfinite = "Prueba la finitud elemento por elemento" +PythonNumpyIsinf = "Prueba el infinito elemento por elemento" +PythonNumpyMean = "Promedio d" +PythonNumpyMin = "Valor máximo de un" +PythonNumpyMax = "Valor mínimo de un" +PythonNumpyMedian = "Valor mediano de a" +PythonNumpyMinimum = "Elementos de matriz mínimos por elemento" +PythonNumpyMaximum = "Máximo por elemento de elementos de matriz" +PythonNumpyPolyfit = "Ajuste de polinomio de mínimos cuadrados" +PythonNumpyPolyval = "Evaluar un polinomio en valores específicos" +PythonNumpyRoll = "Cambiar el contenido de a por n" +PythonNumpySort = "Ordenar por" +PythonNumpyStd = "Calcule la desviación estándar de un" +PythonNumpySum = "Calcule la suma de a" +PythonNumpyTrace = "Calcule la suma de los elementos diagonales de un" +PythonNumpyTrapz = "Integrar usando la regla trapezoidal compuesta" +PythonNumpyWhere = "Devuelve elementos elegidos de xoy según c" +PythonNumpyVectorize = "Vectorizar la función genérica de python f" +PythonNumpyAcos = "Aplicar acos artículo por artículo" +PythonNumpyAcosh = "Aplicar un elemento por elemento" +PythonNumpyArctan2 = "Aplicar arctan2 elemento por elemento" +PythonNumpyAround = "Aplicar alrededor del elemento" +PythonNumpyAsin = "Aplicar asin elemento por elemento" +PythonNumpyAsinh = "Aplicar asinh elemento por elemento" +PythonNumpyAtan = "Aplicar un artículo por artículo" +PythonNumpyAtanh = "Aplicar atanh elemento por elemento" +PythonNumpyCeil = "Aplicar el techo por elemento" +PythonNumpyCos = "Aplicar cos elemento por elemento" +PythonNumpyCosh = "Aplicar cosh elemento por elemento" +PythonNumpyDegrees = "Aplicar grados elemento por elemento" +PythonNumpyExp = "Aplicar exp por artículo" +PythonNumpyExpm1 = "Aplicar expm1 elemento por elemento" +PythonNumpyFloor = "Aplicar suelo por elemento" +PythonNumpyLog = "Aplicar diario por artículo" +PythonNumpyLog10 = "Aplicar log10 elemento por elemento" +PythonNumpyLog2 = "Aplicar log2 elemento por elemento" +PythonNumpyRadians = "Aplicar radianes por elemento" +PythonNumpySin = "Aplicar el pecado por elemento" +PythonNumpySinh = "Aplicar sinh elemento por elemento" +PythonNumpySqrt = "Aplicar elemento sqrt por elemento" +PythonNumpyTan = "Aplicar el bronceado por elemento" +PythonNumpyTanh = "Aplicar tanh por artículo" +PythonNumpyBool = "Bool tipo de numpy" +PythonNumpyFloat = "Flotador tipo de numpy" +PythonNumpyUint8 = "Escriba uint8 de numpy" +PythonNumpyInt8 = "Escriba int8 de numpy" +PythonNumpyUint16 = "Escriba uint16 desde numpy" +PythonNumpyInt16 = "Escriba int16 de numpy" +PythonNumpyNan = "Nan representación de numpy" +PythonNumpyInf = "Inf representación de numpy" +PythonNumpyE = "2.718281828459045" +PythonNumpyPi = "3.141592653589793" +PythonNumpyFft = "Transformada de Fourier discreta unidimensional" +PythonNumpyIfft = "Transformada de Fourier discreta inversa unidimensional" +PythonNumpyDet = "Determinante de un" +PythonNumpyEig = "Autovalores y autovectores derechos de un" +PythonNumpyCholesky = "Descomposición de Cholesky" +PythonNumpyInv = "Matriz inversa a" +PythonNumpyNorm = "Matriz o estándar vectorial" PythonOct = "Convert integer to octal" PythonPhase = "Phase of z" PythonPlot = "Plot y versus x as lines" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index efb491f411b..5846f7c4a72 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -74,6 +74,7 @@ PythonImportKandinsky = "Importation du module kandinsky" PythonImportRandom = "Importation du module random" PythonImportMath = "Importation du module math" PythonImportMatplotlibPyplot = "Importation de matplotlib.pyplot" +PythonImportNumpy = "Importation de ulab.numpy" PythonImportTurtle = "Importation du module turtle" PythonImportTime = "Importation du module time" PythonIndex = "Indice première occurrence de x" @@ -98,6 +99,103 @@ PythonMax = "Maximum" PythonMin = "Minimum" PythonModf = "Parties fractionnaire et entière" PythonMonotonic = "Renvoie la valeur de l'horloge" +PythonNumpyFunction = "Préfixe fonction du module numpy" +PythonNumpyFftFunction = "Préfixe fonction du module numpy.fft" +PythonNumpyLinalgFunction = "Préfixe fonction du module numpy.linalg" +PythonNumpyArray = "Convertir un tableau en ndarray" +PythonNumpyArange = "Faire un tableau à partir de la plage (i)" +PythonNumpyConcatenate = "Concaténer a et b" +PythonNumpyDiag = "Extraire ou construire un tableau diagonal" +PythonNumpyZeros = "Tableau de forme s rempli de 0" +PythonNumpyOnes = "Tableau de forme s rempli de 1" +PythonNumpyEmpty = "Tableau uninitialisé de forme s" +PythonNumpyEye = "Tableau avec des 1 sur la diagonale et des 0 ailleurs" +PythonNumpyFull = "Tableau de forme s rempli de v" +PythonNumpyLinspace = "Nombres espacés sur un intervalle spécifié" +PythonNumpyLogspace = "Nombres espacés sur une échelle logarithmique" +PythonNumpyCopy = "Copie du tableau" +PythonNumpyDtype = "Dtype du tableau" +PythonNumpyFlat = "Itérateur plat du tableau" +PythonNumpyFlatten = "Version aplatie du tableau" +PythonNumpyShape = "Obtenir la forme du tableau" +PythonNumpyReshape = "Remplacer la forme du tableau par s" +PythonNumpySize = "Nombre d'éléments dans le tableau" +PythonNumpyTranspose = "Version transposée du tableau" +PythonNumpySortWithArguments = "Version triée du tableau" +PythonNumpyNdinfo = "Imprimer des informations sur un" +PythonNumpyAll = "Tester si tous les éléments de a sont trye" +PythonNumpyAny = "Tester si un élément de a est vrai" +PythonNumpyArgmax = "Indice de la valeur maximale de a" +PythonNumpyArgmin = "Indice de la valeur minimale de a" +PythonNumpyArgsort = "Indices qui trieraient un tableau" +PythonNumpyClip = "Couper les valeurs dans un tableau" +PythonNumpyConvolve = "Convolution linéaire discrète de a et b" +PythonNumpyDiff = "Dérivée du a" +PythonNumpyInterp = "Valeurs interpolées linéairement de a" +PythonNumpyDot = "Produit scalaire de a et b" +PythonNumpyCross = "Produit vectoriel de a et b" +PythonNumpyEqual = "a == a élément par élément" +PythonNumpyNot_equal = "a != a élément par élément" +PythonNumpyFlip = "Tableau de retournement" +PythonNumpyIsfinite = "Testez la finitude élément par élément" +PythonNumpyIsinf = "Testez l'infinité élément par élément" +PythonNumpyMean = "Moyenne d" +PythonNumpyMin = "Valeur maximale de a" +PythonNumpyMax = "Valeur minimale de a" +PythonNumpyMedian = "Valeur médiane de a" +PythonNumpyMinimum = "Minimum d'éléments de tableau par élément" +PythonNumpyMaximum = "Maximum par élément d'éléments de tableau" +PythonNumpyPolyfit = "Ajustement polynomial des moindres carrés" +PythonNumpyPolyval = "Évaluer un polynôme à des valeurs spécifiques" +PythonNumpyRoll = "Décaler le contenu de a par n" +PythonNumpySort = "Trier a" +PythonNumpyStd = "Calculer l'écart type de a" +PythonNumpySum = "Calculer la somme de a" +PythonNumpyTrace = "Calculer la somme des éléments diagonaux de a" +PythonNumpyTrapz = "Intégrer à l'aide de la règle trapézoïdale composite" +PythonNumpyWhere = "Renvoie des éléments choisis parmi x ou y selon c" +PythonNumpyVectorize = "Vectoriser la fonction python générique f" +PythonNumpyAcos = "Appliquer acos élément par élément" +PythonNumpyAcosh = "Appliquer acosh élément par élément" +PythonNumpyArctan2 = "Appliquer arctan2 élément par élément" +PythonNumpyAround = "Appliquer autour de l'élément" +PythonNumpyAsin = "Appliquer asin élément par élément" +PythonNumpyAsinh = "Appliquer asinh élément par élément" +PythonNumpyAtan = "Appliquer un élément par élément" +PythonNumpyAtanh = "Appliquer atanh élément par élément" +PythonNumpyCeil = "Appliquer le plafond par élément" +PythonNumpyCos = "Appliquer cos élément par élément" +PythonNumpyCosh = "Appliquer cosh élément par élément" +PythonNumpyDegrees = "Appliquer des degrés élément par élément" +PythonNumpyExp = "Appliquer exp par élément" +PythonNumpyExpm1 = "Appliquer expm1 élément par élément" +PythonNumpyFloor = "Appliquer le sol par élément" +PythonNumpyLog = "Appliquer le journal par élément" +PythonNumpyLog10 = "Appliquer log10 élément par élément" +PythonNumpyLog2 = "Appliquer log2 élément par élément" +PythonNumpyRadians = "Appliquer des radians par élément" +PythonNumpySin = "Appliquer le péché par élément" +PythonNumpySinh = "Appliquer sinh élément par élément" +PythonNumpySqrt = "Appliquer sqrt élément par élément" +PythonNumpyTan = "Appliquer le bronzage par élément" +PythonNumpyTanh = "Appliquer tanh par élément" +PythonNumpyBool = "Type bool de numpy" +PythonNumpyFloat = "Type float de numpy" +PythonNumpyUint8 = "Tapez uint8 de numpy" +PythonNumpyInt8 = "Tapez int8 de numpy" +PythonNumpyUint16 = "Tapez uint16 de numpy" +PythonNumpyInt16 = "Tapez int16 de numpy" +PythonNumpyNan = "Nan représentation de numpy" +PythonNumpyInf = "Inf représentation de numpy" +PythonNumpyE = "2.718281828459045" +PythonNumpyPi = "3.141592653589793" +PythonNumpyFft = "Transformée de Fourier discrète à une dimension" +PythonNumpyIfft = "Transformée de Fourier discrète inverse unidimensionnelle" +PythonNumpyDet = "Déterminant de a" +PythonNumpyEig = "Valeurs propres et vecteurs propres droits de a" +PythonNumpyCholesky = "Décomposition de Cholesky" +PythonNumpyInv = "Matrice inverse a" +PythonNumpyNorm = "Norme matricielle ou vectorielle" PythonOct = "Conversion en octal" PythonPhase = "Argument de z" PythonPlot = "Trace y en fonction de x" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index 90a82696f66..f5365e63396 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -74,6 +74,7 @@ PythonImportKandinsky = "Kandinsky modul importálása" PythonImportRandom = "Véletlenszerü modul importálása" PythonImportMath = "math modul importálása" PythonImportMatplotlibPyplot = "matplotlib.pyplot modul importálása" +PythonImportNumpy = "ulab.numpy modul importálása" PythonImportTurtle = "turtle modul importálása" PythonImportTime = "time modul importálása" PythonIndex = "Az elsö x esemény indexe" @@ -98,6 +99,103 @@ PythonMax = "Maximum" PythonMin = "Minimum" PythonModf = "x-nek tört és egész részei" PythonMonotonic = "Az óra értékét adja vissza" +PythonNumpyFunction = "numpy elötag" +PythonNumpyFftFunction = "numpy.fft elötag" +PythonNumpyLinalgFunction = "numpy.linalg elötag" +PythonNumpyArray = "Egy tömb konvertálása ndarray -re" +PythonNumpyArange = "Készítsen táblázatot az (i) tartományból" +PythonNumpyConcatenate = "Összekapcsolás a és b" +PythonNumpyDiag = "Bontson ki vagy készítsen átlós tömböt" +PythonNumpyZeros = "S alakú tömb 0 -val kitöltve" +PythonNumpyOnes = "S alakú tömb 1-el" +PythonNumpyEmpty = "Az űrlap inicializálatlan tömbje" +PythonNumpyEye = "Asztal 1 -es átlóval és 0 -val máshol" +PythonNumpyFull = "S alakú tömb tele v" +PythonNumpyLinspace = "Számok meghatározott intervallumon belül" +PythonNumpyLogspace = "A számok logaritmikus skálán helyezkednek el" +PythonNumpyCopy = "A táblázat másolata" +PythonNumpyDtype = "Táblázat típusa" +PythonNumpyFlat = "Lapos tömb iterátor" +PythonNumpyFlatten = "Az asztal lapított változata" +PythonNumpyShape = "Szerezd meg a tömb alakját" +PythonNumpyReshape = "Cserélje le a tömb alakját az s -vel" +PythonNumpySize = "A tömb elemeinek száma" +PythonNumpyTranspose = "A táblázat átültetett változata" +PythonNumpySortWithArguments = "A táblázat rendezett változata" +PythonNumpyNdinfo = "Információk nyomtatása a" +PythonNumpyAll = "Ellenőrizze, hogy egy elem minden eleme trye" +PythonNumpyAny = "Ellenőrizze, hogy az a eleme igaz -e" +PythonNumpyArgmax = "A maximális érték indexe a" +PythonNumpyArgmin = "A minimális értékének a indexe" +PythonNumpyArgsort = "Nyomok, amelyek rendeznek egy tömböt" +PythonNumpyClip = "Vágja le az értékeket egy tömbben" +PythonNumpyConvolve = "A és b diszkrét lineáris konvolúciója" +PythonNumpyDiff = "Származik a" +PythonNumpyInterp = "A lineárisan interpolált értékei" +PythonNumpyDot = "Az a és b pontszerű szorzata" +PythonNumpyCross = "Az a és b keresztterméke" +PythonNumpyEqual = "A == elemenként" +PythonNumpyNot_equal = "A! = Elemenként" +PythonNumpyFlip = "Fordulóasztal" +PythonNumpyIsfinite = "Tesztelje a végességet elemenként" +PythonNumpyIsinf = "Tesztelje a végtelen elemet elemenként" +PythonNumpyMean = "Átlagos d" +PythonNumpyMin = "Maximális értéke a" +PythonNumpyMax = "Minimális értéke a" +PythonNumpyMedian = "Medián értéke a" +PythonNumpyMinimum = "Minimális tömb elemek elemenként" +PythonNumpyMaximum = "Maximum tömb elemenként" +PythonNumpyPolyfit = "Legkevesebb négyzet polinom illeszkedés" +PythonNumpyPolyval = "Polinom értékelése meghatározott értékeken" +PythonNumpyRoll = "Az a tartalmának eltolása n -vel" +PythonNumpySort = "Rendezés ide" +PythonNumpyStd = "Számítsa ki a szórását a" +PythonNumpySum = "Számítsa ki a összegét" +PythonNumpyTrace = "Számítsa ki az a átlós elemeinek összegét!" +PythonNumpyTrapz = "Integrálja az összetett trapéz vonalzó használatával" +PythonNumpyWhere = "A c szerint x vagy y közül választott elemeket adja vissza" +PythonNumpyVectorize = "Vektorizálja az általános python függvényt f" +PythonNumpyAcos = "Alkalmazza az acos -t elemenként" +PythonNumpyAcosh = "Alkalmazza az elemeket elemenként" +PythonNumpyArctan2 = "Alkalmazza az arctan2 elemet elemenként" +PythonNumpyAround = "Alkalmazza az elem körül" +PythonNumpyAsin = "Alkalmazza az asszonyt elemenként" +PythonNumpyAsinh = "Alkalmazza az elemet elemenként" +PythonNumpyAtan = "Alkalmazzon egy elemet elemenként" +PythonNumpyAtanh = "Alkalmazza az atanh elemenként" +PythonNumpyCeil = "Alkalmazza a mennyezetet elemenként" +PythonNumpyCos = "Alkalmazza a cos elemet elemenként" +PythonNumpyCosh = "Alkalmazza a cosh elemet elemenként" +PythonNumpyDegrees = "Alkalmazza a fokokat elemenként" +PythonNumpyExp = "Alkalmazza az exp -ot elemenként" +PythonNumpyExpm1 = "Alkalmazza az expm1 elemet elemenként" +PythonNumpyFloor = "A talajt elemenként vigye fel" +PythonNumpyLog = "Napló alkalmazása tétel szerint" +PythonNumpyLog10 = "Alkalmazza a log10 elemet elemenként" +PythonNumpyLog2 = "Alkalmazza a log2 elemet elemenként" +PythonNumpyRadians = "Alkalmazzon radiánt elemenként" +PythonNumpySin = "Alkalmazza a bűnt elemenként" +PythonNumpySinh = "Alkalmazza a sinh elemet elemenként" +PythonNumpySqrt = "Alkalmazza az sqrt elemet elemenként" +PythonNumpyTan = "Vigye fel a barnulást elemenként" +PythonNumpyTanh = "Alkalmazzon tannt elemenként" +PythonNumpyBool = "Bull típusú numpy" +PythonNumpyFloat = "Lebegő típusú számológép" +PythonNumpyUint8 = "Írja be az uint8 számot" +PythonNumpyInt8 = "Írja be a numpy int8 típusát" +PythonNumpyUint16 = "Írja be az uint16 parancsot a numpy -ból" +PythonNumpyInt16 = "Írja be a numpy int16 típusát" +PythonNumpyNan = "A numpy nanos ábrázolása" +PythonNumpyInf = "A numpy inf ábrázolása" +PythonNumpyE = "2.718281828459045" +PythonNumpyPi = "3.141592653589793" +PythonNumpyFft = "Egydimenziós diszkrét Fourier-transzformáció" +PythonNumpyIfft = "Egydimenziós inverz diszkrét Fourier-transzformáció" +PythonNumpyDet = "Meghatározó a" +PythonNumpyEig = "Sajátértékek és jobb sajátvektorok a" +PythonNumpyCholesky = "Cholesky bomlás" +PythonNumpyInv = "Fordított mátrix a" +PythonNumpyNorm = "Mátrix vagy vektor standard" PythonOct = "Decimális szám konvertálása octális számra" PythonPhase = "z fázisa" PythonPlot = "y-t jelöli x függvényében" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index da188e0bed4..9402ad69a5c 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -74,6 +74,7 @@ PythonImportKandinsky = "Importa modulo kandinsky" PythonImportRandom = "Importa modulo random" PythonImportMath = "Importa modulo math" PythonImportMatplotlibPyplot = "Importa modulo matplotlib.pyplot" +PythonImportNumpy = "Importa modulo ulab.numpy" PythonImportTurtle = "Importa del modulo turtle" PythonImportTime = "Importa del modulo time" PythonImportOs = "Importa modulo os" @@ -104,6 +105,103 @@ PythonMax = "Massimo" PythonMin = "Minimo" PythonModf = "Parti frazionarie e intere" PythonMonotonic = "Restituisce il valore dell'orologio" +PythonNumpyFunction = "Prefisso modulo numpy" +PythonNumpyFftFunction = "Prefisso modulo numpy.fft" +PythonNumpyLinalgFunction = "Prefisso modulo numpy.linalg" +PythonNumpyArray = "Converti un array in ndarray" +PythonNumpyArange = "Crea una tabella dall'intervallo (i)" +PythonNumpyConcatenate = "Concatena a e b" +PythonNumpyDiag = "Estrai o costruisci un array diagonale" +PythonNumpyZeros = "Matrice a forma di S riempita con 0" +PythonNumpyOnes = "Array a forma di S riempito con 1" +PythonNumpyEmpty = "Matrice non inizializzata della forma s" +PythonNumpyEye = "Tabella con 1 in diagonale e 0 altrove" +PythonNumpyFull = "Matrice a forma di S riempita con v" +PythonNumpyLinspace = "Numeri spaziati su un intervallo specificato" +PythonNumpyLogspace = "Numeri spaziati su una scala logaritmica" +PythonNumpyCopy = "Copia della tabella" +PythonNumpyDtype = "Tipo di tabella" +PythonNumpyFlat = "Iteratore flat array" +PythonNumpyFlatten = "Versione appiattita del tavolo" +PythonNumpyShape = "Ottieni la forma dell'array" +PythonNumpyReshape = "Sostituisci la forma dell'array con s" +PythonNumpySize = "Numero di elementi nell'array" +PythonNumpyTranspose = "Versione trasposta della tabella" +PythonNumpySortWithArguments = "Versione ordinata della tabella" +PythonNumpyNdinfo = "Stampa informazioni su a" +PythonNumpyAll = "Verifica se tutti gli elementi di a sono provati" +PythonNumpyAny = "Verifica se un elemento di a è vero" +PythonNumpyArgmax = "Indice del valore massimo di a" +PythonNumpyArgmin = "Pedice del valore minimo di a" +PythonNumpyArgsort = "Indizi che ordinerebbero un array" +PythonNumpyClip = "Taglia i valori in un array" +PythonNumpyConvolve = "Convoluzione lineare discreta di a e b" +PythonNumpyDiff = "Derivato da a" +PythonNumpyInterp = "Valori interpolati linearmente di a" +PythonNumpyDot = "Prodotto scalare di a e b" +PythonNumpyCross = "Prodotto incrociato di a e b" +PythonNumpyEqual = "A == un elemento per elemento" +PythonNumpyNot_equal = "A! = Un elemento per elemento" +PythonNumpyFlip = "Tavolo di turnaround" +PythonNumpyIsfinite = "Testa la finitezza elemento per elemento" +PythonNumpyIsinf = "Prova l'infinito elemento per elemento" +PythonNumpyMean = "d . medio" +PythonNumpyMin = "Valore massimo di a" +PythonNumpyMax = "Valore minimo di a" +PythonNumpyMedian = "Valore medio di a" +PythonNumpyMinimum = "Elementi minimi dell'array per elemento" +PythonNumpyMaximum = "Massimo per elemento di elementi dell'array" +PythonNumpyPolyfit = "Approssimazione polinomiale ai minimi quadrati" +PythonNumpyPolyval = "Valuta un polinomio a valori specifici" +PythonNumpyRoll = "Sposta il contenuto di a di n" +PythonNumpySort = "Ordina per" +PythonNumpyStd = "Calcola la deviazione standard di a" +PythonNumpySum = "Calcola la somma di a" +PythonNumpyTrace = "Calcola la somma degli elementi diagonali di a" +PythonNumpyTrapz = "Integrare utilizzando il righello trapezoidale composito" +PythonNumpyWhere = "Restituisce elementi scelti da x o y secondo c" +PythonNumpyVectorize = "Vettorizza la funzione Python generica f" +PythonNumpyAcos = "Applica acos articolo per articolo" +PythonNumpyAcosh = "Applica acosh articolo per articolo" +PythonNumpyArctan2 = "Applica arctan2 elemento per elemento" +PythonNumpyAround = "Applicare intorno all'elemento" +PythonNumpyAsin = "Applica asin elemento per elemento" +PythonNumpyAsinh = "Applica asinh elemento per elemento" +PythonNumpyAtan = "Applicare un articolo per articolo" +PythonNumpyAtanh = "Applicare atanh elemento per elemento" +PythonNumpyCeil = "Applicare il soffitto per elemento" +PythonNumpyCos = "Applica cos elemento per elemento" +PythonNumpyCosh = "Applicare cosh elemento per elemento" +PythonNumpyDegrees = "Applica gradi elemento per elemento" +PythonNumpyExp = "Applica esperienza per articolo" +PythonNumpyExpm1 = "Applica expm1 elemento per elemento" +PythonNumpyFloor = "Applicare terreno per elemento" +PythonNumpyLog = "Applica giornale per articolo" +PythonNumpyLog10 = "Applica log10 elemento per elemento" +PythonNumpyLog2 = "Applica log2 elemento per elemento" +PythonNumpyRadians = "Applica radianti per elemento" +PythonNumpySin = "Applica sin per elemento" +PythonNumpySinh = "Applica sinh elemento per elemento" +PythonNumpySqrt = "Applica sqrt elemento per elemento" +PythonNumpyTan = "Applicare l'abbronzatura per elemento" +PythonNumpyTanh = "Applicare tanh per articolo" +PythonNumpyBool = "Tipo bool di numpy" +PythonNumpyFloat = "Tipo galleggiante di numpy" +PythonNumpyUint8 = "Digita uint8 di numpy" +PythonNumpyInt8 = "Digita int8 di numpy" +PythonNumpyUint16 = "Digita uint16 da numpy" +PythonNumpyInt16 = "Digita int16 di numpy" +PythonNumpyNan = "Nan rappresentazione di numpy" +PythonNumpyInf = "Inf rappresentazione di numpy" +PythonNumpyE = "2.718281828459045" +PythonNumpyPi = "3.141592653589933" +PythonNumpyFft = "Trasformata di Fourier discreta unidimensionale" +PythonNumpyIfft = "Trasformata di Fourier discreta inversa unidimensionale" +PythonNumpyDet = "Determinante di a" +PythonNumpyEig = "Autovalori e autovettori giusti di a" +PythonNumpyCholesky = "Decomposizione Cholesky" +PythonNumpyInv = "matrice inversa a" +PythonNumpyNorm = "Matrice o standard vettoriale" PythonOct = "Conversione in ottale" PythonPhase = "Argomento di z" PythonPlot = "Disegna y in f. di x come linee" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index fb6669df374..fa54b6bc1c9 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -74,6 +74,7 @@ PythonImportKandinsky = "Importeer kandinsky module" PythonImportRandom = "Importeer random module" PythonImportMath = "Importeer math module" PythonImportMatplotlibPyplot = "Importeer matplotlib.pyplot module" +PythonImportNumpy = "Importeer ulab.numpy module" PythonImportTime = "Importeer time module" PythonImportOs = "Importeer os module" PythonOsUname = " Krijg systeeminfo" @@ -104,6 +105,103 @@ PythonMax = "Maximum" PythonMin = "Minimum" PythonModf = "Fractionele en gehele delen van x" PythonMonotonic = "Waarde van een monotone klok" +PythonNumpyFunction = "numpy module prefix" +PythonNumpyFftFunction = "numpy.fft module prefix" +PythonNumpyLinalgFunction = "numpy.linalg module prefix" +PythonNumpyArray = "Converteer een array naar ndarray" +PythonNumpyArange = "Maak een tabel uit de reeks (i)" +PythonNumpyConcatenate = "Samenvoegen a en b" +PythonNumpyDiag = "Een diagonale array extraheren of construeren" +PythonNumpyZeros = "S-vormarray gevuld met 0" +PythonNumpyOnes = "S-vormige array gevuld met 1" +PythonNumpyEmpty = "Niet-geïnitialiseerde matrix van vorm s" +PythonNumpyEye = "Tabel met enen op de diagonaal en nullen elders" +PythonNumpyFull = "S-vormarray gevuld met v" +PythonNumpyLinspace = "Getallen verdeeld over een opgegeven interval" +PythonNumpyLogspace = "Getallen op een logaritmische schaal verdeeld" +PythonNumpyCopy = "Kopie van tabel" +PythonNumpyDtype = "Tafeltype:" +PythonNumpyFlat = "Flat array iterator" +PythonNumpyFlatten = "Afgeplatte versie van de tafel" +PythonNumpyShape = "De vorm van de array verkrijgen" +PythonNumpyReshape = "Vervang matrixvorm door s" +PythonNumpySize = "Aantal elementen in de array" +PythonNumpyTranspose = "Getransponeerde versie van de tabel" +PythonNumpySortWithArguments = "Gesorteerde versie van de tafel" +PythonNumpyNdinfo = "Informatie afdrukken over a" +PythonNumpyAll = "Test of alle elementen van a trye zijn" +PythonNumpyAny = "Test of een element van a waar is" +PythonNumpyArgmax = "Index van de maximale waarde van a" +PythonNumpyArgmin = "Subscript van de minimumwaarde van a" +PythonNumpyArgsort = "Aanwijzingen die een array zouden sorteren" +PythonNumpyClip = "Knip waarden in een array" +PythonNumpyConvolve = "Discrete lineaire convolutie van a en b" +PythonNumpyDiff = "Afgeleid van a" +PythonNumpyInterp = "Lineair geïnterpoleerde waarden van a" +PythonNumpyDot = "Puntproduct van a en b" +PythonNumpyCross = "Kruisproduct van a en b" +PythonNumpyEqual = "A == een element voor element" +PythonNumpyNot_equal = "A! = Een element voor element" +PythonNumpyFlip = "Omslagtabel" +PythonNumpyIsfinite = "Test de eindigheid element voor element" +PythonNumpyIsinf = "Test het oneindige element voor element" +PythonNumpyMean = "gemiddelde d" +PythonNumpyMin = "Maximale waarde van a" +PythonNumpyMax = "Minimale waarde van a" +PythonNumpyMedian = "Mediane waarde van a" +PythonNumpyMinimum = "Minimum array-elementen per element" +PythonNumpyMaximum = "Maximum per element van array-elementen" +PythonNumpyPolyfit = "Kleinste kwadraten polynoom fit" +PythonNumpyPolyval = "Een polynoom evalueren op specifieke waarden" +PythonNumpyRoll = "Verschuif de inhoud van a met n" +PythonNumpySort = "Sorteren op" +PythonNumpyStd = "Bereken de standaarddeviatie van a" +PythonNumpySum = "Bereken de som van a" +PythonNumpyTrace = "Bereken de som van de diagonale elementen van a" +PythonNumpyTrapz = "Integreer met behulp van de samengestelde trapeziumvormige liniaal" +PythonNumpyWhere = "Retourneert elementen gekozen uit x of y volgens c" +PythonNumpyVectorize = "Vectoriseer de generieke python-functie f" +PythonNumpyAcos = "Acos item per item toepassen" +PythonNumpyAcosh = "Acosh item voor item toepassen" +PythonNumpyArctan2 = "Arctan2 element voor element toepassen" +PythonNumpyAround = "Toepassen rond het element" +PythonNumpyAsin = "Asin element voor element toepassen" +PythonNumpyAsinh = "Asinh element voor element toepassen" +PythonNumpyAtan = "Eén item per item toepassen" +PythonNumpyAtanh = "Atanh element voor element toepassen" +PythonNumpyCeil = "Breng het plafond per element aan" +PythonNumpyCos = "Pas co element voor element toe" +PythonNumpyCosh = "Cosh element voor element toepassen" +PythonNumpyDegrees = "Graden element voor element toepassen" +PythonNumpyExp = "exp per item toepassen" +PythonNumpyExpm1 = "expm1 element voor element toepassen" +PythonNumpyFloor = "Grond per element aanbrengen" +PythonNumpyLog = "Journaal per item toepassen" +PythonNumpyLog10 = "Pas log10 element voor element toe" +PythonNumpyLog2 = "Log2 element voor element toepassen" +PythonNumpyRadians = "Pas radialen toe per element" +PythonNumpySin = "Zonde per element toepassen" +PythonNumpySinh = "Sinh element voor element toepassen" +PythonNumpySqrt = "Sqrt element voor element toepassen" +PythonNumpyTan = "Breng de kleur aan per element" +PythonNumpyTanh = "Tanh toepassen per item" +PythonNumpyBool = "Bool type numpy" +PythonNumpyFloat = "Float type numpy" +PythonNumpyUint8 = "Typ uint8 van numpy" +PythonNumpyInt8 = "Typ int8 van numpy" +PythonNumpyUint16 = "Typ uint16 van numpy" +PythonNumpyInt16 = "Typ int16 van numpy" +PythonNumpyNan = "Nan vertegenwoordiging van numpy" +PythonNumpyInf = "Inf representatie van numpy" +PythonNumpyE = "2.718281828459045" +PythonNumpyPi = "3.141592653589793" +PythonNumpyFft = "Eendimensionale discrete fouriertransformatie" +PythonNumpyIfft = "Eendimensionale inverse discrete fouriertransformatie" +PythonNumpyDet = "Determinant van a" +PythonNumpyEig = "Eigenwaarden en rechter eigenvectoren van a" +PythonNumpyCholesky = "Cholesky-decompositie" +PythonNumpyInv = "Inverse matrix a" +PythonNumpyNorm = "Matrix- of vectorstandaard" PythonOct = "Integer omzetten naar octaal" PythonPhase = "Fase van z in radialen" PythonPlot = "Plot y versus x als lijnen" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index 2cfc976cd1a..be87d228577 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -74,6 +74,7 @@ PythonImportKandinsky = "Importar módulo kandinsky" PythonImportRandom = "Importar módulo random" PythonImportMath = "Importar módulo math" PythonImportMatplotlibPyplot = "Importar módulo matplotlib.pyplot" +PythonImportNumpy = "Importar módulo ulab.numpy" PythonImportTime = "Importar módulo time" PythonImportTurtle = "Importar módulo turtle" PythonIndex = "Índice da primeira ocorrência de x" @@ -98,6 +99,103 @@ PythonMax = "Máximo" PythonMin = "Mínimo" PythonModf = "Partes inteira e frácionária de x" PythonMonotonic = "Devolve o valor do relógio" +PythonNumpyFunction = "Prefixo do módulo numpy" +PythonNumpyFftFunction = "Prefixo do módulo numpy.fft" +PythonNumpyLinalgFunction = "Prefixo do módulo numpy.linalg" +PythonNumpyArray = "Converter uma matriz em ndarray" +PythonNumpyArange = "Faça uma mesa do intervalo (i)" +PythonNumpyConcatenate = "Concatenar a e b" +PythonNumpyDiag = "Extraia ou construa uma matriz diagonal" +PythonNumpyZeros = "Matriz de forma S preenchida com 0" +PythonNumpyOnes = "Matriz em forma de S preenchida com 1" +PythonNumpyEmpty = "Matriz não inicializada de formulários" +PythonNumpyEye = "Tabela com 1s na diagonal e 0s em outros lugares" +PythonNumpyFull = "Matriz de forma S preenchida com v" +PythonNumpyLinspace = "Números espaçados em um intervalo especificado" +PythonNumpyLogspace = "Números espaçados em escala logarítmica" +PythonNumpyCopy = "Cópia da tabela" +PythonNumpyDtype = "Tipo de mesa" +PythonNumpyFlat = "Iterador de matriz plana" +PythonNumpyFlatten = "Versão achatada da mesa" +PythonNumpyShape = "Obtenha a forma da matriz" +PythonNumpyReshape = "Substitua a forma da matriz por s" +PythonNumpySize = "Número de elementos na matriz" +PythonNumpyTranspose = "Versão transposta da tabela" +PythonNumpySortWithArguments = "Versão ordenada da tabela" +PythonNumpyNdinfo = "Imprimir informações sobre um" +PythonNumpyAll = "Teste se todos os elementos de um são trye" +PythonNumpyAny = "Teste se um elemento de a é verdadeiro" +PythonNumpyArgmax = "Índice do valor máximo de um" +PythonNumpyArgmin = "Subscrito do valor mínimo de um" +PythonNumpyArgsort = "Pistas que classificariam um array" +PythonNumpyClip = "Corte os valores em uma matriz" +PythonNumpyConvolve = "Convolução linear discreta de a e b" +PythonNumpyDiff = "Derivado de um" +PythonNumpyInterp = "Valores linearmente interpolados de um" +PythonNumpyDot = "Produto escalar de a e b" +PythonNumpyCross = "Produto cruzado de a e b" +PythonNumpyEqual = "A == um elemento por elemento" +PythonNumpyNot_equal = "A! = Um elemento por elemento" +PythonNumpyFlip = "Mesa giratória" +PythonNumpyIsfinite = "Teste a finitude elemento por elemento" +PythonNumpyIsinf = "Teste o infinito elemento por elemento" +PythonNumpyMean = "D médio" +PythonNumpyMin = "Valor máximo de a" +PythonNumpyMax = "Valor mínimo de a" +PythonNumpyMedian = "Valor mediano de a" +PythonNumpyMinimum = "Elementos mínimos da matriz por elemento" +PythonNumpyMaximum = "Máximo por elemento de elementos da matriz" +PythonNumpyPolyfit = "Ajuste polinomial de mínimos quadrados" +PythonNumpyPolyval = "Avalie um polinômio em valores específicos" +PythonNumpyRoll = "Mudar o conteúdo de a por n" +PythonNumpySort = "Classificar para" +PythonNumpyStd = "Calcule o desvio padrão de um" +PythonNumpySum = "Calcule a soma de um" +PythonNumpyTrace = "Calcule a soma dos elementos diagonais de um" +PythonNumpyTrapz = "Integre usando a régua trapezoidal composta" +PythonNumpyWhere = "Retorna elementos escolhidos de x ou y de acordo com c" +PythonNumpyVectorize = "Vectorize a função python genérica f" +PythonNumpyAcos = "Aplicar acos item por item" +PythonNumpyAcosh = "Aplicar acosh item por item" +PythonNumpyArctan2 = "Aplicar arctan2 elemento por elemento" +PythonNumpyAround = "Aplicar ao redor do elemento" +PythonNumpyAsin = "Aplicar asin elemento a elemento" +PythonNumpyAsinh = "Aplicar asinh elemento por elemento" +PythonNumpyAtan = "Aplicar um item por item" +PythonNumpyAtanh = "Aplicar atanh elemento por elemento" +PythonNumpyCeil = "Aplicar o teto por elemento" +PythonNumpyCos = "Aplicar cos elemento por elemento" +PythonNumpyCosh = "Aplicar cosh elemento por elemento" +PythonNumpyDegrees = "Aplicar graus elemento a elemento" +PythonNumpyExp = "Aplicar exp por item" +PythonNumpyExpm1 = "Aplicar expm1 elemento a elemento" +PythonNumpyFloor = "Aplicar solo por elemento" +PythonNumpyLog = "Aplicar diário por item" +PythonNumpyLog10 = "Aplicar log10 elemento a elemento" +PythonNumpyLog2 = "Aplicar log2 elemento por elemento" +PythonNumpyRadians = "Aplicar radianos por elemento" +PythonNumpySin = "Aplicar pecado por elemento" +PythonNumpySinh = "Aplicar sinh elemento a elemento" +PythonNumpySqrt = "Aplicar sqrt elemento a elemento" +PythonNumpyTan = "Aplicar o bronzeado por elemento" +PythonNumpyTanh = "Aplicar tanh por item" +PythonNumpyBool = "Tipo Bool de entorpecido" +PythonNumpyFloat = "Tipo flutuante de entorpecimento" +PythonNumpyUint8 = "Digite uint8 de numpy" +PythonNumpyInt8 = "Digite int8 de numpy" +PythonNumpyUint16 = "Digite uint16 de numpy" +PythonNumpyInt16 = "Digite int16 de numpy" +PythonNumpyNan = "Representação Nan de numpy" +PythonNumpyInf = "Representação de inf de numpy" +PythonNumpyE = "2.718281828459045" +PythonNumpyPi = "3,141592653589793" +PythonNumpyFft = "Transformada discreta de Fourier unidimensional" +PythonNumpyIfft = "Transformada de Fourier discreta inversa unidimensional" +PythonNumpyDet = "Determinante de um" +PythonNumpyEig = "Valores próprios e vetores próprios direitos de um" +PythonNumpyCholesky = "Decomposição de Cholesky" +PythonNumpyInv = "Matriz inversa a" +PythonNumpyNorm = "Matriz ou padrão vetorial" PythonOct = "Converter número inteiro em octal" PythonPhase = "Argumento de z" PythonPlot = "Desenhar y em função de x" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index fd3d4294565..106d57dbbf0 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -90,6 +90,7 @@ PythonCommandImportIon = "import ion" PythonCommandImportKandinsky = "import kandinsky" PythonCommandImportMath = "import math" PythonCommandImportMatplotlibPyplot = "import matplotlib.pyplot" +PythonCommandImportFromNumpy = "from ulab import numpy as np" PythonCommandImportRandom = "import random" PythonCommandImportOs = "import os" PythonCommandImportFromOs = "from os import *" @@ -170,6 +171,115 @@ PythonCommandMax = "max(list)" PythonCommandMin = "min(list)" PythonCommandModf = "modf(x)" PythonCommandMonotonic = "monotonic()" +PythonCommandNumpyArray = "np.array(a)" +PythonCommandNumpyArange = "np.arange(i)" +PythonCommandNumpyConcatenate = "np.concatenate(a,b)" +PythonCommandNumpyDiag = "np.diag(a)" +PythonCommandNumpyZeros = "np.zeros(s)" +PythonCommandNumpyOnes = "np.ones(s)" +PythonCommandNumpyEmpty = "np.empty(s)" +PythonCommandNumpyEye = "np.eye(n, m)" +PythonCommandNumpyFull = "np.full(s, v)" +PythonCommandNumpyLinspace = "np.linspace(s, e)" +PythonCommandNumpyLogspace = "np.logspace(s, e)" +PythonCommandNumpyCopy = "ndarray.copy()" +PythonCommandNumpyCopyWithoutArg = ".copy()" +PythonCommandNumpyDtype = "ndarray.dtype" +PythonCommandNumpyDtypeWithoutArg = ".dtype" +PythonCommandNumpyFlat = "ndarray.flat" +PythonCommandNumpyFlatWithoutArg = ".flat" +PythonCommandNumpyFlatten = "ndarray.flatten()" +PythonCommandNumpyFlattenWithoutArg = ".flatten()" +PythonCommandNumpyShape = "ndarray.shape" +PythonCommandNumpyShapeWithoutArg = ".shape" +PythonCommandNumpyReshape = "ndarray.reshape(s)" +PythonCommandNumpyReshapeWithoutArg = ".reshape(\x11)" +PythonCommandNumpySize = "ndarray.size" +PythonCommandNumpySizeWithoutArg = ".size" +PythonCommandNumpyTranspose = "ndarray.transpose()" +PythonCommandNumpyTransposeWithoutArg = ".transpose()" +PythonCommandNumpySort = "ndarray.sort()" +PythonCommandNumpySortWithoutArg = ".sort()" +PythonCommandNumpyNdinfo = "np.ndinfo(a)" +PythonCommandNumpyAll = "np.all(a)" +PythonCommandNumpyAny = "np.any(a)" +PythonCommandNumpyArgmax = "np.argmax(a)" +PythonCommandNumpyArgmin = "np.argmin(a)" +PythonCommandNumpyArgsort = "np.argsort(a)" +PythonCommandNumpyClip = "np.clip(a, min, max)" +PythonCommandNumpyConvolve = "np.convolve(a, b)" +PythonCommandNumpyDiff = "np.diff(a)" +PythonCommandNumpyInterp = "np.interp(a)" +PythonCommandNumpyDot = "np.dot(a, b)" +PythonCommandNumpyCross = "np.cross(a, b)" +PythonCommandNumpyEqual = "np.equal(a, b)" +PythonCommandNumpyNot_equal = "np.not_equal(a, b)" +PythonCommandNumpyFlip = "np.flip(a)" +PythonCommandNumpyIsfinite = "np.isfinite(a)" +PythonCommandNumpyIsinf = "np.isinf(a)" +PythonCommandNumpyMean = "np.mean(a)" +PythonCommandNumpyMin = "np.min(a)" +PythonCommandNumpyMax = "np.max(a)" +PythonCommandNumpyMedian = "np.median(a)" +PythonCommandNumpyMinimum = "np.minimum(a, b)" +PythonCommandNumpyMaximum = "np.maximum(a, b)" +PythonCommandNumpyPolyfit = "np.polyfit(a, b, y)" +PythonCommandNumpyPolyval = "np.polyval(p, x)" +PythonCommandNumpyRoll = "np.roll(a, n)" +PythonCommandNumpySortWithArguments = "np.sort(a)" +PythonCommandNumpyStd = "np.std(a)" +PythonCommandNumpySum = "np.sum(a)" +PythonCommandNumpyTrace = "np.trace(a)" +PythonCommandNumpyTrapz = "np.trapz(y)" +PythonCommandNumpyWhere = "np.where(c, x, y)" +PythonCommandNumpyVectorize = "np.vectorize(f)" +PythonCommandNumpyAcos = "np.acos(a)" +PythonCommandNumpyAcosh = "np.acosh(a)" +PythonCommandNumpyArctan2 = "np.arctan2(a)" +PythonCommandNumpyAround = "np.around(a)" +PythonCommandNumpyAsin = "np.asin(a)" +PythonCommandNumpyAsinh = "np.asinh(a)" +PythonCommandNumpyAtan = "np.atan(a)" +PythonCommandNumpyAtanh = "np.atanh(a)" +PythonCommandNumpyCeil = "np.ceil(a)" +PythonCommandNumpyCos = "np.cos(a)" +PythonCommandNumpyCosh = "np.cosh(a)" +PythonCommandNumpyDegrees = "np.degrees(a)" +PythonCommandNumpyExp = "np.exp(a)" +PythonCommandNumpyExpm1 = "np.expm1(a)" +PythonCommandNumpyFloor = "np.floor(a)" +PythonCommandNumpyLog = "np.log(a)" +PythonCommandNumpyLog10 = "np.log10(a)" +PythonCommandNumpyLog2 = "np.log2(a)" +PythonCommandNumpyRadians = "np.radians(a)" +PythonCommandNumpySin = "np.sin(a)" +PythonCommandNumpySinh = "np.sinh(a)" +PythonCommandNumpySqrt = "np.sqrt(a)" +PythonCommandNumpyTan = "np.tan(a)" +PythonCommandNumpyTanh = "np.tanh(a)" +PythonCommandNumpyBool = "np.bool" +PythonCommandNumpyFloat = "np.float" +PythonCommandNumpyUint8 = "np.uint8" +PythonCommandNumpyInt8 = "np.int8" +PythonCommandNumpyUint16 = "np.uint16" +PythonCommandNumpyInt16 = "np.int16" +PythonCommandNumpyNan = "np.nan" +PythonCommandNumpyInf = "np.inf" +PythonCommandNumpyE = "np.e" +PythonCommandNumpyPi = "np.pi" +PythonCommandNumpyFft = "np.fft.fft(a)" +PythonCommandNumpyIfft = "np.fft.ifft(a)" +PythonCommandNumpyDet = "np.linalg.det(a)" +PythonCommandNumpyEig = "np.linalg.eig(a)" +PythonCommandNumpyCholesky = "np.linalg.cholesky(a)" +PythonCommandNumpyInv = "np.linalg.inv(a)" +PythonCommandNumpyNorm = "np.linalg.norm(a)" +PythonCommandNumpyFunction = "np.function" +PythonCommandNumpyFunctionWithoutArg = "np.\x11" +PythonCommandNumpyFftFunction = "np.fft.function" +PythonCommandNumpyFftFunctionWithoutArg = "np.fft.\x11" +PythonCommandNumpyLinalgFunction = "np.linalg.function" +PythonCommandNumpyLinalgFunctionWithoutArg = "np.linalg.\x11" PythonCommandOct = "oct(x)" PythonCommandPhase = "phase(z)" PythonCommandPlot = "plot(x,y,color)" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 3a17fe503f4..7d4c5384f59 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -135,6 +135,127 @@ const ToolboxMessageTree MatplotlibPyplotModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGray, I18n::Message::PythonColorGray, false) }; + const ToolboxMessageTree NumpyNdarrayModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArray, I18n::Message::PythonNumpyArray), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArange, I18n::Message::PythonNumpyArange), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyConcatenate, I18n::Message::PythonNumpyConcatenate), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDiag, I18n::Message::PythonNumpyDiag), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyZeros, I18n::Message::PythonNumpyZeros), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyOnes, I18n::Message::PythonNumpyOnes), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyEmpty, I18n::Message::PythonNumpyEmpty), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyEye, I18n::Message::PythonNumpyEye), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFull, I18n::Message::PythonNumpyFull), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLinspace, I18n::Message::PythonNumpyLinspace), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLogspace, I18n::Message::PythonNumpyLogspace), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCopy, I18n::Message::PythonNumpyCopy, false, I18n::Message::PythonCommandNumpyCopyWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDtype, I18n::Message::PythonNumpyDtype, false, I18n::Message::PythonCommandNumpyDtypeWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFlat, I18n::Message::PythonNumpyFlat, false, I18n::Message::PythonCommandNumpyFlatWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFlatten, I18n::Message::PythonNumpyFlatten, false, I18n::Message::PythonCommandNumpyFlattenWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyShape, I18n::Message::PythonNumpyShape, false, I18n::Message::PythonCommandNumpyShapeWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyReshape, I18n::Message::PythonNumpyReshape, false, I18n::Message::PythonCommandNumpyReshapeWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySize, I18n::Message::PythonNumpySize, false, I18n::Message::PythonCommandNumpySizeWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTranspose, I18n::Message::PythonNumpyTranspose, false, I18n::Message::PythonCommandNumpyTransposeWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySort, I18n::Message::PythonNumpySortWithArguments, false, I18n::Message::PythonCommandNumpySortWithoutArg), +}; + +const ToolboxMessageTree NumpyFunctionsModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyNdinfo, I18n::Message::PythonNumpyNdinfo), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAll, I18n::Message::PythonNumpyAll), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAny, I18n::Message::PythonNumpyAny), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArgmax, I18n::Message::PythonNumpyArgmax), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArgmin, I18n::Message::PythonNumpyArgmin), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArgsort, I18n::Message::PythonNumpyArgsort), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyClip, I18n::Message::PythonNumpyClip), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyConvolve, I18n::Message::PythonNumpyConvolve), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDiff, I18n::Message::PythonNumpyDiff), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInterp, I18n::Message::PythonNumpyInterp), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDot, I18n::Message::PythonNumpyDot), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCross, I18n::Message::PythonNumpyCross), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyEqual, I18n::Message::PythonNumpyEqual), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyNot_equal, I18n::Message::PythonNumpyNot_equal), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFlip, I18n::Message::PythonNumpyFlip), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyIsfinite, I18n::Message::PythonNumpyIsfinite), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyIsinf, I18n::Message::PythonNumpyIsinf), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMean, I18n::Message::PythonNumpyMean), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMin, I18n::Message::PythonNumpyMin), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMax, I18n::Message::PythonNumpyMax), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMedian, I18n::Message::PythonNumpyMedian), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMinimum, I18n::Message::PythonNumpyMinimum), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMaximum, I18n::Message::PythonNumpyMaximum), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyPolyfit, I18n::Message::PythonNumpyPolyfit), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyPolyval, I18n::Message::PythonNumpyPolyval), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyRoll, I18n::Message::PythonNumpyRoll), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySortWithArguments, I18n::Message::PythonNumpySort), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyStd, I18n::Message::PythonNumpyStd), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySum, I18n::Message::PythonNumpySum), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTrace, I18n::Message::PythonNumpyTrace), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTrapz, I18n::Message::PythonNumpyTrapz), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyWhere, I18n::Message::PythonNumpyWhere), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyVectorize, I18n::Message::PythonNumpyVectorize), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAcos, I18n::Message::PythonNumpyAcos), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAcosh, I18n::Message::PythonNumpyAcosh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArctan2, I18n::Message::PythonNumpyArctan2), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAround, I18n::Message::PythonNumpyAround), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAsin, I18n::Message::PythonNumpyAsin), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAsinh, I18n::Message::PythonNumpyAsinh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAtan, I18n::Message::PythonNumpyAtan), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAtanh, I18n::Message::PythonNumpyAtanh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCeil, I18n::Message::PythonNumpyCeil), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCos, I18n::Message::PythonNumpyCos), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCosh, I18n::Message::PythonNumpyCosh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDegrees, I18n::Message::PythonNumpyDegrees), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyExp, I18n::Message::PythonNumpyExp), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyExpm1, I18n::Message::PythonNumpyExpm1), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFloor, I18n::Message::PythonNumpyFloor), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLog, I18n::Message::PythonNumpyLog), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLog10, I18n::Message::PythonNumpyLog10), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLog2, I18n::Message::PythonNumpyLog2), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyRadians, I18n::Message::PythonNumpyRadians), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySin, I18n::Message::PythonNumpySin), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySinh, I18n::Message::PythonNumpySinh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySqrt, I18n::Message::PythonNumpySqrt), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTan, I18n::Message::PythonNumpyTan), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTanh, I18n::Message::PythonNumpyTanh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyBool, I18n::Message::PythonNumpyBool), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFloat, I18n::Message::PythonNumpyFloat), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyUint8, I18n::Message::PythonNumpyUint8), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInt8, I18n::Message::PythonNumpyInt8), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyUint16, I18n::Message::PythonNumpyUint16), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInt16, I18n::Message::PythonNumpyInt16), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyNan, I18n::Message::PythonNumpyNan), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInf, I18n::Message::PythonNumpyInf), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyE, I18n::Message::PythonNumpyE), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyPi, I18n::Message::PythonNumpyPi) +}; + +const ToolboxMessageTree NumpyFftModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFftFunction, I18n::Message::PythonTurtleFunction, false, I18n::Message::PythonCommandNumpyFftFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFft, I18n::Message::PythonNumpyFft), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyIfft, I18n::Message::PythonNumpyIfft) +}; + +const ToolboxMessageTree NumpyLinalgModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLinalgFunction, I18n::Message::PythonTurtleFunction, false, I18n::Message::PythonCommandNumpyLinalgFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDet, I18n::Message::PythonNumpyDet), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyEig, I18n::Message::PythonNumpyEig), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCholesky, I18n::Message::PythonNumpyCholesky), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInv, I18n::Message::PythonNumpyInv), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyNorm, I18n::Message::PythonNumpyNorm) +}; + +const ToolboxMessageTree NumpyModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromNumpy, I18n::Message::PythonImportTurtle, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFunction, I18n::Message::PythonTurtleFunction, false, I18n::Message::PythonCommandNumpyPythonCommandNumpyFunctionWithoutArgFunction), + ToolboxMessageTree::Node(I18n::Message::NumpyNdarray, NumpyNdarrayModuleChildren), + ToolboxMessageTree::Node(I18n::Message::Functions, NumpyFunctionsModuleChildren), + ToolboxMessageTree::Node(I18n::Message::NumpyFftModule, NumpyFftModuleChildren), + ToolboxMessageTree::Node(I18n::Message::NumpyLinalgModule, NumpyLinalgModuleChildren) +}; + +const ToolboxMessageTree UlabModuleChildren[] = { + ToolboxMessageTree::Node(I18n::Message::NumpyModule, NumpyModuleChildren) +}; + const ToolboxMessageTree TurtleModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportTurtle, I18n::Message::PythonImportTurtle, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromTurtle, I18n::Message::PythonImportTurtle, false), @@ -231,6 +352,7 @@ const ToolboxMessageTree modulesChildren[] = { ToolboxMessageTree::Node(I18n::Message::MathModule, MathModuleChildren), ToolboxMessageTree::Node(I18n::Message::CmathModule, CMathModuleChildren), ToolboxMessageTree::Node(I18n::Message::MatplotlibPyplotModule, MatplotlibPyplotModuleChildren), + ToolboxMessageTree::Node(I18n::Message::UlabModule, UlabModuleChildren), ToolboxMessageTree::Node(I18n::Message::TurtleModule, TurtleModuleChildren), ToolboxMessageTree::Node(I18n::Message::RandomModule, RandomModuleChildren), ToolboxMessageTree::Node(I18n::Message::KandinskyModule, KandinskyModuleChildren), diff --git a/apps/code/toolbox.universal.i18n b/apps/code/toolbox.universal.i18n index 4dd5345def6..df6a9171d42 100644 --- a/apps/code/toolbox.universal.i18n +++ b/apps/code/toolbox.universal.i18n @@ -3,9 +3,14 @@ IonModule = "ion" KandinskyModule = "kandinsky" MathModule = "math" MatplotlibPyplotModule = "matplotlib.pyplot" +NumpyModule = "numpy" +NumpyFftModule = "fft" +NumpyLinalgModule = "linalg" +NumpyNdarray = "ndarray" OsModule = "os" TimeModule = "time" TurtleModule = "turtle" +UlabModule = "ulab" ForLoopMenu = "For" IfStatementMenu = "If" WhileLoopMenu = "While" diff --git a/escher/include/escher/nested_menu_controller.h b/escher/include/escher/nested_menu_controller.h index 44ac41eca47..ca671a1b750 100644 --- a/escher/include/escher/nested_menu_controller.h +++ b/escher/include/escher/nested_menu_controller.h @@ -41,7 +41,7 @@ class NestedMenuController : public StackViewController, public ListViewDataSour int depth() const; void resetStack(); private: - constexpr static int k_maxModelTreeDepth = 3; + constexpr static int k_maxModelTreeDepth = 4; State m_statesStack[k_maxModelTreeDepth]; }; From a15c682e3ee6d83465dd73a8cd34a67da9cefd8d Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 4 Sep 2021 21:23:23 +0200 Subject: [PATCH 028/355] [code/ulab] Replaced translations by official documentation --- apps/code/catalog.de.i18n | 94 -------------- apps/code/catalog.en.i18n | 94 -------------- apps/code/catalog.es.i18n | 94 -------------- apps/code/catalog.fr.i18n | 94 -------------- apps/code/catalog.hu.i18n | 94 -------------- apps/code/catalog.it.i18n | 94 -------------- apps/code/catalog.nl.i18n | 94 -------------- apps/code/catalog.pt.i18n | 94 -------------- apps/code/python_toolbox.cpp | 204 ++++++++++++++++--------------- apps/code/toolbox.de.i18n | 1 + apps/code/toolbox.en.i18n | 1 + apps/code/toolbox.es.i18n | 1 + apps/code/toolbox.fr.i18n | 1 + apps/code/toolbox.hu.i18n | 1 + apps/code/toolbox.it.i18n | 1 + apps/code/toolbox.nl.i18n | 1 + apps/code/toolbox.pt.i18n | 1 + apps/code/toolbox.universal.i18n | 1 + 18 files changed, 113 insertions(+), 852 deletions(-) diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index 2c942a56ee6..da6c16f996f 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -108,100 +108,6 @@ PythonMonotonic = "Wert einer monotonen Uhr" PythonNumpyFunction = "numpy Modul-Präfix" PythonNumpyFftFunction = "numpy.fft Modul-Präfix" PythonNumpyLinalgFunction = "numpy.linalg Modul-Präfix" -PythonNumpyArray = "Konvertieren Sie ein Array in ndarray" -PythonNumpyArange = "Erstellen Sie eine Tabelle aus dem Bereich (i)" -PythonNumpyConcatenate = "Verketten Sie a und b" -PythonNumpyDiag = "Extrahiere oder konstruiere ein diagonales Array" -PythonNumpyZeros = "S-förmiges Array gefüllt mit 0" -PythonNumpyOnes = "S-förmiges Array gefüllt mit 1" -PythonNumpyEmpty = "Nicht initialisiertes Array der Form s" -PythonNumpyEye = "Tabelle mit Einsen auf der Diagonale und Nullen an anderer Stelle" -PythonNumpyFull = "S-förmiges Array gefüllt mit v" -PythonNumpyLinspace = "Zahlen, die über ein bestimmtes Intervall verteilt sind" -PythonNumpyLogspace = "Zahlen mit logarithmischem Abstand" -PythonNumpyCopy = "Kopie der Tabelle" -PythonNumpyDtype = "Tischtyp" -PythonNumpyFlat = "Flat-Array-Iterator" -PythonNumpyFlatten = "Abgeflachte Version der Tabelle" -PythonNumpyShape = "Holen Sie sich die Form des Arrays" -PythonNumpyReshape = "Array-Form durch s ersetzen" -PythonNumpySize = "Anzahl der Elemente im Array" -PythonNumpyTranspose = "Transponierte Version der Tabelle" -PythonNumpySortWithArguments = "Sortierte Version der Tabelle" -PythonNumpyNdinfo = "Informationen zu a . drucken" -PythonNumpyAll = "Testen Sie, ob alle Elemente von a trye sind" -PythonNumpyAny = "Teste, ob ein Element von a wahr ist" -PythonNumpyArgmax = "Index des Maximalwertes von a" -PythonNumpyArgmin = "Tiefgestellter Wert des Mindestwertes von a" -PythonNumpyArgsort = "Hinweise, die ein Array sortieren würden" -PythonNumpyClip = "Werte in einem Array ausschneiden" -PythonNumpyConvolve = "Diskrete lineare Faltung von a und b" -PythonNumpyDiff = "Abgeleitet von a" -PythonNumpyInterp = "Linear interpolierte Werte von a" -PythonNumpyDot = "Punktprodukt von a und b" -PythonNumpyCross = "Kreuzprodukt von a und b" -PythonNumpyEqual = "A == Element für Element" -PythonNumpyNot_equal = "A! = Element für Element" -PythonNumpyFlip = "Turnaround-Tabelle" -PythonNumpyIsfinite = "Testen Sie die Endlichkeit Element für Element" -PythonNumpyIsinf = "Teste die Unendlichkeit Element für Element" -PythonNumpyMean = "Durchschnitt d" -PythonNumpyMin = "Maximalwert von a" -PythonNumpyMax = "Mindestwert von a" -PythonNumpyMedian = "Medianwert von a" -PythonNumpyMinimum = "Minimale Array-Elemente pro Element" -PythonNumpyMaximum = "Maximum pro Element von Array-Elementen" -PythonNumpyPolyfit = "Polynomanpassung der kleinsten Quadrate" -PythonNumpyPolyval = "Bewerte ein Polynom bei bestimmten Werten" -PythonNumpyRoll = "Verschiebe den Inhalt von a um n" -PythonNumpySort = "Sortieren nach" -PythonNumpyStd = "Berechnen Sie die Standardabweichung von a" -PythonNumpySum = "Berechnen Sie die Summe von a" -PythonNumpyTrace = "Berechnen Sie die Summe der diagonalen Elemente von a" -PythonNumpyTrapz = "Integrieren Sie mit dem zusammengesetzten Trapezlineal" -PythonNumpyWhere = "Gibt Elemente aus x oder y gemäß c . zurück" -PythonNumpyVectorize = "Vektorisieren Sie die generische Python-Funktion f" -PythonNumpyAcos = "Wenden Sie acos Artikel für Artikel an" -PythonNumpyAcosh = "Wenden Sie acosh Artikel für Artikel an" -PythonNumpyArctan2 = "arctan2 Element für Element anwenden" -PythonNumpyAround = "Um das Element herum auftragen" -PythonNumpyAsin = "Element für Element anwenden" -PythonNumpyAsinh = "Wenden Sie asinh Element für Element an" -PythonNumpyAtan = "Wenden Sie ein Element für Element an" -PythonNumpyAtanh = "Wenden Sie atanh Element für Element an" -PythonNumpyCeil = "Bringen Sie die Decke nach Element an" -PythonNumpyCos = "Wenden Sie cos Element für Element an" -PythonNumpyCosh = "Wenden Sie cosh Element für Element an" -PythonNumpyDegrees = "Grade Element für Element anwenden" -PythonNumpyExp = "Exp pro Artikel anwenden" -PythonNumpyExpm1 = "Wenden Sie expm1 Element für Element an" -PythonNumpyFloor = "Boden nach Element auftragen" -PythonNumpyLog = "Tagebuch nach Artikel anwenden" -PythonNumpyLog10 = "Wenden Sie log10 Element für Element an" -PythonNumpyLog2 = "Wenden Sie log2 Element für Element an" -PythonNumpyRadians = "Wenden Sie Radiant pro Element an" -PythonNumpySin = "Wende Sünde nach Element an" -PythonNumpySinh = "Wenden Sie sinh Element für Element an" -PythonNumpySqrt = "Wenden Sie sqrt Element für Element an" -PythonNumpyTan = "Trage die Bräune nach Element auf" -PythonNumpyTanh = "Tanh pro Artikel auftragen" -PythonNumpyBool = "Bool Typ von numpy" -PythonNumpyFloat = "Float-Typ von numpy" -PythonNumpyUint8 = "Geben Sie uint8 von numpy . ein" -PythonNumpyInt8 = "Geben Sie int8 von numpy . ein" -PythonNumpyUint16 = "Geben Sie uint16 von numpy ein" -PythonNumpyInt16 = "Geben Sie int16 von numpy . ein" -PythonNumpyNan = "Nan-Darstellung von numpy" -PythonNumpyInf = "Inf-Darstellung von numpy" -PythonNumpyE = "2.718281828459045" -PythonNumpyPi = "3.141592653589793" -PythonNumpyFft = "Eindimensionale diskrete Fourier-Transformation" -PythonNumpyIfft = "Eindimensionale inverse diskrete Fourier-Transformation" -PythonNumpyDet = "Determinante von a" -PythonNumpyEig = "Eigenwerte und rechte Eigenvektoren von a" -PythonNumpyCholesky = "Cholesky-Zerlegung" -PythonNumpyInv = "Inverse Matrix a" -PythonNumpyNorm = "Matrix- oder Vektorstandard" PythonOct = "Ganzzahl in Oktal umwandeln" PythonPhase = "Phase von z" PythonPlot = "Plotten von y gegen x als Linien" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index 09de24ccb23..363dd820e0a 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -102,100 +102,6 @@ PythonMonotonic = "Value of a monotonic clock" PythonNumpyFunction = "numpy module prefix" PythonNumpyFftFunction = "numpy.fft module prefix" PythonNumpyLinalgFunction = "numpy.linalg module prefix" -PythonNumpyArray = "Convert an array to ndarray" -PythonNumpyArange = "Make a table from the range (i)" -PythonNumpyConcatenate = "Concatenate a and b" -PythonNumpyDiag = "Extract or construct a diagonal array" -PythonNumpyZeros = "S shape array filled with 0" -PythonNumpyOnes = "S shape array filled with 1" -PythonNumpyEmpty = "Uninitialized array of form s" -PythonNumpyEye = "Table with 1s on the diagonal and 0s elsewhere" -PythonNumpyFull = "S shape array filled with v" -PythonNumpyLinspace = "Numbers spaced over a specified interval" -PythonNumpyLogspace = "Numbers spaced on a logarithmic scale" -PythonNumpyCopy = "Copy of table" -PythonNumpyDtype = "Table type" -PythonNumpyFlat = "Flat array iterator" -PythonNumpyFlatten = "Flattened version of the table" -PythonNumpyShape = "Get the shape of the array" -PythonNumpyReshape = "Replace array shape with s" -PythonNumpySize = "Number of elements in the array" -PythonNumpyTranspose = "Transposed version of the table" -PythonNumpySortWithArguments = "Sorted version of the table" -PythonNumpyNdinfo = "Print information about a" -PythonNumpyAll = "Test if all elements of a are trye" -PythonNumpyAny = "Test if an element of a is true" -PythonNumpyArgmax = "Index of the maximum value of a" -PythonNumpyArgmin = "Subscript of the minimum value of a" -PythonNumpyArgsort = "Clues that would sort an array" -PythonNumpyClip = "Cut values in an array" -PythonNumpyConvolve = "Discrete linear convolution of a and b" -PythonNumpyDiff = "Derived from a" -PythonNumpyInterp = "Linearly interpolated values of a" -PythonNumpyDot = "Dot product of a and b" -PythonNumpyCross = "Cross product of a and b" -PythonNumpyEqual = "A == a element by element" -PythonNumpyNot_equal = "A! = A element by element" -PythonNumpyFlip = "Turnaround table" -PythonNumpyIsfinite = "Test the finiteness element by element" -PythonNumpyIsinf = "Test the infinity element by element" -PythonNumpyMean = "Average d" -PythonNumpyMin = "Maximum value of a" -PythonNumpyMax = "Minimum value of a" -PythonNumpyMedian = "Median value of a" -PythonNumpyMinimum = "Minimum array elements per element" -PythonNumpyMaximum = "Maximum per element of array elements" -PythonNumpyPolyfit = "Least squares polynomial fit" -PythonNumpyPolyval = "Evaluate a polynomial at specific values" -PythonNumpyRoll = "Shift the content of a by n" -PythonNumpySort = "Sort to" -PythonNumpyStd = "Calculate the standard deviation of a" -PythonNumpySum = "Calculate the sum of a" -PythonNumpyTrace = "Calculate the sum of the diagonal elements of a" -PythonNumpyTrapz = "Integrate using the composite trapezoidal ruler" -PythonNumpyWhere = "Returns elements chosen from x or y according to c" -PythonNumpyVectorize = "Vectorize the generic python function f" -PythonNumpyAcos = "Apply acos item by item" -PythonNumpyAcosh = "Apply acosh item by item" -PythonNumpyArctan2 = "Apply arctan2 element by element" -PythonNumpyAround = "Apply around the element" -PythonNumpyAsin = "Apply asin element by element" -PythonNumpyAsinh = "Apply asinh element by element" -PythonNumpyAtan = "Apply one item by item" -PythonNumpyAtanh = "Apply atanh element by element" -PythonNumpyCeil = "Apply the ceiling by element" -PythonNumpyCos = "Apply cos element by element" -PythonNumpyCosh = "Apply cosh element by element" -PythonNumpyDegrees = "Apply degrees element by element" -PythonNumpyExp = "Apply exp per item" -PythonNumpyExpm1 = "Apply expm1 element by element" -PythonNumpyFloor = "Apply soil by element" -PythonNumpyLog = "Apply journal by item" -PythonNumpyLog10 = "Apply log10 element by element" -PythonNumpyLog2 = "Apply log2 element by element" -PythonNumpyRadians = "Apply radians per element" -PythonNumpySin = "Apply sin by element" -PythonNumpySinh = "Apply sinh element by element" -PythonNumpySqrt = "Apply sqrt element by element" -PythonNumpyTan = "Apply the tan by element" -PythonNumpyTanh = "Apply tanh per item" -PythonNumpyBool = "Bool type of numpy" -PythonNumpyFloat = "Float type of numpy" -PythonNumpyUint8 = "Type uint8 of numpy" -PythonNumpyInt8 = "Type int8 of numpy" -PythonNumpyUint16 = "Type uint16 from numpy" -PythonNumpyInt16 = "Type int16 of numpy" -PythonNumpyNan = "Nan representation of numpy" -PythonNumpyInf = "Inf representation of numpy" -PythonNumpyE = "2.718281828459045" -PythonNumpyPi = "3.141592653589793" -PythonNumpyFft = "One-dimensional discrete fourier transform" -PythonNumpyIfft = "One-dimensional inverse discrete fourier transform" -PythonNumpyDet = "Determinant of a" -PythonNumpyEig = "Eigenvalues and right eigenvectors of a" -PythonNumpyCholesky = "Cholesky decomposition" -PythonNumpyInv = "Inverse matrix a" -PythonNumpyNorm = "Matrix or vector standard" PythonOct = "Convert integer to octal" PythonPhase = "Phase of z" PythonPlot = "Plot y versus x as lines" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index 48bff282b2c..d39456839c3 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -102,100 +102,6 @@ PythonMonotonic = "Value of a monotonic clock" PythonNumpyFunction = "numpy module prefix" PythonNumpyFftFunction = "numpy.fft module prefix" PythonNumpyLinalgFunction = "numpy.linalg module prefix" -PythonNumpyArray = "Convertir una matriz a ndarray" -PythonNumpyArange = "Haz una tabla de la gama (i)" -PythonNumpyConcatenate = "Concatenar ayb" -PythonNumpyDiag = "Extraer o construir una matriz diagonal" -PythonNumpyZeros = "Matriz en forma de S rellena con 0" -PythonNumpyOnes = "Matriz en forma de S llena con 1" -PythonNumpyEmpty = "Matriz no inicializada de formulario s" -PythonNumpyEye = "Tabla con 1 en la diagonal y 0 en el resto" -PythonNumpyFull = "Matriz en forma de S rellena con v" -PythonNumpyLinspace = "Números espaciados en un intervalo específico" -PythonNumpyLogspace = "Números espaciados en una escala logarítmica" -PythonNumpyCopy = "Copia de la tabla" -PythonNumpyDtype = "Tipo de mesa" -PythonNumpyFlat = "Iterador de matriz plana" -PythonNumpyFlatten = "Versión aplanada de la mesa." -PythonNumpyShape = "Obtén la forma de la matriz" -PythonNumpyReshape = "Reemplazar la forma de la matriz con s" -PythonNumpySize = "Número de elementos en la matriz" -PythonNumpyTranspose = "Versión transpuesta de la tabla" -PythonNumpySortWithArguments = "Versión ordenada de la tabla" -PythonNumpyNdinfo = "Imprimir información sobre un" -PythonNumpyAll = "Prueba si todos los elementos de a son probables" -PythonNumpyAny = "Prueba si un elemento de a es verdadero" -PythonNumpyArgmax = "Índice del valor máximo de un" -PythonNumpyArgmin = "Subíndice del valor mínimo de un" -PythonNumpyArgsort = "Pistas que ordenarían una matriz" -PythonNumpyClip = "Cortar valores en una matriz" -PythonNumpyConvolve = "Convolución lineal discreta de ayb" -PythonNumpyDiff = "Derivado de un" -PythonNumpyInterp = "Valores interpolados linealmente de a" -PythonNumpyDot = "Producto escalar de ayb" -PythonNumpyCross = "Producto cruzado de ayb" -PythonNumpyEqual = "A == un elemento por elemento" -PythonNumpyNot_equal = "A! = Un elemento por elemento" -PythonNumpyFlip = "Tabla de cambio" -PythonNumpyIsfinite = "Prueba la finitud elemento por elemento" -PythonNumpyIsinf = "Prueba el infinito elemento por elemento" -PythonNumpyMean = "Promedio d" -PythonNumpyMin = "Valor máximo de un" -PythonNumpyMax = "Valor mínimo de un" -PythonNumpyMedian = "Valor mediano de a" -PythonNumpyMinimum = "Elementos de matriz mínimos por elemento" -PythonNumpyMaximum = "Máximo por elemento de elementos de matriz" -PythonNumpyPolyfit = "Ajuste de polinomio de mínimos cuadrados" -PythonNumpyPolyval = "Evaluar un polinomio en valores específicos" -PythonNumpyRoll = "Cambiar el contenido de a por n" -PythonNumpySort = "Ordenar por" -PythonNumpyStd = "Calcule la desviación estándar de un" -PythonNumpySum = "Calcule la suma de a" -PythonNumpyTrace = "Calcule la suma de los elementos diagonales de un" -PythonNumpyTrapz = "Integrar usando la regla trapezoidal compuesta" -PythonNumpyWhere = "Devuelve elementos elegidos de xoy según c" -PythonNumpyVectorize = "Vectorizar la función genérica de python f" -PythonNumpyAcos = "Aplicar acos artículo por artículo" -PythonNumpyAcosh = "Aplicar un elemento por elemento" -PythonNumpyArctan2 = "Aplicar arctan2 elemento por elemento" -PythonNumpyAround = "Aplicar alrededor del elemento" -PythonNumpyAsin = "Aplicar asin elemento por elemento" -PythonNumpyAsinh = "Aplicar asinh elemento por elemento" -PythonNumpyAtan = "Aplicar un artículo por artículo" -PythonNumpyAtanh = "Aplicar atanh elemento por elemento" -PythonNumpyCeil = "Aplicar el techo por elemento" -PythonNumpyCos = "Aplicar cos elemento por elemento" -PythonNumpyCosh = "Aplicar cosh elemento por elemento" -PythonNumpyDegrees = "Aplicar grados elemento por elemento" -PythonNumpyExp = "Aplicar exp por artículo" -PythonNumpyExpm1 = "Aplicar expm1 elemento por elemento" -PythonNumpyFloor = "Aplicar suelo por elemento" -PythonNumpyLog = "Aplicar diario por artículo" -PythonNumpyLog10 = "Aplicar log10 elemento por elemento" -PythonNumpyLog2 = "Aplicar log2 elemento por elemento" -PythonNumpyRadians = "Aplicar radianes por elemento" -PythonNumpySin = "Aplicar el pecado por elemento" -PythonNumpySinh = "Aplicar sinh elemento por elemento" -PythonNumpySqrt = "Aplicar elemento sqrt por elemento" -PythonNumpyTan = "Aplicar el bronceado por elemento" -PythonNumpyTanh = "Aplicar tanh por artículo" -PythonNumpyBool = "Bool tipo de numpy" -PythonNumpyFloat = "Flotador tipo de numpy" -PythonNumpyUint8 = "Escriba uint8 de numpy" -PythonNumpyInt8 = "Escriba int8 de numpy" -PythonNumpyUint16 = "Escriba uint16 desde numpy" -PythonNumpyInt16 = "Escriba int16 de numpy" -PythonNumpyNan = "Nan representación de numpy" -PythonNumpyInf = "Inf representación de numpy" -PythonNumpyE = "2.718281828459045" -PythonNumpyPi = "3.141592653589793" -PythonNumpyFft = "Transformada de Fourier discreta unidimensional" -PythonNumpyIfft = "Transformada de Fourier discreta inversa unidimensional" -PythonNumpyDet = "Determinante de un" -PythonNumpyEig = "Autovalores y autovectores derechos de un" -PythonNumpyCholesky = "Descomposición de Cholesky" -PythonNumpyInv = "Matriz inversa a" -PythonNumpyNorm = "Matriz o estándar vectorial" PythonOct = "Convert integer to octal" PythonPhase = "Phase of z" PythonPlot = "Plot y versus x as lines" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index 5846f7c4a72..d8d9daced60 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -102,100 +102,6 @@ PythonMonotonic = "Renvoie la valeur de l'horloge" PythonNumpyFunction = "Préfixe fonction du module numpy" PythonNumpyFftFunction = "Préfixe fonction du module numpy.fft" PythonNumpyLinalgFunction = "Préfixe fonction du module numpy.linalg" -PythonNumpyArray = "Convertir un tableau en ndarray" -PythonNumpyArange = "Faire un tableau à partir de la plage (i)" -PythonNumpyConcatenate = "Concaténer a et b" -PythonNumpyDiag = "Extraire ou construire un tableau diagonal" -PythonNumpyZeros = "Tableau de forme s rempli de 0" -PythonNumpyOnes = "Tableau de forme s rempli de 1" -PythonNumpyEmpty = "Tableau uninitialisé de forme s" -PythonNumpyEye = "Tableau avec des 1 sur la diagonale et des 0 ailleurs" -PythonNumpyFull = "Tableau de forme s rempli de v" -PythonNumpyLinspace = "Nombres espacés sur un intervalle spécifié" -PythonNumpyLogspace = "Nombres espacés sur une échelle logarithmique" -PythonNumpyCopy = "Copie du tableau" -PythonNumpyDtype = "Dtype du tableau" -PythonNumpyFlat = "Itérateur plat du tableau" -PythonNumpyFlatten = "Version aplatie du tableau" -PythonNumpyShape = "Obtenir la forme du tableau" -PythonNumpyReshape = "Remplacer la forme du tableau par s" -PythonNumpySize = "Nombre d'éléments dans le tableau" -PythonNumpyTranspose = "Version transposée du tableau" -PythonNumpySortWithArguments = "Version triée du tableau" -PythonNumpyNdinfo = "Imprimer des informations sur un" -PythonNumpyAll = "Tester si tous les éléments de a sont trye" -PythonNumpyAny = "Tester si un élément de a est vrai" -PythonNumpyArgmax = "Indice de la valeur maximale de a" -PythonNumpyArgmin = "Indice de la valeur minimale de a" -PythonNumpyArgsort = "Indices qui trieraient un tableau" -PythonNumpyClip = "Couper les valeurs dans un tableau" -PythonNumpyConvolve = "Convolution linéaire discrète de a et b" -PythonNumpyDiff = "Dérivée du a" -PythonNumpyInterp = "Valeurs interpolées linéairement de a" -PythonNumpyDot = "Produit scalaire de a et b" -PythonNumpyCross = "Produit vectoriel de a et b" -PythonNumpyEqual = "a == a élément par élément" -PythonNumpyNot_equal = "a != a élément par élément" -PythonNumpyFlip = "Tableau de retournement" -PythonNumpyIsfinite = "Testez la finitude élément par élément" -PythonNumpyIsinf = "Testez l'infinité élément par élément" -PythonNumpyMean = "Moyenne d" -PythonNumpyMin = "Valeur maximale de a" -PythonNumpyMax = "Valeur minimale de a" -PythonNumpyMedian = "Valeur médiane de a" -PythonNumpyMinimum = "Minimum d'éléments de tableau par élément" -PythonNumpyMaximum = "Maximum par élément d'éléments de tableau" -PythonNumpyPolyfit = "Ajustement polynomial des moindres carrés" -PythonNumpyPolyval = "Évaluer un polynôme à des valeurs spécifiques" -PythonNumpyRoll = "Décaler le contenu de a par n" -PythonNumpySort = "Trier a" -PythonNumpyStd = "Calculer l'écart type de a" -PythonNumpySum = "Calculer la somme de a" -PythonNumpyTrace = "Calculer la somme des éléments diagonaux de a" -PythonNumpyTrapz = "Intégrer à l'aide de la règle trapézoïdale composite" -PythonNumpyWhere = "Renvoie des éléments choisis parmi x ou y selon c" -PythonNumpyVectorize = "Vectoriser la fonction python générique f" -PythonNumpyAcos = "Appliquer acos élément par élément" -PythonNumpyAcosh = "Appliquer acosh élément par élément" -PythonNumpyArctan2 = "Appliquer arctan2 élément par élément" -PythonNumpyAround = "Appliquer autour de l'élément" -PythonNumpyAsin = "Appliquer asin élément par élément" -PythonNumpyAsinh = "Appliquer asinh élément par élément" -PythonNumpyAtan = "Appliquer un élément par élément" -PythonNumpyAtanh = "Appliquer atanh élément par élément" -PythonNumpyCeil = "Appliquer le plafond par élément" -PythonNumpyCos = "Appliquer cos élément par élément" -PythonNumpyCosh = "Appliquer cosh élément par élément" -PythonNumpyDegrees = "Appliquer des degrés élément par élément" -PythonNumpyExp = "Appliquer exp par élément" -PythonNumpyExpm1 = "Appliquer expm1 élément par élément" -PythonNumpyFloor = "Appliquer le sol par élément" -PythonNumpyLog = "Appliquer le journal par élément" -PythonNumpyLog10 = "Appliquer log10 élément par élément" -PythonNumpyLog2 = "Appliquer log2 élément par élément" -PythonNumpyRadians = "Appliquer des radians par élément" -PythonNumpySin = "Appliquer le péché par élément" -PythonNumpySinh = "Appliquer sinh élément par élément" -PythonNumpySqrt = "Appliquer sqrt élément par élément" -PythonNumpyTan = "Appliquer le bronzage par élément" -PythonNumpyTanh = "Appliquer tanh par élément" -PythonNumpyBool = "Type bool de numpy" -PythonNumpyFloat = "Type float de numpy" -PythonNumpyUint8 = "Tapez uint8 de numpy" -PythonNumpyInt8 = "Tapez int8 de numpy" -PythonNumpyUint16 = "Tapez uint16 de numpy" -PythonNumpyInt16 = "Tapez int16 de numpy" -PythonNumpyNan = "Nan représentation de numpy" -PythonNumpyInf = "Inf représentation de numpy" -PythonNumpyE = "2.718281828459045" -PythonNumpyPi = "3.141592653589793" -PythonNumpyFft = "Transformée de Fourier discrète à une dimension" -PythonNumpyIfft = "Transformée de Fourier discrète inverse unidimensionnelle" -PythonNumpyDet = "Déterminant de a" -PythonNumpyEig = "Valeurs propres et vecteurs propres droits de a" -PythonNumpyCholesky = "Décomposition de Cholesky" -PythonNumpyInv = "Matrice inverse a" -PythonNumpyNorm = "Norme matricielle ou vectorielle" PythonOct = "Conversion en octal" PythonPhase = "Argument de z" PythonPlot = "Trace y en fonction de x" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index f5365e63396..b2011816bc1 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -102,100 +102,6 @@ PythonMonotonic = "Az óra értékét adja vissza" PythonNumpyFunction = "numpy elötag" PythonNumpyFftFunction = "numpy.fft elötag" PythonNumpyLinalgFunction = "numpy.linalg elötag" -PythonNumpyArray = "Egy tömb konvertálása ndarray -re" -PythonNumpyArange = "Készítsen táblázatot az (i) tartományból" -PythonNumpyConcatenate = "Összekapcsolás a és b" -PythonNumpyDiag = "Bontson ki vagy készítsen átlós tömböt" -PythonNumpyZeros = "S alakú tömb 0 -val kitöltve" -PythonNumpyOnes = "S alakú tömb 1-el" -PythonNumpyEmpty = "Az űrlap inicializálatlan tömbje" -PythonNumpyEye = "Asztal 1 -es átlóval és 0 -val máshol" -PythonNumpyFull = "S alakú tömb tele v" -PythonNumpyLinspace = "Számok meghatározott intervallumon belül" -PythonNumpyLogspace = "A számok logaritmikus skálán helyezkednek el" -PythonNumpyCopy = "A táblázat másolata" -PythonNumpyDtype = "Táblázat típusa" -PythonNumpyFlat = "Lapos tömb iterátor" -PythonNumpyFlatten = "Az asztal lapított változata" -PythonNumpyShape = "Szerezd meg a tömb alakját" -PythonNumpyReshape = "Cserélje le a tömb alakját az s -vel" -PythonNumpySize = "A tömb elemeinek száma" -PythonNumpyTranspose = "A táblázat átültetett változata" -PythonNumpySortWithArguments = "A táblázat rendezett változata" -PythonNumpyNdinfo = "Információk nyomtatása a" -PythonNumpyAll = "Ellenőrizze, hogy egy elem minden eleme trye" -PythonNumpyAny = "Ellenőrizze, hogy az a eleme igaz -e" -PythonNumpyArgmax = "A maximális érték indexe a" -PythonNumpyArgmin = "A minimális értékének a indexe" -PythonNumpyArgsort = "Nyomok, amelyek rendeznek egy tömböt" -PythonNumpyClip = "Vágja le az értékeket egy tömbben" -PythonNumpyConvolve = "A és b diszkrét lineáris konvolúciója" -PythonNumpyDiff = "Származik a" -PythonNumpyInterp = "A lineárisan interpolált értékei" -PythonNumpyDot = "Az a és b pontszerű szorzata" -PythonNumpyCross = "Az a és b keresztterméke" -PythonNumpyEqual = "A == elemenként" -PythonNumpyNot_equal = "A! = Elemenként" -PythonNumpyFlip = "Fordulóasztal" -PythonNumpyIsfinite = "Tesztelje a végességet elemenként" -PythonNumpyIsinf = "Tesztelje a végtelen elemet elemenként" -PythonNumpyMean = "Átlagos d" -PythonNumpyMin = "Maximális értéke a" -PythonNumpyMax = "Minimális értéke a" -PythonNumpyMedian = "Medián értéke a" -PythonNumpyMinimum = "Minimális tömb elemek elemenként" -PythonNumpyMaximum = "Maximum tömb elemenként" -PythonNumpyPolyfit = "Legkevesebb négyzet polinom illeszkedés" -PythonNumpyPolyval = "Polinom értékelése meghatározott értékeken" -PythonNumpyRoll = "Az a tartalmának eltolása n -vel" -PythonNumpySort = "Rendezés ide" -PythonNumpyStd = "Számítsa ki a szórását a" -PythonNumpySum = "Számítsa ki a összegét" -PythonNumpyTrace = "Számítsa ki az a átlós elemeinek összegét!" -PythonNumpyTrapz = "Integrálja az összetett trapéz vonalzó használatával" -PythonNumpyWhere = "A c szerint x vagy y közül választott elemeket adja vissza" -PythonNumpyVectorize = "Vektorizálja az általános python függvényt f" -PythonNumpyAcos = "Alkalmazza az acos -t elemenként" -PythonNumpyAcosh = "Alkalmazza az elemeket elemenként" -PythonNumpyArctan2 = "Alkalmazza az arctan2 elemet elemenként" -PythonNumpyAround = "Alkalmazza az elem körül" -PythonNumpyAsin = "Alkalmazza az asszonyt elemenként" -PythonNumpyAsinh = "Alkalmazza az elemet elemenként" -PythonNumpyAtan = "Alkalmazzon egy elemet elemenként" -PythonNumpyAtanh = "Alkalmazza az atanh elemenként" -PythonNumpyCeil = "Alkalmazza a mennyezetet elemenként" -PythonNumpyCos = "Alkalmazza a cos elemet elemenként" -PythonNumpyCosh = "Alkalmazza a cosh elemet elemenként" -PythonNumpyDegrees = "Alkalmazza a fokokat elemenként" -PythonNumpyExp = "Alkalmazza az exp -ot elemenként" -PythonNumpyExpm1 = "Alkalmazza az expm1 elemet elemenként" -PythonNumpyFloor = "A talajt elemenként vigye fel" -PythonNumpyLog = "Napló alkalmazása tétel szerint" -PythonNumpyLog10 = "Alkalmazza a log10 elemet elemenként" -PythonNumpyLog2 = "Alkalmazza a log2 elemet elemenként" -PythonNumpyRadians = "Alkalmazzon radiánt elemenként" -PythonNumpySin = "Alkalmazza a bűnt elemenként" -PythonNumpySinh = "Alkalmazza a sinh elemet elemenként" -PythonNumpySqrt = "Alkalmazza az sqrt elemet elemenként" -PythonNumpyTan = "Vigye fel a barnulást elemenként" -PythonNumpyTanh = "Alkalmazzon tannt elemenként" -PythonNumpyBool = "Bull típusú numpy" -PythonNumpyFloat = "Lebegő típusú számológép" -PythonNumpyUint8 = "Írja be az uint8 számot" -PythonNumpyInt8 = "Írja be a numpy int8 típusát" -PythonNumpyUint16 = "Írja be az uint16 parancsot a numpy -ból" -PythonNumpyInt16 = "Írja be a numpy int16 típusát" -PythonNumpyNan = "A numpy nanos ábrázolása" -PythonNumpyInf = "A numpy inf ábrázolása" -PythonNumpyE = "2.718281828459045" -PythonNumpyPi = "3.141592653589793" -PythonNumpyFft = "Egydimenziós diszkrét Fourier-transzformáció" -PythonNumpyIfft = "Egydimenziós inverz diszkrét Fourier-transzformáció" -PythonNumpyDet = "Meghatározó a" -PythonNumpyEig = "Sajátértékek és jobb sajátvektorok a" -PythonNumpyCholesky = "Cholesky bomlás" -PythonNumpyInv = "Fordított mátrix a" -PythonNumpyNorm = "Mátrix vagy vektor standard" PythonOct = "Decimális szám konvertálása octális számra" PythonPhase = "z fázisa" PythonPlot = "y-t jelöli x függvényében" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index 9402ad69a5c..8627e679c44 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -108,100 +108,6 @@ PythonMonotonic = "Restituisce il valore dell'orologio" PythonNumpyFunction = "Prefisso modulo numpy" PythonNumpyFftFunction = "Prefisso modulo numpy.fft" PythonNumpyLinalgFunction = "Prefisso modulo numpy.linalg" -PythonNumpyArray = "Converti un array in ndarray" -PythonNumpyArange = "Crea una tabella dall'intervallo (i)" -PythonNumpyConcatenate = "Concatena a e b" -PythonNumpyDiag = "Estrai o costruisci un array diagonale" -PythonNumpyZeros = "Matrice a forma di S riempita con 0" -PythonNumpyOnes = "Array a forma di S riempito con 1" -PythonNumpyEmpty = "Matrice non inizializzata della forma s" -PythonNumpyEye = "Tabella con 1 in diagonale e 0 altrove" -PythonNumpyFull = "Matrice a forma di S riempita con v" -PythonNumpyLinspace = "Numeri spaziati su un intervallo specificato" -PythonNumpyLogspace = "Numeri spaziati su una scala logaritmica" -PythonNumpyCopy = "Copia della tabella" -PythonNumpyDtype = "Tipo di tabella" -PythonNumpyFlat = "Iteratore flat array" -PythonNumpyFlatten = "Versione appiattita del tavolo" -PythonNumpyShape = "Ottieni la forma dell'array" -PythonNumpyReshape = "Sostituisci la forma dell'array con s" -PythonNumpySize = "Numero di elementi nell'array" -PythonNumpyTranspose = "Versione trasposta della tabella" -PythonNumpySortWithArguments = "Versione ordinata della tabella" -PythonNumpyNdinfo = "Stampa informazioni su a" -PythonNumpyAll = "Verifica se tutti gli elementi di a sono provati" -PythonNumpyAny = "Verifica se un elemento di a è vero" -PythonNumpyArgmax = "Indice del valore massimo di a" -PythonNumpyArgmin = "Pedice del valore minimo di a" -PythonNumpyArgsort = "Indizi che ordinerebbero un array" -PythonNumpyClip = "Taglia i valori in un array" -PythonNumpyConvolve = "Convoluzione lineare discreta di a e b" -PythonNumpyDiff = "Derivato da a" -PythonNumpyInterp = "Valori interpolati linearmente di a" -PythonNumpyDot = "Prodotto scalare di a e b" -PythonNumpyCross = "Prodotto incrociato di a e b" -PythonNumpyEqual = "A == un elemento per elemento" -PythonNumpyNot_equal = "A! = Un elemento per elemento" -PythonNumpyFlip = "Tavolo di turnaround" -PythonNumpyIsfinite = "Testa la finitezza elemento per elemento" -PythonNumpyIsinf = "Prova l'infinito elemento per elemento" -PythonNumpyMean = "d . medio" -PythonNumpyMin = "Valore massimo di a" -PythonNumpyMax = "Valore minimo di a" -PythonNumpyMedian = "Valore medio di a" -PythonNumpyMinimum = "Elementi minimi dell'array per elemento" -PythonNumpyMaximum = "Massimo per elemento di elementi dell'array" -PythonNumpyPolyfit = "Approssimazione polinomiale ai minimi quadrati" -PythonNumpyPolyval = "Valuta un polinomio a valori specifici" -PythonNumpyRoll = "Sposta il contenuto di a di n" -PythonNumpySort = "Ordina per" -PythonNumpyStd = "Calcola la deviazione standard di a" -PythonNumpySum = "Calcola la somma di a" -PythonNumpyTrace = "Calcola la somma degli elementi diagonali di a" -PythonNumpyTrapz = "Integrare utilizzando il righello trapezoidale composito" -PythonNumpyWhere = "Restituisce elementi scelti da x o y secondo c" -PythonNumpyVectorize = "Vettorizza la funzione Python generica f" -PythonNumpyAcos = "Applica acos articolo per articolo" -PythonNumpyAcosh = "Applica acosh articolo per articolo" -PythonNumpyArctan2 = "Applica arctan2 elemento per elemento" -PythonNumpyAround = "Applicare intorno all'elemento" -PythonNumpyAsin = "Applica asin elemento per elemento" -PythonNumpyAsinh = "Applica asinh elemento per elemento" -PythonNumpyAtan = "Applicare un articolo per articolo" -PythonNumpyAtanh = "Applicare atanh elemento per elemento" -PythonNumpyCeil = "Applicare il soffitto per elemento" -PythonNumpyCos = "Applica cos elemento per elemento" -PythonNumpyCosh = "Applicare cosh elemento per elemento" -PythonNumpyDegrees = "Applica gradi elemento per elemento" -PythonNumpyExp = "Applica esperienza per articolo" -PythonNumpyExpm1 = "Applica expm1 elemento per elemento" -PythonNumpyFloor = "Applicare terreno per elemento" -PythonNumpyLog = "Applica giornale per articolo" -PythonNumpyLog10 = "Applica log10 elemento per elemento" -PythonNumpyLog2 = "Applica log2 elemento per elemento" -PythonNumpyRadians = "Applica radianti per elemento" -PythonNumpySin = "Applica sin per elemento" -PythonNumpySinh = "Applica sinh elemento per elemento" -PythonNumpySqrt = "Applica sqrt elemento per elemento" -PythonNumpyTan = "Applicare l'abbronzatura per elemento" -PythonNumpyTanh = "Applicare tanh per articolo" -PythonNumpyBool = "Tipo bool di numpy" -PythonNumpyFloat = "Tipo galleggiante di numpy" -PythonNumpyUint8 = "Digita uint8 di numpy" -PythonNumpyInt8 = "Digita int8 di numpy" -PythonNumpyUint16 = "Digita uint16 da numpy" -PythonNumpyInt16 = "Digita int16 di numpy" -PythonNumpyNan = "Nan rappresentazione di numpy" -PythonNumpyInf = "Inf rappresentazione di numpy" -PythonNumpyE = "2.718281828459045" -PythonNumpyPi = "3.141592653589933" -PythonNumpyFft = "Trasformata di Fourier discreta unidimensionale" -PythonNumpyIfft = "Trasformata di Fourier discreta inversa unidimensionale" -PythonNumpyDet = "Determinante di a" -PythonNumpyEig = "Autovalori e autovettori giusti di a" -PythonNumpyCholesky = "Decomposizione Cholesky" -PythonNumpyInv = "matrice inversa a" -PythonNumpyNorm = "Matrice o standard vettoriale" PythonOct = "Conversione in ottale" PythonPhase = "Argomento di z" PythonPlot = "Disegna y in f. di x come linee" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index fa54b6bc1c9..f46b4606f28 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -108,100 +108,6 @@ PythonMonotonic = "Waarde van een monotone klok" PythonNumpyFunction = "numpy module prefix" PythonNumpyFftFunction = "numpy.fft module prefix" PythonNumpyLinalgFunction = "numpy.linalg module prefix" -PythonNumpyArray = "Converteer een array naar ndarray" -PythonNumpyArange = "Maak een tabel uit de reeks (i)" -PythonNumpyConcatenate = "Samenvoegen a en b" -PythonNumpyDiag = "Een diagonale array extraheren of construeren" -PythonNumpyZeros = "S-vormarray gevuld met 0" -PythonNumpyOnes = "S-vormige array gevuld met 1" -PythonNumpyEmpty = "Niet-geïnitialiseerde matrix van vorm s" -PythonNumpyEye = "Tabel met enen op de diagonaal en nullen elders" -PythonNumpyFull = "S-vormarray gevuld met v" -PythonNumpyLinspace = "Getallen verdeeld over een opgegeven interval" -PythonNumpyLogspace = "Getallen op een logaritmische schaal verdeeld" -PythonNumpyCopy = "Kopie van tabel" -PythonNumpyDtype = "Tafeltype:" -PythonNumpyFlat = "Flat array iterator" -PythonNumpyFlatten = "Afgeplatte versie van de tafel" -PythonNumpyShape = "De vorm van de array verkrijgen" -PythonNumpyReshape = "Vervang matrixvorm door s" -PythonNumpySize = "Aantal elementen in de array" -PythonNumpyTranspose = "Getransponeerde versie van de tabel" -PythonNumpySortWithArguments = "Gesorteerde versie van de tafel" -PythonNumpyNdinfo = "Informatie afdrukken over a" -PythonNumpyAll = "Test of alle elementen van a trye zijn" -PythonNumpyAny = "Test of een element van a waar is" -PythonNumpyArgmax = "Index van de maximale waarde van a" -PythonNumpyArgmin = "Subscript van de minimumwaarde van a" -PythonNumpyArgsort = "Aanwijzingen die een array zouden sorteren" -PythonNumpyClip = "Knip waarden in een array" -PythonNumpyConvolve = "Discrete lineaire convolutie van a en b" -PythonNumpyDiff = "Afgeleid van a" -PythonNumpyInterp = "Lineair geïnterpoleerde waarden van a" -PythonNumpyDot = "Puntproduct van a en b" -PythonNumpyCross = "Kruisproduct van a en b" -PythonNumpyEqual = "A == een element voor element" -PythonNumpyNot_equal = "A! = Een element voor element" -PythonNumpyFlip = "Omslagtabel" -PythonNumpyIsfinite = "Test de eindigheid element voor element" -PythonNumpyIsinf = "Test het oneindige element voor element" -PythonNumpyMean = "gemiddelde d" -PythonNumpyMin = "Maximale waarde van a" -PythonNumpyMax = "Minimale waarde van a" -PythonNumpyMedian = "Mediane waarde van a" -PythonNumpyMinimum = "Minimum array-elementen per element" -PythonNumpyMaximum = "Maximum per element van array-elementen" -PythonNumpyPolyfit = "Kleinste kwadraten polynoom fit" -PythonNumpyPolyval = "Een polynoom evalueren op specifieke waarden" -PythonNumpyRoll = "Verschuif de inhoud van a met n" -PythonNumpySort = "Sorteren op" -PythonNumpyStd = "Bereken de standaarddeviatie van a" -PythonNumpySum = "Bereken de som van a" -PythonNumpyTrace = "Bereken de som van de diagonale elementen van a" -PythonNumpyTrapz = "Integreer met behulp van de samengestelde trapeziumvormige liniaal" -PythonNumpyWhere = "Retourneert elementen gekozen uit x of y volgens c" -PythonNumpyVectorize = "Vectoriseer de generieke python-functie f" -PythonNumpyAcos = "Acos item per item toepassen" -PythonNumpyAcosh = "Acosh item voor item toepassen" -PythonNumpyArctan2 = "Arctan2 element voor element toepassen" -PythonNumpyAround = "Toepassen rond het element" -PythonNumpyAsin = "Asin element voor element toepassen" -PythonNumpyAsinh = "Asinh element voor element toepassen" -PythonNumpyAtan = "Eén item per item toepassen" -PythonNumpyAtanh = "Atanh element voor element toepassen" -PythonNumpyCeil = "Breng het plafond per element aan" -PythonNumpyCos = "Pas co element voor element toe" -PythonNumpyCosh = "Cosh element voor element toepassen" -PythonNumpyDegrees = "Graden element voor element toepassen" -PythonNumpyExp = "exp per item toepassen" -PythonNumpyExpm1 = "expm1 element voor element toepassen" -PythonNumpyFloor = "Grond per element aanbrengen" -PythonNumpyLog = "Journaal per item toepassen" -PythonNumpyLog10 = "Pas log10 element voor element toe" -PythonNumpyLog2 = "Log2 element voor element toepassen" -PythonNumpyRadians = "Pas radialen toe per element" -PythonNumpySin = "Zonde per element toepassen" -PythonNumpySinh = "Sinh element voor element toepassen" -PythonNumpySqrt = "Sqrt element voor element toepassen" -PythonNumpyTan = "Breng de kleur aan per element" -PythonNumpyTanh = "Tanh toepassen per item" -PythonNumpyBool = "Bool type numpy" -PythonNumpyFloat = "Float type numpy" -PythonNumpyUint8 = "Typ uint8 van numpy" -PythonNumpyInt8 = "Typ int8 van numpy" -PythonNumpyUint16 = "Typ uint16 van numpy" -PythonNumpyInt16 = "Typ int16 van numpy" -PythonNumpyNan = "Nan vertegenwoordiging van numpy" -PythonNumpyInf = "Inf representatie van numpy" -PythonNumpyE = "2.718281828459045" -PythonNumpyPi = "3.141592653589793" -PythonNumpyFft = "Eendimensionale discrete fouriertransformatie" -PythonNumpyIfft = "Eendimensionale inverse discrete fouriertransformatie" -PythonNumpyDet = "Determinant van a" -PythonNumpyEig = "Eigenwaarden en rechter eigenvectoren van a" -PythonNumpyCholesky = "Cholesky-decompositie" -PythonNumpyInv = "Inverse matrix a" -PythonNumpyNorm = "Matrix- of vectorstandaard" PythonOct = "Integer omzetten naar octaal" PythonPhase = "Fase van z in radialen" PythonPlot = "Plot y versus x als lijnen" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index be87d228577..51329736533 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -102,100 +102,6 @@ PythonMonotonic = "Devolve o valor do relógio" PythonNumpyFunction = "Prefixo do módulo numpy" PythonNumpyFftFunction = "Prefixo do módulo numpy.fft" PythonNumpyLinalgFunction = "Prefixo do módulo numpy.linalg" -PythonNumpyArray = "Converter uma matriz em ndarray" -PythonNumpyArange = "Faça uma mesa do intervalo (i)" -PythonNumpyConcatenate = "Concatenar a e b" -PythonNumpyDiag = "Extraia ou construa uma matriz diagonal" -PythonNumpyZeros = "Matriz de forma S preenchida com 0" -PythonNumpyOnes = "Matriz em forma de S preenchida com 1" -PythonNumpyEmpty = "Matriz não inicializada de formulários" -PythonNumpyEye = "Tabela com 1s na diagonal e 0s em outros lugares" -PythonNumpyFull = "Matriz de forma S preenchida com v" -PythonNumpyLinspace = "Números espaçados em um intervalo especificado" -PythonNumpyLogspace = "Números espaçados em escala logarítmica" -PythonNumpyCopy = "Cópia da tabela" -PythonNumpyDtype = "Tipo de mesa" -PythonNumpyFlat = "Iterador de matriz plana" -PythonNumpyFlatten = "Versão achatada da mesa" -PythonNumpyShape = "Obtenha a forma da matriz" -PythonNumpyReshape = "Substitua a forma da matriz por s" -PythonNumpySize = "Número de elementos na matriz" -PythonNumpyTranspose = "Versão transposta da tabela" -PythonNumpySortWithArguments = "Versão ordenada da tabela" -PythonNumpyNdinfo = "Imprimir informações sobre um" -PythonNumpyAll = "Teste se todos os elementos de um são trye" -PythonNumpyAny = "Teste se um elemento de a é verdadeiro" -PythonNumpyArgmax = "Índice do valor máximo de um" -PythonNumpyArgmin = "Subscrito do valor mínimo de um" -PythonNumpyArgsort = "Pistas que classificariam um array" -PythonNumpyClip = "Corte os valores em uma matriz" -PythonNumpyConvolve = "Convolução linear discreta de a e b" -PythonNumpyDiff = "Derivado de um" -PythonNumpyInterp = "Valores linearmente interpolados de um" -PythonNumpyDot = "Produto escalar de a e b" -PythonNumpyCross = "Produto cruzado de a e b" -PythonNumpyEqual = "A == um elemento por elemento" -PythonNumpyNot_equal = "A! = Um elemento por elemento" -PythonNumpyFlip = "Mesa giratória" -PythonNumpyIsfinite = "Teste a finitude elemento por elemento" -PythonNumpyIsinf = "Teste o infinito elemento por elemento" -PythonNumpyMean = "D médio" -PythonNumpyMin = "Valor máximo de a" -PythonNumpyMax = "Valor mínimo de a" -PythonNumpyMedian = "Valor mediano de a" -PythonNumpyMinimum = "Elementos mínimos da matriz por elemento" -PythonNumpyMaximum = "Máximo por elemento de elementos da matriz" -PythonNumpyPolyfit = "Ajuste polinomial de mínimos quadrados" -PythonNumpyPolyval = "Avalie um polinômio em valores específicos" -PythonNumpyRoll = "Mudar o conteúdo de a por n" -PythonNumpySort = "Classificar para" -PythonNumpyStd = "Calcule o desvio padrão de um" -PythonNumpySum = "Calcule a soma de um" -PythonNumpyTrace = "Calcule a soma dos elementos diagonais de um" -PythonNumpyTrapz = "Integre usando a régua trapezoidal composta" -PythonNumpyWhere = "Retorna elementos escolhidos de x ou y de acordo com c" -PythonNumpyVectorize = "Vectorize a função python genérica f" -PythonNumpyAcos = "Aplicar acos item por item" -PythonNumpyAcosh = "Aplicar acosh item por item" -PythonNumpyArctan2 = "Aplicar arctan2 elemento por elemento" -PythonNumpyAround = "Aplicar ao redor do elemento" -PythonNumpyAsin = "Aplicar asin elemento a elemento" -PythonNumpyAsinh = "Aplicar asinh elemento por elemento" -PythonNumpyAtan = "Aplicar um item por item" -PythonNumpyAtanh = "Aplicar atanh elemento por elemento" -PythonNumpyCeil = "Aplicar o teto por elemento" -PythonNumpyCos = "Aplicar cos elemento por elemento" -PythonNumpyCosh = "Aplicar cosh elemento por elemento" -PythonNumpyDegrees = "Aplicar graus elemento a elemento" -PythonNumpyExp = "Aplicar exp por item" -PythonNumpyExpm1 = "Aplicar expm1 elemento a elemento" -PythonNumpyFloor = "Aplicar solo por elemento" -PythonNumpyLog = "Aplicar diário por item" -PythonNumpyLog10 = "Aplicar log10 elemento a elemento" -PythonNumpyLog2 = "Aplicar log2 elemento por elemento" -PythonNumpyRadians = "Aplicar radianos por elemento" -PythonNumpySin = "Aplicar pecado por elemento" -PythonNumpySinh = "Aplicar sinh elemento a elemento" -PythonNumpySqrt = "Aplicar sqrt elemento a elemento" -PythonNumpyTan = "Aplicar o bronzeado por elemento" -PythonNumpyTanh = "Aplicar tanh por item" -PythonNumpyBool = "Tipo Bool de entorpecido" -PythonNumpyFloat = "Tipo flutuante de entorpecimento" -PythonNumpyUint8 = "Digite uint8 de numpy" -PythonNumpyInt8 = "Digite int8 de numpy" -PythonNumpyUint16 = "Digite uint16 de numpy" -PythonNumpyInt16 = "Digite int16 de numpy" -PythonNumpyNan = "Representação Nan de numpy" -PythonNumpyInf = "Representação de inf de numpy" -PythonNumpyE = "2.718281828459045" -PythonNumpyPi = "3,141592653589793" -PythonNumpyFft = "Transformada discreta de Fourier unidimensional" -PythonNumpyIfft = "Transformada de Fourier discreta inversa unidimensional" -PythonNumpyDet = "Determinante de um" -PythonNumpyEig = "Valores próprios e vetores próprios direitos de um" -PythonNumpyCholesky = "Decomposição de Cholesky" -PythonNumpyInv = "Matriz inversa a" -PythonNumpyNorm = "Matriz ou padrão vetorial" PythonOct = "Converter número inteiro em octal" PythonPhase = "Argumento de z" PythonPlot = "Desenhar y em função de x" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 7d4c5384f59..2a26f98a7b9 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -136,116 +136,116 @@ const ToolboxMessageTree MatplotlibPyplotModuleChildren[] = { }; const ToolboxMessageTree NumpyNdarrayModuleChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArray, I18n::Message::PythonNumpyArray), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArange, I18n::Message::PythonNumpyArange), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyConcatenate, I18n::Message::PythonNumpyConcatenate), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDiag, I18n::Message::PythonNumpyDiag), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyZeros, I18n::Message::PythonNumpyZeros), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyOnes, I18n::Message::PythonNumpyOnes), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyEmpty, I18n::Message::PythonNumpyEmpty), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyEye, I18n::Message::PythonNumpyEye), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFull, I18n::Message::PythonNumpyFull), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLinspace, I18n::Message::PythonNumpyLinspace), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLogspace, I18n::Message::PythonNumpyLogspace), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCopy, I18n::Message::PythonNumpyCopy, false, I18n::Message::PythonCommandNumpyCopyWithoutArg), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDtype, I18n::Message::PythonNumpyDtype, false, I18n::Message::PythonCommandNumpyDtypeWithoutArg), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFlat, I18n::Message::PythonNumpyFlat, false, I18n::Message::PythonCommandNumpyFlatWithoutArg), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFlatten, I18n::Message::PythonNumpyFlatten, false, I18n::Message::PythonCommandNumpyFlattenWithoutArg), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyShape, I18n::Message::PythonNumpyShape, false, I18n::Message::PythonCommandNumpyShapeWithoutArg), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyReshape, I18n::Message::PythonNumpyReshape, false, I18n::Message::PythonCommandNumpyReshapeWithoutArg), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySize, I18n::Message::PythonNumpySize, false, I18n::Message::PythonCommandNumpySizeWithoutArg), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTranspose, I18n::Message::PythonNumpyTranspose, false, I18n::Message::PythonCommandNumpyTransposeWithoutArg), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySort, I18n::Message::PythonNumpySortWithArguments, false, I18n::Message::PythonCommandNumpySortWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArray), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArange), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyConcatenate), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDiag), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyZeros), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyOnes), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyEmpty), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyEye), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFull), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLinspace), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLogspace), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCopy), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDtype), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFlat), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFlatten), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyShape), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyReshape), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySize), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTranspose), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySort), }; const ToolboxMessageTree NumpyFunctionsModuleChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyNdinfo, I18n::Message::PythonNumpyNdinfo), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAll, I18n::Message::PythonNumpyAll), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAny, I18n::Message::PythonNumpyAny), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArgmax, I18n::Message::PythonNumpyArgmax), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArgmin, I18n::Message::PythonNumpyArgmin), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArgsort, I18n::Message::PythonNumpyArgsort), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyClip, I18n::Message::PythonNumpyClip), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyConvolve, I18n::Message::PythonNumpyConvolve), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDiff, I18n::Message::PythonNumpyDiff), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInterp, I18n::Message::PythonNumpyInterp), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDot, I18n::Message::PythonNumpyDot), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCross, I18n::Message::PythonNumpyCross), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyEqual, I18n::Message::PythonNumpyEqual), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyNot_equal, I18n::Message::PythonNumpyNot_equal), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFlip, I18n::Message::PythonNumpyFlip), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyIsfinite, I18n::Message::PythonNumpyIsfinite), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyIsinf, I18n::Message::PythonNumpyIsinf), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMean, I18n::Message::PythonNumpyMean), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMin, I18n::Message::PythonNumpyMin), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMax, I18n::Message::PythonNumpyMax), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMedian, I18n::Message::PythonNumpyMedian), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMinimum, I18n::Message::PythonNumpyMinimum), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMaximum, I18n::Message::PythonNumpyMaximum), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyPolyfit, I18n::Message::PythonNumpyPolyfit), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyPolyval, I18n::Message::PythonNumpyPolyval), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyRoll, I18n::Message::PythonNumpyRoll), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySortWithArguments, I18n::Message::PythonNumpySort), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyStd, I18n::Message::PythonNumpyStd), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySum, I18n::Message::PythonNumpySum), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTrace, I18n::Message::PythonNumpyTrace), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTrapz, I18n::Message::PythonNumpyTrapz), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyWhere, I18n::Message::PythonNumpyWhere), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyVectorize, I18n::Message::PythonNumpyVectorize), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAcos, I18n::Message::PythonNumpyAcos), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAcosh, I18n::Message::PythonNumpyAcosh), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArctan2, I18n::Message::PythonNumpyArctan2), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAround, I18n::Message::PythonNumpyAround), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAsin, I18n::Message::PythonNumpyAsin), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAsinh, I18n::Message::PythonNumpyAsinh), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAtan, I18n::Message::PythonNumpyAtan), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAtanh, I18n::Message::PythonNumpyAtanh), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCeil, I18n::Message::PythonNumpyCeil), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCos, I18n::Message::PythonNumpyCos), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCosh, I18n::Message::PythonNumpyCosh), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDegrees, I18n::Message::PythonNumpyDegrees), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyExp, I18n::Message::PythonNumpyExp), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyExpm1, I18n::Message::PythonNumpyExpm1), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFloor, I18n::Message::PythonNumpyFloor), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLog, I18n::Message::PythonNumpyLog), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLog10, I18n::Message::PythonNumpyLog10), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLog2, I18n::Message::PythonNumpyLog2), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyRadians, I18n::Message::PythonNumpyRadians), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySin, I18n::Message::PythonNumpySin), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySinh, I18n::Message::PythonNumpySinh), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySqrt, I18n::Message::PythonNumpySqrt), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTan, I18n::Message::PythonNumpyTan), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTanh, I18n::Message::PythonNumpyTanh), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyBool, I18n::Message::PythonNumpyBool), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFloat, I18n::Message::PythonNumpyFloat), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyUint8, I18n::Message::PythonNumpyUint8), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInt8, I18n::Message::PythonNumpyInt8), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyUint16, I18n::Message::PythonNumpyUint16), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInt16, I18n::Message::PythonNumpyInt16), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyNan, I18n::Message::PythonNumpyNan), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInf, I18n::Message::PythonNumpyInf), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyE, I18n::Message::PythonNumpyE), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyPi, I18n::Message::PythonNumpyPi) + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyNdinfo), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAll), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAny), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArgmax), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArgmin), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArgsort), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyClip), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyConvolve), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDiff), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInterp), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDot), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCross), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyEqual), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyNot_equal), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFlip), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyIsfinite), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyIsinf), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMean), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMin), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMax), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMedian), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMinimum), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMaximum), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyPolyfit), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyPolyval), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyRoll), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySortWithArguments), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyStd), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySum), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTrace), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTrapz), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyWhere), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyVectorize), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAcos), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAcosh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArctan2), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAround), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAsin), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAsinh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAtan), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAtanh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCeil), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCos), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCosh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDegrees), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyExp), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyExpm1), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFloor), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLog), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLog10), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLog2), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyRadians), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySin), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySinh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySqrt), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTan), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTanh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyBool), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFloat), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyUint8), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInt8), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyUint16), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInt16), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyNan), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInf), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyE), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyPi) }; const ToolboxMessageTree NumpyFftModuleChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFftFunction, I18n::Message::PythonTurtleFunction, false, I18n::Message::PythonCommandNumpyFftFunctionWithoutArg), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFft, I18n::Message::PythonNumpyFft), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyIfft, I18n::Message::PythonNumpyIfft) + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFftFunction, I18n::Message::PythonNumpyFftFunction, false, I18n::Message::PythonCommandNumpyFftFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFft), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyIfft) }; const ToolboxMessageTree NumpyLinalgModuleChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLinalgFunction, I18n::Message::PythonTurtleFunction, false, I18n::Message::PythonCommandNumpyLinalgFunctionWithoutArg), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDet, I18n::Message::PythonNumpyDet), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyEig, I18n::Message::PythonNumpyEig), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCholesky, I18n::Message::PythonNumpyCholesky), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInv, I18n::Message::PythonNumpyInv), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyNorm, I18n::Message::PythonNumpyNorm) + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLinalgFunction, I18n::Message::PythonNumpyLinalgFunction, false, I18n::Message::PythonCommandNumpyLinalgFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDet), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyEig), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCholesky), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInv), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyNorm) }; const ToolboxMessageTree NumpyModuleChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromNumpy, I18n::Message::PythonImportTurtle, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFunction, I18n::Message::PythonTurtleFunction, false, I18n::Message::PythonCommandNumpyPythonCommandNumpyFunctionWithoutArgFunction), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromNumpy, I18n::Message::PythonImportNumpy, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFunction, I18n::Message::PythonNumpyFunction, false, I18n::Message::PythonCommandNumpyFunctionWithoutArg), ToolboxMessageTree::Node(I18n::Message::NumpyNdarray, NumpyNdarrayModuleChildren), ToolboxMessageTree::Node(I18n::Message::Functions, NumpyFunctionsModuleChildren), ToolboxMessageTree::Node(I18n::Message::NumpyFftModule, NumpyFftModuleChildren), @@ -253,7 +253,8 @@ const ToolboxMessageTree NumpyModuleChildren[] = { }; const ToolboxMessageTree UlabModuleChildren[] = { - ToolboxMessageTree::Node(I18n::Message::NumpyModule, NumpyModuleChildren) + ToolboxMessageTree::Node(I18n::Message::NumpyModule, NumpyModuleChildren), + ToolboxMessageTree::Leaf(I18n::Message::UlabDocumentation, I18n::Message::UlabDocumentationLink) }; const ToolboxMessageTree TurtleModuleChildren[] = { @@ -622,8 +623,11 @@ KDCoordinate PythonToolbox::rowHeight(int j) { } bool PythonToolbox::selectLeaf(int selectedRow) { - m_selectableTableView.deselectTable(); ToolboxMessageTree * node = (ToolboxMessageTree *)m_messageTreeModel->childAtIndex(selectedRow); + if(node->text() == I18n::Message::UlabDocumentationLink){ + return true; + } + m_selectableTableView.deselectTable(); if(node->insertedText() == I18n::Message::IonSelector){ m_ionKeys.setSender(sender()); Container::activeApp()->displayModalViewController(static_cast(&m_ionKeys), 0.f, 0.f, Metric::PopUpTopMargin, Metric::PopUpLeftMargin, 0, Metric::PopUpRightMargin); diff --git a/apps/code/toolbox.de.i18n b/apps/code/toolbox.de.i18n index 34840329bfb..82dd2b142c1 100644 --- a/apps/code/toolbox.de.i18n +++ b/apps/code/toolbox.de.i18n @@ -4,3 +4,4 @@ Modules = "Module" LoopsAndTests = "Schleifen und Tests" Files = "Dateien" Exceptions = "Ausnahmen" +UlabDocumentation = "Dokumentation" diff --git a/apps/code/toolbox.en.i18n b/apps/code/toolbox.en.i18n index 81e60c7da2b..59eadf40323 100644 --- a/apps/code/toolbox.en.i18n +++ b/apps/code/toolbox.en.i18n @@ -4,3 +4,4 @@ Modules = "Modules" LoopsAndTests = "Loops and tests" Files = "Files" Exceptions = "Exceptions" +UlabDocumentation = "Documentation" diff --git a/apps/code/toolbox.es.i18n b/apps/code/toolbox.es.i18n index 81e60c7da2b..905e28a8e9d 100644 --- a/apps/code/toolbox.es.i18n +++ b/apps/code/toolbox.es.i18n @@ -4,3 +4,4 @@ Modules = "Modules" LoopsAndTests = "Loops and tests" Files = "Files" Exceptions = "Exceptions" +UlabDocumentation = "Documentación" diff --git a/apps/code/toolbox.fr.i18n b/apps/code/toolbox.fr.i18n index 724abb7a518..077e4550a45 100644 --- a/apps/code/toolbox.fr.i18n +++ b/apps/code/toolbox.fr.i18n @@ -4,3 +4,4 @@ Modules = "Modules" LoopsAndTests = "Boucles et tests" Files = "Fichiers" Exceptions = "Exceptions" +UlabDocumentation = "Documentation" diff --git a/apps/code/toolbox.hu.i18n b/apps/code/toolbox.hu.i18n index 89015122024..fbdff6f74f6 100644 --- a/apps/code/toolbox.hu.i18n +++ b/apps/code/toolbox.hu.i18n @@ -4,3 +4,4 @@ Modules = "Modulok" LoopsAndTests = "Hurkok és tesztek" Files = "Fájlok" Exceptions = "Kivételek" +UlabDocumentation = "Dokumentáció" diff --git a/apps/code/toolbox.it.i18n b/apps/code/toolbox.it.i18n index d7b219d872e..7069ccb4e7e 100644 --- a/apps/code/toolbox.it.i18n +++ b/apps/code/toolbox.it.i18n @@ -4,3 +4,4 @@ Modules = "Moduli" LoopsAndTests = "Cicli e test" Files = "Files" Exceptions = "Exceptions" +UlabDocumentation = "Documentazione" diff --git a/apps/code/toolbox.nl.i18n b/apps/code/toolbox.nl.i18n index 849bd76a6ab..4df9ed1a753 100644 --- a/apps/code/toolbox.nl.i18n +++ b/apps/code/toolbox.nl.i18n @@ -4,3 +4,4 @@ Modules = "Modules" LoopsAndTests = "Herhalingen en testen" Files = "Files" Exceptions = "Exceptions" +UlabDocumentation = "Documentatie" diff --git a/apps/code/toolbox.pt.i18n b/apps/code/toolbox.pt.i18n index f7cfad07b03..4ea2d75fd1e 100644 --- a/apps/code/toolbox.pt.i18n +++ b/apps/code/toolbox.pt.i18n @@ -4,3 +4,4 @@ Modules = "Módulos" LoopsAndTests = "Laços e testes" Files = "Files" Exceptions = "Exceptions" +UlabDocumentation = "Documentação" diff --git a/apps/code/toolbox.universal.i18n b/apps/code/toolbox.universal.i18n index df6a9171d42..5a307394c43 100644 --- a/apps/code/toolbox.universal.i18n +++ b/apps/code/toolbox.universal.i18n @@ -64,3 +64,4 @@ PythonCommandReturn = "return " RandomModule = "random" IonSelector = "Key selector" PressAKey = "Press a key" +UlabDocumentationLink = "micropython-ulab.readthedocs.io" From 9eb81a6daf6e57ae35f2a5b632dd2302336b1bda Mon Sep 17 00:00:00 2001 From: "Yaya.Cout" Date: Sun, 5 Sep 2021 11:49:42 +0200 Subject: [PATCH 029/355] Fix OmegaVersion in settings --- apps/settings/main_controller.cpp | 2 +- apps/settings/sub_menu/about_controller.cpp | 8 ++++---- ion/include/ion.h | 2 +- ion/src/shared/platform_info.cpp | 12 ++++++------ 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index 6b65f15d3ea..1e19235fc29 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -20,7 +20,7 @@ constexpr SettingsMessageTree s_modelMathOptionsChildren[6] = {SettingsMessageTr constexpr SettingsMessageTree s_modelFontChildren[2] = {SettingsMessageTree(I18n::Message::LargeFont), SettingsMessageTree(I18n::Message::SmallFont)}; constexpr SettingsMessageTree s_accessibilityChildren[6] = {SettingsMessageTree(I18n::Message::AccessibilityInvertColors), SettingsMessageTree(I18n::Message::AccessibilityMagnify),SettingsMessageTree(I18n::Message::AccessibilityGamma),SettingsMessageTree(I18n::Message::AccessibilityGammaRed),SettingsMessageTree(I18n::Message::AccessibilityGammaGreen),SettingsMessageTree(I18n::Message::AccessibilityGammaBlue)}; constexpr SettingsMessageTree s_contributorsChildren[23] = {SettingsMessageTree(I18n::Message::Developers), SettingsMessageTree(I18n::Message::QuentinGuidee), SettingsMessageTree(I18n::Message::JoachimLeFournis), SettingsMessageTree(I18n::Message::MaximeFriess), SettingsMessageTree(I18n::Message::JeanBaptisteBoric), SettingsMessageTree(I18n::Message::SandraSimmons), SettingsMessageTree(I18n::Message::David), SettingsMessageTree(I18n::Message::DamienNicolet), SettingsMessageTree(I18n::Message::EvannDreumont), SettingsMessageTree(I18n::Message::SzaboLevente), SettingsMessageTree(I18n::Message::VenceslasDuet), SettingsMessageTree(I18n::Message::CharlotteThomas), SettingsMessageTree(I18n::Message::AntoninLoubiere), SettingsMessageTree(I18n::Message::CyprienMejat), SettingsMessageTree(I18n::Message::BetaTesters), SettingsMessageTree(I18n::Message::TimeoArnouts), SettingsMessageTree(I18n::Message::JulieC), SettingsMessageTree(I18n::Message::LelahelHideux), SettingsMessageTree(I18n::Message::Madil), SettingsMessageTree(I18n::Message::HilaireLeRoux), SettingsMessageTree(I18n::Message::HectorNussbaumer), SettingsMessageTree(I18n::Message::RaphaelDyda), SettingsMessageTree(I18n::Message::ThibautC)}; -constexpr SettingsMessageTree s_modelAboutChildren[8] = {SettingsMessageTree(I18n::Message::Username), SettingsMessageTree(I18n::Message::SoftwareVersion), SettingsMessageTree(I18n::Message::OmegaVersion), SettingsMessageTree(I18n::Message::MicroPythonVersion), SettingsMessageTree(I18n::Message::MemUse), SettingsMessageTree(I18n::Message::SerialNumber), SettingsMessageTree(I18n::Message::FccId), SettingsMessageTree(I18n::Message::Contributors, s_contributorsChildren)}; +constexpr SettingsMessageTree s_modelAboutChildren[8] = {SettingsMessageTree(I18n::Message::Username), SettingsMessageTree(I18n::Message::SoftwareVersion), SettingsMessageTree(I18n::Message::UpsilonVersion), SettingsMessageTree(I18n::Message::MicroPythonVersion), SettingsMessageTree(I18n::Message::MemUse), SettingsMessageTree(I18n::Message::SerialNumber), SettingsMessageTree(I18n::Message::FccId), SettingsMessageTree(I18n::Message::Contributors, s_contributorsChildren)}; MainController::MainController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate) : ViewController(parentResponder), diff --git a/apps/settings/sub_menu/about_controller.cpp b/apps/settings/sub_menu/about_controller.cpp index 220a2c39647..40900e67fc4 100644 --- a/apps/settings/sub_menu/about_controller.cpp +++ b/apps/settings/sub_menu/about_controller.cpp @@ -59,13 +59,13 @@ bool AboutController::handleEvent(Ion::Events::Event event) { } return true; } - if (childLabel == I18n::Message::OmegaVersion) { + if (childLabel == I18n::Message::UpsilonVersion) { MessageTableCellWithBuffer * myCell = (MessageTableCellWithBuffer *)m_selectableTableView.selectedCell(); - if (strcmp(myCell->accessoryText(), Ion::omegaVersion()) == 0) { + if (strcmp(myCell->accessoryText(), Ion::UpsilonVersion()) == 0) { myCell->setAccessoryText(MP_STRINGIFY(OMEGA_STATE)); //Change for public/dev return true; } - myCell->setAccessoryText(Ion::omegaVersion()); + myCell->setAccessoryText(Ion::UpsilonVersion()); return true; } if (childLabel == I18n::Message::MemUse) { @@ -162,7 +162,7 @@ void AboutController::willDisplayCellForIndex(HighlightCell * cell, int index) { static const char * messages[] = { (const char*) Ion::username(), Ion::softwareVersion(), - Ion::omegaVersion(), + Ion::UpsilonVersion(), mpVersion, "", Ion::serialNumber(), diff --git a/ion/include/ion.h b/ion/include/ion.h index 854c3c5394f..06d9da2e70a 100644 --- a/ion/include/ion.h +++ b/ion/include/ion.h @@ -35,7 +35,7 @@ namespace Ion { const char * serialNumber(); const volatile char * username(); const char * softwareVersion(); -const char * omegaVersion(); +const char * UpsilonVersion(); const char * patchLevel(); const char * fccId(); const char * pcbVersion(); diff --git a/ion/src/shared/platform_info.cpp b/ion/src/shared/platform_info.cpp index cdf676a0c65..1950cf09e67 100644 --- a/ion/src/shared/platform_info.cpp +++ b/ion/src/shared/platform_info.cpp @@ -32,7 +32,7 @@ class PlatformInfo { m_storageSize(Ion::Storage::k_storageSize), m_footer(Magic), m_ohm_header(OmegaMagic), - m_omegaVersion{OMEGA_VERSION}, + m_UpsilonVersion{OMEGA_VERSION}, #ifdef OMEGA_USERNAME m_username{OMEGA_USERNAME}, #else @@ -48,14 +48,14 @@ class PlatformInfo { assert(m_ohm_footer == OmegaMagic); return m_version; } - const char * omegaVersion() const { + const char * UpsilonVersion() const { assert(m_storageAddress != nullptr); assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); assert(m_ohm_header == OmegaMagic); assert(m_ohm_footer == OmegaMagic); - return m_omegaVersion; + return m_UpsilonVersion; } const volatile char * username() const volatile { assert(m_storageAddress != nullptr); @@ -85,7 +85,7 @@ class PlatformInfo { size_t m_storageSize; uint32_t m_footer; uint32_t m_ohm_header; - const char m_omegaVersion[16]; + const char m_UpsilonVersion[16]; const volatile char m_username[16]; uint32_t m_ohm_footer; }; @@ -96,8 +96,8 @@ const char * Ion::softwareVersion() { return platform_infos.version(); } -const char * Ion::omegaVersion() { - return platform_infos.omegaVersion(); +const char * Ion::UpsilonVersion() { + return platform_infos.UpsilonVersion(); } const volatile char * Ion::username() { From 0f4502ebce4e6c23456a55b8719ca27d4d8ef248 Mon Sep 17 00:00:00 2001 From: Laury Date: Sun, 5 Sep 2021 16:27:44 +0200 Subject: [PATCH 030/355] [code/ulab] Added scipy --- apps/code/catalog.de.i18n | 6 +++++ apps/code/catalog.en.i18n | 6 +++++ apps/code/catalog.es.i18n | 6 +++++ apps/code/catalog.fr.i18n | 6 +++++ apps/code/catalog.hu.i18n | 6 +++++ apps/code/catalog.it.i18n | 6 +++++ apps/code/catalog.nl.i18n | 6 +++++ apps/code/catalog.pt.i18n | 6 +++++ apps/code/catalog.universal.i18n | 22 ++++++++++++++++++ apps/code/python_toolbox.cpp | 38 ++++++++++++++++++++++++++++++++ apps/code/toolbox.universal.i18n | 5 +++++ 11 files changed, 113 insertions(+) diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index da6c16f996f..d168f9c1329 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -75,6 +75,7 @@ PythonImportRandom = "Random-Modul importieren" PythonImportMath = "Math-Modul importieren" PythonImportMatplotlibPyplot = "Matplotlib.pyplot-Modul importieren" PythonImportNumpy = "Ulab.numpy-Modul importieren" +PythonImportScipy = "Ulab.scipy-Modul importieren" PythonImportOs = "OS-Modul importieren" PythonOsUname = "Informationen über das System holen" PythonOsGetlogin = "Benutzernamen holen" @@ -108,6 +109,11 @@ PythonMonotonic = "Wert einer monotonen Uhr" PythonNumpyFunction = "numpy Modul-Präfix" PythonNumpyFftFunction = "numpy.fft Modul-Präfix" PythonNumpyLinalgFunction = "numpy.linalg Modul-Präfix" +PythonScipyFunction = "scipy Modul-Präfix" +PythonScipyLinalgFunction = "scipy.linalg Modul-Präfix" +PythonScipyOptimizeFunction = "scipy.optimize Modul-Präfix" +PythonScipySignalFunction = "scipy.signal Modul-Präfix" +PythonScipySpecialFunction = "scipy.special Modul-Präfix" PythonOct = "Ganzzahl in Oktal umwandeln" PythonPhase = "Phase von z" PythonPlot = "Plotten von y gegen x als Linien" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index 363dd820e0a..3b5a707bf8d 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -75,6 +75,7 @@ PythonImportRandom = "Import random module" PythonImportMath = "Import math module" PythonImportMatplotlibPyplot = "Import matplotlib.pyplot module" PythonImportNumpy = "Import ulab.numpy module" +PythonImportScipy = "Import ulab.scipy module" PythonImportTime = "Import time module" PythonImportTurtle = "Import turtle module" PythonIndex = "Index of the first x occurrence" @@ -102,6 +103,11 @@ PythonMonotonic = "Value of a monotonic clock" PythonNumpyFunction = "numpy module prefix" PythonNumpyFftFunction = "numpy.fft module prefix" PythonNumpyLinalgFunction = "numpy.linalg module prefix" +PythonScipyFunction = "scipy module prefix" +PythonScipyLinalgFunction = "scipy.linalg module prefix" +PythonScipyOptimizeFunction = "scipy.optimize module prefix" +PythonScipySignalFunction = "scipy.signal module prefix" +PythonScipySpecialFunction = "scipy.special module prefix" PythonOct = "Convert integer to octal" PythonPhase = "Phase of z" PythonPlot = "Plot y versus x as lines" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index d39456839c3..09ad6235dd8 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -75,6 +75,7 @@ PythonImportRandom = "Import random module" PythonImportMath = "Import math module" PythonImportMatplotlibPyplot = "Import matplotlib.pyplot module" PythonImportNumpy = "Import ulab.numpy module" +PythonImportScipy = "Import ulab.scipy module" PythonImportTime = "Import time module" PythonImportTurtle = "Import turtle module" PythonIndex = "Index of the first x occurrence" @@ -102,6 +103,11 @@ PythonMonotonic = "Value of a monotonic clock" PythonNumpyFunction = "numpy module prefix" PythonNumpyFftFunction = "numpy.fft module prefix" PythonNumpyLinalgFunction = "numpy.linalg module prefix" +PythonScipyFunction = "scipy module prefix" +PythonScipyLinalgFunction = "scipy.linalg module prefix" +PythonScipyOptimizeFunction = "scipy.optimize module prefix" +PythonScipySignalFunction = "scipy.signal module prefix" +PythonScipySpecialFunction = "scipy.special module prefix" PythonOct = "Convert integer to octal" PythonPhase = "Phase of z" PythonPlot = "Plot y versus x as lines" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index d8d9daced60..af0676142a1 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -75,6 +75,7 @@ PythonImportRandom = "Importation du module random" PythonImportMath = "Importation du module math" PythonImportMatplotlibPyplot = "Importation de matplotlib.pyplot" PythonImportNumpy = "Importation de ulab.numpy" +PythonImportScipy = "Importation de ulab.scipy" PythonImportTurtle = "Importation du module turtle" PythonImportTime = "Importation du module time" PythonIndex = "Indice première occurrence de x" @@ -102,6 +103,11 @@ PythonMonotonic = "Renvoie la valeur de l'horloge" PythonNumpyFunction = "Préfixe fonction du module numpy" PythonNumpyFftFunction = "Préfixe fonction du module numpy.fft" PythonNumpyLinalgFunction = "Préfixe fonction du module numpy.linalg" +PythonScipyFunction = "Préfixe fonction du module scipy" +PythonScipyLinalgFunction = "Préfixe fonction du module scipy.linalg" +PythonScipyOptimizeFunction = "Préfixe fonction du module scipy.optimize" +PythonScipySignalFunction = "Préfixe fonction du module scipy.signal" +PythonScipySpecialFunction = "Préfixe fonction du module scipy.special" PythonOct = "Conversion en octal" PythonPhase = "Argument de z" PythonPlot = "Trace y en fonction de x" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index b2011816bc1..b64a41192b2 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -75,6 +75,7 @@ PythonImportRandom = "Véletlenszerü modul importálása" PythonImportMath = "math modul importálása" PythonImportMatplotlibPyplot = "matplotlib.pyplot modul importálása" PythonImportNumpy = "ulab.numpy modul importálása" +PythonImportScipy = "ulab.scipy modul importálása" PythonImportTurtle = "turtle modul importálása" PythonImportTime = "time modul importálása" PythonIndex = "Az elsö x esemény indexe" @@ -102,6 +103,11 @@ PythonMonotonic = "Az óra értékét adja vissza" PythonNumpyFunction = "numpy elötag" PythonNumpyFftFunction = "numpy.fft elötag" PythonNumpyLinalgFunction = "numpy.linalg elötag" +PythonScipyFunction = "scipy elötag" +PythonScipyLinalgFunction = "scipy.linalg elötag" +PythonScipyOptimizeFunction = "scipy.optimize elötag" +PythonScipySignalFunction = "scipy.signal elötag" +PythonScipySpecialFunction = "scipy.special elötag" PythonOct = "Decimális szám konvertálása octális számra" PythonPhase = "z fázisa" PythonPlot = "y-t jelöli x függvényében" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index 8627e679c44..2a61d0a5b69 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -75,6 +75,7 @@ PythonImportRandom = "Importa modulo random" PythonImportMath = "Importa modulo math" PythonImportMatplotlibPyplot = "Importa modulo matplotlib.pyplot" PythonImportNumpy = "Importa modulo ulab.numpy" +PythonImportScipy = "Importa modulo ulab.scipy" PythonImportTurtle = "Importa del modulo turtle" PythonImportTime = "Importa del modulo time" PythonImportOs = "Importa modulo os" @@ -108,6 +109,11 @@ PythonMonotonic = "Restituisce il valore dell'orologio" PythonNumpyFunction = "Prefisso modulo numpy" PythonNumpyFftFunction = "Prefisso modulo numpy.fft" PythonNumpyLinalgFunction = "Prefisso modulo numpy.linalg" +PythonScipyFunction = "Prefisso modulo scipy" +PythonScipyLinalgFunction = "Prefisso modulo scipy.linalg" +PythonScipyOptimizeFunction = "Prefisso modulo scipy.optimize" +PythonScipySignalFunction = "Prefisso modulo scipy.signal" +PythonScipySpecialFunction = "Prefisso modulo scipy.special" PythonOct = "Conversione in ottale" PythonPhase = "Argomento di z" PythonPlot = "Disegna y in f. di x come linee" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index f46b4606f28..1d8349e2499 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -75,6 +75,7 @@ PythonImportRandom = "Importeer random module" PythonImportMath = "Importeer math module" PythonImportMatplotlibPyplot = "Importeer matplotlib.pyplot module" PythonImportNumpy = "Importeer ulab.numpy module" +PythonImportScipy = "Importeer ulab.scipy module" PythonImportTime = "Importeer time module" PythonImportOs = "Importeer os module" PythonOsUname = " Krijg systeeminfo" @@ -108,6 +109,11 @@ PythonMonotonic = "Waarde van een monotone klok" PythonNumpyFunction = "numpy module prefix" PythonNumpyFftFunction = "numpy.fft module prefix" PythonNumpyLinalgFunction = "numpy.linalg module prefix" +PythonScipyFunction = "scipy module prefix" +PythonScipyLinalgFunction = "scipy.linalg module prefix" +PythonScipyOptimizeFunction = "scipy.optimize module prefix" +PythonScipySignalFunction = "scipy.signal module prefix" +PythonScipySpecialFunction = "scipy.special module prefix" PythonOct = "Integer omzetten naar octaal" PythonPhase = "Fase van z in radialen" PythonPlot = "Plot y versus x als lijnen" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index 51329736533..39472850783 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -75,6 +75,7 @@ PythonImportRandom = "Importar módulo random" PythonImportMath = "Importar módulo math" PythonImportMatplotlibPyplot = "Importar módulo matplotlib.pyplot" PythonImportNumpy = "Importar módulo ulab.numpy" +PythonImportScipy = "Importar módulo ulab.scipy" PythonImportTime = "Importar módulo time" PythonImportTurtle = "Importar módulo turtle" PythonIndex = "Índice da primeira ocorrência de x" @@ -102,6 +103,11 @@ PythonMonotonic = "Devolve o valor do relógio" PythonNumpyFunction = "Prefixo do módulo numpy" PythonNumpyFftFunction = "Prefixo do módulo numpy.fft" PythonNumpyLinalgFunction = "Prefixo do módulo numpy.linalg" +PythonScipyFunction = "Prefixo do módulo scipy" +PythonScipyLinalgFunction = "Prefixo do módulo scipy.linalg" +PythonScipyOptimizeFunction = "Prefixo do módulo scipy.optimize" +PythonScipySignalFunction = "Prefixo do módulo scipy.signal" +PythonScipySpecialFunction = "Prefixo do módulo scipy.special" PythonOct = "Converter número inteiro em octal" PythonPhase = "Argumento de z" PythonPlot = "Desenhar y em função de x" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index 106d57dbbf0..510fe65a0a2 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -91,6 +91,7 @@ PythonCommandImportKandinsky = "import kandinsky" PythonCommandImportMath = "import math" PythonCommandImportMatplotlibPyplot = "import matplotlib.pyplot" PythonCommandImportFromNumpy = "from ulab import numpy as np" +PythonCommandImportFromScipy = "from ulab import scipy as spy" PythonCommandImportRandom = "import random" PythonCommandImportOs = "import os" PythonCommandImportFromOs = "from os import *" @@ -306,6 +307,27 @@ PythonCommandReverseWithoutArg = ".reverse()" PythonCommandRound = "round(x,n)" PythonCommandScatter = "scatter(x,y)" PythonCommandSeed = "seed(x)" +PythonCommandScipyFunction = "spy.function" +PythonCommandScipyFunctionWithoutArg = "spy.\x11" +PythonCommandScipyLinalgFunction = "spy.linalg.function" +PythonCommandScipyLinalgFunctionWithoutArg = "spy.linalg.\x11" +PythonCommandScipyOptimizeFunction = "spy.optimize.function" +PythonCommandScipyOptimizeFunctionWithoutArg = "spy.optimize.\x11" +PythonCommandScipySignalFunction = "spy.signal.function" +PythonCommandScipySignalFunctionWithoutArg = "spy.signal.\x11" +PythonCommandScipySpecialFunction = "spy.special.function" +PythonCommandScipySpecialFunctionWithoutArg = "spy.special.\x11" +PythonCommandScipyLinalgChoSolve = "spy.linalg.cho_solve(a, b)" +PythonCommandScipyLinalgSolveTriangular = "spy.linalg.solve_triangular(a, b)" +PythonCommandScipyOptimizeBisect = "spy.optimize.bisect(f, a, b)" +PythonCommandScipyOptimizeFmin = "spy.optimize.fmin(f, x0)" +PythonCommandScipyOptimizeNewton = "spy.optimize.newton(f, x0)" +PythonCommandScipySignalSosfilt = "spy.signal.sosfilt(sos, x)" +PythonCommandScipySignalSpectrogram = "spy.signal.spectrogram(y)" +PythonCommandScipySpecialErf = "spy.erf(a)" +PythonCommandScipySpecialErfc = "spy.erfc(a)" +PythonCommandScipySpecialGamma = "spy.gamma(a)" +PythonCommandScipySpecialGammaln = "spy.gammaln(a)" PythonCommandSetPixel = "set_pixel(x,y,color)" PythonCommandShow = "show()" PythonCommandSin = "sin(x)" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 2a26f98a7b9..ef64e1b4dc1 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -252,8 +252,46 @@ const ToolboxMessageTree NumpyModuleChildren[] = { ToolboxMessageTree::Node(I18n::Message::NumpyLinalgModule, NumpyLinalgModuleChildren) }; +const ToolboxMessageTree ScipyLinalgModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyLinalgFunction, I18n::Message::PythonScipyLinalgFunction, false, I18n::Message::PythonCommandScipyLinalgFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyLinalgChoSolve), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyLinalgSolveTriangular) +}; + +const ToolboxMessageTree ScipyOptimizeModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyOptimizeFunction, I18n::Message::PythonScipyOptimizeFunction, false, I18n::Message::PythonCommandScipyOptimizeFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyOptimizeBisect), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyOptimizeFmin), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyOptimizeNewton) +}; + +const ToolboxMessageTree ScipySignalModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipySignalFunction, I18n::Message::PythonScipySignalFunction, false, I18n::Message::PythonCommandScipySignalFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipySignalSosfilt), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipySignalSpectrogram) +}; + +const ToolboxMessageTree ScipySpecialModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipySpecialFunction, I18n::Message::PythonScipySpecialFunction, false, I18n::Message::PythonCommandScipySpecialFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipySpecialErf), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipySpecialErfc), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipySpecialGamma), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipySpecialGammaln), +}; + +const ToolboxMessageTree ScipyModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromScipy, I18n::Message::PythonImportScipy, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyFunction, I18n::Message::PythonScipyFunction, false, I18n::Message::PythonCommandScipyFunctionWithoutArg), + ToolboxMessageTree::Node(I18n::Message::ScipyLinalgModule, ScipyLinalgModuleChildren), + ToolboxMessageTree::Node(I18n::Message::ScipyOptimizeModule, ScipyOptimizeModuleChildren), + ToolboxMessageTree::Node(I18n::Message::ScipySignalModule, ScipySignalModuleChildren), + ToolboxMessageTree::Node(I18n::Message::ScipySpecialModule, ScipySpecialModuleChildren), + +}; + const ToolboxMessageTree UlabModuleChildren[] = { ToolboxMessageTree::Node(I18n::Message::NumpyModule, NumpyModuleChildren), + ToolboxMessageTree::Node(I18n::Message::ScipyModule, ScipyModuleChildren), ToolboxMessageTree::Leaf(I18n::Message::UlabDocumentation, I18n::Message::UlabDocumentationLink) }; diff --git a/apps/code/toolbox.universal.i18n b/apps/code/toolbox.universal.i18n index 5a307394c43..99810151a96 100644 --- a/apps/code/toolbox.universal.i18n +++ b/apps/code/toolbox.universal.i18n @@ -6,6 +6,11 @@ MatplotlibPyplotModule = "matplotlib.pyplot" NumpyModule = "numpy" NumpyFftModule = "fft" NumpyLinalgModule = "linalg" +ScipyModule = "scipy" +ScipyLinalgModule = "linalg" +ScipyOptimizeModule = "optimize" +ScipySignalModule = "signal" +ScipySpecialModule = "special" NumpyNdarray = "ndarray" OsModule = "os" TimeModule = "time" From 98b665ac4b0604355cd345a3fb45d3b3de0bae63 Mon Sep 17 00:00:00 2001 From: Laury Date: Sun, 5 Sep 2021 16:57:09 +0200 Subject: [PATCH 031/355] [code/ulab] Disabled scipy in toolbox for n0100 + added 2 forgotten numpy functions --- apps/code/catalog.universal.i18n | 2 ++ apps/code/python_toolbox.cpp | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index 510fe65a0a2..ad07c7ba93f 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -201,6 +201,8 @@ PythonCommandNumpyTranspose = "ndarray.transpose()" PythonCommandNumpyTransposeWithoutArg = ".transpose()" PythonCommandNumpySort = "ndarray.sort()" PythonCommandNumpySortWithoutArg = ".sort()" +PythonCommandNumpySetPrintOptions = "np.set_printoptions()" +PythonCommandNumpyGetPrintOptions = "np.get_printoptions()" PythonCommandNumpyNdinfo = "np.ndinfo(a)" PythonCommandNumpyAll = "np.all(a)" PythonCommandNumpyAny = "np.any(a)" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index ef64e1b4dc1..2dc5a6e817e 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -225,7 +225,9 @@ const ToolboxMessageTree NumpyFunctionsModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyNan), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInf), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyE), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyPi) + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyPi), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySetPrintOptions), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyGetPrintOptions) }; const ToolboxMessageTree NumpyFftModuleChildren[] = { @@ -252,6 +254,7 @@ const ToolboxMessageTree NumpyModuleChildren[] = { ToolboxMessageTree::Node(I18n::Message::NumpyLinalgModule, NumpyLinalgModuleChildren) }; +#if !defined(DEVICE_N0100) const ToolboxMessageTree ScipyLinalgModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyLinalgFunction, I18n::Message::PythonScipyLinalgFunction, false, I18n::Message::PythonCommandScipyLinalgFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyLinalgChoSolve), @@ -286,12 +289,15 @@ const ToolboxMessageTree ScipyModuleChildren[] = { ToolboxMessageTree::Node(I18n::Message::ScipyOptimizeModule, ScipyOptimizeModuleChildren), ToolboxMessageTree::Node(I18n::Message::ScipySignalModule, ScipySignalModuleChildren), ToolboxMessageTree::Node(I18n::Message::ScipySpecialModule, ScipySpecialModuleChildren), - }; +#endif + const ToolboxMessageTree UlabModuleChildren[] = { ToolboxMessageTree::Node(I18n::Message::NumpyModule, NumpyModuleChildren), +#if !defined(DEVICE_N0100) ToolboxMessageTree::Node(I18n::Message::ScipyModule, ScipyModuleChildren), +#endif ToolboxMessageTree::Leaf(I18n::Message::UlabDocumentation, I18n::Message::UlabDocumentationLink) }; From 74c500df015161c475c10dff40a13a692b785fa8 Mon Sep 17 00:00:00 2001 From: Laury Date: Sun, 5 Sep 2021 16:57:50 +0200 Subject: [PATCH 032/355] [code/ulab] removed unnecessary module utils --- python/port/mod/ulab/ulab.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/port/mod/ulab/ulab.h b/python/port/mod/ulab/ulab.h index 647672d8bd3..032768542ef 100644 --- a/python/port/mod/ulab/ulab.h +++ b/python/port/mod/ulab/ulab.h @@ -270,7 +270,7 @@ // frombuffer adds 600 bytes to the firmware #ifndef ULAB_NUMPY_HAS_FROMBUFFER -#define ULAB_NUMPY_HAS_FROMBUFFER (1) +#define ULAB_NUMPY_HAS_FROMBUFFER (0) #endif // functions that create an array @@ -644,7 +644,7 @@ #endif #ifndef ULAB_HAS_UTILS_MODULE -#define ULAB_HAS_UTILS_MODULE (1) +#define ULAB_HAS_UTILS_MODULE (0) #endif #ifndef ULAB_UTILS_HAS_FROM_INT16_BUFFER From 19ee32986fd63eafa8ddc66544a95970577deb3a Mon Sep 17 00:00:00 2001 From: Laury Date: Sun, 5 Sep 2021 21:10:56 +0200 Subject: [PATCH 033/355] [reader] Make improve coding style and added empty view message --- apps/home/apps_layout.csv | 4 +- apps/reader/Makefile | 2 +- apps/reader/app.cpp | 5 +- apps/reader/app.h | 3 +- apps/reader/base.de.i18n | 6 +- apps/reader/base.en.i18n | 2 +- apps/reader/base.es.i18n | 6 +- apps/reader/base.fr.i18n | 6 +- apps/reader/base.hu.i18n | 6 +- apps/reader/base.it.i18n | 6 +- apps/reader/base.nl.i18n | 6 +- apps/reader/base.pt.i18n | 6 +- apps/reader/list_book_controller.cpp | 126 +++++---- apps/reader/list_book_controller.h | 48 ++-- apps/reader/read_book_controller.cpp | 67 ++--- apps/reader/read_book_controller.h | 2 +- apps/reader/utility.cpp | 145 +++++----- apps/reader/utility.h | 4 +- apps/reader/word_wrap_view.cpp | 247 ++++++++---------- apps/reader/word_wrap_view.h | 22 +- .../src/alternate_empty_view_controller.cpp | 3 +- 21 files changed, 342 insertions(+), 380 deletions(-) diff --git a/apps/home/apps_layout.csv b/apps/home/apps_layout.csv index 77e47e4b882..b17d33f7d24 100644 --- a/apps/home/apps_layout.csv +++ b/apps/home/apps_layout.csv @@ -1,2 +1,2 @@ -Default,calculation,rpn,graph,code,statistics,probability,solver,atomic,sequence,regression,settings -HidePython,calculation,rpn,graph,code,statistics,probability,solver,atomic,sequence,regression,settings +Default,calculation,rpn,graph,code,statistics,probability,solver,atomic,sequence,regression,reader,settings +HidePython,calculation,rpn,graph,code,statistics,probability,solver,atomic,sequence,regression,reader,settings diff --git a/apps/reader/Makefile b/apps/reader/Makefile index 71adec9e03b..f786e1510a8 100644 --- a/apps/reader/Makefile +++ b/apps/reader/Makefile @@ -1,4 +1,4 @@ -apps += reader::App +apps += Reader::App app_headers += apps/reader/app.h app_sreader_src = $(addprefix apps/reader/,\ diff --git a/apps/reader/app.cpp b/apps/reader/app.cpp index 18ae33da03a..5d9b8b1563b 100644 --- a/apps/reader/app.cpp +++ b/apps/reader/app.cpp @@ -4,7 +4,7 @@ #include "apps/i18n.h" -namespace reader { +namespace Reader { I18n::Message App::Descriptor::name() { return I18n::Message::ReaderApp; @@ -32,7 +32,8 @@ App::Descriptor * App::Snapshot::descriptor() { App::App(Snapshot * snapshot) : ::App(snapshot, &m_stackViewController), m_listBookController(&m_stackViewController), - m_stackViewController(nullptr, &m_listBookController) + m_alternateEmptyViewController(&m_stackViewController, &m_listBookController, &m_listBookController), + m_stackViewController(&m_modalViewController, &m_alternateEmptyViewController) { } diff --git a/apps/reader/app.h b/apps/reader/app.h index 1204862c4b0..8de726e9f35 100644 --- a/apps/reader/app.h +++ b/apps/reader/app.h @@ -4,7 +4,7 @@ #include #include "list_book_controller.h" -namespace reader { +namespace Reader { class App : public ::App { public: @@ -22,6 +22,7 @@ class App : public ::App { private: App(Snapshot * snapshot); ListBookController m_listBookController; + AlternateEmptyViewController m_alternateEmptyViewController; StackViewController m_stackViewController; }; diff --git a/apps/reader/base.de.i18n b/apps/reader/base.de.i18n index 68a64986da6..3dcf262d7a2 100644 --- a/apps/reader/base.de.i18n +++ b/apps/reader/base.de.i18n @@ -1,3 +1,3 @@ -ReaderApp = "Reader" -ReaderAppCapital = "READER" -NoFileToDisplay = "Keine Dateien zum Anzeigen!" \ No newline at end of file +ReaderApp = "Leser" +ReaderAppCapital = "LESER" +NoFileToDisplay = "Keine Dateien zum Anzeigen" \ No newline at end of file diff --git a/apps/reader/base.en.i18n b/apps/reader/base.en.i18n index 0101ebe2aad..0b0a2de2aef 100644 --- a/apps/reader/base.en.i18n +++ b/apps/reader/base.en.i18n @@ -1,3 +1,3 @@ ReaderApp = "Reader" ReaderAppCapital = "READER" -NoFileToDisplay = "No file to display!" \ No newline at end of file +NoFileToDisplay = "No file to display" \ No newline at end of file diff --git a/apps/reader/base.es.i18n b/apps/reader/base.es.i18n index 51be1eaf458..bcbe8f0cdea 100644 --- a/apps/reader/base.es.i18n +++ b/apps/reader/base.es.i18n @@ -1,3 +1,3 @@ -ReaderApp = "Reader" -ReaderAppCapital = "READER" -NoFileToDisplay ="No hay archivos para mostrar!" \ No newline at end of file +ReaderApp = "Lector" +ReaderAppCapital = "LECTOR" +NoFileToDisplay ="No hay archivos para mostrar" \ No newline at end of file diff --git a/apps/reader/base.fr.i18n b/apps/reader/base.fr.i18n index 220c36fa1f5..f8b97e0364e 100644 --- a/apps/reader/base.fr.i18n +++ b/apps/reader/base.fr.i18n @@ -1,3 +1,3 @@ -ReaderApp = "Reader" -ReaderAppCapital = "READER" -NoFileToDisplay = "Aucun fichier à afficher!" \ No newline at end of file +ReaderApp = "Liseuse" +ReaderAppCapital = "LISEUSE" +NoFileToDisplay = "Aucun fichier à afficher" \ No newline at end of file diff --git a/apps/reader/base.hu.i18n b/apps/reader/base.hu.i18n index 264d6e8784a..09357ced763 100644 --- a/apps/reader/base.hu.i18n +++ b/apps/reader/base.hu.i18n @@ -1,3 +1,3 @@ -ReaderApp = "Reader" -ReaderAppCapital = "READER" -NoFileToDisplay ="Nincs megjeleníthető fájl" \ No newline at end of file +ReaderApp = "Olvasó" +ReaderAppCapital = "OLVASÓ" +NoFileToDisplay = "Nincs megjeleníthető fájl" \ No newline at end of file diff --git a/apps/reader/base.it.i18n b/apps/reader/base.it.i18n index 4464ff0771b..c553356d9a6 100644 --- a/apps/reader/base.it.i18n +++ b/apps/reader/base.it.i18n @@ -1,3 +1,3 @@ -ReaderApp = "Reader" -ReaderAppCapital = "READER" -NoFileToDisplay ="essun file da visualizzare" \ No newline at end of file +ReaderApp = "Lettore" +ReaderAppCapital = "LETTORE" +NoFileToDisplay = "essun file da visualizzare" \ No newline at end of file diff --git a/apps/reader/base.nl.i18n b/apps/reader/base.nl.i18n index 55693de3e16..57675267376 100644 --- a/apps/reader/base.nl.i18n +++ b/apps/reader/base.nl.i18n @@ -1,3 +1,3 @@ -ReaderApp = "Reader" -ReaderAppCapital = "READER" -NoFileToDisplay ="Geen bestanden om weer te geven" \ No newline at end of file +ReaderApp = "Lezer" +ReaderAppCapital = "LEZER" +NoFileToDisplay = "Geen bestanden om weer te geven" \ No newline at end of file diff --git a/apps/reader/base.pt.i18n b/apps/reader/base.pt.i18n index 886d4930cbe..2aa88319b9c 100644 --- a/apps/reader/base.pt.i18n +++ b/apps/reader/base.pt.i18n @@ -1,3 +1,3 @@ -ReaderApp = "Reader" -ReaderAppCapital = "READER" -NoFileToDisplay ="Nenhum arquivo para exibir" \ No newline at end of file +ReaderApp = "Leitor" +ReaderAppCapital = "LEITOR" +NoFileToDisplay = "Nenhum arquivo para exibir" \ No newline at end of file diff --git a/apps/reader/list_book_controller.cpp b/apps/reader/list_book_controller.cpp index d2635129c69..814f60d5f2d 100644 --- a/apps/reader/list_book_controller.cpp +++ b/apps/reader/list_book_controller.cpp @@ -1,102 +1,100 @@ #include "list_book_controller.h" #include "utility.h" +#include #include "apps/i18n.h" -namespace reader +namespace Reader { -View* ListBookController::view() -{ - return &m_tableView; +View* ListBookController::view() { + return &m_tableView; } ListBookController::ListBookController(Responder * parentResponder): - ViewController(parentResponder), - m_tableView(this, this, this), - m_readBookController(this) + ViewController(parentResponder), + m_tableView(this, this, this), + m_readBookController(this) { - m_nbFiles = filesWithExtension(".txt", m_files, NB_FILES); - cleanRemovedBookRecord(); + m_filesNumber = filesWithExtension(".txt", m_files, k_maxFilesNumber); + cleanRemovedBookRecord(); } -int ListBookController::numberOfRows() const -{ - return m_nbFiles; +int ListBookController::numberOfRows() const { + return m_filesNumber; } -KDCoordinate ListBookController::cellHeight() -{ - return 50; +KDCoordinate ListBookController::cellHeight() { + return Metric::StoreRowHeight; } -HighlightCell * ListBookController::reusableCell(int index) -{ - return &m_cells[index]; +HighlightCell * ListBookController::reusableCell(int index) { + return &m_cells[index]; } - -int ListBookController::reusableCellCount() const -{ - return NB_CELLS; + +int ListBookController::reusableCellCount() const { + return k_cellsNumber; } -void ListBookController::willDisplayCellForIndex(HighlightCell * cell, int index) -{ - MessageTableCell* myTextCell = static_cast(cell); - MessageTextView* textView = static_cast(myTextCell->labelView()); - textView->setText(m_files[index].name); - myTextCell->setMessageFont(KDFont::LargeFont); +void ListBookController::willDisplayCellForIndex(HighlightCell * cell, int index) { + MessageTableCell* myTextCell = static_cast(cell); + MessageTextView* textView = static_cast(myTextCell->labelView()); + textView->setText(m_files[index].name); + myTextCell->setMessageFont(KDFont::LargeFont); //TODO set cell font at building ? } -void ListBookController::didBecomeFirstResponder() -{ - if (selectedRow() < 0) { - selectCellAtLocation(0, 0); - } - Container::activeApp()->setFirstResponder(&m_tableView); - if(m_nbFiles == 0) - { - Container::activeApp()->displayWarning(I18n::Message::NoFileToDisplay); - } +void ListBookController::didBecomeFirstResponder() { + if (selectedRow() < 0) { + selectCellAtLocation(0, 0); + } + Container::activeApp()->setFirstResponder(&m_tableView); } -bool ListBookController::handleEvent(Ion::Events::Event event) -{ - if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) - { - - m_readBookController.setBook(m_files[selectedRow()]); - static_cast(parentResponder())->push(&m_readBookController); - Container::activeApp()->setFirstResponder(&m_readBookController); - return true; - } +bool ListBookController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) + { + m_readBookController.setBook(m_files[selectedRow()]); + static_cast(parentResponder())->push(&m_readBookController); + Container::activeApp()->setFirstResponder(&m_readBookController); + return true; + } - return false; + return false; } -bool ListBookController::hasBook(const char* filename) const -{ - for(int i=0;inumberOfRecordsWithExtension("txt"); for(int i=0; irecordWithExtensionAtIndex("txt", i); - if(!hasBook(r.fullName())) - { - r.destroy(); - } + Ion::Storage::Record r = Ion::Storage::sharedStorage()->recordWithExtensionAtIndex("txt", i); + if(!hasBook(r.fullName())) + { + r.destroy(); } + } +} + +bool ListBookController::isEmpty() const { + return m_filesNumber == 0; +} + +I18n::Message ListBookController::emptyMessage() { + return I18n::Message::NoFileToDisplay; +} + +Responder * ListBookController::defaultController() { + return parentResponder(); } } \ No newline at end of file diff --git a/apps/reader/list_book_controller.h b/apps/reader/list_book_controller.h index 82eecf20717..ddbd3f553c2 100644 --- a/apps/reader/list_book_controller.h +++ b/apps/reader/list_book_controller.h @@ -6,35 +6,35 @@ #include "read_book_controller.h" -namespace reader +namespace Reader { -class ListBookController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource +class ListBookController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource, public AlternateEmptyViewDefaultDelegate { public: - ListBookController(Responder * parentResponder); - View* view() override; - - int numberOfRows() const override; - KDCoordinate cellHeight() override; - HighlightCell * reusableCell(int index) override; - int reusableCellCount() const override; - void willDisplayCellForIndex(HighlightCell * cell, int index) override; - void didBecomeFirstResponder() override; - bool handleEvent(Ion::Events::Event event) override; - bool hasBook(const char* filename) const; - void cleanRemovedBookRecord(); + ListBookController(Responder * parentResponder); + View* view() override; + + int numberOfRows() const override; + KDCoordinate cellHeight() override; + HighlightCell * reusableCell(int index) override; + int reusableCellCount() const override; + void willDisplayCellForIndex(HighlightCell * cell, int index) override; + void didBecomeFirstResponder() override; + bool handleEvent(Ion::Events::Event event) override; + bool hasBook(const char* filename) const; + void cleanRemovedBookRecord(); + bool isEmpty() const override; + I18n::Message emptyMessage() override; + Responder * defaultController() override; private: - SelectableTableView m_tableView; - - static const int NB_FILES = 20; - External::Archive::File m_files[NB_FILES]; - int m_nbFiles = 0; - - static const int NB_CELLS = 6; - MessageTableCell m_cells[NB_CELLS]; - - ReadBookController m_readBookController; + SelectableTableView m_tableView; + static const int k_maxFilesNumber = 20; + External::Archive::File m_files[k_maxFilesNumber]; + int m_filesNumber = 0; + static const int k_cellsNumber = 6; + MessageTableCellWithChevron m_cells[k_cellsNumber]; + ReadBookController m_readBookController; }; } diff --git a/apps/reader/read_book_controller.cpp b/apps/reader/read_book_controller.cpp index 7bdd9380e5f..144385918db 100644 --- a/apps/reader/read_book_controller.cpp +++ b/apps/reader/read_book_controller.cpp @@ -1,6 +1,6 @@ #include "read_book_controller.h" -namespace reader +namespace Reader { ReadBookController::ReadBookController(Responder * parentResponder) : @@ -8,60 +8,49 @@ ReadBookController::ReadBookController(Responder * parentResponder) : { } -View * ReadBookController::view() -{ +View * ReadBookController::view() { return &m_readerView; } -void ReadBookController::setBook(const External::Archive::File& file) -{ - m_file = &file; - loadPosition(); - m_readerView.setText(reinterpret_cast(file.data), file.dataLength); +void ReadBookController::setBook(const External::Archive::File& file) { + m_file = &file; + loadPosition(); + m_readerView.setText(reinterpret_cast(file.data), file.dataLength); } -bool ReadBookController::handleEvent(Ion::Events::Event event) -{ - if(event == Ion::Events::Down) - { - m_readerView.nextPage(); - return true; - } - if(event == Ion::Events::Up) - { - m_readerView.previousPage(); - return true; - } - if(event == Ion::Events::Back || event == Ion::Events::Home) - { - savePosition(); - } - return false; +bool ReadBookController::handleEvent(Ion::Events::Event event) { + if(event == Ion::Events::Down) { + m_readerView.nextPage(); + return true; + } + if(event == Ion::Events::Up) { + m_readerView.previousPage(); + return true; + } + if(event == Ion::Events::Back || event == Ion::Events::Home) { + savePosition(); + } + return false; } -void ReadBookController::savePosition() const -{ +void ReadBookController::savePosition() const { int pageOffset = m_readerView.getPageOffset(); Ion::Storage::Record::ErrorStatus status = Ion::Storage::sharedStorage()->createRecordWithFullName(m_file->name, &pageOffset, sizeof(pageOffset)); - if(Ion::Storage::Record::ErrorStatus::NameTaken == status) - { - Ion::Storage::Record::Data data; - data.buffer = &pageOffset; - data.size = sizeof(pageOffset); - status = Ion::Storage::sharedStorage()->recordNamed(m_file->name).setValue(data); + if(Ion::Storage::Record::ErrorStatus::NameTaken == status) { + Ion::Storage::Record::Data data; + data.buffer = &pageOffset; + data.size = sizeof(pageOffset); + status = Ion::Storage::sharedStorage()->recordNamed(m_file->name).setValue(data); } } -void ReadBookController::loadPosition() -{ +void ReadBookController::loadPosition() { Ion::Storage::Record r = Ion::Storage::sharedStorage()->recordNamed(m_file->name); - if(Ion::Storage::sharedStorage()->hasRecord(r)) - { + if(Ion::Storage::sharedStorage()->hasRecord(r)) { int pageOffset = *(static_cast(r.value().buffer)); m_readerView.setPageOffset(pageOffset); } - else - { + else { m_readerView.setPageOffset(0); } } diff --git a/apps/reader/read_book_controller.h b/apps/reader/read_book_controller.h index c4349be4e0d..77d4f57f03c 100644 --- a/apps/reader/read_book_controller.h +++ b/apps/reader/read_book_controller.h @@ -5,7 +5,7 @@ #include "apps/external/archive.h" #include "word_wrap_view.h" -namespace reader { +namespace Reader { class ReadBookController : public ViewController { public: diff --git a/apps/reader/utility.cpp b/apps/reader/utility.cpp index 2f82b61f38c..123fe392067 100644 --- a/apps/reader/utility.cpp +++ b/apps/reader/utility.cpp @@ -8,109 +8,100 @@ #include #endif -namespace reader +namespace Reader { -bool stringEndsWith(const char* str, const char* pattern) -{ - int strLength = strlen(str); - int patternLength = strlen(pattern); - if (patternLength > strLength) - return false; +bool stringEndsWith(const char* str, const char* pattern) { + int strLength = strlen(str); + int patternLength = strlen(pattern); + if (patternLength > strLength) { + return false; + } - const char* strIter = str + strlen(str); - const char* patternIter = pattern + strlen(pattern); + const char* strIter = str + strlen(str); + const char* patternIter = pattern + strlen(pattern); - while(*strIter == *patternIter) - { - if(patternIter == pattern) - return true; - strIter--; - patternIter--; + while(*strIter == *patternIter) { + if(patternIter == pattern) { + return true; } - return false; + strIter--; + patternIter--; + } + return false; } -void stringNCopy(char* dest, int max, const char* src, int len) -{ - while(len>0 && max >1 && *src) - { - *dest = *src; - dest++; - src++; - len--; - max--; - } - *dest=0; +void stringNCopy(char* dest, int max, const char* src, int len) { + while(len>0 && max >1 && *src) + { + *dest = *src; + dest++; + src++; + len--; + max--; + } + *dest=0; } #ifdef DEVICE -int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize) -{ - size_t nbTotalFiles = External::Archive::numberOfFiles(); - int nbFiles = 0; - for(size_t i=0; i < nbTotalFiles; ++i) +int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize) { + size_t nbTotalFiles = External::Archive::numberOfFiles(); + int nbFiles = 0; + for(size_t i=0; i < nbTotalFiles; ++i) + { + External::Archive::File file; + External::Archive::fileAtIndex(i, file); + if(stringEndsWith(file.name, ".txt")) { - External::Archive::File file; - External::Archive::fileAtIndex(i, file); - if(stringEndsWith(file.name, ".txt")) - { - files[nbFiles] = file; - nbFiles++; - if(nbFiles == filesSize) - break; - } + files[nbFiles] = file; + nbFiles++; + if(nbFiles == filesSize) + break; } - return nbFiles; + } + return nbFiles; } #else -static void fillFileData(External::Archive::File& file) -{ - file.data = nullptr; - file.dataLength = 0; +static void fillFileData(External::Archive::File& file) { + file.data = nullptr; + file.dataLength = 0; - struct stat info; - if (stat(file.name, &info) != 0) - { - return; - } - - unsigned char* content = new unsigned char[info.st_size]; - if (content == NULL) - { - return ; - } - FILE *fp = fopen(file.name, "rb"); - if (fp == NULL) - { - return ; - } - - fread(content, info.st_size, 1, fp); - fclose(fp); - file.data = content; - file.dataLength = info.st_size; + struct stat info; + if (stat(file.name, &info) != 0) { + return; + } + + unsigned char* content = new unsigned char[info.st_size]; + if (content == NULL) { + return; + } + FILE *fp = fopen(file.name, "rb"); + if (fp == NULL) { + return ; + } + + fread(content, info.st_size, 1, fp); + fclose(fp); + file.data = content; + file.dataLength = info.st_size; } -int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize) -{ +int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize) { dirent *file; DIR *d = opendir("."); int nb = 0; - if (d) - { - while ((file = readdir(d)) != NULL) - { - if(stringEndsWith(file->d_name, extension)) - { + if (d) { + while ((file = readdir(d)) != NULL) { + if(stringEndsWith(file->d_name, extension)) { files[nb].name = strdup(file->d_name);//will probably leak fillFileData(files[nb]); nb++; - if(nb == filesSize) - break; + if(nb == filesSize) { + break; + } } } closedir(d); diff --git a/apps/reader/utility.h b/apps/reader/utility.h index 6893ab95c1d..36e1d1b4bfe 100644 --- a/apps/reader/utility.h +++ b/apps/reader/utility.h @@ -3,11 +3,11 @@ #include -namespace reader +namespace Reader { bool stringEndsWith(const char* str, const char* end); -int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize) ; +int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize); void stringNCopy(char* dest, int max, const char* src, int len); } diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index 9e4cccd62cb..c022658cc48 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -2,155 +2,138 @@ #include "utility.h" -namespace reader +namespace Reader { -void WordWrapTextView::nextPage() -{ - if(m_nextPageOffset >= m_length) - return; - m_pageOffset = m_nextPageOffset; - markRectAsDirty(bounds()); +void WordWrapTextView::nextPage() { + if(m_nextPageOffset >= m_length) { + return; + } + m_pageOffset = m_nextPageOffset; + markRectAsDirty(bounds()); } -void WordWrapTextView::setText(const char* text, int length) -{ - PointerTextView::setText(text); - m_length = length; +void WordWrapTextView::setText(const char* text, int length) { + PointerTextView::setText(text); + m_length = length; } -void WordWrapTextView::previousPage() -{ - if(m_pageOffset <= 0) - return; - - const int spaceWidth = m_font->stringSize(" ").width(); +void WordWrapTextView::previousPage() { + if(m_pageOffset <= 0) { + return; + } + const int spaceWidth = m_font->stringSize(" ").width(); + + const char * endOfWord = text() + m_pageOffset; + const char * startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); + + KDPoint textPosition(m_frame.width() - margin, m_frame.height() - margin); + + while(startOfWord>=text()) { + endOfWord = UTF8Helper::EndOfWord(startOfWord); + KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + KDPoint previousTextPosition = KDPoint(textPosition.x()-textSize.width(), textPosition.y()); + + if(previousTextPosition.x() < margin) { + textPosition = KDPoint(m_frame.width() - margin, textPosition.y() - textSize.height()); + previousTextPosition = KDPoint(textPosition.x() - textSize.width(), textPosition.y()); + } + if(textPosition.y() - textSize.height() < margin) { + break; + } - const char * endOfWord = text() + m_pageOffset; - const char * startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); - KDPoint textPosition(m_frame.width() - margin, m_frame.height() - margin); - - while(startOfWord>=text()) - { - endOfWord = UTF8Helper::EndOfWord(startOfWord); - KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); - KDPoint previousTextPosition = KDPoint(textPosition.x()-textSize.width(), textPosition.y()); - - if(previousTextPosition.x() < margin) - { - textPosition = KDPoint(m_frame.width() - margin, textPosition.y() - textSize.height()); - previousTextPosition = KDPoint(textPosition.x() - textSize.width(), textPosition.y()); - } - if(textPosition.y() - textSize.height() < margin) - { - break; - } - - - --startOfWord; - while(startOfWord >= text() && *startOfWord == ' ') - { - previousTextPosition = KDPoint(previousTextPosition.x() - spaceWidth, previousTextPosition.y()); - --startOfWord; - } - if(previousTextPosition.x() < margin) - { - previousTextPosition = KDPoint(m_frame.width() - margin, previousTextPosition.y() - textSize.height()); - } - + --startOfWord; + while(startOfWord >= text() && *startOfWord == ' ') { + previousTextPosition = KDPoint(previousTextPosition.x() - spaceWidth, previousTextPosition.y()); + --startOfWord; + } + if(previousTextPosition.x() < margin) { + previousTextPosition = KDPoint(m_frame.width() - margin, previousTextPosition.y() - textSize.height()); + } - while(startOfWord >= text() && *startOfWord == '\n') - { - previousTextPosition = KDPoint(m_frame.width() - margin, previousTextPosition.y() - textSize.height()); - --startOfWord; - } - - if(previousTextPosition.y() - textSize.height() < margin) - { - break; - } - - textPosition = previousTextPosition; - endOfWord = startOfWord; - startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); + + while(startOfWord >= text() && *startOfWord == '\n') { + previousTextPosition = KDPoint(m_frame.width() - margin, previousTextPosition.y() - textSize.height()); + --startOfWord; + } + + if(previousTextPosition.y() - textSize.height() < margin) { + break; } - if(startOfWord == text()) - m_pageOffset = 0; - else - m_pageOffset = UTF8Helper::EndOfWord(startOfWord) - text() + 1; - markRectAsDirty(bounds()); + + textPosition = previousTextPosition; + endOfWord = startOfWord; + startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); + } + if(startOfWord == text()) { + m_pageOffset = 0; + } + else { + m_pageOffset = UTF8Helper::EndOfWord(startOfWord) - text() + 1; + } + markRectAsDirty(bounds()); } -void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const -{ - ctx->fillRect(KDRect(0, 0, bounds().width(), bounds().height()), m_backgroundColor); - - const char * endOfFile = text() + m_length; - const char * startOfWord = text() + m_pageOffset; - const char * endOfWord = UTF8Helper::EndOfWord(startOfWord); - KDPoint textPosition(margin, margin); - - const int wordMaxLength = 128; - char word[wordMaxLength]; - - const int spaceWidth = m_font->stringSize(" ").width(); - - while(startOfWord < endOfFile) - { - - KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); - KDPoint nextTextPosition = KDPoint(textPosition.x()+textSize.width(), textPosition.y()); - - if(nextTextPosition.x() > m_frame.width() - margin) - { - textPosition = KDPoint(margin, textPosition.y() + textSize.height()); - nextTextPosition = KDPoint(margin + textSize.width(), textPosition.y()); - } - if(textPosition.y() + textSize.height() > m_frame.height() - margin) - { - break; - } - - stringNCopy(word, wordMaxLength, startOfWord, endOfWord-startOfWord); - ctx->drawString(word, textPosition, m_font, m_textColor, m_backgroundColor); - - while(*endOfWord == ' ') - { - nextTextPosition = KDPoint(nextTextPosition.x() + spaceWidth, nextTextPosition.y()); - ++endOfWord; - } - if(nextTextPosition.x() > m_frame.width() - margin) - { - nextTextPosition = KDPoint(margin, nextTextPosition.y() + textSize.height()); - } - - while(*endOfWord == '\n') - { - nextTextPosition = KDPoint(margin, nextTextPosition.y() + textSize.height()); - ++endOfWord; - } - - if(nextTextPosition.y() + textSize.height() > m_frame.height() - margin) - { - break; - } - - textPosition = nextTextPosition; - startOfWord = endOfWord; - endOfWord = UTF8Helper::EndOfWord(startOfWord); +void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { + ctx->fillRect(KDRect(0, 0, bounds().width(), bounds().height()), m_backgroundColor); + + const char * endOfFile = text() + m_length; + const char * startOfWord = text() + m_pageOffset; + const char * endOfWord = UTF8Helper::EndOfWord(startOfWord); + KDPoint textPosition(margin, margin); + + const int wordMaxLength = 128; + char word[wordMaxLength]; + + const int spaceWidth = m_font->stringSize(" ").width(); + + while(startOfWord < endOfFile) { + + KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + KDPoint nextTextPosition = KDPoint(textPosition.x()+textSize.width(), textPosition.y()); + + if(nextTextPosition.x() > m_frame.width() - margin) { + textPosition = KDPoint(margin, textPosition.y() + textSize.height()); + nextTextPosition = KDPoint(margin + textSize.width(), textPosition.y()); + } + if(textPosition.y() + textSize.height() > m_frame.height() - margin) { + break; + } + + stringNCopy(word, wordMaxLength, startOfWord, endOfWord-startOfWord); + ctx->drawString(word, textPosition, m_font, m_textColor, m_backgroundColor); + + while(*endOfWord == ' ') { + nextTextPosition = KDPoint(nextTextPosition.x() + spaceWidth, nextTextPosition.y()); + ++endOfWord; } - m_nextPageOffset = startOfWord - text(); + if(nextTextPosition.x() > m_frame.width() - margin) { + nextTextPosition = KDPoint(margin, nextTextPosition.y() + textSize.height()); + } + + while(*endOfWord == '\n') { + nextTextPosition = KDPoint(margin, nextTextPosition.y() + textSize.height()); + ++endOfWord; + } + + if(nextTextPosition.y() + textSize.height() > m_frame.height() - margin) { + break; + } + + textPosition = nextTextPosition; + startOfWord = endOfWord; + endOfWord = UTF8Helper::EndOfWord(startOfWord); + } + m_nextPageOffset = startOfWord - text(); } -int WordWrapTextView::getPageOffset() const -{ - return m_pageOffset; +int WordWrapTextView::getPageOffset() const { + return m_pageOffset; } -void WordWrapTextView::setPageOffset(int o) -{ - m_pageOffset = o; +void WordWrapTextView::setPageOffset(int o) { + m_pageOffset = o; } } \ No newline at end of file diff --git a/apps/reader/word_wrap_view.h b/apps/reader/word_wrap_view.h index 4075ff4e229..0fc57e2950a 100644 --- a/apps/reader/word_wrap_view.h +++ b/apps/reader/word_wrap_view.h @@ -3,23 +3,23 @@ #include -namespace reader +namespace Reader { class WordWrapTextView : public PointerTextView { public: - void drawRect(KDContext * ctx, KDRect rect) const override; - void setText(const char*, int length); - void nextPage(); - void previousPage(); - int getPageOffset() const; - void setPageOffset(int o); + void drawRect(KDContext * ctx, KDRect rect) const override; + void setText(const char*, int length); + void nextPage(); + void previousPage(); + int getPageOffset() const; + void setPageOffset(int o); protected: - int m_pageOffset = 0; - mutable int m_nextPageOffset = 0; - int m_length = 0; + int m_pageOffset = 0; + mutable int m_nextPageOffset = 0; + int m_length = 0; - static const int margin = 10; + static const int margin = 10; }; } diff --git a/escher/src/alternate_empty_view_controller.cpp b/escher/src/alternate_empty_view_controller.cpp index be61f67f2e3..2d64ae2d79e 100644 --- a/escher/src/alternate_empty_view_controller.cpp +++ b/escher/src/alternate_empty_view_controller.cpp @@ -57,8 +57,7 @@ const char * AlternateEmptyViewController::title() { bool AlternateEmptyViewController::handleEvent(Ion::Events::Event event) { if (m_contentView.alternateEmptyViewDelegate()->isEmpty()) { if (event != Ion::Events::Home && event != Ion::Events::OnOff) { - m_contentView.alternateEmptyViewDelegate()->defaultController()->handleEvent(Ion::Events::Back); - return true; + return m_contentView.alternateEmptyViewDelegate()->defaultController()->handleEvent(Ion::Events::Back); } return false; } From 367f64dac8fc958bbe4fec135463b74e3d416046 Mon Sep 17 00:00:00 2001 From: Laury Date: Wed, 8 Sep 2021 21:42:39 +0200 Subject: [PATCH 034/355] [reader] Fixed a bug when going backwards --- apps/reader/word_wrap_view.cpp | 104 ++++++++++++++++++--------------- apps/reader/word_wrap_view.h | 3 +- 2 files changed, 58 insertions(+), 49 deletions(-) diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index c022658cc48..1ee80f82d1b 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -1,3 +1,4 @@ + #include "word_wrap_view.h" #include "utility.h" @@ -22,51 +23,55 @@ void WordWrapTextView::previousPage() { if(m_pageOffset <= 0) { return; } - const int spaceWidth = m_font->stringSize(" ").width(); - const char * endOfWord = text() + m_pageOffset; + const int charWidth = m_font->glyphSize().width(); + const int charHeight = m_font->glyphSize().height(); + + const char * endOfWord = text() + m_pageOffset - 1; const char * startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); - - KDPoint textPosition(m_frame.width() - margin, m_frame.height() - margin); + + KDPoint textEndPosition(m_frame.width() - k_margin, m_frame.height() - k_margin); while(startOfWord>=text()) { - endOfWord = UTF8Helper::EndOfWord(startOfWord); + startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); + endOfWord = UTF8Helper::EndOfWord(startOfWord); KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); - KDPoint previousTextPosition = KDPoint(textPosition.x()-textSize.width(), textPosition.y()); - - if(previousTextPosition.x() < margin) { - textPosition = KDPoint(m_frame.width() - margin, textPosition.y() - textSize.height()); - previousTextPosition = KDPoint(textPosition.x() - textSize.width(), textPosition.y()); + KDPoint textStartPosition = KDPoint(textEndPosition.x()-textSize.width(), textEndPosition.y()); + + if(textStartPosition.x() < k_margin) { + textEndPosition = KDPoint(m_frame.width() - k_margin, textEndPosition.y() - charHeight); + textStartPosition = KDPoint(textEndPosition.x() - textSize.width(), textEndPosition.y()); } - if(textPosition.y() - textSize.height() < margin) { + if(textEndPosition.y() - textSize.height() < k_margin) { break; } - --startOfWord; - while(startOfWord >= text() && *startOfWord == ' ') { - previousTextPosition = KDPoint(previousTextPosition.x() - spaceWidth, previousTextPosition.y()); + while(startOfWord >= text() && (*startOfWord == ' ' || *startOfWord == '\n')) { + if(*startOfWord == ' ') { + textStartPosition = KDPoint(textStartPosition.x() - charWidth, textStartPosition.y()); + } + else { + textStartPosition = KDPoint(m_frame.width() - k_margin, textStartPosition.y() - charHeight); + } --startOfWord; } - if(previousTextPosition.x() < margin) { - previousTextPosition = KDPoint(m_frame.width() - margin, previousTextPosition.y() - textSize.height()); - } - - - while(startOfWord >= text() && *startOfWord == '\n') { - previousTextPosition = KDPoint(m_frame.width() - margin, previousTextPosition.y() - textSize.height()); - --startOfWord; - } - if(previousTextPosition.y() - textSize.height() < margin) { + if(textStartPosition.y() < k_margin) { // If out of page, quit break; } - textPosition = previousTextPosition; + if(textStartPosition.y() != textEndPosition.y()) { // If line changed, x is at start of line + textStartPosition = KDPoint(m_frame.width() - k_margin, textStartPosition.y()); + } + if(textStartPosition.x() < k_margin) { // Go to line if left overflow + textStartPosition = KDPoint(m_frame.width() - k_margin, textStartPosition.y() - charHeight); + } + + textEndPosition = textStartPosition; endOfWord = startOfWord; - startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); } - if(startOfWord == text()) { + if(startOfWord + 1 == text()) { m_pageOffset = 0; } else { @@ -81,52 +86,57 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { const char * endOfFile = text() + m_length; const char * startOfWord = text() + m_pageOffset; const char * endOfWord = UTF8Helper::EndOfWord(startOfWord); - KDPoint textPosition(margin, margin); + KDPoint textPosition(k_margin, k_margin); const int wordMaxLength = 128; char word[wordMaxLength]; - const int spaceWidth = m_font->stringSize(" ").width(); + const int charWidth = m_font->glyphSize().width(); + const int charHeight = m_font->glyphSize().height(); while(startOfWord < endOfFile) { - KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); KDPoint nextTextPosition = KDPoint(textPosition.x()+textSize.width(), textPosition.y()); - if(nextTextPosition.x() > m_frame.width() - margin) { - textPosition = KDPoint(margin, textPosition.y() + textSize.height()); - nextTextPosition = KDPoint(margin + textSize.width(), textPosition.y()); + if(nextTextPosition.x() > m_frame.width() - k_margin) { // Right overflow + textPosition = KDPoint(k_margin, textPosition.y() + textSize.height()); + nextTextPosition = KDPoint(k_margin + textSize.width(), textPosition.y()); } - if(textPosition.y() + textSize.height() > m_frame.height() - margin) { + + if(textPosition.y() + textSize.height() > m_frame.height() - k_margin) { // Bottom overflow break; } stringNCopy(word, wordMaxLength, startOfWord, endOfWord-startOfWord); ctx->drawString(word, textPosition, m_font, m_textColor, m_backgroundColor); - while(*endOfWord == ' ') { - nextTextPosition = KDPoint(nextTextPosition.x() + spaceWidth, nextTextPosition.y()); + while(*endOfWord == ' ' || *endOfWord == '\n') { + if(*endOfWord == ' ') { + nextTextPosition = KDPoint(nextTextPosition.x() + charWidth, nextTextPosition.y()); + } + else { + nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + charHeight); + } ++endOfWord; } - if(nextTextPosition.x() > m_frame.width() - margin) { - nextTextPosition = KDPoint(margin, nextTextPosition.y() + textSize.height()); - } - while(*endOfWord == '\n') { - nextTextPosition = KDPoint(margin, nextTextPosition.y() + textSize.height()); - ++endOfWord; - } - - if(nextTextPosition.y() + textSize.height() > m_frame.height() - margin) { + if(nextTextPosition.y() + textSize.height() > m_frame.height() - k_margin) { // If out of page, quit break; } + if(nextTextPosition.y() != textPosition.y()) { // If line changed, x is at start of line + nextTextPosition = KDPoint(k_margin, nextTextPosition.y()); + } + if(nextTextPosition.x() > m_frame.width() - k_margin) { // Go to line if right overflow + nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + textSize.height()); + } textPosition = nextTextPosition; startOfWord = endOfWord; endOfWord = UTF8Helper::EndOfWord(startOfWord); } - m_nextPageOffset = startOfWord - text(); -} + + m_nextPageOffset = endOfWord - text(); +}; int WordWrapTextView::getPageOffset() const { return m_pageOffset; diff --git a/apps/reader/word_wrap_view.h b/apps/reader/word_wrap_view.h index 0fc57e2950a..0bce2f67e2e 100644 --- a/apps/reader/word_wrap_view.h +++ b/apps/reader/word_wrap_view.h @@ -18,8 +18,7 @@ class WordWrapTextView : public PointerTextView { int m_pageOffset = 0; mutable int m_nextPageOffset = 0; int m_length = 0; - - static const int margin = 10; + static const int k_margin = 10; }; } From 1487acbe3836861c93acc203abd6cffe2c2141e4 Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 11 Sep 2021 19:57:36 +0200 Subject: [PATCH 035/355] [git] Added .vs to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8645c08d24b..02d20574c5e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ epsilon.map .vscode .DS_Store .gradle +.vs From 48a0da8a95bfa18a03e41d4748759e5aada655a8 Mon Sep 17 00:00:00 2001 From: FaustinM Date: Sun, 12 Sep 2021 00:06:44 +0200 Subject: [PATCH 036/355] Add more python functions in ion module Ion.battery_level() return a int with the current battery level Ion.battery_ischarging() return a bool with the current battery charging state bugfix for upsilon --- .gitignore | 2 ++ apps/code/catalog.de.i18n | 2 ++ apps/code/catalog.en.i18n | 2 ++ apps/code/catalog.es.i18n | 2 ++ apps/code/catalog.fr.i18n | 2 ++ apps/code/catalog.hu.i18n | 2 ++ apps/code/catalog.it.i18n | 2 ++ apps/code/catalog.nl.i18n | 2 ++ apps/code/catalog.pt.i18n | 2 ++ apps/code/catalog.universal.i18n | 2 ++ apps/code/python_toolbox.cpp | 2 ++ apps/settings/main_controller.cpp | 3 +-- python/port/genhdr/qstrdefs.in.h | 2 ++ python/port/mod/ion/modion.cpp | 8 ++++++++ python/port/mod/ion/modion.h | 2 ++ python/port/mod/ion/modion_table.cpp | 12 ++++++++++++ python/port/mod/os/modos.cpp | 2 +- 17 files changed, 48 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 8645c08d24b..91981ffaa62 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ epsilon.map .vscode .DS_Store .gradle + +.idea/ diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index 86d0f083ae5..614dca9ea07 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -94,6 +94,8 @@ PythonIsInfinite = "Prüfen, ob x unendlich ist" PythonIsNaN = "Prüfen, ob x keine Zahl ist" PythonIsKeyDown = "Wahr, wenn die Taste k gedrückt ist" PythonBattery = "Return battery voltage" +PythonBatteryLevel = "Return battery level" +PythonBatteryIscharging = "Return if battery is charging" PythonKandinskyFunction = "Kandinsky-Modul Funktionspräfix" PythonLdexp = "Liefert x*(2**i), Inverse von frexp" PythonLength = "Länge eines Objekts" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index ce5a8b6e40d..86b357fb6e8 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -87,6 +87,8 @@ PythonIsFinite = "Check if x is finite" PythonIsInfinite = "Check if x is infinity" PythonIsKeyDown = "Return True if the k key is down" PythonBattery = "Return battery voltage" +PythonBatteryLevel = "Return battery level" +PythonBatteryIscharging = "Return if battery is charging" PythonIsNaN = "Check if x is a NaN" PythonKandinskyFunction = "kandinsky module function prefix" PythonLdexp = "Return x*(2**i), inverse of frexp" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index 95b8a9fa9b0..dbf21c95d7e 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -87,6 +87,8 @@ PythonIsFinite = "Check if x is finite" PythonIsInfinite = "Check if x is infinity" PythonIsKeyDown = "Return True if the k key is down" PythonBattery = "Return battery voltage" +PythonBatteryLevel = "Return battery level" +PythonBatteryIscharging = "Return if battery is charging" PythonIsNaN = "Check if x is a NaN" PythonKandinskyFunction = "kandinsky module function prefix" PythonLdexp = "Return x*(2**i), inverse of frexp" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index 3d7157bcda3..258a0a9404d 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -87,6 +87,8 @@ PythonIsFinite = "Teste si x est fini" PythonIsInfinite = "Teste si x est infini" PythonIsKeyDown = "Renvoie True si touche k enfoncée" PythonBattery = "Renvoie le voltage de la batterie" +PythonBatteryLevel = "Renvoie le niveau de la batterie" +PythonBatteryIscharging = "Chargement en cours" PythonIsNaN = "Teste si x est NaN" PythonKandinskyFunction = "Préfixe fonction module kandinsky" PythonLdexp = "Inverse de frexp : x*(2**i)" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index 49b4fcd0732..c0356ed025e 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -87,6 +87,8 @@ PythonIsFinite = "x véges-e" PythonIsInfinite = "x végtelen-e" PythonIsKeyDown = "True-t válaszol ha a k gomb le van nyomva" PythonBattery = "Return battery voltage" +PythonBatteryLevel = "Return battery level" +PythonBatteryIscharging = "Return if battery is charging" PythonIsNaN = "Ellenörizze hogy x nem NaN" PythonKandinskyFunction = "kandinsky modul funkció elötag" PythonLdexp = "frexp ellentéte : x*(2**i)" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index 2b3ff91e38e..a5e41368cd9 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -93,6 +93,8 @@ PythonIsFinite = "Testa se x è finito" PythonIsInfinite = "Testa se x est infinito" PythonIsKeyDown = "Restituisce True premendo tasto k" PythonBattery = "Return battery voltage" +PythonBatteryLevel = "Return battery level" +PythonBatteryIscharging = "Return if battery is charging" PythonIsNaN = "Testa se x è NaN" PythonKandinskyFunction = "Prefisso funzione modulo kandinsky" PythonLdexp = "Inversa di frexp : x*(2**i)" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index 721c4cbac23..994c1d31cfa 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -93,6 +93,8 @@ PythonIsFinite = "Controleer of x eindig is" PythonIsInfinite = "Controleer of x oneindig is" PythonIsKeyDown = "Geef True als k toets omlaag is" PythonBattery = "Return battery voltage" +PythonBatteryLevel = "Return battery level" +PythonBatteryIscharging = "Return if battery is charging" PythonIsNaN = "Controleer of x geen getal is" PythonKandinskyFunction = "kandinsky module voorvoegsel" PythonLdexp = "Geeft x*(2**i), inversie van frexp" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index da257f9744e..053942095a6 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -87,6 +87,8 @@ PythonIsFinite = "Verificar se x é finito" PythonIsInfinite = "Verificar se x é infinito" PythonIsKeyDown = "Devolve True se tecla k pressionada" PythonBattery = "Return battery voltage" +PythonBatteryLevel = "Return battery level" +PythonBatteryIscharging = "Return if battery is charging" PythonIsNaN = "Verificar se x é um NaN" PythonKandinskyFunction = "Prefixo da função do módulo kandinsky" PythonLdexp = "Devolve x*(2**i), inverso de frexp" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index 55269653a05..27f122ebbe2 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -158,6 +158,8 @@ PythonCommandKeyAns = "KEY_ANS" PythonCommandKeyExe = "KEY_EXE" PythonCommandIsKeyDown = "keydown(k)" PythonCommandBattery = "battery()" +PythonCommandBatteryLevel = "battery_level()" +PythonCommandBatteryIscharging = "battery_ischarging()" PythonCommandLdexp = "ldexp(x,i)" PythonCommandLength = "len(object)" PythonCommandLgamma = "lgamma(x)" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 31440884b13..5be82f7a90f 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -373,6 +373,8 @@ const ToolboxMessageTree IonModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIonFunction, I18n::Message::PythonIonFunction, false, I18n::Message::PythonCommandIonFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIsKeyDown, I18n::Message::PythonIsKeyDown), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBattery, I18n::Message::PythonBattery), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBatteryLevel, I18n::Message::PythonBatteryLevel), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBatteryIscharging, I18n::Message::PythonBatteryIscharging), ToolboxMessageTree::Leaf(I18n::Message::IonSelector, I18n::Message::IonSelector) }; diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index 320f765fb1f..97e0f315282 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -20,8 +20,7 @@ constexpr SettingsMessageTree s_modelMathOptionsChildren[6] = {SettingsMessageTr constexpr SettingsMessageTree s_modelFontChildren[2] = {SettingsMessageTree(I18n::Message::LargeFont), SettingsMessageTree(I18n::Message::SmallFont)}; constexpr SettingsMessageTree s_accessibilityChildren[6] = {SettingsMessageTree(I18n::Message::AccessibilityInvertColors), SettingsMessageTree(I18n::Message::AccessibilityMagnify),SettingsMessageTree(I18n::Message::AccessibilityGamma),SettingsMessageTree(I18n::Message::AccessibilityGammaRed),SettingsMessageTree(I18n::Message::AccessibilityGammaGreen),SettingsMessageTree(I18n::Message::AccessibilityGammaBlue)}; constexpr SettingsMessageTree s_contributorsChildren[23] = {SettingsMessageTree(I18n::Message::Developers), SettingsMessageTree(I18n::Message::QuentinGuidee), SettingsMessageTree(I18n::Message::JoachimLeFournis), SettingsMessageTree(I18n::Message::MaximeFriess), SettingsMessageTree(I18n::Message::JeanBaptisteBoric), SettingsMessageTree(I18n::Message::SandraSimmons), SettingsMessageTree(I18n::Message::David), SettingsMessageTree(I18n::Message::DamienNicolet), SettingsMessageTree(I18n::Message::EvannDreumont), SettingsMessageTree(I18n::Message::SzaboLevente), SettingsMessageTree(I18n::Message::VenceslasDuet), SettingsMessageTree(I18n::Message::CharlotteThomas), SettingsMessageTree(I18n::Message::AntoninLoubiere), SettingsMessageTree(I18n::Message::CyprienMejat), SettingsMessageTree(I18n::Message::BetaTesters), SettingsMessageTree(I18n::Message::TimeoArnouts), SettingsMessageTree(I18n::Message::JulieC), SettingsMessageTree(I18n::Message::LelahelHideux), SettingsMessageTree(I18n::Message::Madil), SettingsMessageTree(I18n::Message::HilaireLeRoux), SettingsMessageTree(I18n::Message::HectorNussbaumer), SettingsMessageTree(I18n::Message::RaphaelDyda), SettingsMessageTree(I18n::Message::ThibautC)}; -constexpr SettingsMessageTree s_modelAboutChildren[8] = {SettingsMessageTree(I18n::Message::Username), SettingsMessageTree(I18n::Message::SoftwareVersion), SettingsMessageTree(I18n::Message::UpsilonVersion), SettingsMessageTree(I18n::Message::MicroPythonVersion), SettingsMessageTree(I18n::Message::MemUse), SettingsMessageTree(I18n::Message::SerialNumber), SettingsMessageTree(I18n::Message::FccId), SettingsMessageTree(I18n::Message::Contributors, s_contributorsChildren)}; -constexpr SettingsMessageTree s_modelAboutChildren[9] = {SettingsMessageTree(I18n::Message::Username), SettingsMessageTree(I18n::Message::SoftwareVersion), SettingsMessageTree(I18n::Message::OmegaVersion), SettingsMessageTree(I18n::Message::MicroPythonVersion), SettingsMessageTree(I18n::Message::Battery), SettingsMessageTree(I18n::Message::MemUse), SettingsMessageTree(I18n::Message::SerialNumber), SettingsMessageTree(I18n::Message::FccId), SettingsMessageTree(I18n::Message::Contributors, s_contributorsChildren)}; +constexpr SettingsMessageTree s_modelAboutChildren[9] = {SettingsMessageTree(I18n::Message::Username), SettingsMessageTree(I18n::Message::SoftwareVersion), SettingsMessageTree(I18n::Message::UpsilonVersion), SettingsMessageTree(I18n::Message::MicroPythonVersion), SettingsMessageTree(I18n::Message::Battery), SettingsMessageTree(I18n::Message::MemUse), SettingsMessageTree(I18n::Message::SerialNumber), SettingsMessageTree(I18n::Message::FccId), SettingsMessageTree(I18n::Message::Contributors, s_contributorsChildren)}; MainController::MainController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate) : ViewController(parentResponder), diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index d5f4ec63f95..eed86bcce6b 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -368,6 +368,8 @@ Q(zip) Q(ion) Q(keydown) Q(battery) +Q(battery_level) +Q(battery_ischarging) Q(KEY_LEFT) Q(KEY_UP) Q(KEY_DOWN) diff --git a/python/port/mod/ion/modion.cpp b/python/port/mod/ion/modion.cpp index dc6ca004573..6a892a9f597 100644 --- a/python/port/mod/ion/modion.cpp +++ b/python/port/mod/ion/modion.cpp @@ -15,4 +15,12 @@ mp_obj_t modion_keyboard_keydown(mp_obj_t key_o) { mp_obj_t modion_battery() { return mp_obj_new_float(Ion::Battery::voltage()); +} + +mp_obj_t modion_battery_level(){ + return mp_obj_new_int(static_cast(Ion::Battery::level())); +} + +mp_obj_t modion_battery_ischarging(){ + return mp_obj_new_bool(Ion::Battery::isCharging()); } \ No newline at end of file diff --git a/python/port/mod/ion/modion.h b/python/port/mod/ion/modion.h index 4afa4035839..b4c99df027c 100644 --- a/python/port/mod/ion/modion.h +++ b/python/port/mod/ion/modion.h @@ -2,4 +2,6 @@ mp_obj_t modion_keyboard_keydown(mp_obj_t key_o); mp_obj_t modion_battery(); +mp_obj_t modion_battery_level(); +mp_obj_t modion_battery_ischarging(); extern const mp_obj_type_t file_type; \ No newline at end of file diff --git a/python/port/mod/ion/modion_table.cpp b/python/port/mod/ion/modion_table.cpp index 751d29443df..4b120d494d4 100644 --- a/python/port/mod/ion/modion_table.cpp +++ b/python/port/mod/ion/modion_table.cpp @@ -21,9 +21,21 @@ const mp_obj_fun_builtin_fixed_t modion_battery_obj = { {(mp_fun_0_t)modion_battery} }; +const mp_obj_fun_builtin_fixed_t modion_battery_level_obj = { + {&mp_type_fun_builtin_0}, + {(mp_fun_0_t)modion_battery_level} +}; + +const mp_obj_fun_builtin_fixed_t modion_battery_ischarging_obj = { + {&mp_type_fun_builtin_0}, + {(mp_fun_0_t)modion_battery_ischarging} +}; + extern "C" const mp_rom_map_elem_t modion_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ion) }, { MP_ROM_QSTR(MP_QSTR_battery), MP_ROM_PTR(&modion_battery_obj) }, + { MP_ROM_QSTR(MP_QSTR_battery_level), MP_ROM_PTR(&modion_battery_level_obj) }, + { MP_ROM_QSTR(MP_QSTR_battery_ischarging), MP_ROM_PTR(&modion_battery_ischarging_obj) }, { MP_ROM_QSTR(MP_QSTR_keydown), MP_ROM_PTR(&modion_keyboard_keydown_obj) }, { MP_ROM_QSTR(MP_QSTR_KEY_LEFT), MP_OBJ_NEW_SMALL_INT(Ion::Keyboard::Key::Left) }, { MP_ROM_QSTR(MP_QSTR_KEY_UP), MP_OBJ_NEW_SMALL_INT(Ion::Keyboard::Key::Up) }, diff --git a/python/port/mod/os/modos.cpp b/python/port/mod/os/modos.cpp index 3958aef61de..b404c96ac0f 100644 --- a/python/port/mod/os/modos.cpp +++ b/python/port/mod/os/modos.cpp @@ -116,4 +116,4 @@ mp_obj_t modos_listdir(void) { } return list; -} \ No newline at end of file +} From 2f3339dff7d334bd04f653fcee69cd32e44e0e80 Mon Sep 17 00:00:00 2001 From: FaustinM Date: Sun, 12 Sep 2021 00:15:55 +0200 Subject: [PATCH 037/355] Removed a modified file from pull request --- python/port/mod/os/modos.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/python/port/mod/os/modos.cpp b/python/port/mod/os/modos.cpp index b404c96ac0f..a8333006d33 100644 --- a/python/port/mod/os/modos.cpp +++ b/python/port/mod/os/modos.cpp @@ -117,3 +117,4 @@ mp_obj_t modos_listdir(void) { return list; } + From 2156ca7e42fc84194b5dfd253b48e9d4adcb9e9b Mon Sep 17 00:00:00 2001 From: Laury Date: Sun, 12 Sep 2021 21:28:07 +0200 Subject: [PATCH 038/355] [calculation] Added additional output about second degree polynomials --- apps/calculation/Makefile | 1 + .../second_degree_list_controller.cpp | 190 ++++++++++++++++++ .../second_degree_list_controller.h | 25 +++ apps/calculation/base.de.i18n | 8 +- apps/calculation/base.en.i18n | 6 + apps/calculation/base.es.i18n | 8 +- apps/calculation/base.fr.i18n | 8 +- apps/calculation/base.hu.i18n | 6 + apps/calculation/base.it.i18n | 8 +- apps/calculation/base.nl.i18n | 8 +- apps/calculation/base.pt.i18n | 8 +- apps/calculation/calculation.cpp | 4 + apps/calculation/calculation.h | 1 + apps/calculation/history_controller.cpp | 3 + apps/calculation/history_controller.h | 2 + poincare/src/power.cpp | 50 ++++- 16 files changed, 322 insertions(+), 14 deletions(-) create mode 100644 apps/calculation/additional_outputs/second_degree_list_controller.cpp create mode 100644 apps/calculation/additional_outputs/second_degree_list_controller.h diff --git a/apps/calculation/Makefile b/apps/calculation/Makefile index 43f8f92c3fd..a75d97dd85e 100644 --- a/apps/calculation/Makefile +++ b/apps/calculation/Makefile @@ -19,6 +19,7 @@ app_calculation_src = $(addprefix apps/calculation/,\ additional_outputs/list_controller.cpp \ additional_outputs/matrix_list_controller.cpp \ additional_outputs/rational_list_controller.cpp \ + additional_outputs/second_degree_list_controller.cpp \ additional_outputs/trigonometry_graph_cell.cpp \ additional_outputs/trigonometry_list_controller.cpp \ additional_outputs/trigonometry_model.cpp \ diff --git a/apps/calculation/additional_outputs/second_degree_list_controller.cpp b/apps/calculation/additional_outputs/second_degree_list_controller.cpp new file mode 100644 index 00000000000..e4cb2a0c7f0 --- /dev/null +++ b/apps/calculation/additional_outputs/second_degree_list_controller.cpp @@ -0,0 +1,190 @@ +#include "../app.h" +#include +#include "../../shared/poincare_helpers.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "second_degree_list_controller.h" + +using namespace Poincare; +using namespace Shared; + +namespace Calculation { + +void SecondDegreeListController::setExpression(Poincare::Expression e) { + ExpressionsListController::setExpression(e); + assert(!m_expression.isUninitialized()); + + Expression polynomialCoefficients[Expression::k_maxNumberOfPolynomialCoefficients]; + + Context * context = App::app()->localContext(); + Preferences * preferences = Preferences::sharedPreferences(); + + PoincareHelpers::Reduce(&m_expression, context, ExpressionNode::ReductionTarget::SystemForAnalysis); + + int degree = m_expression.getPolynomialReducedCoefficients( + "x", + polynomialCoefficients, + context, + Expression::UpdatedComplexFormatWithExpressionInput(preferences->complexFormat(), m_expression, context), + preferences->angleUnit(), + GlobalPreferences::sharedGlobalPreferences()->unitFormat(), + ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition); + + assert(degree == 2); + + Expression a = polynomialCoefficients[2]; + Expression b = polynomialCoefficients[1]; + Expression c = polynomialCoefficients[0]; + + bool aIsNotOne = !(a.type() == ExpressionNode::Type::Rational && static_cast(a).isOne()); + + Expression delta = Subtraction::Builder(Power::Builder(b.clone(), Rational::Builder(2)), Multiplication::Builder(Rational::Builder(4), a.clone(), c.clone())); + PoincareHelpers::Simplify(&delta, context, ExpressionNode::ReductionTarget::SystemForApproximation); + + Expression alpha = Opposite::Builder(Division::Builder(b.clone(), Multiplication::Builder(Rational::Builder(2), a.clone()))); + PoincareHelpers::Simplify(&alpha, context, ExpressionNode::ReductionTarget::User); + + Expression beta = Opposite::Builder(Division::Builder(delta.clone(), Multiplication::Builder(Rational::Builder(4), a.clone()))); + PoincareHelpers::Simplify(&beta, context, ExpressionNode::ReductionTarget::User); + + /* + * Because when can't apply reduce or simplify to keep the canonised + * we must beautify the expression manually + */ + + Expression canonised; + if (alpha.type() == ExpressionNode::Type::Opposite) { + canonised = Addition::Builder(Symbol::Builder("x", strlen("x")), alpha.childAtIndex(0).clone()); + } + else { + canonised = Subtraction::Builder(Symbol::Builder("x", strlen("x")), alpha.clone()); + } + canonised = Power::Builder(Parenthesis::Builder(canonised.clone()), Rational::Builder(2)); + if (aIsNotOne) { + canonised = Multiplication::Builder(a.clone(), canonised.clone()); + } + if (beta.type() == ExpressionNode::Type::Opposite) { + canonised = Subtraction::Builder(canonised.clone(), beta.childAtIndex(0).clone()); + } + else { + canonised = Addition::Builder(canonised.clone(), beta.clone()); + } + + + Expression x0; + Expression x1; + + + if (delta.nullStatus(context) == ExpressionNode::NullStatus::Null) { + // x0 = x1 = -b/(2a) + x0 = Division::Builder(Opposite::Builder(b), Multiplication::Builder(Rational::Builder(2), a)); + m_numberOfSolutions = 1; + PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::User); + } + else { + // x0 = (-b-sqrt(delta))/(2a) + x0 = Division::Builder(Subtraction::Builder(Opposite::Builder(b.clone()), SquareRoot::Builder(delta.clone())), Multiplication::Builder(Rational::Builder(2), a.clone())); + // x1 = (-b+sqrt(delta))/(2a) + x1 = Division::Builder(Addition::Builder(Opposite::Builder(b), SquareRoot::Builder(delta.clone())), Multiplication::Builder(Rational::Builder(2), a)); + m_numberOfSolutions = 2; + PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::User); + PoincareHelpers::Simplify(&x1, context, ExpressionNode::ReductionTarget::User); + if (x0.type() == ExpressionNode::Type::Unreal) { + assert(x1.type() == ExpressionNode::Type::Unreal); + m_numberOfSolutions = 0; + } + } + + Expression factorized; + + if (m_numberOfSolutions == 2) { + if (x0.type() == ExpressionNode::Type::Opposite) { + factorized = Parenthesis::Builder(Addition::Builder(Symbol::Builder("x", strlen("x")), x0.childAtIndex(0).clone())); + } + else { + factorized = Parenthesis::Builder(Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0.clone())); + } + + if (x1.type() == ExpressionNode::Type::Opposite) { + factorized = Multiplication::Builder(factorized.clone(), Parenthesis::Builder(Addition::Builder(Symbol::Builder("x", strlen("x")), x1.childAtIndex(0).clone()))); + } + else { + factorized = Multiplication::Builder(factorized.clone(), Parenthesis::Builder(Subtraction::Builder(Symbol::Builder("x", strlen("x")), x1.clone()))); + } + + if (aIsNotOne) { + factorized = Multiplication::Builder(a.clone(), factorized.clone()); + } + } + else if (m_numberOfSolutions == 1) { + if (x0.type() == ExpressionNode::Type::Opposite) { + factorized = Power::Builder(Parenthesis::Builder(Addition::Builder(Symbol::Builder("x", strlen("x")), x0.childAtIndex(0).clone())), Rational::Builder(2)); + } + else { + factorized = Power::Builder(Parenthesis::Builder(Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0.clone())), Rational::Builder(2)); + } + + if (aIsNotOne) { + factorized = Multiplication::Builder(a.clone(), factorized.clone()); + } + } + + PoincareHelpers::Simplify(&delta, context, ExpressionNode::ReductionTarget::User); + + m_layouts[0] = PoincareHelpers::CreateLayout(canonised); + if (m_numberOfSolutions > 0) { + m_layouts[1] = PoincareHelpers::CreateLayout(factorized); + m_layouts[2] = PoincareHelpers::CreateLayout(delta); + m_layouts[3] = PoincareHelpers::CreateLayout(x0); + if (m_numberOfSolutions > 1) { + m_layouts[4] = PoincareHelpers::CreateLayout(x1); + } + } + else { + m_layouts[1] = PoincareHelpers::CreateLayout(delta); + } +} + +I18n::Message SecondDegreeListController::messageAtIndex(int index) { + if (m_numberOfSolutions > 0) { + if (index == 0) { + return I18n::Message::CanonicalForm; + } + if (index == 1) { + return I18n::Message::FactorizedForm; + } + if (index == 2) { + return I18n::Message::Discriminant; + } + if (index == 3) { + if (m_numberOfSolutions == 1) { + return I18n::Message::OnlyRoot; + } + else { + return I18n::Message::FirstRoot; + } + } + return I18n::Message::SecondRoot; + } + else { + switch (index) { + case 0: + return I18n::Message::CanonicalForm; + default: + return I18n::Message::Discriminant; + } + } +} + +} diff --git a/apps/calculation/additional_outputs/second_degree_list_controller.h b/apps/calculation/additional_outputs/second_degree_list_controller.h new file mode 100644 index 00000000000..52300d97218 --- /dev/null +++ b/apps/calculation/additional_outputs/second_degree_list_controller.h @@ -0,0 +1,25 @@ +#ifndef CALCULATION_ADDITIONAL_OUTPUTS_SECOND_DEGREE_CONTROLLER_H +#define CALCULATION_ADDITIONAL_OUTPUTS_SECOND_DEGREE_CONTROLLER_H + +#include "expressions_list_controller.h" + +namespace Calculation { + +class SecondDegreeListController : public ExpressionsListController { +public: + SecondDegreeListController(EditExpressionController * editExpressionController) : + ExpressionsListController(editExpressionController), + m_numberOfSolutions(0) {} + + void setExpression(Poincare::Expression e) override; + +private: + I18n::Message messageAtIndex(int index) override; + int m_numberOfSolutions; +}; + +} + +#endif + + diff --git a/apps/calculation/base.de.i18n b/apps/calculation/base.de.i18n index 406f27e8499..5d12d9b2214 100644 --- a/apps/calculation/base.de.i18n +++ b/apps/calculation/base.de.i18n @@ -11,4 +11,10 @@ AdditionalDeterminant = "Determinante" AdditionalInverse = "Inverse" AdditionalRowEchelonForm = "Stufenform" AdditionalReducedRowEchelonForm = "Reduzierte Stufenform" -AdditionalTrace = "Spur" \ No newline at end of file +AdditionalTrace = "Spur" +CanonicalForm = "Kanonische Form" +FactorizedForm = "Factorisierte Form" +Discriminant = "Diskriminante" +OnlyRoot = "Wurzel" +FirstRoot = "Erste Wurzel" +SecondRoot = "Zweite Wurzel" \ No newline at end of file diff --git a/apps/calculation/base.en.i18n b/apps/calculation/base.en.i18n index 967c0905063..ada600b3c71 100644 --- a/apps/calculation/base.en.i18n +++ b/apps/calculation/base.en.i18n @@ -12,3 +12,9 @@ AdditionalInverse = "Inverse" AdditionalRowEchelonForm = "Row echelon form" AdditionalReducedRowEchelonForm = "Reduced row echelon form" AdditionalTrace = "Trace" +CanonicalForm = "Canonical form" +FactorizedForm = "Factorized form" +Discriminant = "Discriminant" +OnlyRoot = "Root" +FirstRoot = "First root" +SecondRoot = "Second root" \ No newline at end of file diff --git a/apps/calculation/base.es.i18n b/apps/calculation/base.es.i18n index 057481a0dbd..1b9ffde00cb 100644 --- a/apps/calculation/base.es.i18n +++ b/apps/calculation/base.es.i18n @@ -11,4 +11,10 @@ AdditionalDeterminant = "Determinante" AdditionalInverse = "Inversa" AdditionalRowEchelonForm = "Matriz escalonada" AdditionalReducedRowEchelonForm = "Matriz escalonada reducida" -AdditionalTrace = "Traza" \ No newline at end of file +AdditionalTrace = "Traza" +CanonicalForm = "Forma canónica" +FactorizedForm = "Forma factorizada" +Discriminant = "Discriminante" +OnlyRoot = "Raíz" +FirstRoot = "Primera raíz" +SecondRoot = "Segunda raíz" \ No newline at end of file diff --git a/apps/calculation/base.fr.i18n b/apps/calculation/base.fr.i18n index 0e155e29474..a8432eeb09d 100644 --- a/apps/calculation/base.fr.i18n +++ b/apps/calculation/base.fr.i18n @@ -11,4 +11,10 @@ AdditionalDeterminant = "Déterminant" AdditionalInverse = "Inverse" AdditionalRowEchelonForm = "Forme échelonnée" AdditionalReducedRowEchelonForm = "Forme échelonnée réduite" -AdditionalTrace = "Trace" \ No newline at end of file +AdditionalTrace = "Trace" +CanonicalForm = "Forme canonique" +FactorizedForm = "Forme factorisée" +Discriminant = "Discriminant" +OnlyRoot = "Racine" +FirstRoot = "Première racine" +SecondRoot = "Seconde racine" \ No newline at end of file diff --git a/apps/calculation/base.hu.i18n b/apps/calculation/base.hu.i18n index 39397adee2f..c798ac81719 100644 --- a/apps/calculation/base.hu.i18n +++ b/apps/calculation/base.hu.i18n @@ -12,3 +12,9 @@ AdditionalInverse = "inverz" AdditionalRowEchelonForm = "Sor echelon forma" AdditionalReducedRowEchelonForm = "Csökkentett sorú Echelon forma" AdditionalTrace = "Nyomkövetés" +CanonicalForm = "Kanonikus forma" +FactorizedForm = "Factorizált forma" +Discriminant = "Discriminant" +OnlyRoot = "Gyökér" +FirstRoot = "Első gyökér" +SecondRoot = "Második gyökér" \ No newline at end of file diff --git a/apps/calculation/base.it.i18n b/apps/calculation/base.it.i18n index ca41028937e..c3961427173 100644 --- a/apps/calculation/base.it.i18n +++ b/apps/calculation/base.it.i18n @@ -11,4 +11,10 @@ AdditionalDeterminant = "Determinante" AdditionalInverse = "Inversa" AdditionalRowEchelonForm = "Matrice a scalini" AdditionalReducedRowEchelonForm = "Matrice ridotta a scalini" -AdditionalTrace = "Traccia" \ No newline at end of file +AdditionalTrace = "Traccia" +CanonicalForm = "Forma canonica" +FactorizedForm = "Forma fattorizzata" +Discriminant = "Discriminante" +OnlyRoot = "Radice" +FirstRoot = "Prima radice" +SecondRoot = "Seconda radice" \ No newline at end of file diff --git a/apps/calculation/base.nl.i18n b/apps/calculation/base.nl.i18n index 51e412cb40c..b156ddd985d 100644 --- a/apps/calculation/base.nl.i18n +++ b/apps/calculation/base.nl.i18n @@ -11,4 +11,10 @@ AdditionalDeterminant = "Determinant" AdditionalInverse = "Inverse" AdditionalRowEchelonForm = "Echelonvorm" AdditionalReducedRowEchelonForm = "Gereduceerde echelonvorm" -AdditionalTrace = "Spoor" \ No newline at end of file +AdditionalTrace = "Spoor" +CanonicalForm = "Canonische vorm" +FactorizedForm = "Factorized vorm" +Discriminant = "Discriminant" +OnlyRoot = "Wortel" +FirstRoot = "Eerste wortel" +SecondRoot = "Tweede wortel" \ No newline at end of file diff --git a/apps/calculation/base.pt.i18n b/apps/calculation/base.pt.i18n index 941363a04b7..ae4c401babe 100644 --- a/apps/calculation/base.pt.i18n +++ b/apps/calculation/base.pt.i18n @@ -11,4 +11,10 @@ AdditionalDeterminant = "Determinante" AdditionalInverse = "Matriz inversa" AdditionalRowEchelonForm = "Matriz escalonada" AdditionalReducedRowEchelonForm = "Matriz escalonada reduzida" -AdditionalTrace = "Traço" \ No newline at end of file +AdditionalTrace = "Traço" +CanonicalForm = "Forma canónica" +FactorizedForm = "Factorized form" +Discriminant = "Discriminante" +OnlyRoot = "Raiz" +FirstRoot = "Primeira raiz" +SecondRoot = "Segunda raiz" \ No newline at end of file diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index c293cbbe841..fb02fea6f75 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -272,6 +273,9 @@ Calculation::AdditionalInformationType Calculation::additionalInformationType(Co if (o.type() == ExpressionNode::Type::Matrix) { return AdditionalInformationType::Matrix; } + if (o.polynomialDegree(context, "x") == 2) { + return AdditionalInformationType::SecondDegree; + } return AdditionalInformationType::None; } diff --git a/apps/calculation/calculation.h b/apps/calculation/calculation.h index be7f87c9bcc..4c026cbc42b 100644 --- a/apps/calculation/calculation.h +++ b/apps/calculation/calculation.h @@ -39,6 +39,7 @@ friend CalculationStore; None = 0, Integer, Rational, + SecondDegree, Trigonometry, Unit, Matrix, diff --git a/apps/calculation/history_controller.cpp b/apps/calculation/history_controller.cpp index 6d51e7862f9..9a868730973 100644 --- a/apps/calculation/history_controller.cpp +++ b/apps/calculation/history_controller.cpp @@ -16,6 +16,7 @@ HistoryController::HistoryController(EditExpressionController * editExpressionCo m_complexController(editExpressionController), m_integerController(editExpressionController), m_rationalController(editExpressionController), + m_secondDegreeController(editExpressionController), m_trigonometryController(editExpressionController), m_unitController(editExpressionController), m_matrixController(editExpressionController) @@ -100,6 +101,8 @@ bool HistoryController::handleEvent(Ion::Events::Event event) { Expression e = calculationAtIndex(focusRow)->exactOutput(); if (additionalInfoType == Calculation::AdditionalInformationType::Complex) { vc = &m_complexController; + } else if (additionalInfoType == Calculation::AdditionalInformationType::SecondDegree) { + vc = &m_secondDegreeController; } else if (additionalInfoType == Calculation::AdditionalInformationType::Trigonometry) { vc = &m_trigonometryController; // Find which of the input or output is the cosine/sine diff --git a/apps/calculation/history_controller.h b/apps/calculation/history_controller.h index e289eb5fc43..b919e823c79 100644 --- a/apps/calculation/history_controller.h +++ b/apps/calculation/history_controller.h @@ -8,6 +8,7 @@ #include "additional_outputs/complex_list_controller.h" #include "additional_outputs/integer_list_controller.h" #include "additional_outputs/rational_list_controller.h" +#include "additional_outputs/second_degree_list_controller.h" #include "additional_outputs/trigonometry_list_controller.h" #include "additional_outputs/unit_list_controller.h" #include "additional_outputs/matrix_list_controller.h" @@ -47,6 +48,7 @@ class HistoryController : public ViewController, public ListViewDataSource, publ ComplexListController m_complexController; IntegerListController m_integerController; RationalListController m_rationalController; + SecondDegreeListController m_secondDegreeController; TrigonometryListController m_trigonometryController; UnitListController m_unitController; MatrixListController m_matrixController; diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index b5f8de0214b..68d80943de3 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -68,18 +69,33 @@ int PowerNode::polynomialDegree(Context * context, const char * symbolName) cons if (op0Deg < 0) { return -1; } + Integer i; + bool foundInteger = false; if (childAtIndex(1)->type() == ExpressionNode::Type::Rational) { RationalNode * r = static_cast(childAtIndex(1)); if (!r->isInteger() || Number(r).sign() == Sign::Negative) { return -1; } - Integer numeratorInt = r->signedNumerator(); - if (!numeratorInt.isExtractable()) { + foundInteger = true; + i = r->signedNumerator(); + } + else if(childAtIndex(1)->type() == ExpressionNode::Type::BasedInteger) { + BasedIntegerNode * b = static_cast(childAtIndex(1)); + if (Number(b).sign() == Sign::Negative) { + return -1; + } + foundInteger = true; + i = b->integer(); + } + + if (foundInteger) { + if (!i.isExtractable()) { return -1; } - op0Deg *= numeratorInt.extractedInt(); + op0Deg *= i.extractedInt(); return op0Deg; } + return -1; } @@ -356,10 +372,9 @@ int Power::getPolynomialCoefficients(Context * context, const char * symbolName, } /* Here we only consider the case x^4 as privateGetPolynomialCoefficients is * supposed to be called after reducing the expression. */ - if (childAtIndex(0).type() == ExpressionNode::Type::Symbol - && strcmp(childAtIndex(0).convert().name(), symbolName) == 0 - && childAtIndex(1).type() == ExpressionNode::Type::Rational) - { + int n; + bool foundInteger = false; + if (childAtIndex(1).type() == ExpressionNode::Type::Rational) { Rational r = childAtIndex(1).convert(); if (!r.isInteger() || r.sign() == ExpressionNode::Sign::Negative) { return -1; @@ -368,7 +383,26 @@ int Power::getPolynomialCoefficients(Context * context, const char * symbolName, if (!num.isExtractable()) { return -1; } - int n = num.extractedInt(); + foundInteger = true; + n = num.extractedInt(); + } + else if(childAtIndex(1).type() == ExpressionNode::Type::BasedInteger) { + BasedInteger b = childAtIndex(1).convert(); + if (Number(b).sign() == ExpressionNode::Sign::Negative) { + return -1; + } + foundInteger = true; + Integer i = b.integer(); + if (!i.isExtractable()) { + return -1; + } + n = i.extractedInt(); + } + + if (childAtIndex(0).type() == ExpressionNode::Type::Symbol + && strcmp(childAtIndex(0).convert().name(), symbolName) == 0 + && foundInteger) + { if (n <= k_maxPolynomialDegree) { for (int i = 0; i < n; i++) { coefficients[i] = Rational::Builder(0); From f6379d16fdd04bd8f4bad3348ddd5c579c4057d2 Mon Sep 17 00:00:00 2001 From: Hugo Berthet-Rambaud Date: Mon, 13 Sep 2021 07:54:37 +0200 Subject: [PATCH 039/355] Update README.md just changed cd Omega to cd Upsilon in binpack commmands --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ceb8bd5bc46..17418fc97e9 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Also, you can change the number of processes that run in parallel during the bui ```bash git clone --recursive https://github.com/Lauryy06/Upsilon.git -cd Omega +cd Upsilon git checkout omega-master make clean make OMEGA_USERNAME="{Your name, max 15 characters}" -j4 From fe973b0535445ce905e773eafda8fc59a9de7944 Mon Sep 17 00:00:00 2001 From: Hugo Berthet-Rambaud Date: Mon, 13 Sep 2021 08:38:53 +0200 Subject: [PATCH 040/355] update README.md changed cd Omega to cd Upsilon --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 17418fc97e9..d55a0692261 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ These can be used to distribute Upsilon (so that it can be flashed by anyone wit ```bash git clone --recursive https://github.com/Lauryy06/Upsilon.git -cd Omega +cd Upsilon git checkout omega-master make clean make MODEL=n0100 OMEGA_USERNAME="" -j8 From 959efdc995138da900f93a4588e6d7ff3836f677 Mon Sep 17 00:00:00 2001 From: "Yaya.Cout" Date: Fri, 17 Sep 2021 19:28:53 +0200 Subject: [PATCH 041/355] [reader] Disabled reader app on N100 --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index f83a5922f12..4f54b9284d5 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,10 @@ include build/toolchain.$(TOOLCHAIN).mak include build/variants.mak include build/helpers.mk +ifeq (${MODEL}, n0100) + EPSILON_APPS := $(filter-out reader,$(EPSILON_APPS)) +endif + ifeq (${MODEL}, n0110) apps_list = ${EPSILON_APPS} else From a9fc3b785070e5ff17918546922a050da49c080e Mon Sep 17 00:00:00 2001 From: lemoustachu Date: Sat, 18 Sep 2021 08:41:08 +0200 Subject: [PATCH 042/355] update tutorial on readme.md to build Upsilon --- README.md | 176 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 142 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index d55a0692261..d14ffb7f198 100644 --- a/README.md +++ b/README.md @@ -26,65 +26,170 @@ Upsilon is a fork of Omega, an user-made OS that runs on the Numworks calculator ### Manual -*As of today, only the manual installation is available.* +*As of today, only the manual installation is available. You can refer to this [website](https://www.numworks.com/resources/engineering/software/build/) for the first step if you get errors.* -First of all, follow **step 1** [here](https://www.numworks.com/resources/engineering/software/build/). Then: + + +### 1. Install SDK + +
- Model n0100 +1.1 Linux -(note: you can change the `EPSILON_I18N=en` flag to `fr`, `nl`, `pt`, `it`, `de`, `es` or `hu`). +
+ Debian or Ubuntu + +
+ + You just have to install dependencies by running these command with superuser privileges in a Terminal: + + ```bash + apt-get install build-essential git imagemagick libx11-dev libxext-dev libfreetype6-dev libpng-dev libjpeg-dev pkg-config gcc-arm-none-eabi binutils-arm-none-eabi + ``` + + And there you can go to step 2! + +
+ +
+ +
+ Fedora + +
+ + First install basics dev tools. + + ```bash + dnf install make automake gcc gcc-c++ kernel-devel + ``` + + Then install required packages. + + ```bash + dnf install git ImageMagick libX11-devel libXext-devel freetype-devel libpng-devel libjpeg-devel pkg-config + ``` + + Then, install GCC cross compiler for ARM. + + ```bash + dnf install arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++ + ``` + +
+ +
+
+ +
+1.2 Mac + +It's recommended to use [Homebrew](https://brew.sh/). Once it's installed, just run: +```bash +brew install numworks/tap/epsilon-sdk +``` +and it will install all dependencies. + +
+ +And there you can go to step 2! + + +
+ +
+1.3 Windows + +[Msys2](https://www.msys2.org/) environment is recommended to get most of the required tools on Windows easily. It's where you'll paste all the commands of this tutorial. Once it's installed, paste these commands into the Msys2 terminal. + +```bash +pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-pkg-config mingw-w64-x86_64-libusb git make python +echo "export PATH=/mingw64/bin:$PATH" >> .bashrc +``` + +Next, you'll need to install the [GCC toolchain for ARM](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads). When prompted for an install location, choose `C:\msys64\home\User\gcc-arm\`. You'll then need to add this folder to your $PATH. Just enter: + +```bash +echo "export PATH=$PATH:$HOME/gcc-arm/bin" >> .bashrc +``` +Just restart and you can go to step 2! +
+ +
+
+ +### 2. Set up repo + +
+ +Clone repo and use 'upsilon-dev' branch by pasting these two commands: ```bash git clone --recursive https://github.com/Lauryy06/Upsilon.git cd Upsilon -git checkout omega-master +git checkout upsilon-dev +``` +
+
+ +### 3.Choose the target + +
+ +
+ Model n0100 + +(note: you can change the `EPSILON_I18N=en` flag to `fr`, `nl`, `pt`, `it`, `de`, `es` or `hu`). + +```bash make MODEL=n0100 clean make MODEL=n0100 EPSILON_I18N=en OMEGA_USERNAME="{Your name, max 15 characters}" -j4 +``` + +Now, run either: + +```bash make MODEL=n0100 epsilon_flash ``` +to directly flash the calculator after pressing simultaneously `reset` and `6` buttons and pluging in. -Important: Don't forget the `--recursive` tag, because Omega relies on submodules. -Also, you can change the number of processes that run in parallel during the build by changing the value of the `-j` flag. +
+ +or: + +```bash +make MODEL=n0100 OMEGA_USERNAME="" binpack -j4 +``` +to make binpack wich you can flash to the caculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). Binpacks are a great way to share a custom build of Upsilon to friends.
Model n0110 + ```bash -git clone --recursive https://github.com/Lauryy06/Upsilon.git -cd Upsilon -git checkout omega-master make clean make OMEGA_USERNAME="{Your name, max 15 characters}" -j4 +``` + +Now, run either: + +```bash make epsilon_flash ``` +to directly flash the calculator after pressing simultaneously `reset` and `6` buttons and pluging in. -Important: Don't forget the `--recursive` tag, because Omega relies on submodules. -Also, you can change the number of processes that run in parallel during the build by changing the value of the `-j` flag. - -
+
-
- Bin files - -These can be used to distribute Upsilon (so that it can be flashed by anyone with [Webdfu_Numworks](https://ti-planet.github.io/webdfu_numworks/)). +or: ```bash -git clone --recursive https://github.com/Lauryy06/Upsilon.git -cd Upsilon -git checkout omega-master -make clean -make MODEL=n0100 OMEGA_USERNAME="" -j8 -make MODEL=n0100 OMEGA_USERNAME="" binpack -j8 -make OMEGA_USERNAME="" -j8 -make OMEGA_USERNAME="" binpack -j8 +make OMEGA_USERNAME="" binpack -j4 ``` +to make binpack wich you can flash to the caculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0110/). Binpacks are a great way to share a custom build of Upsilon to friends. -Important: Don't forget the `--recursive` tag, because Upsilon relies on submodules. -Also, you can change the number of processes that run in parallel during the build by changing the value of the `-j` flag. -
@@ -103,17 +208,12 @@ source emsdk_env.sh Then, compile Upsilon : ```bash -git clone --recursive https://github.com/Lauryy06/Upsilon.git -cd Upsilon -git checkout omega-master make clean make PLATFORM=simulator TARGET=web OMEGA_USERNAME="{Your name, max 15 characters}" -j4 ``` The simulator is now in `output/release/simulator/web/simulator.zip` -Important: Don't forget the `--recursive` tag, because Upsilon relies on submodules. -Also, you can change the number of processes that run in parallel during the build by changing the value of the `-j` flag.
@@ -136,6 +236,14 @@ You can then put epsilon.3dsx on a SD card to run it from the HBC or use 3dslink +
+ +Important: Don't forget the `--recursive` tag, because Upsilon relies on submodules. +Also, you can change the number of processes that run in parallel during the build by changing the value of the `-j` flag. +Don't forget to put your pseudo instead of `{your pseudo, max 15 char}`. If you don't want one, just remove the `OMEGA_USERNAME=""` argument. + +
+ If you need help, you can join our Discord server here : https://discord.gg/Q9buEMduXG

Omega Banner Discord

From 3318780d7dbe0efee402af909f6e83d1e4d6d26e Mon Sep 17 00:00:00 2001 From: lemoustachu Date: Sat, 18 Sep 2021 08:44:40 +0200 Subject: [PATCH 043/355] update README.md by adding beaks to clearify the code --- README.md | 68 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index d14ffb7f198..d62375dab88 100644 --- a/README.md +++ b/README.md @@ -37,54 +37,62 @@ Upsilon is a fork of Omega, an user-made OS that runs on the Numworks calculator
1.1 Linux -
- Debian or Ubuntu +
-
- You just have to install dependencies by running these command with superuser privileges in a Terminal: +
+Debian or Ubuntu - ```bash - apt-get install build-essential git imagemagick libx11-dev libxext-dev libfreetype6-dev libpng-dev libjpeg-dev pkg-config gcc-arm-none-eabi binutils-arm-none-eabi - ``` +
- And there you can go to step 2! +You just have to install dependencies by running these command with superuser privileges in a Terminal: -
+```bash +apt-get install build-essential git imagemagick libx11-dev libxext-dev libfreetype6-dev libpng-dev libjpeg-dev pkg-config gcc-arm-none-eabi binutils-arm-none-eabi +``` -
+And there you can go to step 2! -
- Fedora +
-
+
- First install basics dev tools. +
+Fedora - ```bash - dnf install make automake gcc gcc-c++ kernel-devel - ``` +
- Then install required packages. +First install basics dev tools. - ```bash - dnf install git ImageMagick libX11-devel libXext-devel freetype-devel libpng-devel libjpeg-devel pkg-config - ``` +```bash +dnf install make automake gcc gcc-c++ kernel-devel +``` - Then, install GCC cross compiler for ARM. +Then install required packages. - ```bash - dnf install arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++ - ``` +```bash +dnf install git ImageMagick libX11-devel libXext-devel freetype-devel libpng-devel libjpeg-devel pkg-config +``` -
+Then, install GCC cross compiler for ARM. + +```bash +dnf install arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++ +``` + +
+ +
+ +
-
1.2 Mac +
+ It's recommended to use [Homebrew](https://brew.sh/). Once it's installed, just run: ```bash brew install numworks/tap/epsilon-sdk @@ -95,12 +103,17 @@ and it will install all dependencies. And there you can go to step 2! +
+ +
1.3 Windows +
+ [Msys2](https://www.msys2.org/) environment is recommended to get most of the required tools on Windows easily. It's where you'll paste all the commands of this tutorial. Once it's installed, paste these commands into the Msys2 terminal. ```bash @@ -116,7 +129,6 @@ echo "export PATH=$PATH:$HOME/gcc-arm/bin" >> .bashrc Just restart and you can go to step 2!
-

### 2. Set up repo From 62f43f393d168388b9f771be08c041cddf5751e5 Mon Sep 17 00:00:00 2001 From: lemoustachu Date: Sat, 18 Sep 2021 08:47:49 +0200 Subject: [PATCH 044/355] removed abusive double
in readme --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index d62375dab88..1668be6ec8a 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,6 @@ Just restart and you can go to step 2! ### 2. Set up repo -
Clone repo and use 'upsilon-dev' branch by pasting these two commands: @@ -143,11 +142,10 @@ cd Upsilon git checkout upsilon-dev ```
-
+ ### 3.Choose the target -
Model n0100 From 62e5b3e1359323227d0d2d21dfed33ea1783a1b4 Mon Sep 17 00:00:00 2001 From: lemoustachu Date: Sat, 18 Sep 2021 08:49:47 +0200 Subject: [PATCH 045/355] update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 1668be6ec8a..1280e6b4f11 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,9 @@ Upsilon is a fork of Omega, an user-made OS that runs on the Numworks calculator, which brings many features to it, but was discontinued because of a policy change from Numworks. Upsilon is for the people who want to see a future for user-made OSes for Numworks, even after the closure and archiving of Omega. + + + ### Some new features - Enhancements for the Kandinsky python module - A support for wallpapers @@ -22,6 +25,8 @@ Upsilon is a fork of Omega, an user-made OS that runs on the Numworks calculator - Improvements for the Periodic table application - *And everything that has been added to Omega before its termination!* [See Omega's changelog here](https://github.com/Omega-Numworks/Omega/wiki/Changelog) | [Main Omega features + screenshots](https://github.com/Omega-Numworks/Omega/wiki/Main-features). +
+ ## Installation ### Manual From 7c02c9e72f044456d89b2c5b792ad7c8f41e88f1 Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 18 Sep 2021 12:23:32 +0200 Subject: [PATCH 046/355] [reader] Binded python font choice to reader files --- apps/reader/word_wrap_view.cpp | 2 +- apps/reader/word_wrap_view.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index 1ee80f82d1b..5cbc012b17d 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -1,6 +1,6 @@ +#include #include "word_wrap_view.h" - #include "utility.h" namespace Reader diff --git a/apps/reader/word_wrap_view.h b/apps/reader/word_wrap_view.h index 0bce2f67e2e..cd17964aee1 100644 --- a/apps/reader/word_wrap_view.h +++ b/apps/reader/word_wrap_view.h @@ -1,6 +1,7 @@ #ifndef _WORD_WRAP_VIEW_H_ #define _WORD_WRAP_VIEW_H_ +#include #include namespace Reader @@ -8,6 +9,7 @@ namespace Reader class WordWrapTextView : public PointerTextView { public: + WordWrapTextView() : PointerTextView(GlobalPreferences::sharedGlobalPreferences()->font()) {}; void drawRect(KDContext * ctx, KDRect rect) const override; void setText(const char*, int length); void nextPage(); From 0bed38576ac0b9bbca6a1a8a691383206ddba116 Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 18 Sep 2021 21:07:18 +0200 Subject: [PATCH 047/355] [reader] Fix bug when changing page --- apps/reader/word_wrap_view.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index 5cbc012b17d..5ed46b1d42e 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -69,7 +69,7 @@ void WordWrapTextView::previousPage() { } textEndPosition = textStartPosition; - endOfWord = startOfWord; + endOfWord = startOfWord + 1; } if(startOfWord + 1 == text()) { m_pageOffset = 0; @@ -120,6 +120,10 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { ++endOfWord; } + //We must change value of startOfWord now to avoid having + //two times the same word if the break below is used + startOfWord = endOfWord; + if(nextTextPosition.y() + textSize.height() > m_frame.height() - k_margin) { // If out of page, quit break; } @@ -131,11 +135,10 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { } textPosition = nextTextPosition; - startOfWord = endOfWord; endOfWord = UTF8Helper::EndOfWord(startOfWord); } - m_nextPageOffset = endOfWord - text(); + m_nextPageOffset = startOfWord - text(); }; int WordWrapTextView::getPageOffset() const { From 37c7b85da8d31e9c495789f71047a47c0079a386 Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 18 Sep 2021 21:15:28 +0200 Subject: [PATCH 048/355] [reader] Removed unused include --- apps/reader/word_wrap_view.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index 5ed46b1d42e..c8cb708cf6a 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -1,5 +1,4 @@ -#include #include "word_wrap_view.h" #include "utility.h" From 05227184a12114201f57472f0c77e841b0bdb6d7 Mon Sep 17 00:00:00 2001 From: Lauryy06 <80424145+Lauryy06@users.noreply.github.com> Date: Sat, 18 Sep 2021 21:47:27 +0200 Subject: [PATCH 049/355] [readme] Add a space line 152 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1280e6b4f11..6e906d0d842 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ git checkout upsilon-dev
-### 3.Choose the target +### 3. Choose the target
From 0b564510e7ddf7333b3d4e10c100ad03ac30b06a Mon Sep 17 00:00:00 2001 From: "Yaya.Cout" Date: Mon, 20 Sep 2021 17:21:41 +0200 Subject: [PATCH 050/355] [code/time] Add time in python toolbox --- apps/code/catalog.de.i18n | 6 ++++++ apps/code/catalog.en.i18n | 6 ++++++ apps/code/catalog.es.i18n | 6 ++++++ apps/code/catalog.fr.i18n | 6 ++++++ apps/code/catalog.hu.i18n | 6 ++++++ apps/code/catalog.it.i18n | 6 ++++++ apps/code/catalog.nl.i18n | 6 ++++++ apps/code/catalog.pt.i18n | 6 ++++++ apps/code/catalog.universal.i18n | 6 ++++++ apps/code/python_toolbox.cpp | 8 +++++++- 10 files changed, 61 insertions(+), 1 deletion(-) diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index 614dca9ea07..54bfd169786 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -142,6 +142,12 @@ PythonShow = "Figur anzeigen" PythonSin = "Sinus" PythonSinh = "Hyperbolischer Sinus" PythonSleep = "Ausführung aussetzen für t Sekunden" +PythonLocalTime = "Zeit in Tupel umwandeln" +PythonMktime = "Tupel in Zeit umwandeln" +PythonTime = "Abrufen des aktuellen Zeitstempels" +PythonSetLocaltime = "Zeit aus einem Tupel von localtime()" +PythonRTCmode = "Aktuellen RTC-Modus abrufen" +PythonSetRTCmode = "RTC-Modus festlegen" PythonSort = "Die Liste sortieren" PythonSqrt = "Quadratwurzel" PythonSum = "Summe der Elemente einer Liste" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index 86b357fb6e8..93d28a08434 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -136,6 +136,12 @@ PythonShow = "Display the figure" PythonSin = "Sine" PythonSinh = "Hyperbolic sine" PythonSleep = "Suspend the execution for t seconds" +PythonLocalTime = "Convert time into tuple" +PythonMktime = "Convert tuple into time" +PythonTime = "Get the current timestamp" +PythonSetLocaltime = "Set time from a tuple of localtime()" +PythonRTCmode = "Get current RTC mode" +PythonSetRTCmode = "Set RTC mode" PythonSort = "Sort the list" PythonSqrt = "Square root" PythonSum = "Sum the items of a list" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index dbf21c95d7e..e80957719ee 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -136,6 +136,12 @@ PythonShow = "Display the figure" PythonSin = "Sine" PythonSinh = "Hyperbolic sine" PythonSleep = "Suspend the execution for t seconds" +PythonLocalTime = "Convertir el tiempo en tupla" +PythonMktime = "Convertir tupla en tiempo" +PythonTime = "Obtener la marca de tiempo actual" +PythonSetLocaltime = "Establecer tiempo desde una tupla de localtime()" +PythonRTCmode = "Obtener el modo RTC actual" +PythonSetRTCmode = "Establecer modo RTC" PythonSort = "Sort the list" PythonSqrt = "Square root" PythonSum = "Sum the items of a list" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index 258a0a9404d..196a353a486 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -136,6 +136,12 @@ PythonShow = "Affiche la figure" PythonSin = "Sinus" PythonSinh = "Sinus hyperbolique" PythonSleep = "Suspend l'exécution t secondes" +PythonLocalTime = "Convertir le temps en tuple" +PythonMktime = "Convertir le tuple en temps" +PythonTime = "Obtenir l'horodatage actuel" +PythonSetLocaltime = "Définir l'heure à partir d'un tuple de localtime()" +PythonRTCmode = "Obtenir le mode RTC actuel" +PythonSetRTCmode = "Définir le mode RTC" PythonSort = "Trie la liste" PythonSqrt = "Racine carrée" PythonSum = "Somme des éléments de la liste" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index c0356ed025e..02b0e584443 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -136,6 +136,12 @@ PythonShow = "Mutassa az ábrát" PythonSin = "Szinusz" PythonSinh = "Hiperbolikus szinusz" PythonSleep = "t másodpercre meg állitani a programmot" +PythonLocalTime = "Idő konvertálása csomóvá" +PythonMktime = "A tuple konvertálása az időben" +PythonTime = "Az aktuális időbélyeg letöltése" +PythonSetLocaltime = "Állítsd be az időt egy tufából a localtime()" +PythonRTCmode = "Aktuális RTC mód" +PythonSetRTCmode = "RTC mód beállítása" PythonSort = "A listát rendezni" PythonSqrt = "Négyzetgyök" PythonSum = "Összeadni a lista elemeit" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index a5e41368cd9..2d78b4e749d 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -142,6 +142,12 @@ PythonShow = "Mostra la figura" PythonSin = "Seno" PythonSinh = "Seno iperbolico" PythonSleep = "Sospende l'esecuzione t secondi" +PythonLocalTime = "Converti il tempo in tuple" +PythonMktime = "Converti tuple in tempo" +PythonTime = "Ottieni il timestamp corrente" +PythonSetLocaltime = "Imposta il tempo da una tupla di localtime()" +PythonRTCmode = "Ottieni la modalità RTC corrente" +PythonSetRTCmode = "Imposta modalità RTC" PythonSort = "Ordina l'elenco" PythonSqrt = "Radice quadrata" PythonSum = "Somma degli elementi della lista" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index 994c1d31cfa..b89fb09fc0a 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -142,6 +142,12 @@ PythonShow = "Figuur weergeven" PythonSin= "Sinus" PythonSinh = "Sinus hyperbolicus" PythonSleep = "Stel executie voor t seconden uit" +PythonLocalTime = "Zet tijd om in tuple" +PythonMktime = "Tuple omzetten in tijd" +PythonTime = "Haal de huidige tijdstempel" +PythonSetLocaltime = "Stel de tijd in vanaf een tuple van localtime()" +PythonRTCmode = "Huidige RTC-modus kregen" +PythonSetRTCmode = "RTC-modus instellen" PythonSort = "Sorteer de lijst" PythonSqrt = "Vierkantswortel" PythonSum = "Sommeer de items van een lijst" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index 053942095a6..7437f45d5aa 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -136,6 +136,12 @@ PythonShow = "Mostrar a figura" PythonSin = "Seno" PythonSinh = "Seno hiperbólico" PythonSleep = "Suspender a execução por t segundos" +PythonLocalTime = "Convert o tempo em tupla" +PythonMktime = "Convert tuple em tempo" +PythonTime = "Obter o estamp de tempo atual" +PythonSetLocaltime = "Definir tempo a partir de uma tupla de localtime()" +PythonRTCmode = "Obter o modo RTC atual" +PythonSetRTCmode = "Definir o modo RTC" PythonSort = "Ordenar a lista" PythonSqrt = "Raiz quadrada" PythonSum = "Soma dos itens da lista" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index 27f122ebbe2..2dd492f00a0 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -339,6 +339,12 @@ PythonCommandSin = "sin(x)" PythonCommandSinComplex = "sin(z)" PythonCommandSinh = "sinh(x)" PythonCommandSleep = "sleep(t)" +PythonCommandLocalTime = "localtime([timestamp])" +PythonCommandMktime = "mktime(tm)" +PythonCommandTime = "time()" +PythonCommandSetLocaltime = "setlocaltime(tm)" +PythonCommandRTCmode = "rtcmode()" +PythonCommandSetRTCmode = "setrtcmode(mode)" PythonCommandSort = "list.sort()" PythonCommandSortWithoutArg = ".sort()" PythonCommandSorted = "sorted(list)" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 5be82f7a90f..34056c8a4ea 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -382,8 +382,14 @@ const ToolboxMessageTree TimeModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportTime, I18n::Message::PythonImportTime, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromTime, I18n::Message::PythonImportTime, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandTimeFunction, I18n::Message::PythonTimeFunction, false, I18n::Message::PythonCommandTimeFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandTime, I18n::Message::PythonTime), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandMonotonic, I18n::Message::PythonMonotonic, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSleep, I18n::Message::PythonSleep) + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSleep, I18n::Message::PythonSleep), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandLocalTime, I18n::Message::PythonLocalTime), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandMktime, I18n::Message::PythonMktime), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSetLocaltime, I18n::Message::PythonSetLocaltime), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRTCmode, I18n::Message::PythonRTCmode), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSetRTCmode, I18n::Message::PythonSetRTCmode), }; const ToolboxMessageTree OsModuleChildren[] = { From 2a66fbb75dc921198ae013fd9e3df1ffeed7eb96 Mon Sep 17 00:00:00 2001 From: ArtichOwO Date: Thu, 23 Sep 2021 00:29:09 +0200 Subject: [PATCH 051/355] [apps/usb] Fixed "white bg" text on usb prompt --- apps/apps_container.cpp | 2 +- apps/apps_container.h | 3 ++- apps/apps_container_prompt_beta.cpp | 12 +++++++++++- apps/apps_container_prompt_none.cpp | 3 ++- apps/apps_container_prompt_update.cpp | 10 +++++++++- apps/on_boarding/prompt_controller.cpp | 8 ++++---- apps/on_boarding/prompt_controller.h | 4 ++-- apps/shared/message_view.cpp | 5 +++-- apps/shared/message_view.h | 2 +- apps/usb/usb_connected_controller.cpp | 14 ++++++++++++-- 10 files changed, 47 insertions(+), 16 deletions(-) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index acf3feb383d..3963d8c7dc6 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -29,7 +29,7 @@ AppsContainer::AppsContainer() : m_globalContext(), m_variableBoxController(), m_examPopUpController(this), - m_promptController(k_promptMessages, k_promptColors, k_promptNumberOfMessages), + m_promptController(k_promptMessages, k_promptFGColors, k_promptBGColors, k_promptNumberOfMessages), m_batteryTimer(), m_suspendTimer(), m_backlightDimmingTimer(), diff --git a/apps/apps_container.h b/apps/apps_container.h index ab945b6efec..f52d50b6c50 100644 --- a/apps/apps_container.h +++ b/apps/apps_container.h @@ -66,7 +66,8 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag bool updateAlphaLock(); static I18n::Message k_promptMessages[]; - static KDColor k_promptColors[]; + static KDColor k_promptFGColors[]; + static KDColor k_promptBGColors[]; static int k_promptNumberOfMessages; AppsWindow m_window; EmptyBatteryWindow m_emptyBatteryWindow; diff --git a/apps/apps_container_prompt_beta.cpp b/apps/apps_container_prompt_beta.cpp index 613acc99d9a..729062c6ac5 100644 --- a/apps/apps_container_prompt_beta.cpp +++ b/apps/apps_container_prompt_beta.cpp @@ -10,7 +10,7 @@ I18n::Message AppsContainer::k_promptMessages[] = { I18n::Message::BetaVersionMessage5, I18n::Message::BetaVersionMessage6}; -KDColor AppsContainer::k_promptColors[] = { +KDColor AppsContainer::k_promptFGColors[] = { KDColorBlack, KDColorBlack, KDColorBlack, @@ -20,4 +20,14 @@ KDColor AppsContainer::k_promptColors[] = { KDColorBlack, Palette::AccentText}; +KDColor AppsContainer::k_promptBGColors[] = { + KDColorWhite, + KDColorWhite, + KDColorWhite, + KDColorWhite, + KDColorWhite, + KDColorWhite, + KDColorWhite, + KDColorWhite}; + int AppsContainer::k_promptNumberOfMessages = 8; diff --git a/apps/apps_container_prompt_none.cpp b/apps/apps_container_prompt_none.cpp index 65556d670f8..e750df2b65d 100644 --- a/apps/apps_container_prompt_none.cpp +++ b/apps/apps_container_prompt_none.cpp @@ -2,7 +2,8 @@ I18n::Message AppsContainer::k_promptMessages[] = {}; -KDColor AppsContainer::k_promptColors[] = {}; +KDColor AppsContainer::k_promptFGColors[] = {}; +KDColor AppsContainer::k_promptBGColors[] = {}; int AppsContainer::k_promptNumberOfMessages = 0; diff --git a/apps/apps_container_prompt_update.cpp b/apps/apps_container_prompt_update.cpp index e837b592f27..59c93f0f478 100644 --- a/apps/apps_container_prompt_update.cpp +++ b/apps/apps_container_prompt_update.cpp @@ -8,7 +8,7 @@ I18n::Message AppsContainer::k_promptMessages[] = { I18n::Message::UpdateMessage3, I18n::Message::UpdateMessage4}; -KDColor AppsContainer::k_promptColors[] = { +KDColor AppsContainer::k_promptFGColors[] = { KDColorBlack, KDColorBlack, KDColorBlack, @@ -16,4 +16,12 @@ KDColor AppsContainer::k_promptColors[] = { KDColorBlack, Palette::AccentText}; +KDColor AppsContainer::k_promptBGColors[] = { + KDColorWhite, + KDColorWhite, + KDColorWhite, + KDColorBlack, + KDColorWhite, + KDColorBlack}; + int AppsContainer::k_promptNumberOfMessages = 6; diff --git a/apps/on_boarding/prompt_controller.cpp b/apps/on_boarding/prompt_controller.cpp index e938a43a0c8..773a7704198 100644 --- a/apps/on_boarding/prompt_controller.cpp +++ b/apps/on_boarding/prompt_controller.cpp @@ -4,8 +4,8 @@ namespace OnBoarding { -PromptController::MessageViewWithSkip::MessageViewWithSkip(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages) : - MessageView(messages, colors, numberOfMessages), +PromptController::MessageViewWithSkip::MessageViewWithSkip(I18n::Message * messages, KDColor * fgcolors, KDColor * bgcolors, uint8_t numberOfMessages) : + MessageView(messages, fgcolors, bgcolors, numberOfMessages), m_skipView(KDFont::SmallFont, I18n::Message::Skip, 1.0f, 0.5f), m_okView() { @@ -42,9 +42,9 @@ void PromptController::MessageViewWithSkip::layoutSubviews(bool force) { m_okView.setFrame(KDRect(width - okSize.width()-k_okMargin, height-okSize.height()-k_okMargin, okSize), force); } -PromptController::PromptController(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages) : +PromptController::PromptController(I18n::Message * messages, KDColor * fgcolors, KDColor * bgcolors, uint8_t numberOfMessages) : ViewController(nullptr), - m_messageViewWithSkip(messages, colors, numberOfMessages) + m_messageViewWithSkip(messages, fgcolors, bgcolors, numberOfMessages) { } diff --git a/apps/on_boarding/prompt_controller.h b/apps/on_boarding/prompt_controller.h index b0ce7b61554..eb251868bf5 100644 --- a/apps/on_boarding/prompt_controller.h +++ b/apps/on_boarding/prompt_controller.h @@ -10,13 +10,13 @@ namespace OnBoarding { class PromptController : public ViewController { public: - PromptController(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages); + PromptController(I18n::Message * messages, KDColor * fgcolors, KDColor * bgcolors, uint8_t numberOfMessages); View * view() override { return &m_messageViewWithSkip; } bool handleEvent(Ion::Events::Event event) override; private: class MessageViewWithSkip : public MessageView { public: - MessageViewWithSkip(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages); + MessageViewWithSkip(I18n::Message * messages, KDColor * fgcolors, KDColor * bgcolors, uint8_t numberOfMessages); protected: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; diff --git a/apps/shared/message_view.cpp b/apps/shared/message_view.cpp index 3bc257ab0f4..05975da8e95 100644 --- a/apps/shared/message_view.cpp +++ b/apps/shared/message_view.cpp @@ -1,13 +1,14 @@ #include "message_view.h" #include -MessageView::MessageView(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages) { +MessageView::MessageView(I18n::Message * messages, KDColor * fgcolors, KDColor * bgcolors, uint8_t numberOfMessages) { m_numberOfMessages = numberOfMessages < k_maxNumberOfMessages ? numberOfMessages : k_maxNumberOfMessages; for (uint8_t i = 0; i < m_numberOfMessages; i++) { m_messageTextViews[i].setFont(i == 0 ? KDFont::LargeFont : KDFont::SmallFont); m_messageTextViews[i].setMessage(messages[i]); m_messageTextViews[i].setAlignment(0.5f, 0.5f); - m_messageTextViews[i].setTextColor(colors[i]); + m_messageTextViews[i].setTextColor(fgcolors[i]); + m_messageTextViews[i].setBackgroundColor(bgcolors[i]); } } diff --git a/apps/shared/message_view.h b/apps/shared/message_view.h index 41fb9e648a1..f6b773e95dd 100644 --- a/apps/shared/message_view.h +++ b/apps/shared/message_view.h @@ -5,7 +5,7 @@ class MessageView : public View { public: - MessageView(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages); + MessageView(I18n::Message * messages, KDColor * fgcolors, KDColor * bgcolors, uint8_t numberOfMessages); void drawRect(KDContext * ctx, KDRect rect) const override; protected: int numberOfSubviews() const override { return m_numberOfMessages; } diff --git a/apps/usb/usb_connected_controller.cpp b/apps/usb/usb_connected_controller.cpp index 5dafb088c84..71a354c4a13 100644 --- a/apps/usb/usb_connected_controller.cpp +++ b/apps/usb/usb_connected_controller.cpp @@ -13,7 +13,7 @@ static I18n::Message sUSBConnectedMessages[] = { I18n::Message::ConnectedMessage5, I18n::Message::ConnectedMessage6}; -static KDColor sUSBConnectedColors[] = { +static KDColor sUSBConnectedFGColors[] = { Palette::PrimaryText, Palette::PrimaryText, Palette::PrimaryText, @@ -23,9 +23,19 @@ static KDColor sUSBConnectedColors[] = { Palette::PrimaryText, Palette::PrimaryText}; +static KDColor sUSBConnectedBGColors[] = { + Palette::BackgroundHard, + Palette::BackgroundHard, + Palette::BackgroundHard, + Palette::BackgroundHard, + Palette::BackgroundHard, + Palette::BackgroundHard, + Palette::BackgroundHard, + Palette::BackgroundHard}; + USBConnectedController::USBConnectedController() : ViewController(nullptr), - m_messageView(sUSBConnectedMessages, sUSBConnectedColors, 8) + m_messageView(sUSBConnectedMessages, sUSBConnectedFGColors, sUSBConnectedBGColors, 8) { } From db0ae2f7a0506060763f075463ea266b3313f1df Mon Sep 17 00:00:00 2001 From: ArtichOwO Date: Thu, 23 Sep 2021 00:43:17 +0200 Subject: [PATCH 052/355] [apps/shared] Fixed localization text bg --- apps/shared/localization_controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/shared/localization_controller.cpp b/apps/shared/localization_controller.cpp index f518999b3ef..b300e84f228 100644 --- a/apps/shared/localization_controller.cpp +++ b/apps/shared/localization_controller.cpp @@ -18,7 +18,7 @@ LocalizationController::ContentView::ContentView(LocalizationController * contro assert(k_numberOfCountryWarningLines == 2); // textMessages is not overflowed I18n::Message textMessages[k_numberOfCountryWarningLines] = {I18n::Message::CountryWarning1, I18n::Message::CountryWarning2}; for (int i = 0; i < k_numberOfCountryWarningLines; i++) { - m_countryWarningLines[i].setBackgroundColor(Palette::BackgroundHard); + m_countryWarningLines[i].setBackgroundColor(Palette::BackgroundApps); m_countryWarningLines[i].setFont(KDFont::SmallFont); m_countryWarningLines[i].setAlignment(0.5f, 0.5f); m_countryWarningLines[i].setMessage(textMessages[i]); From 48598296cfbe68a5fe2f46ff8d8535f5a1de2f07 Mon Sep 17 00:00:00 2001 From: Mino1289 Date: Fri, 24 Sep 2021 11:09:23 +0200 Subject: [PATCH 053/355] [apps/code] Increase of number of variables loaded in the variable box --- apps/code/variable_box_controller.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/code/variable_box_controller.h b/apps/code/variable_box_controller.h index f8968b41b7f..484f5b82900 100644 --- a/apps/code/variable_box_controller.h +++ b/apps/code/variable_box_controller.h @@ -43,9 +43,9 @@ class VariableBoxController : public AlternateEmptyNestedMenuController { private: constexpr static size_t k_maxNumberOfDisplayedItems = (Ion::Display::Height - Metric::TitleBarHeight - Metric::PopUpTopMargin) / ScriptNodeCell::k_simpleItemHeight + 2; // +2 if the cells are cropped on top and at the bottom - constexpr static size_t k_maxScriptNodesCount = 32; // Chosen without particular reasons + constexpr static size_t k_maxScriptNodesCount = 128; // Chosen without particular reasons (Number of variables in the variables box) constexpr static int k_totalBuiltinNodesCount = 107; - constexpr static uint8_t k_scriptOriginsCount = 3; + constexpr static uint8_t k_scriptOriginsCount = 8; // Number of scripts loaded in the variable box constexpr static uint8_t k_subtitleCellType = NodeCellType; // We don't care as it is not selectable constexpr static uint8_t k_itemCellType = LeafCellType; // So that upper class NestedMenuController knows it's a leaf constexpr static KDCoordinate k_subtitleRowHeight = 23; From 3a573b120421de51b8502ece3292b66d6a42fd68 Mon Sep 17 00:00:00 2001 From: Mino1289 Date: Fri, 24 Sep 2021 11:15:56 +0200 Subject: [PATCH 054/355] [apps/code] fix typo --- apps/code/variable_box_controller.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/code/variable_box_controller.h b/apps/code/variable_box_controller.h index 484f5b82900..5e9b44bbbc5 100644 --- a/apps/code/variable_box_controller.h +++ b/apps/code/variable_box_controller.h @@ -43,7 +43,7 @@ class VariableBoxController : public AlternateEmptyNestedMenuController { private: constexpr static size_t k_maxNumberOfDisplayedItems = (Ion::Display::Height - Metric::TitleBarHeight - Metric::PopUpTopMargin) / ScriptNodeCell::k_simpleItemHeight + 2; // +2 if the cells are cropped on top and at the bottom - constexpr static size_t k_maxScriptNodesCount = 128; // Chosen without particular reasons (Number of variables in the variables box) + constexpr static size_t k_maxScriptNodesCount = 128; // Chosen without particular reasons (Number of functions in the variables box) constexpr static int k_totalBuiltinNodesCount = 107; constexpr static uint8_t k_scriptOriginsCount = 8; // Number of scripts loaded in the variable box constexpr static uint8_t k_subtitleCellType = NodeCellType; // We don't care as it is not selectable From 400d03ebdb7d88145051a0dad150bee31fdd64ce Mon Sep 17 00:00:00 2001 From: Lauryy06 <80424145+Lauryy06@users.noreply.github.com> Date: Sat, 25 Sep 2021 10:25:57 +0200 Subject: [PATCH 055/355] [readme] Addition of a "useful links" section --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6e906d0d842..7fb77586ae6 100644 --- a/README.md +++ b/README.md @@ -263,11 +263,14 @@ If you need help, you can join our Discord server here : https://discord.gg/Q9bu

Omega Banner Discord

--- +## Useful links +* [Upsilon external (to install additional apps and wallpapers)](https://lauryy06.github.io/Upsilon-External/) +* [Ulab documentation](https://micropython-ulab.readthedocs.io/en/latest/) ## Contributing To contribute, please refer to [Omega's Wiki](https://github.com/Omega-Numworks/Omega/wiki/Contributing), the same rules apply here. - + ## Related repositories Here are the main links toward Omega's different websites and repositories, that have been used for the creation of Upsilon. From 15c96cefa6a078e7fdc97d2fe23813b52abb7fb1 Mon Sep 17 00:00:00 2001 From: Lauryy06 <80424145+Lauryy06@users.noreply.github.com> Date: Sat, 25 Sep 2021 10:29:29 +0200 Subject: [PATCH 056/355] [readme] Update french readme --- README.fr.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.fr.md b/README.fr.md index 00ab5f10810..92dc638fd7d 100644 --- a/README.fr.md +++ b/README.fr.md @@ -143,6 +143,10 @@ Si vous avez besoin d'aide, n'hésitez pas à rejoindre notre serveur discord :

Omega Banner Discord

--- +## Liens utiles +* [Upsilon external (pour installer des applications supplémentaires et des fonds d'écran)](https://lauryy06.github.io/Upsilon-External/) +* [Documentation d'ulab](https://micropython-ulab.readthedocs.io/en/latest/) + ## Contribution Pour contribuer, merci de lire le [Wiki d'Omega](https://github.com/Omega-Numworks/Omega/wiki/Contributing), les mêmes règles s'appliquent ici. From efda5614257aceee25b08322cfd42fe1c41d98b4 Mon Sep 17 00:00:00 2001 From: ArtichautCosmique Date: Sat, 25 Sep 2021 18:55:43 +0200 Subject: [PATCH 057/355] [ion/device/flasher] Pimp my flasher (light & verbose) (#27) --- ion/src/device/flasher/display_light.cpp | 8 +- ion/src/device/flasher/display_verbose.cpp | 90 ++++++++++++---------- ion/src/device/flasher/image.h | 11 +++ ion/src/device/shared/ram.ld | 6 +- 4 files changed, 72 insertions(+), 43 deletions(-) create mode 100644 ion/src/device/flasher/image.h diff --git a/ion/src/device/flasher/display_light.cpp b/ion/src/device/flasher/display_light.cpp index 1549c576335..ec5ba11886a 100644 --- a/ion/src/device/flasher/display_light.cpp +++ b/ion/src/device/flasher/display_light.cpp @@ -1,10 +1,16 @@ #include +#include namespace Flasher { namespace Display { void init() { - Ion::Display::pushRectUniform(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColor::RGB24(0xFFFF00)); + KDRect screen = KDRect(0,0,Ion::Display::Width,Ion::Display::Height); + Ion::Display::pushRectUniform(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColor::RGB24(0x5e81ac)); + KDContext * ctx = KDIonContext::sharedContext(); + ctx->setOrigin(KDPointZero); + ctx->setClippingRect(screen); + ctx->drawString("RECOVERY MODE", KDPoint(10, 10), KDFont::LargeFont, KDColorWhite, KDColor::RGB24(0x5e81ac)); } } diff --git a/ion/src/device/flasher/display_verbose.cpp b/ion/src/device/flasher/display_verbose.cpp index aad0b6c4598..ed7b5ff2983 100644 --- a/ion/src/device/flasher/display_verbose.cpp +++ b/ion/src/device/flasher/display_verbose.cpp @@ -1,47 +1,55 @@ #include #include +#include "image.h" namespace Flasher { -namespace Display { - -constexpr static int sNumberOfMessages = 5; -constexpr static int sNumberOfLanguages = 2; - -constexpr static const char * sMessages[sNumberOfLanguages][sNumberOfMessages] = { - {"RECOVERY MODE", - "Your calculator is waiting", - "for a new software.", - "Follow the instructions", - "on your computer to continue."}, - {"MODE RECUPERATION", - "Votre calculatrice attend", - "l'installation d'un nouveau logiciel.", - "Suivez les instructions sur", - "votre ordinateur pour continuer."} -}; - -void init() { - KDRect screen = KDRect(0,0,Ion::Display::Width,Ion::Display::Height); - Ion::Display::pushRectUniform(screen, KDColorWhite); - KDContext * ctx = KDIonContext::sharedContext(); - ctx->setOrigin(KDPointZero); - ctx->setClippingRect(screen); - KDCoordinate margin = 20; - KDCoordinate currentHeight = 0; - for (int i = 0; i < sNumberOfLanguages; i++) { - currentHeight += margin; - const char * title = sMessages[i][0]; - KDSize titleSize = KDFont::LargeFont->stringSize(title); - ctx->drawString(title, KDPoint((Ion::Display::Width-titleSize.width())/2, currentHeight), KDFont::LargeFont); - currentHeight += 2*titleSize.height(); - for (int j = 1; j < sNumberOfMessages; j++) { - const char * message = sMessages[i][j]; - KDSize messageSize = KDFont::SmallFont->stringSize(message); - ctx->drawString(message, KDPoint((Ion::Display::Width-messageSize.width())/2, currentHeight), KDFont::SmallFont); - currentHeight += messageSize.height(); - } - } -} + namespace Display { -} + constexpr static int sNumberOfMessages = 5; + + constexpr static const char * sMessages[sNumberOfMessages] = { + "RECOVERY MODE", + "Your calculator is waiting", + "for Upsilon to be installed.", + "Follow the instructions", + "on your computer to continue.", + }; + + void init() { + KDRect screen = KDRect(0,0,Ion::Display::Width,Ion::Display::Height); + Ion::Display::pushRectUniform(screen, KDColor::RGB24(0x2B2B2B)); + KDContext * ctx = KDIonContext::sharedContext(); + ctx->setOrigin(KDPointZero); + ctx->setClippingRect(screen); + KDCoordinate margin = 30; + KDCoordinate currentHeight = margin; + + /* Title */ + const char * title = sMessages[0]; + KDSize titleSize = KDFont::LargeFont->stringSize(title); + ctx->drawString(title, KDPoint((Ion::Display::Width-titleSize.width())/2, currentHeight), + KDFont::LargeFont, KDColorWhite, KDColor::RGB24(0x2B2B2B)); + currentHeight = (uint16_t)((Ion::Display::Height*2)/3); + + /* Logo */ + for (int i = 0; i < IMAGE_WIDTH; ++i) { + for (int j = 0; j < IMAGE_HEIGHT; ++j) { + ctx->setPixel(KDPoint(i+(uint16_t)((Ion::Display::Width-IMAGE_WIDTH)/2), + j+(titleSize.height()+margin+15)), + KDColor::RGB16(image[i+(j*IMAGE_WIDTH)])); + } + } + + /* Messages */ + const char * message; + for (int i = 1; i < sNumberOfMessages; ++i) { + message = sMessages[i]; + KDSize messageSize = KDFont::SmallFont->stringSize(message); + ctx->drawString(message, KDPoint((Ion::Display::Width-messageSize.width())/2, currentHeight), + KDFont::SmallFont, KDColorWhite, KDColor::RGB24(0x2B2B2B)); + currentHeight += messageSize.height(); + } + } + + } } diff --git a/ion/src/device/flasher/image.h b/ion/src/device/flasher/image.h new file mode 100644 index 00000000000..87317365646 --- /dev/null +++ b/ion/src/device/flasher/image.h @@ -0,0 +1,11 @@ +#ifndef __IMAGE_image_to_show_H +#define __IMAGE_image_to_show_H + +#define IMAGE_WIDTH 80 +#define IMAGE_HEIGHT 80 + +constexpr static uint16_t image[] { + 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x3186, 0x3186, 0x0, 0x0, 0x4a49, 0x7bcf, 0x9cd2, 0xad55, 0xb5b6, 0xbdd6, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd6, 0xb5b6, 0xad55, 0x94b2, 0x73ae, 0x4207, 0x0, 0x0, 0x3186, 0x3165, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x3186, 0x821, 0x0, 0x6b6d, 0xad34, 0xc618, 0xce59, 0xce79, 0xce79, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce79, 0xce79, 0xce59, 0xc5f7, 0xa513, 0x630c, 0x0, 0x18a2, 0x3186, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2965, 0x3165, 0x0, 0x5aeb, 0xad75, 0xce59, 0xce59, 0xce58, 0xce38, 0xc618, 0xc618, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc618, 0xc618, 0xce38, 0xce59, 0xce79, 0xce38, 0xa534, 0x5269, 0x0, 0x3186, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x3165, 0x2924, 0x0, 0x8c71, 0xce38, 0xce59, 0xce38, 0xc638, 0xc618, 0xc618, 0xce59, 0xd69a, 0xdedb, 0xe6fb, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xdefb, 0xdedb, 0xd69a, 0xce58, 0xc618, 0xc618, 0xc638, 0xce38, 0xce59, 0xc618, 0x8410, 0x0, 0x2965, 0x2965, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x3165, 0x2104, 0x2104, 0xad34, 0xce79, 0xce38, 0xc638, 0xc618, 0xc638, 0xdedb, 0xef7d, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xef7d, 0xd6ba, 0xc618, 0xc618, 0xc638, 0xce38, 0xce59, 0x9cd3, 0x0, 0x2945, 0x2965, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2965, 0x2924, 0x2104, 0xad75, 0xce79, 0xc618, 0xc638, 0xc617, 0xdeba, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xf79e, 0xd69a, 0xc617, 0xc638, 0xc638, 0xce79, 0xa514, 0x0, 0x2965, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x3165, 0x0, 0xa534, 0xce59, 0xc618, 0xc618, 0xc638, 0xef5d, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xe73c, 0xc618, 0xc638, 0xc618, 0xce59, 0x9cd2, 0x0, 0x3186, 0x2945, 0x2945, 0x2945, 0x3186, 0x0, 0x8c71, 0xce59, 0xc618, 0xc618, 0xce38, 0xf79e, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xef7d, 0xc618, 0xc638, 0xc618, 0xce59, 0x7bef, 0x0, 0x3186, 0x2945, 0x3185, 0x800, 0x62eb, 0xc638, 0xc638, 0xc618, 0xc618, 0xf79e, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xef7d, 0xc618, 0xc638, 0xc638, 0xc617, 0x4a48, 0x18e3, 0x3165, 0x3165, 0x0, 0xad55, 0xce59, 0xc618, 0xc5f7, 0xef5d, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xe71c, 0xc5f7, 0xc618, 0xce59, 0xa4f3, 0x0, 0x3186, 0x0, 0x6b6d, 0xce38, 0xc638, 0xc617, 0xdeba, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xf79e, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xf79e, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd699, 0xc618, 0xc638, 0xc618, 0x5acb, 0x1082, 0x0, 0xa534, 0xce59, 0xc618, 0xc618, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xef7d, 0xe73c, 0xe73c, 0xe73c, 0xef3c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xef3c, 0xe73c, 0xe73c, 0xe73c, 0xef5d, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xf79e, 0xc617, 0xc618, 0xce59, 0x9cb2, 0x0, 0x4a49, 0xc5f7, 0xc638, 0xbdf7, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xef3c, 0xef5d, 0xef7d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef7d, 0xef5d, 0xef3c, 0xe73c, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xc5f7, 0xce38, 0xbdd6, 0x3186, 0x7bcf, 0xce58, 0xc618, 0xc5f7, 0xef7d, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xef5d, 0xe6fb, 0x8c71, 0x6b6d, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x6b6d, 0x8c51, 0xdefb, 0xef5d, 0xe73c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xe73c, 0xbdf7, 0xc638, 0xc638, 0x6b2c, 0x9cd2, 0xce59, 0xc617, 0xce38, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe71c, 0xf77d, 0xa534, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9cf3, 0xf77d, 0xe71c, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xf7be, 0xc618, 0xc618, 0xce59, 0x8c51, 0xad55, 0xce58, 0xbdf7, 0xd69a, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cd3, 0x0, 0x31a6, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x31a6, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xce59, 0xc5f7, 0xce59, 0x9cf3, 0xb596, 0xce38, 0xbdf7, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xbdf7, 0xce58, 0xad34, 0xbdb6, 0xc638, 0xbdf7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdf7, 0xce38, 0xad75, 0xbdf7, 0xc618, 0xbdf7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xdeba, 0xbdf7, 0xce38, 0xb595, 0xbdf7, 0xc618, 0xbdd7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x20e3, 0x20e3, 0x20e3, 0x20e3, 0x20e3, 0x20e3, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x20e3, 0x18e3, 0x20e3, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xdeba, 0xbdd7, 0xce38, 0xb575, 0xbdf7, 0xc618, 0xbdd7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2104, 0x3186, 0x31a6, 0x31a6, 0x3186, 0x31a6, 0x3186, 0x2103, 0x2104, 0x2104, 0x2104, 0x2103, 0x3185, 0x31a6, 0x3186, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdd7, 0xc638, 0xb575, 0xbdf7, 0xc618, 0xbdd7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2104, 0x18c2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2104, 0x2103, 0x2104, 0x2103, 0x2104, 0x0, 0x0, 0x0, 0x2945, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdd7, 0xc638, 0xb575, 0xbdf7, 0xc618, 0xbdd7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x1061, 0x4208, 0xbdb6, 0xbdf7, 0xbdf7, 0xbdf7, 0xc618, 0xad75, 0x20, 0x2124, 0x2124, 0x18e3, 0x2945, 0xb5b6, 0xc638, 0xa534, 0x0, 0x2945, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdd7, 0xc638, 0xb575, 0xbdd7, 0xc618, 0xbdd7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2944, 0x0, 0x5aeb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xef7d, 0x0, 0x2924, 0x2104, 0x3185, 0x0, 0xb5b6, 0xffff, 0xffff, 0x8c51, 0x0, 0x2965, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdd7, 0xc638, 0xb575, 0xbdd7, 0xc618, 0xbdd7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x1061, 0x4208, 0xbdd7, 0xbdf7, 0xbdf7, 0xf7be, 0xffff, 0xe71c, 0x0, 0x2124, 0x2104, 0x2124, 0x2124, 0x0, 0xdefb, 0xffff, 0xe71c, 0x0, 0x2945, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdd7, 0xc618, 0xb575, 0xbdd7, 0xc618, 0xbdd7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2104, 0x18c2, 0x0, 0x0, 0x0, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x20e3, 0x31a6, 0x0, 0xa514, 0xffff, 0xffff, 0x8410, 0x0, 0x2965, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdd7, 0xc618, 0xb575, 0xbdd7, 0xc618, 0xbdb6, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2104, 0x31a6, 0x2965, 0x39e7, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x2104, 0x2945, 0x0, 0x6b4d, 0xffdf, 0xffff, 0xc618, 0x0, 0x31a6, 0x20e3, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffbe, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdd7, 0xc618, 0xb575, 0xbdd7, 0xc5f7, 0xbdb6, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x18c2, 0x3165, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x2104, 0x2124, 0x18a2, 0x39a6, 0xef7d, 0xffff, 0xdefb, 0x0, 0x2965, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffbe, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdb6, 0xc618, 0xad75, 0xbdd7, 0xc5f7, 0xbdb6, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x18c3, 0x3165, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x2104, 0x2104, 0x2104, 0x0, 0xef5d, 0xffff, 0xef5d, 0x800, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffbe, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xbdb6, 0xc618, 0xad75, 0xbdd7, 0xc5f7, 0xbdb6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x18c3, 0x2965, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x2104, 0x2104, 0x2104, 0x0, 0xef5d, 0xffff, 0xef5d, 0x18a2, 0x2104, 0x2124, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffbe, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xbdb6, 0xc618, 0xad75, 0xbdd7, 0xc5f7, 0xbdb6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x18c3, 0x2965, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x2104, 0x2124, 0x10a2, 0x39c7, 0xf79e, 0xffff, 0xef5d, 0x0, 0x2124, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xbdb6, 0xc618, 0xad75, 0xbdd7, 0xc5f7, 0xbdb6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x18c3, 0x2965, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x2104, 0x2945, 0x0, 0x6b4d, 0xffdf, 0xffff, 0xdedb, 0x0, 0x2965, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xbdb6, 0xc618, 0xad55, 0xbdd7, 0xc5f7, 0xb5b6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x18c3, 0x2965, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x2103, 0x3186, 0x0, 0x9cf3, 0xffff, 0xffff, 0xbdd7, 0x0, 0x31a6, 0x20e3, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb5b6, 0xc618, 0xad55, 0xbdb6, 0xbdf7, 0xb5b6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x18e3, 0x2945, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x18e3, 0x31a6, 0x0, 0xd6ba, 0xffff, 0xffff, 0x7bef, 0x0, 0x2965, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb5b6, 0xc618, 0xad55, 0xbdb6, 0xbdf7, 0xb5b6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x2104, 0x0, 0xef5d, 0xffff, 0xef5d, 0x2104, 0x3186, 0x3186, 0x39e7, 0x0, 0x7bcf, 0xffff, 0xffff, 0xe71c, 0x0, 0x2945, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb5b6, 0xc618, 0xad55, 0xbdb6, 0xbdf7, 0xb5b6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x3185, 0x0, 0xd69a, 0xffff, 0xffdf, 0x5aeb, 0x0, 0x0, 0x0, 0x31a6, 0xef5d, 0xffff, 0xffff, 0x8c51, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb5b6, 0xc617, 0xad55, 0xbdb6, 0xbdf7, 0xb5b6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x3186, 0x0, 0x9cf3, 0xffff, 0xffff, 0xe71c, 0x94b2, 0x8c51, 0xa534, 0xef7d, 0xffff, 0xffff, 0xce38, 0x0, 0x3185, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb5b6, 0xc617, 0xad55, 0xbdb6, 0xbdf7, 0xb5b6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x0, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xce79, 0x0, 0x2104, 0x2124, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb5b6, 0xc5f7, 0xad55, 0xbdb6, 0xbdf7, 0xb5b6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2924, 0x820, 0x2945, 0xbdd7, 0xef5d, 0xf7be, 0xf7be, 0xf79e, 0xdedb, 0x9492, 0x0, 0x20e3, 0x2924, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb5b6, 0xc5f7, 0xad55, 0xbdb6, 0xbdf7, 0xb5b6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x20e3, 0x2924, 0x18c2, 0x0, 0x1082, 0x4a69, 0x528a, 0x39c7, 0x0, 0x0, 0x2965, 0x2124, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb5b6, 0xc5f7, 0xad55, 0xbdb6, 0xbdf7, 0xb596, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2124, 0x3186, 0x2104, 0x0, 0x0, 0x1082, 0x2965, 0x3185, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb596, 0xc5f7, 0xad55, 0xbdb6, 0xbdf7, 0xb596, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2124, 0x2944, 0x2944, 0x2124, 0x2104, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb596, 0xc5f7, 0xad55, 0xb5b6, 0xbdd7, 0xb596, 0xdedb, 0xffff, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffbe, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xffff, 0xd69a, 0xb596, 0xc5f7, 0xad55, 0xb5b6, 0xbdd7, 0xb596, 0xdedb, 0xffff, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffbe, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xffff, 0xd699, 0xb596, 0xbdf7, 0xad54, 0xb5b6, 0xbdd7, 0xb596, 0xdedb, 0xffff, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffbe, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3186, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x3186, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xffff, 0xd699, 0xb596, 0xbdf7, 0xad54, 0xb5b6, 0xbdd7, 0xb596, 0xdedb, 0xffff, 0xffdf, 0xffff, 0xffff, 0xffdf, 0xffff, 0xffbe, 0xe71c, 0xf77d, 0xa4f3, 0x0, 0x18a2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18a2, 0x0, 0x94b2, 0xf77d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xffff, 0xd699, 0xb596, 0xbdf7, 0xad54, 0xb5b6, 0xbdd7, 0xb596, 0xdeda, 0xffff, 0xffdf, 0xffff, 0xffff, 0xffdf, 0xffff, 0xffbe, 0xe73c, 0xef5d, 0xdedb, 0x738e, 0x52aa, 0x5aeb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5aaa, 0x6b6d, 0xd6ba, 0xef5d, 0xe73c, 0xf7be, 0xffff, 0xffdf, 0xffff, 0xffff, 0xffdf, 0xffff, 0xd699, 0xb596, 0xbdf7, 0xad34, 0xb5b6, 0xbdd7, 0xb596, 0xdeba, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xffbe, 0xe73c, 0xef3c, 0xef5d, 0xef5d, 0xe71c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe71c, 0xef5d, 0xef5d, 0xef3c, 0xe73c, 0xf7be, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xd679, 0xb596, 0xbdf7, 0xad34, 0xb596, 0xbdd7, 0xb596, 0xdeba, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xef5d, 0xe73c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xe73c, 0xef5d, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xd679, 0xb596, 0xbdf7, 0xad34, 0xb596, 0xbdd7, 0xb595, 0xdeba, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffbe, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xffbe, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xd679, 0xb595, 0xbdd7, 0xad34, 0xb596, 0xbdd6, 0xb595, 0xdeba, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xd679, 0xb595, 0xbdd7, 0xad34, 0xb596, 0xbdd6, 0xb595, 0xdeba, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xce79, 0xb595, 0xbdd7, 0xad34, 0xb596, 0xbdd6, 0xb575, 0xd6ba, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xce79, 0xb595, 0xbdd7, 0xa534, 0xb596, 0xbdd6, 0xb575, 0xd6ba, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xce79, 0xb595, 0xbdd7, 0xa534, 0xb596, 0xbdd6, 0xb575, 0xd6ba, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xf77d, 0xf7be, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xffdf, 0xe71c, 0xdefc, 0xdefc, 0xdefc, 0xdefc, 0xdefc, 0xe73c, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xce79, 0xb575, 0xbdd7, 0xa534, 0xb596, 0xbdd6, 0xb575, 0xd6ba, 0xffff, 0xffbe, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffde, 0xffdf, 0xf79d, 0xce79, 0xce38, 0xce38, 0xe71c, 0xffdf, 0xffde, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xf7be, 0xffff, 0xe73c, 0x7c33, 0x4311, 0x4311, 0x4311, 0x4311, 0x4311, 0x4331, 0x4310, 0x94f5, 0xf79e, 0xffdf, 0xffde, 0xffdf, 0xffdf, 0xffdf, 0xffde, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffbe, 0xffdf, 0xce79, 0xb575, 0xbdd7, 0xa534, 0xb596, 0xbdb6, 0xb575, 0xd6ba, 0xffff, 0xf7be, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xce79, 0xce59, 0xd69a, 0xd69a, 0xc618, 0xef5d, 0xffdf, 0xf7be, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xf7be, 0xffdf, 0xf7be, 0x73f2, 0x4352, 0x6c55, 0x6c35, 0x6c35, 0x6c35, 0x6c35, 0x6c35, 0x6c55, 0x1ab1, 0x9d35, 0xffff, 0xf7be, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xf7be, 0xffdf, 0xce79, 0xb575, 0xbdd7, 0xa534, 0xb595, 0xbdb6, 0xb575, 0xd69a, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xef7d, 0xce38, 0xd69a, 0xd679, 0xd69a, 0xce59, 0xdedb, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffff, 0xd6bb, 0x3b11, 0x6c55, 0x6414, 0x6415, 0x6415, 0x6415, 0x6415, 0x6414, 0x6415, 0x6435, 0x5b71, 0xef5d, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffbf, 0xffdf, 0xffdf, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xce59, 0xb575, 0xbdd7, 0xa534, 0xb595, 0xbdb6, 0xad75, 0xd69a, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xffdf, 0xf77d, 0xce38, 0xd69a, 0xd69a, 0xd69a, 0xce59, 0xdedb, 0xffff, 0xffdf, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffff, 0xd6bb, 0x3b11, 0x6c55, 0x6414, 0x6414, 0x6414, 0x6414, 0x6414, 0x6414, 0x6414, 0x6c35, 0x5371, 0xef5d, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xf79e, 0xe73c, 0xe71c, 0xef5d, 0xffbe, 0xffbf, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xef7d, 0xe71c, 0xe71c, 0xef7d, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xce59, 0xad75, 0xbdd6, 0xa534, 0xb595, 0xb5b6, 0xad75, 0xd69a, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffbe, 0xf7be, 0xef5d, 0xef3c, 0xe6fb, 0xce38, 0xd69a, 0xd69a, 0xd69a, 0xce59, 0xd69a, 0xef5d, 0xe73c, 0xef7d, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffbe, 0xef7e, 0x6391, 0x53b3, 0x6c55, 0x6c35, 0x6c55, 0x6c55, 0x6c55, 0x6c35, 0x7456, 0x3312, 0x8cb4, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xef5d, 0xce38, 0xce38, 0xce58, 0xc638, 0xd699, 0xf79e, 0xffbe, 0xf7be, 0xffbe, 0xdedb, 0xc638, 0xce38, 0xce38, 0xc638, 0xdefb, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xce59, 0xad75, 0xbdd6, 0xa514, 0xb575, 0xb5b6, 0xad75, 0xd69a, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xef7d, 0xce59, 0xce38, 0xce38, 0xce38, 0xd679, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xce59, 0xce38, 0xce38, 0xc638, 0xdefb, 0xffbe, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xdedb, 0x5b91, 0x3b11, 0x3b11, 0x3b11, 0x3b11, 0x3b11, 0x3b11, 0x3af0, 0x7c33, 0xef5d, 0xffde, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf77d, 0xc638, 0xd679, 0xd69a, 0xd69a, 0xd69a, 0xce38, 0xd69a, 0xf7be, 0xffdf, 0xe6fb, 0xc618, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xc618, 0xe71c, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xce59, 0xad75, 0xbdb6, 0xa514, 0xb575, 0xb5b6, 0xad75, 0xd69a, 0xffdf, 0xf79e, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xd69a, 0xce59, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xc618, 0xef5d, 0xf7be, 0xf79e, 0xf7be, 0xf7be, 0xf79e, 0xffdf, 0xef7d, 0xce7a, 0xc659, 0xce59, 0xce59, 0xce59, 0xce59, 0xd6ba, 0xf79e, 0xffbe, 0xf79e, 0xf7be, 0xf7be, 0xf79e, 0xffbe, 0xdedb, 0xce38, 0xd69a, 0xd679, 0xd69a, 0xd69a, 0xd69a, 0xc638, 0xef5d, 0xf7be, 0xce59, 0xd679, 0xd69a, 0xd69a, 0xd679, 0xd69a, 0xd679, 0xce79, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf79e, 0xffbe, 0xce59, 0xad75, 0xbdb6, 0xa514, 0xad55, 0xb5b6, 0xad75, 0xd679, 0xffbe, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf7be, 0xf79e, 0xce59, 0xd69a, 0xd69a, 0xd679, 0xd679, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd679, 0xd679, 0xd69a, 0xce59, 0xdefb, 0xffbe, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf7be, 0xd6ba, 0xce59, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xce38, 0xe73c, 0xf79e, 0xce58, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd679, 0xce59, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf7be, 0xce38, 0xad75, 0xbdb6, 0xa513, 0xa514, 0xb5b6, 0xad75, 0xce59, 0xf7be, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xd679, 0xce59, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xc618, 0xe73c, 0xf7be, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf7be, 0xf79e, 0xe71c, 0xdedb, 0xdedb, 0xdedb, 0xdedb, 0xdedb, 0xe73c, 0xf7be, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf7be, 0xdeda, 0xce59, 0xd69a, 0xd679, 0xd69a, 0xd679, 0xd69a, 0xc638, 0xef3c, 0xf79e, 0xce59, 0xd679, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd679, 0xce79, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xc618, 0xad75, 0xbdb6, 0x9cd3, 0x9cd3, 0xbdb6, 0xad75, 0xc618, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xef3c, 0xce38, 0xce38, 0xce38, 0xce58, 0xd679, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xce59, 0xce38, 0xce58, 0xc638, 0xd6ba, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf77d, 0xf7be, 0xe71c, 0x7bcf, 0x20e3, 0x0, 0x0, 0x0, 0x0, 0x841, 0x2945, 0x94b2, 0xef7d, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xef5d, 0xc638, 0xd679, 0xd69a, 0xd69a, 0xd69a, 0xce59, 0xce79, 0xf79e, 0xf79e, 0xdedb, 0xc638, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xc618, 0xdefb, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf77d, 0xbdd7, 0xad75, 0xbdb6, 0x9471, 0x8c51, 0xbdb6, 0xb575, 0xb5b6, 0xef5d, 0xf79e, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf79e, 0xef7d, 0xe6fb, 0xe6fb, 0xdeba, 0xce38, 0xd69a, 0xd69a, 0xd69a, 0xce59, 0xce79, 0xe6fb, 0xdefb, 0xe73c, 0xf79e, 0xf77e, 0xf77d, 0xf77d, 0xf77d, 0xf79e, 0xef7d, 0x632c, 0x0, 0x39a6, 0x3186, 0x3186, 0x3186, 0x3186, 0x3186, 0x3186, 0x0, 0x9cb2, 0xf7be, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf79e, 0xe71b, 0xc638, 0xce38, 0xce59, 0xce38, 0xce59, 0xef5d, 0xf79e, 0xf77d, 0xf77d, 0xd69a, 0xc638, 0xce59, 0xce59, 0xc638, 0xd6ba, 0xf79e, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf79e, 0xef3c, 0xb595, 0xb595, 0xbdb6, 0x7bef, 0x6b6d, 0xb5b6, 0xb596, 0xad75, 0xdefb, 0xf79e, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf79d, 0xf79e, 0xf7be, 0xef5d, 0xce38, 0xd69a, 0xd69a, 0xd69a, 0xce59, 0xdeda, 0xf7be, 0xf79e, 0xf79e, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xef7d, 0xf7be, 0xce79, 0x0, 0x4208, 0x3165, 0x3165, 0x3165, 0x3165, 0x3165, 0x3165, 0x31a6, 0x3186, 0x3186, 0xe71c, 0xf79e, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf79e, 0xef5d, 0xdedb, 0xdeba, 0xe6fb, 0xef7d, 0xf79e, 0xf77d, 0xf77d, 0xf77d, 0xf79e, 0xe71c, 0xdeda, 0xdeda, 0xe73c, 0xf79e, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf79e, 0xd6ba, 0xad55, 0xb596, 0xb596, 0x5aeb, 0x4208, 0xad75, 0xb596, 0xad75, 0xc638, 0xf79e, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xe73c, 0xce38, 0xd69a, 0xd679, 0xd69a, 0xce59, 0xd6ba, 0xf79d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xf7be, 0xce58, 0x0, 0x41e7, 0x2965, 0x3165, 0x3165, 0x3165, 0x3165, 0x3165, 0x3165, 0x31a6, 0x2104, 0xdefb, 0xf79e, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xf77d, 0xf79e, 0xf79e, 0xf79e, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xf79e, 0xf79e, 0xf79e, 0xf79d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xbdf7, 0xad75, 0xb5b6, 0xa534, 0x3165, 0x0, 0x9492, 0xb5b6, 0xb575, 0xb575, 0xe71c, 0xef7d, 0xef5d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef5d, 0xce59, 0xce79, 0xd69a, 0xd69a, 0xc618, 0xe6fb, 0xf77d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef5d, 0xf77d, 0xe71c, 0x2924, 0x1082, 0x4208, 0x39c7, 0x39c7, 0x39c7, 0x39c7, 0x39c7, 0x4208, 0x0, 0x738e, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef5d, 0xef5d, 0xef5d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef5d, 0xef5d, 0xef5d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef5d, 0xf77d, 0xdefb, 0xad75, 0xb595, 0xb5b6, 0x8c30, 0x0, 0x841, 0x630c, 0xb596, 0xb595, 0xad75, 0xc618, 0xef7d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef7d, 0xe71c, 0xce38, 0xce38, 0xc638, 0xd6ba, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xf79e, 0xc618, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4a48, 0xd6ba, 0xf77d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xbdd7, 0xad75, 0xb596, 0xb575, 0x5269, 0x18c3, 0x3165, 0x820, 0x9cd2, 0xb5b6, 0xb575, 0xad55, 0xd69a, 0xef7d, 0xef3c, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef3c, 0xef5c, 0xef3c, 0xef5d, 0xef3c, 0xdefb, 0xe71c, 0xef5d, 0xef5d, 0xef3c, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef3c, 0xef7d, 0xdeba, 0xb5b6, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xbdd7, 0xe71c, 0xef7d, 0xef3c, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5c, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5c, 0xef5d, 0xef5d, 0xef5d, 0xef3c, 0xef5d, 0xef3c, 0xef7d, 0xce59, 0xad55, 0xb575, 0xb5b6, 0x8c71, 0x0, 0x3186, 0x3165, 0x18a2, 0x528a, 0xb575, 0xb595, 0xb575, 0xb575, 0xdedb, 0xef5d, 0xe73c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef5d, 0xef5d, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xe73c, 0xef5d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xef5d, 0xe73c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xe73c, 0xef5d, 0xd6ba, 0xad75, 0xb575, 0xb596, 0xad55, 0x41e7, 0x2104, 0x2965, 0x2945, 0x3186, 0x0, 0x7bcf, 0xb5b6, 0xb575, 0xb575, 0xb575, 0xdedb, 0xef5d, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xef5d, 0xd6ba, 0xad75, 0xb575, 0xb575, 0xb5b6, 0x6b6d, 0x0, 0x3186, 0x2945, 0x2945, 0x2945, 0x3165, 0x800, 0x9492, 0xb5b6, 0xad75, 0xad75, 0xad75, 0xd679, 0xef3c, 0xef3c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xef3c, 0xe73c, 0xce59, 0xad55, 0xb575, 0xad75, 0xb5b6, 0x8430, 0x0, 0x3186, 0x2945, 0x2945, 0x2945, 0x2945, 0x2965, 0x2945, 0x20e3, 0x9cb2, 0xb5b6, 0xad75, 0xad75, 0xad55, 0xbdf7, 0xdedb, 0xe73c, 0xef3c, 0xe73c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe73c, 0xef3c, 0xe73c, 0xdeba, 0xbdd7, 0xad55, 0xad75, 0xad75, 0xb5b6, 0x8c71, 0x0, 0x2965, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2965, 0x2924, 0x20e3, 0x9471, 0xb5b6, 0xb575, 0xad75, 0xad55, 0xad75, 0xbdf7, 0xd69a, 0xdefb, 0xe71c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe71c, 0xdefb, 0xd679, 0xbdd7, 0xad55, 0xad55, 0xad75, 0xb575, 0xb596, 0x8430, 0x800, 0x2945, 0x2965, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2965, 0x2945, 0x0, 0x7bcf, 0xad75, 0xb596, 0xad75, 0xad75, 0xad55, 0xad55, 0xb575, 0xbdb6, 0xbdf7, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xbdf7, 0xbdb6, 0xad75, 0xad55, 0xad55, 0xad75, 0xb575, 0xb596, 0xad55, 0x6b6d, 0x0, 0x2965, 0x2965, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2965, 0x3165, 0x0, 0x5269, 0x94b2, 0xb595, 0xb596, 0xb595, 0xad75, 0xad75, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad75, 0xad75, 0xb596, 0xb596, 0xad75, 0x8c71, 0x4208, 0x0, 0x3186, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x3186, 0x18c3, 0x0, 0x5acb, 0x8c71, 0xad34, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb595, 0xa534, 0x8c51, 0x528a, 0x0, 0x20e3, 0x3186, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2965, 0x3165, 0x1062, 0x0, 0x39e7, 0x632c, 0x8410, 0x9492, 0x9cd3, 0xa4f3, 0xa514, 0xa513, 0xa513, 0xa514, 0xa513, 0xa513, 0xa514, 0xa514, 0xa514, 0xa514, 0xa514, 0xa514, 0xa513, 0xa513, 0xa514, 0xa513, 0xa514, 0xa513, 0xa513, 0xa513, 0xa513, 0xa513, 0xa514, 0xa513, 0xa514, 0xa514, 0xa513, 0xa513, 0xa514, 0xa514, 0xa514, 0xa513, 0xa514, 0xa514, 0xa514, 0xa513, 0xa513, 0xa513, 0xa514, 0xa514, 0xa514, 0xa513, 0xa513, 0xa514, 0x9cf3, 0x9cd3, 0x9491, 0x7bef, 0x630c, 0x31a6, 0x0, 0x18a2, 0x3186, 0x2965, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, +}; + +#endif // __IMAGE_image_to_show_H diff --git a/ion/src/device/shared/ram.ld b/ion/src/device/shared/ram.ld index 6cf176697df..ef027f3e0b2 100644 --- a/ion/src/device/shared/ram.ld +++ b/ion/src/device/shared/ram.ld @@ -22,7 +22,11 @@ MEMORY { * object). Using a stack too small would result in some memory being * overwritten (for instance, vtables that live in the .rodata section). */ -STACK_SIZE = 32K; +/* The image is quite large too! + * So we put the stack to 18K so there's still space + * for our image, if not LD will throw an error. */ + +STACK_SIZE = 18K; SECTIONS { .isr_vector_table ORIGIN(RAM_BUFFER) : { From f5f11c64786d7941f3374349251e13dcb571b1f3 Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 25 Sep 2021 19:30:37 +0200 Subject: [PATCH 058/355] [poincare] Added simplification of equals --- poincare/src/equal.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/poincare/src/equal.cpp b/poincare/src/equal.cpp index 3e8ca67bd27..8f3b2932ef5 100644 --- a/poincare/src/equal.cpp +++ b/poincare/src/equal.cpp @@ -52,11 +52,28 @@ Expression Equal::standardEquation(Context * context, Preferences::ComplexFormat Expression Equal::shallowReduce(ExpressionNode::ReductionContext reductionContext) { Expression e = Equal::Builder(Subtraction::Builder(childAtIndex(0).clone(), childAtIndex(1).clone()).shallowReduce(reductionContext), Rational::Builder(0)); - if (e.childAtIndex(0).isIdenticalTo(e.childAtIndex(1))) { + Expression leftSide = e.childAtIndex(0); + if (leftSide.isIdenticalTo(e.childAtIndex(1))) { Expression result = Rational::Builder(1); replaceWithInPlace(result); return result; } + if (leftSide.isUndefined()) { + return leftSide; + } + if (leftSide.type() == ExpressionNode::Type::Multiplication) { + Multiplication m = static_cast(leftSide); + int i = 0; + while (i < numberOfChildren()-1) { + if (m.childAtIndex(i).nullStatus(reductionContext.context()) == ExpressionNode::NullStatus::NonNull) { + m.removeChildAtIndexInPlace(i); + } + else { + i++; + } + } + } + return e; } From 0b54bf13f4a39b68b0e199bc1b7ff90797f698be Mon Sep 17 00:00:00 2001 From: ArtichautCosmique Date: Sat, 25 Sep 2021 19:49:26 +0200 Subject: [PATCH 059/355] [storage][apps/code] Change sizes (#28) --- apps/code/app.h | 2 +- apps/code/variable_box_controller.h | 2 +- ion/include/ion/storage.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/code/app.h b/apps/code/app.h index 494f5c0f8c7..68692a1350b 100644 --- a/apps/code/app.h +++ b/apps/code/app.h @@ -75,7 +75,7 @@ class App : public Shared::InputEventHandlerDelegateApp { VariableBoxController * variableBoxController() { return &m_variableBoxController; } - static constexpr int k_pythonHeapSize = 100000; + static constexpr int k_pythonHeapSize = 67000; private: /* Python delegate: diff --git a/apps/code/variable_box_controller.h b/apps/code/variable_box_controller.h index 5e9b44bbbc5..81504ac0a43 100644 --- a/apps/code/variable_box_controller.h +++ b/apps/code/variable_box_controller.h @@ -43,7 +43,7 @@ class VariableBoxController : public AlternateEmptyNestedMenuController { private: constexpr static size_t k_maxNumberOfDisplayedItems = (Ion::Display::Height - Metric::TitleBarHeight - Metric::PopUpTopMargin) / ScriptNodeCell::k_simpleItemHeight + 2; // +2 if the cells are cropped on top and at the bottom - constexpr static size_t k_maxScriptNodesCount = 128; // Chosen without particular reasons (Number of functions in the variables box) + constexpr static size_t k_maxScriptNodesCount = 64; // Chosen without particular reasons (Number of functions in the variables box) constexpr static int k_totalBuiltinNodesCount = 107; constexpr static uint8_t k_scriptOriginsCount = 8; // Number of scripts loaded in the variable box constexpr static uint8_t k_subtitleCellType = NodeCellType; // We don't care as it is not selectable diff --git a/ion/include/ion/storage.h b/ion/include/ion/storage.h index 10513517738..14d70900463 100644 --- a/ion/include/ion/storage.h +++ b/ion/include/ion/storage.h @@ -17,7 +17,7 @@ class Storage { public: typedef uint16_t record_size_t; - constexpr static size_t k_storageSize = 32768; + constexpr static size_t k_storageSize = 64000; static_assert(UINT16_MAX >= k_storageSize, "record_size_t not big enough"); static Storage * sharedStorage(); From cd40938b26f2ad6ff701bb8785822f1ee4489116 Mon Sep 17 00:00:00 2001 From: Lauryy06 <80424145+Lauryy06@users.noreply.github.com> Date: Sat, 25 Sep 2021 19:52:14 +0200 Subject: [PATCH 060/355] [readme] Add a capital line 10 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7fb77586ae6..6b6df31853d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Discord

-> Vous ne comprenez pas l'anglais ? vous êtes francophone ? Regardez le [*LISEZ-MOI* français](./README.fr.md) ! +> Vous ne comprenez pas l'anglais ? Vous êtes francophone ? Regardez le [*LISEZ-MOI* français](./README.fr.md) ! ## About From 2f7edc50240b6c474e42917a7e29ecfc6ba9966f Mon Sep 17 00:00:00 2001 From: Laporte <40714786+Laporte12974@users.noreply.github.com> Date: Sun, 26 Sep 2021 14:25:00 +0200 Subject: [PATCH 061/355] Update pimp.mak (#29) * Update pimp.mak Mise a jour du pimp.mak pour le nouveau slogan et le logo ascii. * Update pimp.mak --- build/pimp.mak | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/build/pimp.mak b/build/pimp.mak index db88c7458d4..d888d175803 100644 --- a/build/pimp.mak +++ b/build/pimp.mak @@ -1,15 +1,16 @@ # You gotta PIMP MY CALC ifndef NO_PIMP -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m ____ \n") -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m / __ \\____ ___ ___ ____ _____ _ \n") -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m / / / / __ \`__ \\/ _ \\/ __ \`/ __ \`/ \n") -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m / /_/ / / / / / / __/ /_/ / /_/ / \n") -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m \\____/_/ /_/ /_/\\___/\\__, /\\__,_/ \n") -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m /____/ \n") -PLS_IGNORE := $(shell >&2 printf "\e[33m - Omega does what Epsilon't \e[0m\n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m __ __ ______ ____ ____ _____\n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m / / / /_____/ ,____/_/ / / __ \/'_ /\n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m / / / / __ '/ /____/ / / / / / / / / / \n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m / /_/ / /_/ /___, / / /__/ /_/ / / / / \n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m \____/ ____/______/_/____/\____/_/ /_/ \n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m / / \n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m /_/ \n") +PLS_IGNORE := $(shell >&2 printf "\e[33m - When Epsiloff and Omegout \e[0m\n") PLS_IGNORE := $(shell >&2 printf "\n") -PLS_IGNORE := $(shell >&2 printf "\e[32mBuilding O$(OMEGA_VERSION)-E$(EPSILON_VERSION)\n") +PLS_IGNORE := $(shell >&2 printf "\e[32mBuilding U$(UPSILON_VERSION)-E$(EPSILON_VERSION)\n") ifeq (${PLATFORM},device) DISPLAY_TARGET = Numworks $(MODEL) From 8c949d7048402dde1a24e1709b02ec17de4fd32a Mon Sep 17 00:00:00 2001 From: apoleon33 <71031475+apoleon33@users.noreply.github.com> Date: Sun, 26 Sep 2021 20:36:13 +0200 Subject: [PATCH 062/355] [build]changed color of upsilon logo in pimp.mak (#31) --- build/pimp.mak | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/build/pimp.mak b/build/pimp.mak index d888d175803..68ee83b22c4 100644 --- a/build/pimp.mak +++ b/build/pimp.mak @@ -1,14 +1,14 @@ # You gotta PIMP MY CALC ifndef NO_PIMP -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m __ __ ______ ____ ____ _____\n") -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m / / / /_____/ ,____/_/ / / __ \/'_ /\n") -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m / / / / __ '/ /____/ / / / / / / / / / \n") -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m / /_/ / /_/ /___, / / /__/ /_/ / / / / \n") -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m \____/ ____/______/_/____/\____/_/ /_/ \n") -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m / / \n") -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m /_/ \n") -PLS_IGNORE := $(shell >&2 printf "\e[33m - When Epsiloff and Omegout \e[0m\n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[36m __ __ ______ ____ ____ _____\n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[36m / / / /_____/ ,____/_/ / / __ \/'_ /\n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[36m / / / / __ '/ /____/ / / / / / / / / / \n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[36m / /_/ / /_/ /___, / / /__/ /_/ / / / / \n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[36m \____/ ____/______/_/____/\____/_/ /_/ \n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[36m / / \n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[36m /_/ \n") +PLS_IGNORE := $(shell >&2 printf "\e[0;34m - When Epsiloff and Omegout \e[0m\n") PLS_IGNORE := $(shell >&2 printf "\n") PLS_IGNORE := $(shell >&2 printf "\e[32mBuilding U$(UPSILON_VERSION)-E$(EPSILON_VERSION)\n") From 0a5e9e0889f18af9a9dd9b0c8f19a31a27be9bb8 Mon Sep 17 00:00:00 2001 From: ArtichautCosmique Date: Sun, 26 Sep 2021 20:37:56 +0200 Subject: [PATCH 063/355] [apps/code][mpy][makefile] Fix size error on n0100 (#30) --- Makefile | 17 ++++++++++++++++- apps/code/python_toolbox.cpp | 15 +++++++++------ build/config.mak | 1 + python/Makefile | 7 ++++++- python/port/genhdr/qstrdefs.in.h | 2 ++ python/port/mod/ulab/ulab.h | 6 ------ python/port/mpconfigport.h | 18 +++++++++++++++++- python/port/port.cpp | 2 ++ 8 files changed, 53 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 4f54b9284d5..d3b59008c17 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,18 @@ include build/variants.mak include build/helpers.mk ifeq (${MODEL}, n0100) - EPSILON_APPS := $(filter-out reader,$(EPSILON_APPS)) + ifeq ($(filter reader,$(apps_list)),) + $(warning reader app included, removing it on n0100. ) + EPSILON_APPS := $(filter-out reader,$(EPSILON_APPS)) + endif + ifneq ($(words $(EPSILON_I18N)), 1) + $(warning Only use 1 language on n0100, defaulting to en. ) + EPSILON_I18N := en + endif + ifeq ($(INCLUDE_ULAB), 1) + $(warning Removing uLab on n0100. ) + INCLUDE_ULAB := 0 + endif endif ifeq (${MODEL}, n0110) @@ -26,6 +37,10 @@ ifdef FORCE_EXTERNAL apps_list = ${EPSILON_APPS} endif +ifeq ($(INCLUDE_ULAB), 1) + SFLAGS += -DINCLUDE_ULAB +endif + ifdef HOME_DISPLAY_EXTERNALS ifneq ($(filter external,$(apps_list)),) SFLAGS += -DHOME_DISPLAY_EXTERNALS diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 34056c8a4ea..f78040216a3 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -135,7 +135,9 @@ const ToolboxMessageTree MatplotlibPyplotModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGray, I18n::Message::PythonColorGray, false) }; - const ToolboxMessageTree NumpyNdarrayModuleChildren[] = { +#if defined(INCLUDE_ULAB) + +const ToolboxMessageTree NumpyNdarrayModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArray), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArange), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyConcatenate), @@ -254,7 +256,6 @@ const ToolboxMessageTree NumpyModuleChildren[] = { ToolboxMessageTree::Node(I18n::Message::NumpyLinalgModule, NumpyLinalgModuleChildren) }; -#if !defined(DEVICE_N0100) const ToolboxMessageTree ScipyLinalgModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyLinalgFunction, I18n::Message::PythonScipyLinalgFunction, false, I18n::Message::PythonCommandScipyLinalgFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyLinalgChoSolve), @@ -291,16 +292,14 @@ const ToolboxMessageTree ScipyModuleChildren[] = { ToolboxMessageTree::Node(I18n::Message::ScipySpecialModule, ScipySpecialModuleChildren), }; -#endif - const ToolboxMessageTree UlabModuleChildren[] = { ToolboxMessageTree::Node(I18n::Message::NumpyModule, NumpyModuleChildren), -#if !defined(DEVICE_N0100) ToolboxMessageTree::Node(I18n::Message::ScipyModule, ScipyModuleChildren), -#endif ToolboxMessageTree::Leaf(I18n::Message::UlabDocumentation, I18n::Message::UlabDocumentationLink) }; +#endif + const ToolboxMessageTree TurtleModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportTurtle, I18n::Message::PythonImportTurtle, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromTurtle, I18n::Message::PythonImportTurtle, false), @@ -406,7 +405,9 @@ const ToolboxMessageTree modulesChildren[] = { ToolboxMessageTree::Node(I18n::Message::MathModule, MathModuleChildren), ToolboxMessageTree::Node(I18n::Message::CmathModule, CMathModuleChildren), ToolboxMessageTree::Node(I18n::Message::MatplotlibPyplotModule, MatplotlibPyplotModuleChildren), +#if defined(INCLUDE_ULAB) ToolboxMessageTree::Node(I18n::Message::UlabModule, UlabModuleChildren), +#endif ToolboxMessageTree::Node(I18n::Message::TurtleModule, TurtleModuleChildren), ToolboxMessageTree::Node(I18n::Message::RandomModule, RandomModuleChildren), ToolboxMessageTree::Node(I18n::Message::KandinskyModule, KandinskyModuleChildren), @@ -678,9 +679,11 @@ KDCoordinate PythonToolbox::rowHeight(int j) { bool PythonToolbox::selectLeaf(int selectedRow) { ToolboxMessageTree * node = (ToolboxMessageTree *)m_messageTreeModel->childAtIndex(selectedRow); +#if defined(INCLUDE_ULAB) if(node->text() == I18n::Message::UlabDocumentationLink){ return true; } +#endif m_selectableTableView.deselectTable(); if(node->insertedText() == I18n::Message::IonSelector){ m_ionKeys.setSender(sender()); diff --git a/build/config.mak b/build/config.mak index 97caa8945bc..4ca2f781eec 100644 --- a/build/config.mak +++ b/build/config.mak @@ -16,3 +16,4 @@ EPSILON_GETOPT ?= 0 ESCHER_LOG_EVENTS_BINARY ?= 0 THEME_NAME ?= omega_light THEME_REPO ?= local +INCLUDE_ULAB ?= 1 diff --git a/python/Makefile b/python/Makefile index 15a770c736d..6f534bf0170 100644 --- a/python/Makefile +++ b/python/Makefile @@ -154,6 +154,11 @@ port_src += $(addprefix python/port/,\ mod/turtle/modturtle.cpp \ mod/turtle/modturtle_table.c \ mod/turtle/turtle.cpp \ + mphalport.c \ +) + +ifeq ($(INCLUDE_ULAB), 1) +port_src += $(addprefix python/port/,\ mod/ulab/scipy/linalg/linalg.c \ mod/ulab/scipy/optimize/optimize.c \ mod/ulab/scipy/signal/signal.c \ @@ -180,8 +185,8 @@ port_src += $(addprefix python/port/,\ mod/ulab/user/user.c \ mod/ulab/utils/utils.c \ mod/ulab/ulab.c \ - mphalport.c \ ) +endif # Workarounds diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index eed86bcce6b..0642276c9d5 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -604,6 +604,7 @@ Q(username) Q(rename) Q(listdir) +#if defined(INCLUDE_ULAB) // ulab QSTRs Q(threshold) Q(edgeitems) @@ -721,3 +722,4 @@ Q(from_int16_buffer) Q(from_uint16_buffer) Q(from_int32_buffer) Q(from_uint32_buffer) +#endif diff --git a/python/port/mod/ulab/ulab.h b/python/port/mod/ulab/ulab.h index 032768542ef..93cb77abe1d 100644 --- a/python/port/mod/ulab/ulab.h +++ b/python/port/mod/ulab/ulab.h @@ -34,13 +34,7 @@ // Determines, whether scipy is defined in ulab. The sub-modules and functions // of scipy have to be defined separately -#ifndef ULAB_HAS_SCIPY -#if defined(DEVICE_N0100) -#define ULAB_HAS_SCIPY (0) -#else #define ULAB_HAS_SCIPY (1) -#endif -#endif // The maximum number of dimensions the firmware should be able to support // Possible values lie between 1, and 4, inclusive diff --git a/python/port/mpconfigport.h b/python/port/mpconfigport.h index 07a6e3e993d..ba9baba180a 100644 --- a/python/port/mpconfigport.h +++ b/python/port/mpconfigport.h @@ -136,6 +136,19 @@ extern const struct _mp_obj_module_t modpyplot_module; extern const struct _mp_obj_module_t modtime_module; extern const struct _mp_obj_module_t modos_module; extern const struct _mp_obj_module_t modturtle_module; + +#if !defined(INCLUDE_ULAB) + +#define MICROPY_PORT_BUILTIN_MODULES \ + { MP_ROM_QSTR(MP_QSTR_ion), MP_ROM_PTR(&modion_module) }, \ + { MP_ROM_QSTR(MP_QSTR_kandinsky), MP_ROM_PTR(&modkandinsky_module) }, \ + { MP_ROM_QSTR(MP_QSTR_matplotlib), MP_ROM_PTR(&modmatplotlib_module) }, \ + { MP_ROM_QSTR(MP_QSTR_matplotlib_dot_pyplot), MP_ROM_PTR(&modpyplot_module) }, \ + { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&modtime_module) }, \ + { MP_ROM_QSTR(MP_QSTR_os), MP_ROM_PTR(&modos_module) }, \ + { MP_ROM_QSTR(MP_QSTR_turtle), MP_ROM_PTR(&modturtle_module) }, \ + +#else extern const struct _mp_obj_module_t ulab_user_cmodule; #define MICROPY_PORT_BUILTIN_MODULES \ @@ -146,7 +159,10 @@ extern const struct _mp_obj_module_t ulab_user_cmodule; { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&modtime_module) }, \ { MP_ROM_QSTR(MP_QSTR_os), MP_ROM_PTR(&modos_module) }, \ { MP_ROM_QSTR(MP_QSTR_turtle), MP_ROM_PTR(&modturtle_module) }, \ - { MP_ROM_QSTR(MP_QSTR_ulab), MP_ROM_PTR(&ulab_user_cmodule) }, \ + { MP_ROM_QSTR(MP_QSTR_ulab), MP_ROM_PTR(&ulab_user_cmodule) }, \ + +#endif + // Enable setjmp in debug mode. This is to avoid some optimizations done // specifically for x86_64 using inline assembly, which makes the debug binary diff --git a/python/port/port.cpp b/python/port/port.cpp index e90b9de0b1e..76c70737203 100644 --- a/python/port/port.cpp +++ b/python/port/port.cpp @@ -61,7 +61,9 @@ extern "C" { #include "mphalport.h" #include "mod/turtle/modturtle.h" #include "mod/matplotlib/pyplot/modpyplot.h" +#if defined(INCLUDE_ULAB) #include "mod/ulab/ulab.h" +#endif } #include From 56f735e30220c154cf41618b51dfdcf37cb17d2c Mon Sep 17 00:00:00 2001 From: Lauryy06 <80424145+Lauryy06@users.noreply.github.com> Date: Sun, 26 Sep 2021 20:50:53 +0200 Subject: [PATCH 064/355] [github] update metric-workflow.yml --- .github/workflows/metric-workflow.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/metric-workflow.yml b/.github/workflows/metric-workflow.yml index 0cca6ba5360..3816b84d764 100644 --- a/.github/workflows/metric-workflow.yml +++ b/.github/workflows/metric-workflow.yml @@ -31,7 +31,6 @@ jobs: - name: Add comment uses: actions/github-script@v3.0.0 with: - github-token: ${{ secrets.OMEGA_ROBOT_TOKEN }} script: | await github.issues.createComment({ owner: context.repo.owner, From f9a123cc087f98779fcd5ca571c419f67b4572ad Mon Sep 17 00:00:00 2001 From: Laury Date: Tue, 28 Sep 2021 20:48:34 +0200 Subject: [PATCH 065/355] [mpy/ulab] Added support of from and to bytes array methods --- apps/code/catalog.universal.i18n | 3 +++ apps/code/python_toolbox.cpp | 20 +++++++++++--------- python/port/mod/ulab/ulab.h | 4 ++-- python/port/mpconfigport.h | 2 +- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index 2dd492f00a0..08487fcad5e 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -204,6 +204,9 @@ PythonCommandNumpyTranspose = "ndarray.transpose()" PythonCommandNumpyTransposeWithoutArg = ".transpose()" PythonCommandNumpySort = "ndarray.sort()" PythonCommandNumpySortWithoutArg = ".sort()" +PythonCommandNumpyFromBuffer = "ndarray.frombuffer(b)" +PythonCommandNumpyToBytes = "ndarray.tobytes()" +PythonCommandNumpyToBytesWithoutArg = ".tobytes()" PythonCommandNumpySetPrintOptions = "np.set_printoptions()" PythonCommandNumpyGetPrintOptions = "np.get_printoptions()" PythonCommandNumpyNdinfo = "np.ndinfo(a)" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index f78040216a3..bde91dc63ca 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -149,15 +149,17 @@ const ToolboxMessageTree NumpyNdarrayModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFull), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLinspace), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLogspace), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCopy), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDtype), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFlat), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFlatten), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyShape), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyReshape), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySize), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTranspose), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySort), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFromBuffer), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCopy, I18n::Message::Default, false, I18n::Message::PythonCommandNumpyCopyWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDtype, I18n::Message::Default, false, I18n::Message::PythonCommandNumpyDtypeWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFlat, I18n::Message::Default, false, I18n::Message::PythonCommandNumpyFlatWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFlatten, I18n::Message::Default, false, I18n::Message::PythonCommandNumpyFlattenWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyShape, I18n::Message::Default, false, I18n::Message::PythonCommandNumpyShapeWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyReshape, I18n::Message::Default, false, I18n::Message::PythonCommandNumpyReshapeWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySize, I18n::Message::Default, false, I18n::Message::PythonCommandNumpySizeWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTranspose, I18n::Message::Default, false, I18n::Message::PythonCommandNumpyTransposeWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySort, I18n::Message::Default, false, I18n::Message::PythonCommandNumpySortWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyToBytes, I18n::Message::Default, false, I18n::Message::PythonCommandNumpyToBytesWithoutArg) }; const ToolboxMessageTree NumpyFunctionsModuleChildren[] = { diff --git a/python/port/mod/ulab/ulab.h b/python/port/mod/ulab/ulab.h index 93cb77abe1d..f57db68c640 100644 --- a/python/port/mod/ulab/ulab.h +++ b/python/port/mod/ulab/ulab.h @@ -223,7 +223,7 @@ #endif #ifndef NDARRAY_HAS_TOBYTES -#define NDARRAY_HAS_TOBYTES (0) +#define NDARRAY_HAS_TOBYTES (1) #endif #ifndef NDARRAY_HAS_TRANSPOSE @@ -264,7 +264,7 @@ // frombuffer adds 600 bytes to the firmware #ifndef ULAB_NUMPY_HAS_FROMBUFFER -#define ULAB_NUMPY_HAS_FROMBUFFER (0) +#define ULAB_NUMPY_HAS_FROMBUFFER (1) #endif // functions that create an array diff --git a/python/port/mpconfigport.h b/python/port/mpconfigport.h index ba9baba180a..0da15cb9810 100644 --- a/python/port/mpconfigport.h +++ b/python/port/mpconfigport.h @@ -55,7 +55,7 @@ #define MICROPY_PY_ASYNC_AWAIT (0) // Whether to support bytearray object -#define MICROPY_PY_BUILTINS_BYTEARRAY (0) +#define MICROPY_PY_BUILTINS_BYTEARRAY (1) // Whether to support frozenset object #define MICROPY_PY_BUILTINS_FROZENSET (1) From 69d6ad520554f20987f2ead4f35b365e7981f155 Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Wed, 29 Sep 2021 18:30:30 +0200 Subject: [PATCH 066/355] Add get_keys in toolbox and fix setlocaltime description (#33) --- apps/code/catalog.de.i18n | 3 ++- apps/code/catalog.en.i18n | 3 ++- apps/code/catalog.es.i18n | 3 ++- apps/code/catalog.fr.i18n | 3 ++- apps/code/catalog.hu.i18n | 3 ++- apps/code/catalog.it.i18n | 3 ++- apps/code/catalog.nl.i18n | 3 ++- apps/code/catalog.pt.i18n | 3 ++- apps/code/catalog.universal.i18n | 1 + apps/code/python_toolbox.cpp | 4 ++-- 10 files changed, 19 insertions(+), 10 deletions(-) diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index 54bfd169786..39a87f0f560 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -62,6 +62,7 @@ PythonFloor = "Abrunden" PythonFmod = "a modulo b" PythonFrExp = "Mantisse und Exponent von x: (m,e)" PythonGamma = "Gamma-Funktion" +PythonGetKeys = "Gedrückte Tasten erhalten" PythonGetPalette = "Themenpalette erhalten" PythonGetPixel = "Farbe von Pixel (x,y) zurückgeben" PythonGetrandbits = "Ganzzahl mit k Zufallsbits" @@ -145,7 +146,7 @@ PythonSleep = "Ausführung aussetzen für t Sekunden" PythonLocalTime = "Zeit in Tupel umwandeln" PythonMktime = "Tupel in Zeit umwandeln" PythonTime = "Abrufen des aktuellen Zeitstempels" -PythonSetLocaltime = "Zeit aus einem Tupel von localtime()" +PythonSetLocaltime = "Zeit aus einem Tupel einstellen" PythonRTCmode = "Aktuellen RTC-Modus abrufen" PythonSetRTCmode = "RTC-Modus festlegen" PythonSort = "Die Liste sortieren" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index 93d28a08434..41191e4c1a9 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -62,6 +62,7 @@ PythonFloor = "Floor" PythonFmod = "a modulo b" PythonFrExp = "Mantissa and exponent of x: (m,e)" PythonGamma = "Gamma function" +PythonGetKeys = "Get keys pressed" PythonGetPalette = "Get theme palette" PythonGetPixel = "Return pixel (x,y) color" PythonGetrandbits = "Integer with k random bits" @@ -139,7 +140,7 @@ PythonSleep = "Suspend the execution for t seconds" PythonLocalTime = "Convert time into tuple" PythonMktime = "Convert tuple into time" PythonTime = "Get the current timestamp" -PythonSetLocaltime = "Set time from a tuple of localtime()" +PythonSetLocaltime = "Set time from a tuple" PythonRTCmode = "Get current RTC mode" PythonSetRTCmode = "Set RTC mode" PythonSort = "Sort the list" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index e80957719ee..c312acbb187 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -62,6 +62,7 @@ PythonFloor = "Floor" PythonFmod = "a modulo b" PythonFrExp = "Mantissa and exponent of x: (m,e)" PythonGamma = "Gamma function" +PythonGetKeys = "Obtener teclas presionadas" PythonGetPalette = "Get theme palette" PythonGetPixel = "Return pixel (x,y) color" PythonGetrandbits = "Integer with k random bits" @@ -139,7 +140,7 @@ PythonSleep = "Suspend the execution for t seconds" PythonLocalTime = "Convertir el tiempo en tupla" PythonMktime = "Convertir tupla en tiempo" PythonTime = "Obtener la marca de tiempo actual" -PythonSetLocaltime = "Establecer tiempo desde una tupla de localtime()" +PythonSetLocaltime = "Establecer tiempo desde una tupla" PythonRTCmode = "Obtener el modo RTC actual" PythonSetRTCmode = "Establecer modo RTC" PythonSort = "Sort the list" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index 196a353a486..1de09846e04 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -62,6 +62,7 @@ PythonFloor = "Partie entière" PythonFmod = "a modulo b" PythonFrExp = "Mantisse et exposant de x : (m,e)" PythonGamma = "Fonction gamma" +PythonGetKeys = "Obtenir les touches pressées" PythonGetPalette = "Obtient la palette du thème" PythonGetPixel = "Renvoie la couleur du pixel (x,y)" PythonGetrandbits = "Nombre aléatoire sur k bits" @@ -139,7 +140,7 @@ PythonSleep = "Suspend l'exécution t secondes" PythonLocalTime = "Convertir le temps en tuple" PythonMktime = "Convertir le tuple en temps" PythonTime = "Obtenir l'horodatage actuel" -PythonSetLocaltime = "Définir l'heure à partir d'un tuple de localtime()" +PythonSetLocaltime = "Définir l'heure à partir d'un tuple" PythonRTCmode = "Obtenir le mode RTC actuel" PythonSetRTCmode = "Définir le mode RTC" PythonSort = "Trie la liste" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index 02b0e584443..432dccedb92 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -62,6 +62,7 @@ PythonFloor = "Egész része" PythonFmod = "a modulo b" PythonFrExp = "X mantissája és kiállítója" PythonGamma = "Gamma funkció" +PythonGetKeys = "Billentyűk lenyomva" PythonGetPalette = "Téma paletta beszerzése" PythonGetPixel = "Visszatéríti (x,y) színét" PythonGetrandbits = "Váletlenszám visszatérítése k biten" @@ -139,7 +140,7 @@ PythonSleep = "t másodpercre meg állitani a programmot" PythonLocalTime = "Idő konvertálása csomóvá" PythonMktime = "A tuple konvertálása az időben" PythonTime = "Az aktuális időbélyeg letöltése" -PythonSetLocaltime = "Állítsd be az időt egy tufából a localtime()" +PythonSetLocaltime = "Idő beállítása egy csomóból" PythonRTCmode = "Aktuális RTC mód" PythonSetRTCmode = "RTC mód beállítása" PythonSort = "A listát rendezni" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index 2d78b4e749d..691e7913700 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -62,6 +62,7 @@ PythonFloor = "Parte intera" PythonFmod = "a modulo b" PythonFrExp = "Mantissa ed esponente di x : (m,e)" PythonGamma = "Funzione gamma" +PythonGetKeys = "Premere i tasti" PythonGetPalette = "Ottieni la tavolozza del tema" PythonGetPixel = "Restituisce colore del pixel(x,y)" PythonGetrandbits = "Numero aleatorio con k bit" @@ -145,7 +146,7 @@ PythonSleep = "Sospende l'esecuzione t secondi" PythonLocalTime = "Converti il tempo in tuple" PythonMktime = "Converti tuple in tempo" PythonTime = "Ottieni il timestamp corrente" -PythonSetLocaltime = "Imposta il tempo da una tupla di localtime()" +PythonSetLocaltime = "Imposta il tempo da una tupla" PythonRTCmode = "Ottieni la modalità RTC corrente" PythonSetRTCmode = "Imposta modalità RTC" PythonSort = "Ordina l'elenco" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index b89fb09fc0a..0947a8f55ea 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -62,6 +62,7 @@ PythonFloor = "Vloer" PythonFmod = "a modulo b" PythonFrExp = "Mantisse en exponent van x: (m,e)" PythonGamma = "Gammafunctie" +PythonGetKeys = "Get toetsen ingedrukt" PythonGetPalette = "Thema palet krijgen" PythonGetPixel = "Geef pixel (x,y) kleur (rgb)" PythonGetrandbits = "Integer met k willekeurige bits" @@ -145,7 +146,7 @@ PythonSleep = "Stel executie voor t seconden uit" PythonLocalTime = "Zet tijd om in tuple" PythonMktime = "Tuple omzetten in tijd" PythonTime = "Haal de huidige tijdstempel" -PythonSetLocaltime = "Stel de tijd in vanaf een tuple van localtime()" +PythonSetLocaltime = "Stel de tijd in vanaf een tuple" PythonRTCmode = "Huidige RTC-modus kregen" PythonSetRTCmode = "RTC-modus instellen" PythonSort = "Sorteer de lijst" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index 7437f45d5aa..7a566ddc055 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -62,6 +62,7 @@ PythonFloor = "Parte inteira" PythonFmod = "a módulo b" PythonFrExp = "Coeficiente e expoente de x: (m, e)" PythonGamma = "Função gama" +PythonGetKeys = "Obter teclas pressionadas" PythonGetPalette = "Obter paleta temática" PythonGetPixel = "Devolve a cor do pixel (x,y)" PythonGetrandbits = "Número inteiro aleatório com k bits" @@ -139,7 +140,7 @@ PythonSleep = "Suspender a execução por t segundos" PythonLocalTime = "Convert o tempo em tupla" PythonMktime = "Convert tuple em tempo" PythonTime = "Obter o estamp de tempo atual" -PythonSetLocaltime = "Definir tempo a partir de uma tupla de localtime()" +PythonSetLocaltime = "Definir tempo a partir de uma tupla" PythonRTCmode = "Obter o modo RTC atual" PythonSetRTCmode = "Definir o modo RTC" PythonSort = "Ordenar a lista" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index 08487fcad5e..50f41778f67 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -69,6 +69,7 @@ PythonCommandFloor = "floor(x)" PythonCommandFmod = "fmod(a,b)" PythonCommandFrExp = "frexp(x)" PythonCommandGamma = "gamma(x)" +PythonCommandGetKeys = "get_keys()" PythonCommandGetPalette = "get_palette()" PythonCommandGetPixel = "get_pixel(x,y)" PythonCommandGetrandbits = "getrandbits(k)" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index bde91dc63ca..57e40f0135f 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -365,8 +365,8 @@ const ToolboxMessageTree KandinskyModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillRect, I18n::Message::PythonFillRect), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillCircle, I18n::Message::PythonFillCircle), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillPolygon, I18n::Message::PythonFillPolygon), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetPalette, I18n::Message::PythonGetPalette) -}; + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetKeys, I18n::Message::PythonGetKeys), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetPalette, I18n::Message::PythonGetPalette)}; const ToolboxMessageTree IonModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportIon, I18n::Message::PythonImportIon, false), From 9636964cb99ea4824edd9f300bc7f3a95ebaeff6 Mon Sep 17 00:00:00 2001 From: Yolwoocle <54135715+Yolwoocle@users.noreply.github.com> Date: Wed, 29 Sep 2021 18:31:10 +0200 Subject: [PATCH 067/355] Better English translation (#32) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b6df31853d..71d6dc450ce 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Upsilon is a fork of Omega, an user-made OS that runs on the Numworks calculator ### Some new features - Enhancements for the Kandinsky python module -- A support for wallpapers +- Support for wallpapers - Exernal apps - A custom theme - Operator overload for python From 83c63a70117b980ed13fc5d9c9ea8f718f42ef7f Mon Sep 17 00:00:00 2001 From: Lauryy06 <80424145+Lauryy06@users.noreply.github.com> Date: Fri, 1 Oct 2021 21:37:44 +0200 Subject: [PATCH 068/355] [readme] Update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 71d6dc450ce..adf8b58a572 100644 --- a/README.md +++ b/README.md @@ -259,9 +259,9 @@ Don't forget to put your pseudo instead of `{your pseudo, max 15 char}`. If you
-If you need help, you can join our Discord server here : https://discord.gg/Q9buEMduXG +If you need help, you can join our Discord server here : https://discord.gg/NFvzdCBTQn -

Omega Banner Discord

+

Omega Banner Discord

--- ## Useful links * [Upsilon external (to install additional apps and wallpapers)](https://lauryy06.github.io/Upsilon-External/) From 9fdedecbffb30eceaeb55acb7e124b93ce7ecf7c Mon Sep 17 00:00:00 2001 From: Mino1289 <68814671+Mino1289@users.noreply.github.com> Date: Wed, 6 Oct 2021 18:26:17 +0200 Subject: [PATCH 069/355] [apps/toolbox] Order of submenus & New electricity constants (#34) --- apps/math_toolbox.cpp | 55 +++++++++++++++++++++++++++++++++++--- apps/shared.universal.i18n | 36 +++++++++++++++++++++++++ apps/toolbox.de.i18n | 21 +++++++++++++++ apps/toolbox.en.i18n | 21 +++++++++++++++ apps/toolbox.es.i18n | 21 +++++++++++++++ apps/toolbox.fr.i18n | 21 +++++++++++++++ apps/toolbox.hu.i18n | 21 +++++++++++++++ apps/toolbox.it.i18n | 21 +++++++++++++++ apps/toolbox.nl.i18n | 21 +++++++++++++++ apps/toolbox.pt.i18n | 21 +++++++++++++++ 10 files changed, 256 insertions(+), 3 deletions(-) diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index ad82cb08158..e2146ecc65f 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -711,6 +711,54 @@ const ToolboxMessageTree Electromagnetism[] = { }; +const ToolboxMessageTree Resistivity[] = { + ToolboxMessageTree::Leaf(I18n::Message::Silver, I18n::Message::Rstvt_Silver, false, I18n::Message::Rstvt_Silver), + ToolboxMessageTree::Leaf(I18n::Message::Copper, I18n::Message::Rstvt_Copper, false, I18n::Message::Rstvt_Copper), + ToolboxMessageTree::Leaf(I18n::Message::Gold, I18n::Message::Rstvt_Gold, false, I18n::Message::Rstvt_Gold), + ToolboxMessageTree::Leaf(I18n::Message::Aluminium, I18n::Message::Rstvt_Aluminium, false, I18n::Message::Rstvt_Aluminium), + ToolboxMessageTree::Leaf(I18n::Message::Calcium, I18n::Message::Rstvt_Calcium, false, I18n::Message::Rstvt_Calcium), + ToolboxMessageTree::Leaf(I18n::Message::Tungsten, I18n::Message::Rstvt_Tungsten, false, I18n::Message::Rstvt_Tungsten), + ToolboxMessageTree::Leaf(I18n::Message::Zinc, I18n::Message::Rstvt_Zinc, false, I18n::Message::Rstvt_Zinc), + ToolboxMessageTree::Leaf(I18n::Message::Cobalt, I18n::Message::Rstvt_Cobalt, false, I18n::Message::Rstvt_Cobalt), + ToolboxMessageTree::Leaf(I18n::Message::Nickel, I18n::Message::Rstvt_Nickel, false, I18n::Message::Rstvt_Nickel), + ToolboxMessageTree::Leaf(I18n::Message::Lithium, I18n::Message::Rstvt_Lithium, false, I18n::Message::Rstvt_Lithium), + ToolboxMessageTree::Leaf(I18n::Message::Iron, I18n::Message::Rstvt_Iron, false, I18n::Message::Rstvt_Iron), + ToolboxMessageTree::Leaf(I18n::Message::Platinum, I18n::Message::Rstvt_Platinum, false, I18n::Message::Rstvt_Platinum), + ToolboxMessageTree::Leaf(I18n::Message::Tin, I18n::Message::Rstvt_Tin, false, I18n::Message::Rstvt_Tin), + ToolboxMessageTree::Leaf(I18n::Message::Sea_water, I18n::Message::Rstvt_Sea_water, false, I18n::Message::Rstvt_Sea_water), + ToolboxMessageTree::Leaf(I18n::Message::Water, I18n::Message::Rstvt_Water, false, I18n::Message::Rstvt_Water), + ToolboxMessageTree::Leaf(I18n::Message::Air, I18n::Message::Rstvt_Air, false, I18n::Message::Rstvt_Air), + ToolboxMessageTree::Leaf(I18n::Message::Wood, I18n::Message::Rstvt_Wood, false, I18n::Message::Rstvt_Wood), + ToolboxMessageTree::Leaf(I18n::Message::Glass, I18n::Message::Rstvt_Glass, false, I18n::Message::Rstvt_Glass) +}; + +const ToolboxMessageTree Conductivity[] = { + ToolboxMessageTree::Leaf(I18n::Message::Silver, I18n::Message::Cndcvt_Silver, false, I18n::Message::Cndcvt_Silver), + ToolboxMessageTree::Leaf(I18n::Message::Copper, I18n::Message::Cndcvt_Copper, false, I18n::Message::Cndcvt_Copper), + ToolboxMessageTree::Leaf(I18n::Message::Gold, I18n::Message::Cndcvt_Gold, false, I18n::Message::Cndcvt_Gold), + ToolboxMessageTree::Leaf(I18n::Message::Aluminium, I18n::Message::Cndcvt_Aluminium, false, I18n::Message::Cndcvt_Aluminium), + ToolboxMessageTree::Leaf(I18n::Message::Calcium, I18n::Message::Cndcvt_Calcium, false, I18n::Message::Cndcvt_Calcium), + ToolboxMessageTree::Leaf(I18n::Message::Tungsten, I18n::Message::Cndcvt_Tungsten, false, I18n::Message::Cndcvt_Tungsten), + ToolboxMessageTree::Leaf(I18n::Message::Zinc, I18n::Message::Cndcvt_Zinc, false, I18n::Message::Cndcvt_Zinc), + ToolboxMessageTree::Leaf(I18n::Message::Cobalt, I18n::Message::Cndcvt_Cobalt, false, I18n::Message::Cndcvt_Cobalt), + ToolboxMessageTree::Leaf(I18n::Message::Nickel, I18n::Message::Cndcvt_Nickel, false, I18n::Message::Cndcvt_Nickel), + ToolboxMessageTree::Leaf(I18n::Message::Lithium, I18n::Message::Cndcvt_Lithium, false, I18n::Message::Cndcvt_Lithium), + ToolboxMessageTree::Leaf(I18n::Message::Iron, I18n::Message::Cndcvt_Iron, false, I18n::Message::Cndcvt_Iron), + ToolboxMessageTree::Leaf(I18n::Message::Platinum, I18n::Message::Cndcvt_Platinum, false, I18n::Message::Cndcvt_Platinum), + ToolboxMessageTree::Leaf(I18n::Message::Tin, I18n::Message::Cndcvt_Tin, false, I18n::Message::Cndcvt_Tin), + ToolboxMessageTree::Leaf(I18n::Message::Sea_water, I18n::Message::Cndcvt_Sea_water, false, I18n::Message::Cndcvt_Sea_water), + ToolboxMessageTree::Leaf(I18n::Message::Water, I18n::Message::Cndcvt_Water, false, I18n::Message::Cndcvt_Water), + ToolboxMessageTree::Leaf(I18n::Message::Air, I18n::Message::Cndcvt_Air, false, I18n::Message::Cndcvt_Air), + ToolboxMessageTree::Leaf(I18n::Message::Wood, I18n::Message::Cndcvt_Wood, false, I18n::Message::Cndcvt_Wood), + ToolboxMessageTree::Leaf(I18n::Message::Glass, I18n::Message::Cndcvt_Glass, false, I18n::Message::Cndcvt_Glass) +}; + +const ToolboxMessageTree Electricity[] = { + ToolboxMessageTree::Leaf(I18n::Message::ElementalChargeTag, I18n::Message::ElementalCharge, false, I18n::Message::ElementalCharge), + ToolboxMessageTree::Node(I18n::Message::ResistivityConstants, Resistivity), + ToolboxMessageTree::Node(I18n::Message::ConductivityConstants, Conductivity) +}; + const ToolboxMessageTree ParticleMass[] = { ToolboxMessageTree::Leaf(I18n::Message::ElectronMassTag, I18n::Message::ElectronMass, false, I18n::Message::ElectronMass), ToolboxMessageTree::Leaf(I18n::Message::MuonMassTag, I18n::Message::MuonMass, false, I18n::Message::MuonMass), @@ -788,6 +836,7 @@ const ToolboxMessageTree PlanckUnits[] = { const ToolboxMessageTree Physics[] = { ToolboxMessageTree::Node(I18n::Message::FundamentalConstants, FundamentalConstants), ToolboxMessageTree::Node(I18n::Message::Electromagnetism, Electromagnetism), + ToolboxMessageTree::Node(I18n::Message::Electricity, Electricity), ToolboxMessageTree::Node(I18n::Message::NuclearConstants, Nuclear), ToolboxMessageTree::Node(I18n::Message::Thermodynamics, Thermodynamics), ToolboxMessageTree::Node(I18n::Message::Gravitation, Gravitation), @@ -805,15 +854,15 @@ const ToolboxMessageTree menu[] = { ToolboxMessageTree::Leaf(I18n::Message::LogCommandWithArg, I18n::Message::BasedLogarithm), ToolboxMessageTree::Node(I18n::Message::Calculation, calculChildren), ToolboxMessageTree::Node(I18n::Message::ComplexNumber, complexChildren), - ToolboxMessageTree::Node(I18n::Message::Combinatorics, combinatoricsChildren), - ToolboxMessageTree::Node(I18n::Message::Probability, probabilityChildren), + ToolboxMessageTree::Node(I18n::Message::Unit, unitChildren), ToolboxMessageTree::Node(I18n::Message::Arithmetic, arithmeticChildren), ToolboxMessageTree::Node(I18n::Message::Matrices, matricesChildren), + ToolboxMessageTree::Node(I18n::Message::Probability, probabilityChildren), ToolboxMessageTree::Node(I18n::Message::Vectors, vectorsChildren), #if LIST_ARE_DEFINED ToolboxMessageTree::Node(I18n::Message::Lists,listsChildren), #endif - ToolboxMessageTree::Node(I18n::Message::Unit, unitChildren), + ToolboxMessageTree::Node(I18n::Message::Combinatorics, combinatoricsChildren), ToolboxMessageTree::Node(I18n::Message::RandomAndApproximation, randomAndApproximationChildren), ToolboxMessageTree::Node(I18n::Message::HyperbolicTrigonometry, trigonometryChildren), ToolboxMessageTree::Node(I18n::Message::Fluctuation, predictionChildren), diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index fb927a65962..7c42fa08839 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -458,3 +458,39 @@ HartreeConstant = "4.3597447222071·10^-18_J" MagneticFluxQuantum = "2.067833848·10^-15_Wb" ConductanceQuantum = "7.748091729·10^-5_S" CirculationQuantum = "3.6369475516·10^-4_m^2_s^-1" +Cndcvt_Silver = "6.30·10^7_S_m^-1" +Cndcvt_Copper = "5.96·10^7_S_m^-1" +Cndcvt_Gold = "4.11·10^7_S_m^-1" +Cndcvt_Aluminium = "3.77·10^7_S_m^-1" +Cndcvt_Calcium = "2.98·10^7_S_m^-1" +Cndcvt_Tungsten = "1.79·10^7_S_m^-1" +Cndcvt_Zinc = "1.69·10^7_S_m^-1" +Cndcvt_Cobalt = "1.60·10^7_S_m^-1" +Cndcvt_Nickel = "1.43·10^7_S_m^-1" +Cndcvt_Lithium = "1.08·10^7_S_m^-1" +Cndcvt_Iron = "1.00·10^7_S_m^-1" +Cndcvt_Platinum = "9.43·10^6_S_m^-1" +Cndcvt_Tin = "9.17·10^6_S_m^-1" +Cndcvt_Sea_water = "4.80_S_m^-1" +Cndcvt_Water = "5.00·10^-3_S_m^-1" +Cndcvt_Air = "1.00·10^-13_S_m^-1" +Cndcvt_Glass = "1.00·10^-13_S_m^-1" +Cndcvt_Wood = "1.00·10^-3_S_m^-1" +Rstvt_Silver = "1.59·10^-8_Ω_m" +Rstvt_Copper = "1.68·10^-8_Ω_m" +Rstvt_Gold = "2.44·10^-8_Ω_m" +Rstvt_Aluminium = "2.65·10^-8_Ω_m" +Rstvt_Calcium = "3.36·10^-8_Ω_m" +Rstvt_Tungsten = "5.60·10^-8_Ω_m" +Rstvt_Zinc = "5.90·10^-8_Ω_m" +Rstvt_Cobalt = "6.24·10^-8_Ω_m" +Rstvt_Nickel = "6.99·10^-8_Ω_m" +Rstvt_Lithium = "9.28·10^-8_Ω_m" +Rstvt_Iron = "9.70·10^-8_Ω_m" +Rstvt_Platinum = "1.06·10^-7_Ω_m" +Rstvt_Tin = "1.09·10^-7_Ω_m" +Rstvt_Sea_water = "2.1·10^-1_Ω_m" +Rstvt_Water = "1.01·10^3_Ω_m" +Rstvt_Air = "1.00·10^9_Ω_m" +Rstvt_Glass = "5.00·10^14_Ω_m" +Rstvt_Wood = "1.00·10^3_Ω_m" diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index e39e80693bc..4c2ef8e9d0e 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -430,6 +430,27 @@ AvogadroTag = "Avogadro-Konstante" GasTag = "Gaskonstante" Electromagnetism = "Elektromagnetismus" CoulombTag = "Coulomb-Konstante" +ConductivityConstants = "Leitfähigkeitskonstanten" +Electricity = "Elektrizität" +ResistivityConstants = "Konstanten der Widerstandsfähigkeit" +Silver = "Silber" +Copper = "Kupfer" +Gold = "Gold" +Aluminium = "Aluminium" +Calcium = "Kalzium" +Tungsten = "Wolfram" +Zinc = "Zink" +Cobalt = "Kobalt" +Nickel = "Nickel" +Lithium = "Lithium" +Iron = "eisen" +Platinum = "Platin" +Tin = "Zinn" +Sea_water = "Meerwasser" +Water = "Wasser" +Air = "Luft" +Glass = "Glas" +Wood = "Holz" Vacuum_permittivityTag = "Vakuum-Durchlässigkeit" Vacuum_permeabilityTag = "Vakuumdurchlässigkeit" PlanckTag = "Planck - Konstante" diff --git a/apps/toolbox.en.i18n b/apps/toolbox.en.i18n index 7e28841366a..d6c1c235937 100644 --- a/apps/toolbox.en.i18n +++ b/apps/toolbox.en.i18n @@ -430,6 +430,27 @@ AvogadroTag = "Avogadro Constant" GasTag = "Gas Constant" Electromagnetism = "Electromagnetism" CoulombTag = "Coulomb Constant" +ConductivityConstants = "Conductivity constants" +Electricity = "Electricity" +ResistivityConstants = "Resistivity Constants" +Silver = "Silver" +Copper = "Copper" +Gold = "Gold" +Aluminium = "Aluminium" +Calcium = "Calcium" +Tungsten = "Tungsten" +Zinc = "Zinc" +Cobalt = "Cobalt" +Nickel = "Nickel" +Lithium = "Lithium" +Iron = "Iron" +Platinum = "Platinum" +Tin = "Tin" +Sea_water = "Sea Water" +Water = "Water" +Air = "Air" +Glass = "Glass" +Wood = "Wood" Vacuum_permittivityTag = "Vacuum permittivity" Vacuum_permeabilityTag = "Vacuum permeability" PlanckTag = "Planck Constant" diff --git a/apps/toolbox.es.i18n b/apps/toolbox.es.i18n index a61c44d33a8..3ec2d7db5b3 100644 --- a/apps/toolbox.es.i18n +++ b/apps/toolbox.es.i18n @@ -430,6 +430,27 @@ AvogadroTag = "Constante de Avogadro" GasTag = "Constante de gas" Electromagnetism = "Electromagnetismo" CoulombTag = "Constante de Coulomb" +ConductivityConstants = "Constantes de conductividad" +Electricity = "Electricidad" +ResistivityConstants = "Constantes de resistividad" +Silver = "Plata" +Copper = "cobre" +Gold = "Oro" +Aluminium = "Aluminio" +Calcium = "Calcio" +Tungsten = "Tungsteno" +Zinc = "Zinc" +Cobalt = "Cobalto" +Nickel = "Níquel" +Lithium = "Litio" +Iron = "Hierro" +Platinum = "Platino" +Tin = "Tin" +Sea_water = "Agua de mar" +Water = "Agua" +Air = "Aire" +Glass = "Vidrio" +Wood = "Madera" Vacuum_permittivityTag = "Permisividad de vacío" Vacuum_permeabilityTag = "Permeabilidad al vacío" PlanckTag = "Constante de Planck" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index f33aa140fb2..2eb1c183ec2 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -429,6 +429,27 @@ BoltzmannTag = "Constante de Boltzmann" AvogadroTag = "Constante d'Avogadro" GasTag = "Constante des gaz parfaits" Electromagnetism = "Electromagnétisme" +ConductivityConstants = "Constantes de Conductivité" +Electricity = "Electricité" +ResistivityConstants = "Constantes de Resistivité" +Silver = "Argent" +Copper = "Cuivre" +Gold = "Or" +Aluminium = "Aluminium" +Calcium = "Calcium" +Tungsten = "Tungstène" +Zinc = "Zinc" +Cobalt = "Cobalt" +Nickel = "Nickel" +Lithium = "Lithium" +Iron = "Fer" +Platinum = "Platine" +Tin = "Étain" +Sea_water = "Eau de mer" +Water = "Eau" +Air = "Air" +Glass = "Verre" +Wood = "Bois" CoulombTag = "Constante de Coulomb" EscapeVelocity = "Vitesse de libération" EscapeVelocityFromEarth = "De la Terre" diff --git a/apps/toolbox.hu.i18n b/apps/toolbox.hu.i18n index 4cb79e66647..bfcd3a4ccaa 100644 --- a/apps/toolbox.hu.i18n +++ b/apps/toolbox.hu.i18n @@ -430,6 +430,27 @@ AvogadroTag = "Avogadro Állandó" GasTag = "Gázállandó" Electromagnetism = "Elektromágnesesség" CoulombTag = "Coulomb állandó" +ConductivityConstants = "Vezetőképességi konstansok" +Electricity = "Elektromosság" +ResistivityConstants = "Ellenállósági konstansok" +Silver = "Ezüst" +Copper = "Réz" +Gold = "Arany" +Aluminium = "Alumínium" +Calcium = "Kalcium" +Tungsten = "Wolfram" +Zinc = "Cink" +Cobalt = "Cobalt" +Nickel = "Nickel" +Lithium = "Lítium" +Iron = "Vas" +Platinum = "Platina" +Tin = "Bádog" +Sea_water = "Tengeri víz" +Water = "Víz" +Air = "Air" +Glass = "Üveg" +Wood = "Wood" Vacuum_permittivityTag = "Vákuum-engedély" Vacuum_permeabilityTag = "Vákuumpermeabilitás" PlanckTag = "Planck Állandó" diff --git a/apps/toolbox.it.i18n b/apps/toolbox.it.i18n index 56b2192474e..af1d99047f7 100644 --- a/apps/toolbox.it.i18n +++ b/apps/toolbox.it.i18n @@ -430,6 +430,27 @@ AvogadroTag = "Costanto di Avogadro" GasTag = "Costante dei gas" Electromagnetism = "Elettromagnetismo" CoulombTag = "Costante di coulomb" +ConductivityConstants = "Costanti di conducibilità" +Electricity = "Elettricità" +ResistivityConstants = "Costanti di resistività" +Silver = "Argento" +Copper = "Rame" +Gold = "Oro" +Aluminium = "Aluminio" +Calcium = "Calcio" +Tungsten = "Tungsteno" +Zinc = "Zinco" +Cobalt = "Cobalto" +Nickel = "Nichel" +Lithium = "Litio" +Iron = "Ferro" +Platinum = "Platino" +Tin = "Stagno" +Sea_water = "Acqua di mare" +Water = "Acqua" +Air = "Aria" +Glass = "Vetro" +Wood = "Legno" Vacuum_permittivityTag = "Permittività del vuoto" Vacuum_permeabilityTag = "Permeabilità al vuoto" PlanckTag = "Costante di Planck" diff --git a/apps/toolbox.nl.i18n b/apps/toolbox.nl.i18n index 87562bf745f..84a800366b4 100644 --- a/apps/toolbox.nl.i18n +++ b/apps/toolbox.nl.i18n @@ -430,6 +430,27 @@ AvogadroTag = "Avogadroconstante" GasTag = "Gasconstante" Electromagnetism = "Elektromagnetisme" CoulombTag = "Coulombconstante" +ConductivityConstants = "Geleidingsconstanten" +Electricity = "Elektriciteit" +ResistivityConstants = "Weerstandsconstanten" +Silver = "Zilver" +Copper = "Koper" +Gold = "Goud" +Aluminium = "Aluminium" +Calcium = "Calcium" +Tungsten = "Tungsten" +Zinc = "Zink" +Cobalt = "Kobalt" +Nickel = "Nikkel" +Lithium = "Lithium" +Iron = "IJzer" +Platinum = "Platina" +Tin = "Tin" +Sea_water = "Zeewater" +Water = "Water" +Air = "Lucht" +Glass = "Glas" +Wood = "Hout" Vacuum_permittivityTag = "Elektrische veldconstante" Vacuum_permeabilityTag = "Magnetische veldconstante" PlanckTag = "Planckconstante" diff --git a/apps/toolbox.pt.i18n b/apps/toolbox.pt.i18n index af3f6c824e0..41a6709dc92 100644 --- a/apps/toolbox.pt.i18n +++ b/apps/toolbox.pt.i18n @@ -430,6 +430,27 @@ AvogadroTag = "Constante Avogadro" GasTag = "Constante de gás" Electromagnetism = "Eletromagnetismo" CoulombTag = "Constante de Coulomb" +ConductivityConstants = "Constantes de condutividade" +Electricity = "Electricidade" +ResistivityConstants = "Constantes de resistividade" +Silver = "Prata" +Copper = "Cobre" +Gold = "Ouro" +Aluminium = "Alumínio" +Calcium = "Cálcio" +Tungsten = "Tungsténio" +Zinc = "Zinco" +Cobalt = "Cobalto" +Nickel = "Níquel" +Lithium = "Lítio" +Iron = "Ferro de engomar" +Platinum = "Platina" +Tin = "Lata" +Sea_water = "Água do mar" +Water = "Água" +Air = "Ar" +Glass = "Vidro" +Wood = "Madeira" Vacuum_permittivityTag = "Permissividade a vácuo" Vacuum_permeabilityTag = "Permeabilidade ao vácuo" PlanckTag = "Constante de Planck" From 553f3fc68242290f7f1718a9fa44e500aa78a117 Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Thu, 7 Oct 2021 20:36:03 +0200 Subject: [PATCH 070/355] [apps/settings] Use right key to enable setting (#35) --- apps/settings/main_controller.cpp | 2 +- apps/settings/sub_menu/accessibility_controller.cpp | 2 +- apps/settings/sub_menu/datetime_controller.cpp | 2 +- apps/settings/sub_menu/preferences_controller.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index 97e0f315282..0b41d425491 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -62,7 +62,7 @@ bool MainController::handleEvent(Ion::Events::Event event) { } if (model()->childAtIndex(selectedRow())->numberOfChildren() == 0) { if (model()->childAtIndex(selectedRow())->label() == promptMessage()) { - if (event == Ion::Events::OK || event == Ion::Events::EXE) { + if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { globalPreferences->setShowPopUp(!globalPreferences->showPopUp()); m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); return true; diff --git a/apps/settings/sub_menu/accessibility_controller.cpp b/apps/settings/sub_menu/accessibility_controller.cpp index 1df981f379d..10c824df2dd 100644 --- a/apps/settings/sub_menu/accessibility_controller.cpp +++ b/apps/settings/sub_menu/accessibility_controller.cpp @@ -25,7 +25,7 @@ bool AccessibilityController::handleEvent(Ion::Events::Event event) { int redGamma, greenGamma, blueGamma; KDIonContext::sharedContext()->gamma.gamma(redGamma, greenGamma, blueGamma); - if (event == Ion::Events::OK || event == Ion::Events::EXE) { + if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { if (selectedRow() == 0) { invertEnabled = !invertEnabled; } diff --git a/apps/settings/sub_menu/datetime_controller.cpp b/apps/settings/sub_menu/datetime_controller.cpp index f3ca0576049..1f9e801b922 100644 --- a/apps/settings/sub_menu/datetime_controller.cpp +++ b/apps/settings/sub_menu/datetime_controller.cpp @@ -26,7 +26,7 @@ DateTimeController::DateTimeController(Responder * parentResponder) : bool DateTimeController::handleEvent(Ion::Events::Event event) { bool clockEnabled = Ion::RTC::mode() != Ion::RTC::Mode::Disabled; - if (event == Ion::Events::OK || event == Ion::Events::EXE) { + if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { if (selectedRow() == 0) { clockEnabled = !clockEnabled; if (clockEnabled) { diff --git a/apps/settings/sub_menu/preferences_controller.cpp b/apps/settings/sub_menu/preferences_controller.cpp index 2aba6a8691f..dbf254966cb 100644 --- a/apps/settings/sub_menu/preferences_controller.cpp +++ b/apps/settings/sub_menu/preferences_controller.cpp @@ -27,7 +27,7 @@ void PreferencesController::didBecomeFirstResponder() { } bool PreferencesController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::OK || event == Ion::Events::EXE) { + if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { /* Generic behaviour of preference menu*/ assert(m_messageTreeModel->label() != I18n::Message::DisplayMode || selectedRow() != numberOfRows()-1); // In that case, events OK and EXE are handled by the cell setPreferenceWithValueIndex(m_messageTreeModel->label(), selectedRow()); From 9cda9d9e59f27afa91e93ddd409279322cf39e3b Mon Sep 17 00:00:00 2001 From: Faustin Date: Thu, 7 Oct 2021 21:48:19 +0200 Subject: [PATCH 071/355] [apps/code] Add a settings for autocomplete (#36) --- apps/code/python_text_area.cpp | 6 ++ apps/global_preferences.h | 4 ++ apps/settings/Makefile | 1 + apps/settings/main_controller.cpp | 13 ++-- apps/settings/main_controller.h | 3 + apps/settings/main_controller_prompt_beta.cpp | 2 +- apps/settings/main_controller_prompt_none.cpp | 2 +- .../main_controller_prompt_update.cpp | 2 +- .../sub_menu/code_options_controller.cpp | 72 +++++++++++++++++++ .../sub_menu/code_options_controller.h | 26 +++++++ 10 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 apps/settings/sub_menu/code_options_controller.cpp create mode 100644 apps/settings/sub_menu/code_options_controller.h diff --git a/apps/code/python_text_area.cpp b/apps/code/python_text_area.cpp index ab0615f3bf4..480da73048b 100644 --- a/apps/code/python_text_area.cpp +++ b/apps/code/python_text_area.cpp @@ -3,6 +3,7 @@ #include #include #include +#include "../global_preferences.h" extern "C" { #include "py/nlr.h" @@ -444,6 +445,11 @@ void PythonTextArea::addAutocompletion() { } bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIndexToUpdate) { + // If Autocomplete disable, skip this step + if(!GlobalPreferences::sharedGlobalPreferences()->autocomplete()) { + return false; + } + // The variable box should be loaded at this point const char * autocompletionTokenBeginning = nullptr; const char * autocompletionLocation = const_cast(cursorLocation()); diff --git a/apps/global_preferences.h b/apps/global_preferences.h index 4367c20f788..641e4879231 100644 --- a/apps/global_preferences.h +++ b/apps/global_preferences.h @@ -30,6 +30,8 @@ class GlobalPreferences { void setTempExamMode(ExamMode examMode); bool showPopUp() const { return m_showPopUp; } void setShowPopUp(bool showPopUp) { m_showPopUp = showPopUp; } + bool autocomplete() const { return m_autoComplete; } + void setAutocomplete(bool autocomple) { m_autoComplete = autocomple; } int brightnessLevel() const { return m_brightnessLevel; } void setBrightnessLevel(int brightnessLevel); const KDFont * font() const { return m_font; } @@ -44,6 +46,7 @@ class GlobalPreferences { m_examMode(ExamMode::Unknown), m_tempExamMode(ExamMode::Standard), m_showPopUp(true), + m_autoComplete(true), m_brightnessLevel(Ion::Backlight::MaxBrightness), m_font(KDFont::LargeFont) {} I18n::Language m_language; @@ -53,6 +56,7 @@ class GlobalPreferences { mutable ExamMode m_examMode; mutable ExamMode m_tempExamMode; bool m_showPopUp; + bool m_autoComplete; int m_brightnessLevel; const KDFont * m_font; }; diff --git a/apps/settings/Makefile b/apps/settings/Makefile index 588b95c2f1c..e0598ec4ea2 100644 --- a/apps/settings/Makefile +++ b/apps/settings/Makefile @@ -11,6 +11,7 @@ app_settings_src = $(addprefix apps/settings/,\ main_controller_prompt_update.cpp:+update \ sub_menu/about_controller.cpp \ sub_menu/accessibility_controller.cpp \ + sub_menu/code_options_controller.cpp \ sub_menu/about_controller_official.cpp:+official \ sub_menu/about_controller_non_official.cpp:-official \ sub_menu/exam_mode_controller_official.cpp:+official \ diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index 0b41d425491..2b74f96fa7c 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -17,9 +17,14 @@ constexpr SettingsMessageTree s_modelDateTimeChildren[3] = {SettingsMessageTree( constexpr SettingsMessageTree s_symbolChildren[4] = {SettingsMessageTree(I18n::Message::SymbolMultiplicationCross),SettingsMessageTree(I18n::Message::SymbolMultiplicationMiddleDot),SettingsMessageTree(I18n::Message::SymbolMultiplicationStar),SettingsMessageTree(I18n::Message::SymbolMultiplicationAutoSymbol)}; constexpr SettingsMessageTree s_symbolFunctionChildren[3] = {SettingsMessageTree(I18n::Message::SymbolDefaultFunction), SettingsMessageTree(I18n::Message::SymbolArgDefaultFunction), SettingsMessageTree(I18n::Message::SymbolArgFunction)}; constexpr SettingsMessageTree s_modelMathOptionsChildren[6] = {SettingsMessageTree(I18n::Message::AngleUnit, s_modelAngleChildren), SettingsMessageTree(I18n::Message::DisplayMode, s_modelFloatDisplayModeChildren), SettingsMessageTree(I18n::Message::EditionMode, s_modelEditionModeChildren), SettingsMessageTree(I18n::Message::SymbolFunction, s_symbolFunctionChildren), SettingsMessageTree(I18n::Message::ComplexFormat, s_modelComplexFormatChildren), SettingsMessageTree(I18n::Message::SymbolMultiplication, s_symbolChildren)}; -constexpr SettingsMessageTree s_modelFontChildren[2] = {SettingsMessageTree(I18n::Message::LargeFont), SettingsMessageTree(I18n::Message::SmallFont)}; constexpr SettingsMessageTree s_accessibilityChildren[6] = {SettingsMessageTree(I18n::Message::AccessibilityInvertColors), SettingsMessageTree(I18n::Message::AccessibilityMagnify),SettingsMessageTree(I18n::Message::AccessibilityGamma),SettingsMessageTree(I18n::Message::AccessibilityGammaRed),SettingsMessageTree(I18n::Message::AccessibilityGammaGreen),SettingsMessageTree(I18n::Message::AccessibilityGammaBlue)}; constexpr SettingsMessageTree s_contributorsChildren[23] = {SettingsMessageTree(I18n::Message::Developers), SettingsMessageTree(I18n::Message::QuentinGuidee), SettingsMessageTree(I18n::Message::JoachimLeFournis), SettingsMessageTree(I18n::Message::MaximeFriess), SettingsMessageTree(I18n::Message::JeanBaptisteBoric), SettingsMessageTree(I18n::Message::SandraSimmons), SettingsMessageTree(I18n::Message::David), SettingsMessageTree(I18n::Message::DamienNicolet), SettingsMessageTree(I18n::Message::EvannDreumont), SettingsMessageTree(I18n::Message::SzaboLevente), SettingsMessageTree(I18n::Message::VenceslasDuet), SettingsMessageTree(I18n::Message::CharlotteThomas), SettingsMessageTree(I18n::Message::AntoninLoubiere), SettingsMessageTree(I18n::Message::CyprienMejat), SettingsMessageTree(I18n::Message::BetaTesters), SettingsMessageTree(I18n::Message::TimeoArnouts), SettingsMessageTree(I18n::Message::JulieC), SettingsMessageTree(I18n::Message::LelahelHideux), SettingsMessageTree(I18n::Message::Madil), SettingsMessageTree(I18n::Message::HilaireLeRoux), SettingsMessageTree(I18n::Message::HectorNussbaumer), SettingsMessageTree(I18n::Message::RaphaelDyda), SettingsMessageTree(I18n::Message::ThibautC)}; + +// Code Settings +constexpr SettingsMessageTree s_codeChildren[2] = {SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Autocomplete)}; +constexpr SettingsMessageTree s_modelFontChildren[2] = {SettingsMessageTree(I18n::Message::LargeFont), SettingsMessageTree(I18n::Message::SmallFont)}; + + constexpr SettingsMessageTree s_modelAboutChildren[9] = {SettingsMessageTree(I18n::Message::Username), SettingsMessageTree(I18n::Message::SoftwareVersion), SettingsMessageTree(I18n::Message::UpsilonVersion), SettingsMessageTree(I18n::Message::MicroPythonVersion), SettingsMessageTree(I18n::Message::Battery), SettingsMessageTree(I18n::Message::MemUse), SettingsMessageTree(I18n::Message::SerialNumber), SettingsMessageTree(I18n::Message::FccId), SettingsMessageTree(I18n::Message::Contributors, s_contributorsChildren)}; MainController::MainController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate) : @@ -31,6 +36,7 @@ MainController::MainController(Responder * parentResponder, InputEventHandlerDel m_localizationController(this, Metric::CommonTopMargin, LocalizationController::Mode::Language), m_accessibilityController(this), m_dateTimeController(this), + m_codeOptionsController(this), m_examModeController(this), m_aboutController(this), m_preferencesController(this) @@ -103,6 +109,8 @@ bool MainController::handleEvent(Ion::Events::Event event) { subController = &m_dateTimeController; } else if (title == I18n::Message::MathOptions) { subController = &m_mathOptionsController; + } else if (title == I18n::Message::CodeApp) { + subController = &m_codeOptionsController; } else { subController = &m_preferencesController; } @@ -202,9 +210,6 @@ void MainController::willDisplayCellForIndex(HighlightCell * cell, int index) { MessageTableCellWithChevronAndMessage * myTextCell = (MessageTableCellWithChevronAndMessage *)cell; int childIndex = -1; switch (model()->childAtIndex(index)->label()) { - case I18n::Message::FontSizes: - childIndex = GlobalPreferences::sharedGlobalPreferences()->font() == KDFont::LargeFont ? 0 : 1; - break; default: break; } diff --git a/apps/settings/main_controller.h b/apps/settings/main_controller.h index 796f50bbd52..c254fb8d103 100644 --- a/apps/settings/main_controller.h +++ b/apps/settings/main_controller.h @@ -8,6 +8,7 @@ #include "sub_menu/accessibility_controller.h" #include "sub_menu/datetime_controller.h" #include "sub_menu/exam_mode_controller.h" +#include "sub_menu/code_options_controller.h" #include "sub_menu/localization_controller.h" #include "sub_menu/math_options_controller.h" #include "sub_menu/preferences_controller.h" @@ -22,6 +23,7 @@ extern const Shared::SettingsMessageTree s_symbolChildren[4]; extern const Shared::SettingsMessageTree s_symbolFunctionChildren[3]; extern const Shared::SettingsMessageTree s_modelMathOptionsChildren[6]; extern const Shared::SettingsMessageTree s_modelFontChildren[2]; +extern const Shared::SettingsMessageTree s_codeChildren[2]; extern const Shared::SettingsMessageTree s_modelDateTimeChildren[3]; extern const Shared::SettingsMessageTree s_accessibilityChildren[6]; extern const Shared::SettingsMessageTree s_contributorsChildren[23]; @@ -70,6 +72,7 @@ class MainController : public ViewController, public ListViewDataSource, public LocalizationController m_localizationController; AccessibilityController m_accessibilityController; DateTimeController m_dateTimeController; + CodeOptionsController m_codeOptionsController; ExamModeController m_examModeController; AboutController m_aboutController; PreferencesController m_preferencesController; diff --git a/apps/settings/main_controller_prompt_beta.cpp b/apps/settings/main_controller_prompt_beta.cpp index b462cc14b3a..ab9a457bdbd 100644 --- a/apps/settings/main_controller_prompt_beta.cpp +++ b/apps/settings/main_controller_prompt_beta.cpp @@ -10,10 +10,10 @@ constexpr SettingsMessageTree s_modelMenu[] = {SettingsMessageTree(I18n::Message::MathOptions, s_modelMathOptionsChildren), SettingsMessageTree(I18n::Message::Brightness), SettingsMessageTree(I18n::Message::DateTime, s_modelDateTimeChildren), - SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Language), SettingsMessageTree(I18n::Message::Country), SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), + SettingsMessageTree(I18n::Message::CodeApp, s_codeChildren), SettingsMessageTree(I18n::Message::BetaPopUp), SettingsMessageTree(I18n::Message::About, s_modelAboutChildren), SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren)}; diff --git a/apps/settings/main_controller_prompt_none.cpp b/apps/settings/main_controller_prompt_none.cpp index 965fffe0706..78224036310 100644 --- a/apps/settings/main_controller_prompt_none.cpp +++ b/apps/settings/main_controller_prompt_none.cpp @@ -13,7 +13,7 @@ constexpr SettingsMessageTree s_modelMenu[] = SettingsMessageTree(I18n::Message::Language), SettingsMessageTree(I18n::Message::Country), SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), - SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), + SettingsMessageTree(I18n::Message::CodeApp, s_codeChildren), SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; diff --git a/apps/settings/main_controller_prompt_update.cpp b/apps/settings/main_controller_prompt_update.cpp index 3c74faffc1a..22f24059e83 100644 --- a/apps/settings/main_controller_prompt_update.cpp +++ b/apps/settings/main_controller_prompt_update.cpp @@ -10,10 +10,10 @@ constexpr SettingsMessageTree s_modelMenu[] = {SettingsMessageTree(I18n::Message::MathOptions, s_modelMathOptionsChildren), SettingsMessageTree(I18n::Message::Brightness), SettingsMessageTree(I18n::Message::DateTime, s_modelDateTimeChildren), - SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Language), SettingsMessageTree(I18n::Message::Country), SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), + SettingsMessageTree(I18n::Message::CodeApp, s_codeChildren), SettingsMessageTree(I18n::Message::UpdatePopUp), SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; diff --git a/apps/settings/sub_menu/code_options_controller.cpp b/apps/settings/sub_menu/code_options_controller.cpp new file mode 100644 index 00000000000..1aff0745663 --- /dev/null +++ b/apps/settings/sub_menu/code_options_controller.cpp @@ -0,0 +1,72 @@ +#include "code_options_controller.h" +#include +#include "../../global_preferences.h" + +using namespace Shared; + +namespace Settings { + +CodeOptionsController::CodeOptionsController(Responder * parentResponder) : + GenericSubController(parentResponder), + m_preferencesController(this) +{ + for (int i = 0; i < k_totalNumberOfCell; i++) { + m_cells[i].setMessageFont(KDFont::LargeFont); + } + for (int i = 0; i < k_totalNumberOfSwitchCells; i++) { + m_switchCells[i].setMessageFont(KDFont::LargeFont); + } +} + +bool CodeOptionsController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { + switch (selectedRow()){ + case 1: + GlobalPreferences::sharedGlobalPreferences()->setAutocomplete(!GlobalPreferences::sharedGlobalPreferences()->autocomplete()); + m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); + break; + + default: + GenericSubController * subController = nullptr; + subController = &m_preferencesController; + subController->setMessageTreeModel(m_messageTreeModel->childAtIndex(selectedRow())); + StackViewController * stack = stackController(); + m_lastSelect = selectedRow(); + stack->push(subController); + break; + + } + return true; + } + return GenericSubController::handleEvent(event); +} + +HighlightCell * CodeOptionsController::reusableCell(int index, int type) { + assert(type == 0); + assert(index >= 0 && index < k_totalNumberOfCell); + return &m_cells[index]; +} + +int CodeOptionsController::reusableCellCount(int type) { + assert(type == 0); + return k_totalNumberOfCell; +} + +void CodeOptionsController::willDisplayCellForIndex(HighlightCell * cell, int index) { + GenericSubController::willDisplayCellForIndex(cell, index); + I18n::Message thisLabel = m_messageTreeModel->childAtIndex(index)->label(); + + if (thisLabel == I18n::Message::FontSizes){ + MessageTableCellWithChevronAndMessage * myTextCell = (MessageTableCellWithChevronAndMessage *)cell; + myTextCell->setMessage(thisLabel); + GlobalPreferences::sharedGlobalPreferences()->font() == KDFont::LargeFont + ? myTextCell->setSubtitle(I18n::Message::LargeFont) + : myTextCell->setSubtitle(I18n::Message::SmallFont); + } else if (thisLabel == I18n::Message::Autocomplete) { + MessageTableCellWithSwitch * mySwitchCell = (MessageTableCellWithSwitch *)cell; + SwitchView * mySwitch = (SwitchView *)mySwitchCell->accessoryView(); + mySwitch->setState(GlobalPreferences::sharedGlobalPreferences()->autocomplete()); + } +} + +} diff --git a/apps/settings/sub_menu/code_options_controller.h b/apps/settings/sub_menu/code_options_controller.h new file mode 100644 index 00000000000..44bea0a3d5b --- /dev/null +++ b/apps/settings/sub_menu/code_options_controller.h @@ -0,0 +1,26 @@ +#ifndef SETTINGS_CODE_OPTIONS_CONTROLLER_H +#define SETTINGS_CODE_OPTIONS_CONTROLLER_H + +#include "generic_sub_controller.h" +#include "preferences_controller.h" + +namespace Settings { + +class CodeOptionsController : public GenericSubController { +public: + CodeOptionsController(Responder * parentResponder); + bool handleEvent(Ion::Events::Event event) override; + HighlightCell * reusableCell(int index, int type) override; + int reusableCellCount(int type) override; + void willDisplayCellForIndex(HighlightCell * cell, int index) override; +private: + constexpr static int k_totalNumberOfCell = 1; + constexpr static int k_totalNumberOfSwitchCells = 1; + PreferencesController m_preferencesController; + MessageTableCellWithChevronAndMessage m_cells[k_totalNumberOfCell]; + MessageTableCellWithSwitch m_switchCells[k_totalNumberOfCell]; +}; + +} + +#endif From 0906dc919bf48882d3c27ba0d87750cbf85fa930 Mon Sep 17 00:00:00 2001 From: ArtichautCosmique Date: Mon, 11 Oct 2021 20:25:03 +0200 Subject: [PATCH 072/355] [mpy/kandinsky] Fix draw_string() on new line + good colors (#47) * [mpy/kandinsky] Use good colors * [kandinsky/context_text] Avoid returning to pos x=0 on new line, use arg's x coordinate instead --- kandinsky/src/context_text.cpp | 2 +- python/port/mod/kandinsky/modkandinsky.cpp | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/kandinsky/src/context_text.cpp b/kandinsky/src/context_text.cpp index c1fd73a6e8d..4278128a24a 100644 --- a/kandinsky/src/context_text.cpp +++ b/kandinsky/src/context_text.cpp @@ -29,7 +29,7 @@ KDPoint KDContext::pushOrPullString(const char * text, KDPoint p, const KDFont * codePointPointer = decoder.stringPosition(); if (codePoint == UCodePointLineFeed) { assert(position.y() < KDCOORDINATE_MAX - glyphSize.height()); - position = KDPoint(0, position.y() + glyphSize.height()); + position = KDPoint(p.x(), position.y() + glyphSize.height()); codePoint = decoder.nextCodePoint(); } else if (codePoint == UCodePointTabulation) { position = position.translatedBy(KDPoint(k_tabCharacterWidth * glyphSize.width(), 0)); diff --git a/python/port/mod/kandinsky/modkandinsky.cpp b/python/port/mod/kandinsky/modkandinsky.cpp index 29218376c27..8053979f836 100644 --- a/python/port/mod/kandinsky/modkandinsky.cpp +++ b/python/port/mod/kandinsky/modkandinsky.cpp @@ -57,12 +57,11 @@ mp_obj_t modkandinsky_set_pixel(mp_obj_t x, mp_obj_t y, mp_obj_t input) { return mp_const_none; } -//TODO Use good colors mp_obj_t modkandinsky_draw_string(size_t n_args, const mp_obj_t * args) { const char * text = mp_obj_str_get_str(args[0]); KDPoint point(mp_obj_get_int(args[1]), mp_obj_get_int(args[2])); - KDColor textColor = (n_args >= 4) ? MicroPython::Color::Parse(args[3]) : KDColorBlack; - KDColor backgroundColor = (n_args >= 5) ? MicroPython::Color::Parse(args[4]) : KDColorWhite; + KDColor textColor = (n_args >= 4) ? MicroPython::Color::Parse(args[3]) : Palette::PrimaryText; + KDColor backgroundColor = (n_args >= 5) ? MicroPython::Color::Parse(args[4]) : Palette::HomeBackground; const KDFont * font = (n_args >= 6) ? ((mp_obj_is_true(args[5])) ? KDFont::SmallFont : KDFont::LargeFont) : KDFont::LargeFont; MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); KDIonContext::sharedContext()->drawString(text, point, font, textColor, backgroundColor); From 2837b240e9ca6b985a8bfa803276ccbf171c3378 Mon Sep 17 00:00:00 2001 From: ArtichOwO Date: Wed, 7 Jul 2021 01:12:52 +0200 Subject: [PATCH 073/355] [mpy] Added sys module Removed useless "path" and "argv" --- python/port/genhdr/qstrdefs.in.h | 9 +++++++++ python/port/mpconfigport.h | 2 +- python/src/py/modsys.c | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index 0642276c9d5..b22aedf7142 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -723,3 +723,12 @@ Q(from_uint16_buffer) Q(from_int32_buffer) Q(from_uint32_buffer) #endif +// sys QSTRs +Q(sys) +Q(info) +Q(implementation) +Q(byteorder) +Q(exit) +Q(modules) +Q(print_exception) +Q(version_info) diff --git a/python/port/mpconfigport.h b/python/port/mpconfigport.h index 0da15cb9810..b2935e6360c 100644 --- a/python/port/mpconfigport.h +++ b/python/port/mpconfigport.h @@ -97,7 +97,7 @@ #define MICROPY_PY_STRUCT (0) // Whether to provide "sys" module -#define MICROPY_PY_SYS (0) +#define MICROPY_PY_SYS (1) // Whether to provide the "urandom" module #define MICROPY_PY_URANDOM (1) diff --git a/python/src/py/modsys.c b/python/src/py/modsys.c index 29fac7c3193..8e9c460c3ac 100644 --- a/python/src/py/modsys.c +++ b/python/src/py/modsys.c @@ -187,8 +187,8 @@ MP_DEFINE_CONST_FUN_OBJ_1(mp_sys_settrace_obj, mp_sys_settrace); STATIC const mp_rom_map_elem_t mp_module_sys_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_sys) }, - { MP_ROM_QSTR(MP_QSTR_path), MP_ROM_PTR(&MP_STATE_VM(mp_sys_path_obj)) }, - { MP_ROM_QSTR(MP_QSTR_argv), MP_ROM_PTR(&MP_STATE_VM(mp_sys_argv_obj)) }, + //{ MP_ROM_QSTR(MP_QSTR_path), MP_ROM_PTR(&MP_STATE_VM(mp_sys_path_obj)) }, + //{ MP_ROM_QSTR(MP_QSTR_argv), MP_ROM_PTR(&MP_STATE_VM(mp_sys_argv_obj)) }, { MP_ROM_QSTR(MP_QSTR_version), MP_ROM_PTR(&version_obj) }, { MP_ROM_QSTR(MP_QSTR_version_info), MP_ROM_PTR(&mp_sys_version_info_obj) }, { MP_ROM_QSTR(MP_QSTR_implementation), MP_ROM_PTR(&mp_sys_implementation_obj) }, From d4f0c7d3e82d7e030ad752d7c1227e3d5821f269 Mon Sep 17 00:00:00 2001 From: ArtichOwO Date: Wed, 7 Jul 2021 14:24:20 +0200 Subject: [PATCH 074/355] Added sys to the code toolbox --- apps/code/catalog.de.i18n | 8 ++++++++ apps/code/catalog.en.i18n | 8 ++++++++ apps/code/catalog.es.i18n | 8 ++++++++ apps/code/catalog.fr.i18n | 8 ++++++++ apps/code/catalog.hu.i18n | 8 ++++++++ apps/code/catalog.it.i18n | 8 ++++++++ apps/code/catalog.nl.i18n | 9 +++++++++ apps/code/catalog.pt.i18n | 8 ++++++++ apps/code/catalog.universal.i18n | 10 ++++++++++ apps/code/python_toolbox.cpp | 13 +++++++++++++ apps/code/toolbox.universal.i18n | 1 + 11 files changed, 89 insertions(+) diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index 39a87f0f560..0e5a92fda14 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -78,11 +78,19 @@ PythonImportMatplotlibPyplot = "Matplotlib.pyplot-Modul importieren" PythonImportNumpy = "Ulab.numpy-Modul importieren" PythonImportScipy = "Ulab.scipy-Modul importieren" PythonImportOs = "OS-Modul importieren" +PythonImportSys = "SYS-Modul importieren" PythonOsUname = "Informationen über das System holen" PythonOsGetlogin = "Benutzernamen holen" PythonOsRemove = "Datei namens Dateiname entfernen" PythonOsRename = "Datei umbenennen von Alt nach Neu" PythonOsListdir = "Dateien im Speicher auflisten" +PythonSysExit = "Terminate current program" +PythonSysPrintexception = "Print exception" +PythonSysByteorder = "The byte order of the system" +PythonSysImplementation = "Information about Python" +PythonSysModules = "Dictionary of loaded modules" +PythonSysVersion = "Python language version (string)" +PythonSysVersioninfo = "Python language version (tuple)" PythonImportTime = "Time-Modul importieren" PythonImportTurtle = "Turtle-Modul importieren" PythonIndex = "Index des ersten x-Vorkommens" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index 41191e4c1a9..b0ff6c78420 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -176,11 +176,19 @@ PythonTurtleWrite = "Display a text" PythonUniform = "Floating point number in [a,b]" PythonImportTime = "Import time module" PythonImportOs = "Import os module" +PythonImportSys = "Import sys module" PythonOsUname = "Get infos about the system" PythonOsGetlogin = "Get username" PythonOsRemove = "Remove file named filename" PythonOsRename = "Rename file oldname to newname" PythonOsListdir = "List files in memory" +PythonSysExit = "Terminate current program" +PythonSysPrintexception = "Print exception" +PythonSysByteorder = "The byte order of the system" +PythonSysImplementation = "Information about Python" +PythonSysModules = "Dictionary of loaded modules" +PythonSysVersion = "Python language version (string)" +PythonSysVersioninfo = "Python language version (tuple)" PythonTimePrefix = "time module function prefix" PythonTimeSleep = "Wait for n second" PythonMonotonic = "Return monotonic time" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index c312acbb187..e8c62d96737 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -176,11 +176,19 @@ PythonTurtleWrite = "Display a text" PythonUniform = "Floating point number in [a,b]" PythonImportTime = "Import time module" PythonImportOs = "Import os module" +PythonImportSys = "Import sys module" PythonOsUname = " Información del sistema " PythonOsGetlogin = "Get username" PythonOsRemove = "Eliminar un archivo" PythonOsRename = "Renombrar archivo" PythonOsListdir = "Archivos de la lista" +PythonSysExit = "Terminate current program" +PythonSysPrintexception = "Print exception" +PythonSysByteorder = "The byte order of the system" +PythonSysImplementation = "Information about Python" +PythonSysModules = "Dictionary of loaded modules" +PythonSysVersion = "Python language version (string)" +PythonSysVersioninfo = "Python language version (tuple)" PythonTimePrefix = "time module function prefix" PythonTimeSleep = "Esperar n segundos" PythonMonotonic = "Tiempo monótono de retorno" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index 1de09846e04..0fc97fd7400 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -176,11 +176,19 @@ PythonTurtleWrite = "Affiche un texte" PythonUniform = "Nombre décimal dans [a,b]" PythonImportTime = "Importation du module temps" PythonImportOs = "Importation du module os" +PythonImportSys = "Importation du module sys" PythonOsUname = "Donne des infos sur le système" PythonOsGetlogin = "Donne le nom d'utilisateur" PythonOsRemove = "Supprime le fichier nommé filename" PythonOsRename = "Renomme oldname en newname" PythonOsListdir = "Liste les fichiers" +PythonSysExit = "Termine le programme" +PythonSysPrintexception = "Imprime une exception" +PythonSysByteorder = "L'ordre des octets du système" +PythonSysImplementation = "Information sur Python" +PythonSysModules = "Dictionnaire des modules chargés" +PythonSysVersion = "Version du langage Python (string)" +PythonSysVersioninfo = "Version du langage Python (tuple)" PythonTimePrefix = "Préfixe fonction du module temps" PythonTimeSleep = "Attendre n secondes" PythonMonotonic = "Retourne le temps monotone" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index 432dccedb92..1c78c3b1336 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -200,3 +200,11 @@ PythonOsGetlogin = "Get username" PythonOsRemove = "Fájl törlése" PythonOsRename = "Fájl átnevezése" PythonOsListdir = "Fájlok listája" +PythonImportSys = "sys modul importálása" +PythonSysExit = "Terminate current program" +PythonSysPrintexception = "Print exception" +PythonSysByteorder = "The byte order of the system" +PythonSysImplementation = "Information about Python" +PythonSysModules = "Dictionary of loaded modules" +PythonSysVersion = "Python language version (string)" +PythonSysVersioninfo = "Python language version (tuple)" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index 691e7913700..5d14e7cdc00 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -85,6 +85,14 @@ PythonOsGetlogin = "Get username" PythonOsRemove = "Rimuovere un file" PythonOsRename = "Rinomina file" PythonOsListdir = "Elenca file" +PythonImportSys = "Importa modulo sys" +PythonSysExit = "Terminate current program" +PythonSysPrintexception = "Print exception" +PythonSysByteorder = "The byte order of the system" +PythonSysImplementation = "Information about Python" +PythonSysModules = "Dictionary of loaded modules" +PythonSysVersion = "Python language version (string)" +PythonSysVersioninfo = "Python language version (tuple)" PythonIndex = "Indice prima occorrenza di x" PythonInput = "Inserire un valore" PythonInsert = "Inserire x in posizione i-esima" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index 0947a8f55ea..503cb987305 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -84,6 +84,15 @@ PythonOsGetlogin = "Get username" PythonOsRemove = "Een bestand verwijderen" PythonOsRename = "Hernoem bestand" PythonOsListdir = "Lijstbestanden" +PythonImportSys = "Importeer sys module" +PythonImportSys = "Importeer sys module" +PythonSysExit = "Terminate current program" +PythonSysPrintexception = "Print exception" +PythonSysByteorder = "The byte order of the system" +PythonSysImplementation = "Information about Python" +PythonSysModules = "Dictionary of loaded modules" +PythonSysVersion = "Python language version (string)" +PythonSysVersioninfo = "Python language version (tuple)" PythonImportTurtle = "Importeer turtle module" PythonIndex = "Index van de eerste x aanwezigheden" PythonInput = "Wijs een waarde toe" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index 7a566ddc055..fd043ba0a38 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -181,6 +181,14 @@ PythonOsGetlogin = "Get username" PythonOsRemove = "Remover um ficheiro" PythonOsRename = "Renomear ficheiro" PythonOsListdir = "Listar ficheiros" +PythonImportSys = "Import sys module" +PythonSysExit = "Terminate current program" +PythonSysPrintexception = "Print exception" +PythonSysByteorder = "The byte order of the system" +PythonSysImplementation = "Information about Python" +PythonSysModules = "Dictionary of loaded modules" +PythonSysVersion = "Python language version (string)" +PythonSysVersioninfo = "Python language version (tuple)" PythonTimePrefix = "time module function prefix" PythonTimeSleep = "Wait for n second" PythonMonotonic = "Return monotonic time" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index 50f41778f67..be9ca2f03bf 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -96,6 +96,8 @@ PythonCommandImportFromScipy = "from ulab import scipy as spy" PythonCommandImportRandom = "import random" PythonCommandImportOs = "import os" PythonCommandImportFromOs = "from os import *" +PythonCommandImportSys = "import sys" +PythonCommandImportFromSys = "from sys import *" PythonCommandImportTime = "import time" PythonCommandImportTurtle = "import turtle" PythonCommandIndex = "list.index(x)" @@ -373,6 +375,14 @@ PythonOsCommandRename = "rename(oldname, newname)" PythonOsCommandRemoveWithoutArg = "remove(\x11)" PythonOsCommandRenameWithoutArg = "rename(\x11,)" PythonOsCommandListdir = "listdir()" +PythonSysCommandExit = "exit()" +PythonSysCommandPrintexception = "print_exception(exc)" +PythonSysCommandPrintexceptionWithoutArg = "print_exception(\x11)" +PythonSysCommandByteorder = "byteorder" +PythonSysCommandImplementation = "implementation" +PythonSysCommandModules = "modules" +PythonSysCommandVersion = "version" +PythonSysCommandVersioninfo = "version_info" PythonTurtleCommandBackward = "backward(x)" PythonTurtleCommandCircle = "circle(r)" PythonTurtleCommandColor = "color('c')" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 57e40f0135f..339be8840a0 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -403,6 +403,18 @@ const ToolboxMessageTree OsModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonOsCommandListdir, I18n::Message::PythonOsListdir, false) }; +const ToolboxMessageTree SysModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportSys, I18n::Message::PythonImportSys, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromSys, I18n::Message::PythonImportSys, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonSysCommandExit, I18n::Message::PythonSysExit, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonSysCommandPrintexception, I18n::Message::PythonSysPrintexception, false, I18n::Message::PythonSysCommandPrintexceptionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonSysCommandByteorder, I18n::Message::PythonSysByteorder, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonSysCommandImplementation, I18n::Message::PythonSysImplementation, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonSysCommandModules, I18n::Message::PythonSysModules, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonSysCommandVersion, I18n::Message::PythonSysVersion, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonSysCommandVersioninfo, I18n::Message::PythonSysVersioninfo, false) +}; + const ToolboxMessageTree modulesChildren[] = { ToolboxMessageTree::Node(I18n::Message::MathModule, MathModuleChildren), ToolboxMessageTree::Node(I18n::Message::CmathModule, CMathModuleChildren), @@ -415,6 +427,7 @@ const ToolboxMessageTree modulesChildren[] = { ToolboxMessageTree::Node(I18n::Message::KandinskyModule, KandinskyModuleChildren), ToolboxMessageTree::Node(I18n::Message::IonModule, IonModuleChildren), ToolboxMessageTree::Node(I18n::Message::OsModule, OsModuleChildren), + ToolboxMessageTree::Node(I18n::Message::SysModule, SysModuleChildren), ToolboxMessageTree::Node(I18n::Message::TimeModule, TimeModuleChildren) }; diff --git a/apps/code/toolbox.universal.i18n b/apps/code/toolbox.universal.i18n index 99810151a96..e7664f96cb1 100644 --- a/apps/code/toolbox.universal.i18n +++ b/apps/code/toolbox.universal.i18n @@ -13,6 +13,7 @@ ScipySignalModule = "signal" ScipySpecialModule = "special" NumpyNdarray = "ndarray" OsModule = "os" +SysModule = "sys" TimeModule = "time" TurtleModule = "turtle" UlabModule = "ulab" From 83ce9d5e8681815e06416c67bee8074e1cff8df1 Mon Sep 17 00:00:00 2001 From: Laury Date: Fri, 15 Oct 2021 14:43:35 +0200 Subject: [PATCH 075/355] [reader] Rich text support (look at README.md) --- apps/reader/README.md | 27 +++ apps/reader/read_book_controller.cpp | 25 ++- apps/reader/read_book_controller.h | 3 +- apps/reader/utility.cpp | 17 ++ apps/reader/utility.h | 5 +- apps/reader/word_wrap_view.cpp | 276 +++++++++++++++++++++++++-- apps/reader/word_wrap_view.h | 22 ++- 7 files changed, 340 insertions(+), 35 deletions(-) create mode 100644 apps/reader/README.md diff --git a/apps/reader/README.md b/apps/reader/README.md new file mode 100644 index 00000000000..a37dafd7a6e --- /dev/null +++ b/apps/reader/README.md @@ -0,0 +1,27 @@ +# Thanks +Thanks to [Gabriel79](https://github.com/Gabriel79) for the original reader app, his source code available [here](https://github.com/Gabriel79/OmegaWithReaderTutorial) and the [tutorial](https://www.codingame.com/playgrounds/55846/reader-faire-une-application-pour-omega-sur-numworks/introduction) to code it ! + +--- + +# Rich text format +Reader app supports now a rich text format : + + * `$` around a mathematical expression **without spaces** to render it + * `%` around a color-code (see below) to change the color of the text +### Color codes : +|code|color| +| --:| ---:| +|`%d%`|Default color| +|`%r%`|Red| +|`%rl%`|Light red| +|`%m%`|Magenta| +|`%t%`|Turquoise| +|`%pk%`|Pink| +|`%pp%`|Purple| +|`%b%`|Blue| +|`%bl%`|Light blue| +|`%br%`|Brown| +|`%o%`|Orange| +|`%g%`|Green| +|`%gl%`|Light green| +|`%c%`|Cyan| \ No newline at end of file diff --git a/apps/reader/read_book_controller.cpp b/apps/reader/read_book_controller.cpp index 144385918db..622389a9887 100644 --- a/apps/reader/read_book_controller.cpp +++ b/apps/reader/read_book_controller.cpp @@ -27,19 +27,21 @@ bool ReadBookController::handleEvent(Ion::Events::Event event) { m_readerView.previousPage(); return true; } - if(event == Ion::Events::Back || event == Ion::Events::Home) { - savePosition(); - } return false; } +void ReadBookController::viewDidDisappear() { + savePosition(); +} + void ReadBookController::savePosition() const { - int pageOffset = m_readerView.getPageOffset(); - Ion::Storage::Record::ErrorStatus status = Ion::Storage::sharedStorage()->createRecordWithFullName(m_file->name, &pageOffset, sizeof(pageOffset)); + BookSave save = m_readerView.getBookSave(); + + Ion::Storage::Record::ErrorStatus status = Ion::Storage::sharedStorage()->createRecordWithFullName(m_file->name, &save, sizeof(save)); if(Ion::Storage::Record::ErrorStatus::NameTaken == status) { Ion::Storage::Record::Data data; - data.buffer = &pageOffset; - data.size = sizeof(pageOffset); + data.buffer = &save; + data.size = sizeof(save); status = Ion::Storage::sharedStorage()->recordNamed(m_file->name).setValue(data); } } @@ -47,11 +49,14 @@ void ReadBookController::savePosition() const { void ReadBookController::loadPosition() { Ion::Storage::Record r = Ion::Storage::sharedStorage()->recordNamed(m_file->name); if(Ion::Storage::sharedStorage()->hasRecord(r)) { - int pageOffset = *(static_cast(r.value().buffer)); - m_readerView.setPageOffset(pageOffset); + BookSave save = *(static_cast(r.value().buffer)); + m_readerView.setBookSave(save); } else { - m_readerView.setPageOffset(0); + m_readerView.setBookSave({ + 0, + Palette::PrimaryText + }); } } diff --git a/apps/reader/read_book_controller.h b/apps/reader/read_book_controller.h index 77d4f57f03c..0bd006dc3a3 100644 --- a/apps/reader/read_book_controller.h +++ b/apps/reader/read_book_controller.h @@ -11,10 +11,9 @@ class ReadBookController : public ViewController { public: ReadBookController(Responder * parentResponder); View * view() override; - void setBook(const External::Archive::File& file); bool handleEvent(Ion::Events::Event event) override; - + void viewDidDisappear() override; void savePosition() const; void loadPosition(); private: diff --git a/apps/reader/utility.cpp b/apps/reader/utility.cpp index 123fe392067..d941039cd7f 100644 --- a/apps/reader/utility.cpp +++ b/apps/reader/utility.cpp @@ -110,4 +110,21 @@ int filesWithExtension(const char* extension, External::Archive::File* files, in } #endif +const char * EndOfPrintableWord(const char * word, const char * end) { + if (word == end) { + return word; + } + UTF8Decoder decoder(word); + CodePoint codePoint = decoder.nextCodePoint(); + const char * result = word; + while (codePoint != '\n' && codePoint != ' ' && codePoint != '%') { + result = decoder.stringPosition(); + if (result >= end) { + break; + } + codePoint = decoder.nextCodePoint(); + } + return result; +} + } \ No newline at end of file diff --git a/apps/reader/utility.h b/apps/reader/utility.h index 36e1d1b4bfe..55a6e8c3338 100644 --- a/apps/reader/utility.h +++ b/apps/reader/utility.h @@ -2,6 +2,8 @@ #define __UTILITY_H__ #include +#include +#include namespace Reader { @@ -9,6 +11,7 @@ namespace Reader bool stringEndsWith(const char* str, const char* end); int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize); void stringNCopy(char* dest, int max, const char* src, int len); - +const char * EndOfPrintableWord(const char * word, const char * end); + } #endif \ No newline at end of file diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index c8cb708cf6a..49ea133ab89 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -1,10 +1,22 @@ #include "word_wrap_view.h" #include "utility.h" +#include +#include "../shared/poincare_helpers.h" +#include namespace Reader { +WordWrapTextView::WordWrapTextView() : + PointerTextView(GlobalPreferences::sharedGlobalPreferences()->font()), + m_pageOffset(0), + m_nextPageOffset(0), + m_length(0) +{ + +} + void WordWrapTextView::nextPage() { if(m_nextPageOffset >= m_length) { return; @@ -29,12 +41,35 @@ void WordWrapTextView::previousPage() { const char * endOfWord = text() + m_pageOffset - 1; const char * startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); + KDSize textSize = KDSizeZero; + KDPoint textEndPosition(m_frame.width() - k_margin, m_frame.height() - k_margin); while(startOfWord>=text()) { startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); - endOfWord = UTF8Helper::EndOfWord(startOfWord); - KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + endOfWord = UTF8Helper::EndOfWord(startOfWord); + + if (*startOfWord == '%') { + if (updateTextColorBackward(startOfWord)) { + endOfWord = startOfWord - 1; + continue; + } + } + if (*startOfWord == '$' && *(endOfWord-1) == '$') { + const int wordMaxLength = 128; + char word[wordMaxLength]; + stringNCopy(word, wordMaxLength, startOfWord + 1, endOfWord-startOfWord-2); + Poincare::Expression expr = Poincare::Expression::Parse(word, nullptr); + if (expr.isUninitialized()) { + expr = Poincare::Undefined::Builder(); + } + Poincare::Layout layout = Shared::PoincareHelpers::CreateLayout(expr); + textSize = layout.layoutSize(); + + } + else { + textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + } KDPoint textStartPosition = KDPoint(textEndPosition.x()-textSize.width(), textEndPosition.y()); if(textStartPosition.x() < k_margin) { @@ -84,37 +119,85 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { const char * endOfFile = text() + m_length; const char * startOfWord = text() + m_pageOffset; - const char * endOfWord = UTF8Helper::EndOfWord(startOfWord); + const char * endOfWord = EndOfPrintableWord(startOfWord, endOfFile); KDPoint textPosition(k_margin, k_margin); const int wordMaxLength = 128; char word[wordMaxLength]; + Poincare::Layout layout; + + enum class ToDraw { + Text, + Expression, + Nothing + }; + + ToDraw toDraw = ToDraw::Text; + const int charWidth = m_font->glyphSize().width(); const int charHeight = m_font->glyphSize().height(); + int nextLineOffset = charHeight; + + KDSize textSize = KDSizeZero; + + while(startOfWord < endOfFile) { - KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + + if (*startOfWord == '%') { // Look for color keyword (ex '%bl%') + if (updateTextColorForward(startOfWord)) { + startOfWord = endOfWord + 1; + endOfWord = EndOfPrintableWord(startOfWord, endOfFile); + continue; + } + } + + if (*startOfWord == '$' && *(endOfWord-1) == '$') { // Look for expression + stringNCopy(word, wordMaxLength, startOfWord + 1, endOfWord-startOfWord-2); + Poincare::Expression expr = Poincare::Expression::Parse(word, nullptr); + if (expr.isUninitialized()) { + expr = Poincare::Undefined::Builder(); + } + layout = Shared::PoincareHelpers::CreateLayout(expr); + textSize = layout.layoutSize(); + toDraw = ToDraw::Expression; + } + else { + textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + stringNCopy(word, wordMaxLength, startOfWord, endOfWord-startOfWord); + toDraw = ToDraw::Text; + } + KDPoint nextTextPosition = KDPoint(textPosition.x()+textSize.width(), textPosition.y()); if(nextTextPosition.x() > m_frame.width() - k_margin) { // Right overflow - textPosition = KDPoint(k_margin, textPosition.y() + textSize.height()); + textPosition = KDPoint(k_margin, textPosition.y() + nextLineOffset); nextTextPosition = KDPoint(k_margin + textSize.width(), textPosition.y()); + nextLineOffset = charHeight; + } + if (nextLineOffset < textSize.height()) { + nextLineOffset = textSize.height(); } if(textPosition.y() + textSize.height() > m_frame.height() - k_margin) { // Bottom overflow break; } - stringNCopy(word, wordMaxLength, startOfWord, endOfWord-startOfWord); - ctx->drawString(word, textPosition, m_font, m_textColor, m_backgroundColor); + if (toDraw == ToDraw::Expression) { + layout.draw(ctx, textPosition, m_textColor); + } + else if (toDraw == ToDraw::Text) { + ctx->drawString(word, textPosition, m_font, m_textColor, m_backgroundColor); + } while(*endOfWord == ' ' || *endOfWord == '\n') { if(*endOfWord == ' ') { nextTextPosition = KDPoint(nextTextPosition.x() + charWidth, nextTextPosition.y()); } else { - nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + charHeight); + nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + nextLineOffset); + nextLineOffset = charHeight; } ++endOfWord; } @@ -123,6 +206,10 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { //two times the same word if the break below is used startOfWord = endOfWord; + if (endOfWord >= endOfFile) { + break; + } + if(nextTextPosition.y() + textSize.height() > m_frame.height() - k_margin) { // If out of page, quit break; } @@ -130,22 +217,181 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { nextTextPosition = KDPoint(k_margin, nextTextPosition.y()); } if(nextTextPosition.x() > m_frame.width() - k_margin) { // Go to line if right overflow - nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + textSize.height()); + nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + nextLineOffset); + nextLineOffset = charHeight; } textPosition = nextTextPosition; - endOfWord = UTF8Helper::EndOfWord(startOfWord); + endOfWord = EndOfPrintableWord(startOfWord+1, endOfFile); } m_nextPageOffset = startOfWord - text(); }; -int WordWrapTextView::getPageOffset() const { - return m_pageOffset; +BookSave WordWrapTextView::getBookSave() const { + return { + m_pageOffset, + m_textColor + }; } -void WordWrapTextView::setPageOffset(int o) { - m_pageOffset = o; +void WordWrapTextView::setBookSave(BookSave save) { + m_pageOffset = save.offset; + m_textColor = save.color; +} + +bool WordWrapTextView::updateTextColorForward(const char * colorStart) const { + + if (*(colorStart + 1) == '\\') { + m_textColor = Palette::PrimaryText; + return (*(colorStart + 3) == '%' || *(colorStart + 4) == '%'); + } + + int keySize = 1; + KDColor lastColor = m_textColor; + + switch (*(colorStart+1)) + { + case 'r': + if (*(colorStart+2) == 'l') { + m_textColor = Palette::RedLight; + keySize = 2; + } + else { + m_textColor = Palette::Red; + } + break; + case 'm': + m_textColor = Palette::Magenta; + break; + case 't': + m_textColor = Palette::Turquoise; + break; + case 'p': + if (*(colorStart+2) == 'k') { + m_textColor = Palette::Pink; + keySize = 2; + } + else if (*(colorStart+2) == 'p') { + m_textColor = Palette::Purple; + keySize = 2; + } + break; + case 'b': + if (*(colorStart+2) == 'r') { + m_textColor = Palette::Brown; + keySize = 2; + } + if (*(colorStart+2) == 'l') { + m_textColor = Palette::BlueLight; + keySize = 2; + } + else { + m_textColor = Palette::Blue; + } + break; + case 'o': + m_textColor = Palette::Orange; + break; + case 'g': + if (*(colorStart+2) == 'l') { + m_textColor = Palette::GreenLight; + keySize = 2; + } + else { + m_textColor = Palette::Green; + } + break; + case 'c': + m_textColor = Palette::Cyan; + break; + + default: + return false; + } + + if (*(colorStart + keySize + 1) != '%') { + m_textColor = lastColor; + return false; + } + + return true; } -} \ No newline at end of file +bool WordWrapTextView::updateTextColorBackward(const char * colorStart) const { + + if (*(colorStart++) != '\\') { + return false; + } + + int keySize = 1; + KDColor lastColor = m_textColor; + switch (*(colorStart+1)) + { + case 'r': + if (*(colorStart+2) == 'l') { + m_textColor = Palette::RedLight; + keySize = 2; + } + else { + m_textColor = Palette::Red; + } + break; + case 'm': + m_textColor = Palette::Magenta; + break; + case 't': + m_textColor = Palette::Turquoise; + break; + case 'p': + if (*(colorStart+2) == 'k') { + m_textColor = Palette::Pink; + keySize = 2; + } + else if (*(colorStart+2) == 'p') { + m_textColor = Palette::Purple; + keySize = 2; + } + break; + case 'b': + if (*(colorStart+2) == 'r') { + m_textColor = Palette::Brown; + keySize = 2; + } + if (*(colorStart+2) == 'l') { + m_textColor = Palette::BlueLight; + keySize = 2; + } + else { + m_textColor = Palette::Blue; + } + break; + case 'o': + m_textColor = Palette::Orange; + break; + case 'g': + if (*(colorStart+2) == 'l') { + m_textColor = Palette::GreenLight; + keySize = 2; + } + else { + m_textColor = Palette::Green; + } + break; + case 'c': + m_textColor = Palette::Cyan; + break; + + default: + return false; + } + + if (*(colorStart + keySize + 1) != '%') { + m_textColor = lastColor; + return false; + } + + return true; +} + +} diff --git a/apps/reader/word_wrap_view.h b/apps/reader/word_wrap_view.h index cd17964aee1..92526ec88bd 100644 --- a/apps/reader/word_wrap_view.h +++ b/apps/reader/word_wrap_view.h @@ -7,20 +7,28 @@ namespace Reader { +struct BookSave { + int offset; + KDColor color; +}; + class WordWrapTextView : public PointerTextView { public: - WordWrapTextView() : PointerTextView(GlobalPreferences::sharedGlobalPreferences()->font()) {}; + WordWrapTextView(); void drawRect(KDContext * ctx, KDRect rect) const override; void setText(const char*, int length); void nextPage(); void previousPage(); - int getPageOffset() const; - void setPageOffset(int o); -protected: - int m_pageOffset = 0; - mutable int m_nextPageOffset = 0; - int m_length = 0; + BookSave getBookSave() const; + void setBookSave(BookSave save); +private: + bool updateTextColorForward(const char * colorStart) const; + bool updateTextColorBackward(const char * colorStart) const; static const int k_margin = 10; + int m_pageOffset; + mutable int m_nextPageOffset; + int m_length; + mutable KDColor m_textColor; }; } From 9c9758fcb63a2ef0d209efa4a53279dfbaef95e3 Mon Sep 17 00:00:00 2001 From: Laury Date: Fri, 15 Oct 2021 14:50:03 +0200 Subject: [PATCH 076/355] [reader] update README --- apps/reader/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/reader/README.md b/apps/reader/README.md index a37dafd7a6e..1b160f6f442 100644 --- a/apps/reader/README.md +++ b/apps/reader/README.md @@ -11,7 +11,7 @@ Reader app supports now a rich text format : ### Color codes : |code|color| | --:| ---:| -|`%d%`|Default color| +|`%\last_color%`|Stop using last color| |`%r%`|Red| |`%rl%`|Light red| |`%m%`|Magenta| From 917ba7ce835c0cbaef7d58e1a7020c3f2797ed49 Mon Sep 17 00:00:00 2001 From: lolocomotive <49951010+lolocomotive@users.noreply.github.com> Date: Wed, 20 Oct 2021 22:20:19 +0200 Subject: [PATCH 077/355] Allow themes to override default colors (#49) --- themes/themes_manager.py | 70 ++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/themes/themes_manager.py b/themes/themes_manager.py index d7139957332..cdc5997af39 100644 --- a/themes/themes_manager.py +++ b/themes/themes_manager.py @@ -82,44 +82,52 @@ def write_palette_h(data, file_p): file_p.write("class Palette {\n") file_p.write("public:\n") + # Default values - sometimes never used + defaults = { + "YellowDark": "ffb734", + "YellowLight": "ffcc7b", + "PurpleBright": "656975", + "PurpleDark": "414147", + "GrayWhite": "f5f5f5", + "GrayBright": "ececec", + "GrayMiddle": "d9d9d9", + "GrayDark": "a7a7a7", + "GrayVeryDark": "8c8c8c", + "Select": "d4d7e0", + "SelectDark": "b0b8d8", + "WallScreen": "f7f9fa", + "WallScreenDark": "e0e6ed", + "SubTab": "b8bbc5", + "LowBattery": "f30211", + "Red": "ff000c", + "RedLight": "fe6363", + "Magenta": "ff0588", + "Turquoise": "60c1ec", + "Pink": "ffabb6", + "Blue": "5075f2", + "BlueLight": "718fee", + "Orange": "fe871f", + "Green": "50c102", + "GreenLight": "52db8f", + "Brown": "8d7350", + "Purple": "6e2d79", + "BlueishGrey": "919ea4", + "Cyan": "00ffff", + } + for key in data["colors"].keys(): if type(data["colors"][key]) is str: file_p.write(" constexpr static KDColor " + key + " = KDColor::RGB24(0x" + data["colors"][key] + ");\n") + if defaults.keys().__contains__(key): + del defaults[key] else: for sub_key in data["colors"][key].keys(): file_p.write(" constexpr static KDColor " + key + sub_key + " = KDColor::RGB24(0x" + data["colors"][key][sub_key] + ");\n") + if defaults.keys().__contains__(key+sub_key): + del defaults[key+sub_key] - # Default values - Sometimes never used - file_p.write(" constexpr static KDColor YellowDark = KDColor::RGB24(0xffb734);\n") - file_p.write(" constexpr static KDColor YellowLight = KDColor::RGB24(0xffcc7b);\n") - file_p.write(" constexpr static KDColor PurpleBright = KDColor::RGB24(0x656975);\n") - file_p.write(" constexpr static KDColor PurpleDark = KDColor::RGB24(0x414147);\n") - file_p.write(" constexpr static KDColor GrayWhite = KDColor::RGB24(0xf5f5f5);\n") - file_p.write(" constexpr static KDColor GrayBright = KDColor::RGB24(0xececec);\n") - file_p.write(" constexpr static KDColor GrayMiddle = KDColor::RGB24(0xd9d9d9);\n") - file_p.write(" constexpr static KDColor GrayDark = KDColor::RGB24(0xa7a7a7);\n") - file_p.write(" constexpr static KDColor GrayVeryDark = KDColor::RGB24(0x8c8c8c);\n") - file_p.write(" constexpr static KDColor Select = KDColor::RGB24(0xd4d7e0);\n") - file_p.write(" constexpr static KDColor SelectDark = KDColor::RGB24(0xb0b8d8);\n") - file_p.write(" constexpr static KDColor WallScreen = KDColor::RGB24(0xf7f9fa);\n") - file_p.write(" constexpr static KDColor WallScreenDark = KDColor::RGB24(0xe0e6ed);\n") - file_p.write(" constexpr static KDColor SubTab = KDColor::RGB24(0xb8bbc5);\n") - file_p.write(" constexpr static KDColor LowBattery = KDColor::RGB24(0xf30211);\n") - file_p.write(" constexpr static KDColor Red = KDColor::RGB24(0xff000c);\n") - file_p.write(" constexpr static KDColor RedLight = KDColor::RGB24(0xfe6363);\n") - file_p.write(" constexpr static KDColor Magenta = KDColor::RGB24(0xff0588);\n") - file_p.write(" constexpr static KDColor Turquoise = KDColor::RGB24(0x60c1ec);\n") - file_p.write(" constexpr static KDColor Pink = KDColor::RGB24(0xffabb6);\n") - file_p.write(" constexpr static KDColor Blue = KDColor::RGB24(0x5075f2);\n") - file_p.write(" constexpr static KDColor BlueLight = KDColor::RGB24(0x718fee);\n") - file_p.write(" constexpr static KDColor Orange = KDColor::RGB24(0xfe871f);\n") - file_p.write(" constexpr static KDColor Green = KDColor::RGB24(0x50c102);\n") - file_p.write(" constexpr static KDColor GreenLight = KDColor::RGB24(0x52db8f);\n") - file_p.write(" constexpr static KDColor Brown = KDColor::RGB24(0x8d7350);\n") - file_p.write(" constexpr static KDColor Purple = KDColor::RGB24(0x6e2d79);\n") - file_p.write(" constexpr static KDColor BlueishGrey = KDColor::RGB24(0x919ea4);\n") - file_p.write(" constexpr static KDColor Cyan = KDColorBlue;\n") - # End + for key in defaults.keys(): + file_p.write(" constexpr static KDColor " + key + " = KDColor::RGB24(0x" + defaults[key] + ");\n") file_p.write(" constexpr static KDColor DataColor[] = {Red, Blue, Green, YellowDark, Magenta, Turquoise, Pink, Orange};\n") file_p.write(" constexpr static KDColor DataColorLight[] = {RedLight, BlueLight, GreenLight, YellowLight};\n") From bd207cb845bc81cacf60e4b63fdcc8edfe7d28a7 Mon Sep 17 00:00:00 2001 From: lolocomotive <49951010+lolocomotive@users.noreply.github.com> Date: Wed, 20 Oct 2021 22:25:27 +0200 Subject: [PATCH 078/355] Replace hard-coded color values with Palette colors and use the correct palette colors (#51) --- apps/calculation/history_view_cell.cpp | 2 +- apps/code/script_node_cell.cpp | 4 ++-- apps/math_variable_box_empty_controller.cpp | 2 +- apps/shared/scrollable_multiple_expressions_view.cpp | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index 71799403c26..e30995750fb 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -81,7 +81,7 @@ void HistoryViewCell::reloadSubviewHighlight() { m_ellipsis.setHighlighted(false); if (isHighlighted()) { if (m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Input) { - m_inputView.setExpressionBackgroundColor(Palette::ListCellBackgroundSelected); + m_inputView.setExpressionBackgroundColor(Palette::Select); } else if (m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Output) { m_scrollableOutputView.evenOddCell()->setHighlighted(true); } else { diff --git a/apps/code/script_node_cell.cpp b/apps/code/script_node_cell.cpp index 0dc8675c359..b51abdf86a4 100644 --- a/apps/code/script_node_cell.cpp +++ b/apps/code/script_node_cell.cpp @@ -21,10 +21,10 @@ void ScriptNodeCell::ScriptNodeView::drawRect(KDContext * ctx, KDRect rect) cons const int nodeNameLength = m_scriptNode->nameLength(); KDSize nameSize = k_font->stringSize(nodeName, nodeNameLength); const KDCoordinate nodeNameY = k_topMargin; - ctx->drawString(nodeName, KDPoint(0, nodeNameY), k_font, KDColorBlack, backgroundColor, nodeNameLength); + ctx->drawString(nodeName, KDPoint(0, nodeNameY), k_font, Palette::PrimaryText, backgroundColor, nodeNameLength); // If it is needed, draw the parentheses if (m_scriptNode->type() == ScriptNode::Type::WithParentheses) { - ctx->drawString(ScriptNodeCell::k_parentheses, KDPoint(nameSize.width(), nodeNameY), k_font, KDColorBlack, backgroundColor); + ctx->drawString(ScriptNodeCell::k_parentheses, KDPoint(nameSize.width(), nodeNameY), k_font, Palette::PrimaryText, backgroundColor); } /* If it exists, draw the source name. If it did not fit, we would have put diff --git a/apps/math_variable_box_empty_controller.cpp b/apps/math_variable_box_empty_controller.cpp index 4c832c19557..88a306fbc77 100644 --- a/apps/math_variable_box_empty_controller.cpp +++ b/apps/math_variable_box_empty_controller.cpp @@ -5,7 +5,7 @@ MathVariableBoxEmptyController::MathVariableBoxEmptyView::MathVariableBoxEmptyView() : ModalViewEmptyView(), - m_layoutExample(0.5f, 0.5f, KDColorBlack, Palette::WallScreen) + m_layoutExample(0.5f, 0.5f, Palette::PrimaryText, Palette::WallScreen) { initMessageViews(); } diff --git a/apps/shared/scrollable_multiple_expressions_view.cpp b/apps/shared/scrollable_multiple_expressions_view.cpp index 5d0331f5422..49021e1a888 100644 --- a/apps/shared/scrollable_multiple_expressions_view.cpp +++ b/apps/shared/scrollable_multiple_expressions_view.cpp @@ -24,13 +24,13 @@ void AbstractScrollableMultipleExpressionsView::ContentCell::setHighlighted(bool // Do not call HighlightCell::setHighlighted to avoid marking all cell as dirty m_highlighted = highlight; KDColor defaultColor = backgroundColor(); - KDColor color = highlight && m_selectedSubviewPosition == SubviewPosition::Center ? Palette::ExpressionInputBackground : defaultColor; + KDColor color = highlight && m_selectedSubviewPosition == SubviewPosition::Center ? Palette::Select : defaultColor; m_centeredExpressionView.setBackgroundColor(color); - color = highlight && m_selectedSubviewPosition == SubviewPosition::Right ? Palette::ExpressionInputBackground : defaultColor; + color = highlight && m_selectedSubviewPosition == SubviewPosition::Right ? Palette::Select : defaultColor; m_rightExpressionView.setBackgroundColor(color); m_approximateSign.setBackgroundColor(defaultColor); if (leftExpressionView()) { - color = highlight && m_selectedSubviewPosition == SubviewPosition::Left ? Palette::ExpressionInputBackground : defaultColor; + color = highlight && m_selectedSubviewPosition == SubviewPosition::Left ? Palette::Select : defaultColor; leftExpressionView()->setBackgroundColor(color); } } From 3b90969b4256d4edd0fae4965a9bf5b1d5aa26fb Mon Sep 17 00:00:00 2001 From: ArtichautCosmique Date: Wed, 20 Oct 2021 22:26:01 +0200 Subject: [PATCH 079/355] [mpy] Enabled property/etc decorators (#52) --- python/port/genhdr/qstrdefs.in.h | 5 +++++ python/port/mpconfigport.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index b22aedf7142..5a6ba342636 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -363,6 +363,11 @@ Q(upper) Q(value) Q(values) Q(zip) +Q(doc) +Q(property) +Q(getter) +Q(setter) +Q(deleter) // Ion QSTR Q(ion) diff --git a/python/port/mpconfigport.h b/python/port/mpconfigport.h index b2935e6360c..af81426c347 100644 --- a/python/port/mpconfigport.h +++ b/python/port/mpconfigport.h @@ -61,7 +61,7 @@ #define MICROPY_PY_BUILTINS_FROZENSET (1) // Whether to support property object -#define MICROPY_PY_BUILTINS_PROPERTY (0) +#define MICROPY_PY_BUILTINS_PROPERTY (1) // Whether to support unicode strings #define MICROPY_PY_BUILTINS_STR_UNICODE (1) From d91ae29607cd15292db2500312b95ecd922dfd26 Mon Sep 17 00:00:00 2001 From: Lauryy06 <80424145+Lauryy06@users.noreply.github.com> Date: Wed, 20 Oct 2021 22:36:18 +0200 Subject: [PATCH 080/355] [workflow] Change sdk of emscripten for tests --- .github/workflows/ci-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 2173e152392..57dd82735c4 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -140,7 +140,7 @@ jobs: steps: - uses: numworks/setup-emscripten@v1 with: - sdk: latest-upstream + sdk: latest-fastcomp - uses: actions/checkout@v2 with: submodules: 'recursive' From e41910e2f8ba038480f651f25d88fdfd8cd8d62d Mon Sep 17 00:00:00 2001 From: Lauryy06 <80424145+Lauryy06@users.noreply.github.com> Date: Wed, 20 Oct 2021 22:38:49 +0200 Subject: [PATCH 081/355] [workflow] Re-update SDK version of emscripten --- .github/workflows/ci-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 57dd82735c4..89f9ac21f4d 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -140,7 +140,7 @@ jobs: steps: - uses: numworks/setup-emscripten@v1 with: - sdk: latest-fastcomp + sdk: 1.40.1-fastcomp - uses: actions/checkout@v2 with: submodules: 'recursive' From 0550b66c032ff6ab46c250b54a0dd6c1124bf464 Mon Sep 17 00:00:00 2001 From: Hector <54723548+Syycorax@users.noreply.github.com> Date: Wed, 20 Oct 2021 23:11:30 +0200 Subject: [PATCH 082/355] Dark Theme changes (#42) * Dark Theme changes * added reader icon for dark mode * epsilon Dark Theme changes --- themes/themes/local/epsilon_dark.json | 20 +++++++++--------- themes/themes/local/omega_dark.json | 20 +++++++++--------- .../local/omega_dark/apps/reader_icon.png | Bin 0 -> 12809 bytes 3 files changed, 20 insertions(+), 20 deletions(-) create mode 100644 themes/themes/local/omega_dark/apps/reader_icon.png diff --git a/themes/themes/local/epsilon_dark.json b/themes/themes/local/epsilon_dark.json index 22c9516425f..a40cd14d486 100644 --- a/themes/themes/local/epsilon_dark.json +++ b/themes/themes/local/epsilon_dark.json @@ -50,11 +50,11 @@ "BackgroundSelected": "2b281f", "Text": "ffffff", "Comment": "999988", - "Number": "009999", - "Keyword": "ff000c", - "Operator": "d73a49", - "String": "032f62", - "GutterViewBackground": "E4E6E7" + "Number": "1abc9c", + "Keyword": "c03535", + "Operator": "e67e22", + "String": "3498db", + "GutterViewBackground": "1f1f1f" }, "Probability": { "Curve": "ffb734", @@ -76,9 +76,9 @@ "Text": "47443a" }, "Toolbox": { - "HeaderBackground": "656975", - "HeaderText": "000000", - "HeaderBorder": "414147", + "HeaderBackground": "3b3b3b", + "HeaderText": "ffffff", + "HeaderBorder": "3b3b3b", "Background": "ffffff" }, "List": { @@ -97,11 +97,11 @@ "Text": "ffffff" }, "Tab": { - "Background": "4a4a4a", + "Background": "2e2e2e", "BackgroundSelected": "2b281f", "BackgroundActive": "000000", "BackgroundSelectedAndActive": "26272e", - "Text": "000000", + "Text": "ffffff", "TextActive": "656975" }, "SubTab": { diff --git a/themes/themes/local/omega_dark.json b/themes/themes/local/omega_dark.json index 79a72dca0bf..30bcfb95ad8 100644 --- a/themes/themes/local/omega_dark.json +++ b/themes/themes/local/omega_dark.json @@ -50,11 +50,11 @@ "BackgroundSelected": "1f1f1f", "Text": "ffffff", "Comment": "999988", - "Number": "009999", - "Keyword": "ff000c", - "Operator": "d73a49", - "String": "032f62", - "GutterViewBackground": "E4E6E7" + "Number": "1abc9c", + "Keyword": "c03535", + "Operator": "e67E22", + "String": "3498db", + "GutterViewBackground": "1f1f1f" }, "Probability": { "Curve": "00857f", @@ -76,9 +76,9 @@ "Text": "ffffff" }, "Toolbox": { - "HeaderBackground": "b5b5b5", - "HeaderText": "000000", - "HeaderBorder": "b5b5b5", + "HeaderBackground": "3b3b3b", + "HeaderText": "ffffff", + "HeaderBorder": "3b3b3b", "Background": "ffffff" }, "List": { @@ -97,11 +97,11 @@ "Text": "ffffff" }, "Tab": { - "Background": "b5b5b5", + "Background": "2e2e2e", "BackgroundSelected": "8a8a8a", "BackgroundActive": "050505", "BackgroundSelectedAndActive": "1c1c1c", - "Text": "000000", + "Text": "ffffff", "TextActive": "ffffff" }, "SubTab": { diff --git a/themes/themes/local/omega_dark/apps/reader_icon.png b/themes/themes/local/omega_dark/apps/reader_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8578f037255d0d0e276029466fc81cbc68843158 GIT binary patch literal 12809 zcmeHsXH-9c@!r4NW9;6wYppe_)~s38ySgGZ)a38sQsDvsfP0DxGFr&HAo3Ez z#zcM_#+WAo05ng0bPU|IAf7-c7X-}K0Sa{Uc7g(-UbZj*z-zuT%hrQhG``}eC0TpO zdwh1R4PgWp;_?>bWwOn^^M7^{ZaC z)&R%KZPC+lqi(R;32k!LvT5hd;_QzG-Pk=dIfBiT(c3F;2aZMJ=bt+6j=)3WuCNCn z2A%9jNj_?uZ5v`tG<~0^pHrViVMnJq3yKNkHdIVUqM*P(zw#J zGu~m18i0Qw%>B(LJZj|B8BOQICn88iKdQmVl}%E6t?Qf-sUeq!v>V+AcXo42HLx+zHcmVVg;7IC*5#Q4A%{dEY#xZ8$o(g^h}{sQvy|u?Q@X+Y>#E= z%+hiNj23cza?Odn>$&!8iEwus#;CjcFTZEYmYKs37A9eTFvf#Ia9~weK1-Oz&Ybyd ziim9du~qU&QA+PybA#z#$J|EV!^RT!3;Nb{8vmNiYyBb0^g~Y2#T?ws(6J0Z#ii`%>bNR*&acaR4np_E{0|;6YCANKw<)bWOrg+iYS%@( zJ?w5ylX|p$!SCAS&Q7gKlEj>{df)2nG?VQ!{ZZI)N%kA~CPi0xn>LQ~80#fhw~BB_ z_N~}ul=g_{_T5RJ{(KDSc~;wry$8=%up0iM3e1LQXUF233|-Q$2bh0yuvvS#T0b$) zH05e7Yu{(^L5EnSKuC*`WaNT5OJt|=fi9}TrO3TeZ`Mvdr2;bAAkZQ(6E}SjKhQzjcf$ov7^rqs^?z1$L<5CUS=M$IFTt+BR%h1u1 zd`+utA%mf4s=!|}sJ4_8O|}W*b~g%Cnb2W!2z6C0^RxwF845+}gx(_z22f_TZ#VZg z)?PVnMZMX!AO(|oF17pF${(~*=K-#0r`!*3%Bf$GTg&(DmJUafia)Jz ztU?LQgc>f;D)m+9G;ywvv_0`*G{@}qQ-zADOD56?J?;3ZBQ5EUqd()dV35k28fr?u?PQFoG z{w!VmyegxI6NOMg6*J`$CIuC3QlZ0)#^3u;w5UVb;j@*xbsmV*s`A*9`DH9{(W>p8 z2Scm&^*56;L!GP#8($yRqq=@^n!LhkxJ~x7`{pCWU{IsY^oi6?#DCG@&D|FwH9`$i zU;N@gbv3-wk?+kpubP5*7C1slUU-+Hkb7=;rPmCQmOjLx=+P`Pf#vbg&BEo7F^ImVeI=G^_Ld`B8=bO)FU!laD#NM?eSdb2^YHIcZ6;W^v z;nEqEo@(!yt>fMpX%JU?FXHq*_T{Fcv9xn)zOr-z)Px1l-UxrE)c3g+`Zh4f8N4DL zKxOdQ57VFWVn#9Z$okeZNnmB{>Jbgv3oa(9i2g)#^y){WWC9~%+;Y4xe6?Pn@sr_BHmJAJC*URhp3k)ji(Nw_2yM$xl1Q4hF5lzMN_ zBi=ke7^;2rXB2-(;z3uH>vW=vvA!ehg`RV5ZRBePgLI&ryIRN|e{4C*Iywv18uq@g z%>J#NF!^_W=2OjsG(9m71DxbC!*gDKbAwr%hQ7(qIQ9$u7>~cpUZ&yn%qidwM%tsz z=HGB(%@v~n?|k0d>fMGJ>h_T7p?k!B%BK0M!aI}# zyB_HfoqXf_^_1beTzoQ2_9;wUqJS;lGhv@&l@mHqH2eKPMT?g5bAEFa>O5SO3)@-T zQ?$(t_xwkQ{HJrKLW~3@{11;UwH^?KEq@VEQpEYj=W>9vgtu{QOTUm1k6yo5<>?j^ zWmPY?FHQ5lrTopSmfFa+k0E`ssJL;z9(1#qee#@6Nu`rQW z!ETwxzzKNCt`fv_y>b+ss8VZ0kBRl1SzRUkSLP{7DkO135W{Nj*qA#q};B~3x+yZXuY7PEJDg-_xPy?gqROWFg=yp_Tu>StB) z77|q`8KUOyrh!E`G}&2{Blw0ojRp!e%+(DPuTIfjH5)#f8$&VG4GRy96N!_j6ms>+ zam(;STwfjChjgvqIbB{8oz@Ml#J$RWcIV%4Ik$PFpZTj8`>VM9O2TxR_apU%5t>k) zV^s&@$brf}ChKcP$Qax5V&GAR^7lceDVTL=tltRQXa3PJ%IMW^BtYL_jxrxsFutP(}=BAFJW?6+4yAIxz2M>cGm2I|lHGo1~y1 zAYaeVcQ?vpDE#Y*t;TMnul6VTf8MK|g#D?d1lAXh15LZkZLA~6@_>2X& zG`r;5zU-#$QnJEEBQzXl-w-7Y&d2aHH2p=>*;<^9qE+b#4-}%)_NSsI-$4V-l3Cjx z=HP&tHg{rLSkNZU1)5cHv$uihz~Zv!P6~}@*~Ek4wtQBy*OBVs^wJj*I{9IjOjr8+ zD?5c^vUghRUNajr+#?Hl_}8 zSq}0CWHS*Yp3V4(}p8Bg4${LVnvNr%TMWuEq4N7VoCHmHfm64rf?cdpL2@m zespJG2WUah$N)`prW7+~xb1d!K!VHRf}wHHt)3-3yu>7_39I7P%B9I^L8zpC9=$#& zp8Il&9^GHgo75=UT6vr2q$dM#WqMvvu@`SFn5f7c9fYcua@yl6%`AqJzzJE2vhB0< zt>pEpFmX15$?)qw8+ft^71n|MfOM%>Z>cl$9XHL?vS|d5PXoS&MeNTQH3;yq9(P4J zY6VZcd*G|UN;0W^gLZht`aSul05f~x)~AM{d21(qw@chTMk&MREkVmbMa;(Phw!p% z8+R4>?85hEPyNwErL1)=NfKh59qN1lF}GbIiQ3wUn1&o;sz9Wt;YzdGex8!Sez>_| zt>D(8p?Q{#s6HM?V~?K zs^9&*ft2bLHc#ooixzf4u^~lc`;UxlFP2-07dR1zo2GrJgsmQ>YRRl}@*ft5B3#XA zJ)W{loi5V99W?S3{)`*I49M=xm#V0OePkUKJdBBMQO`O9NjfxCW*&x?OKG){S2f!1 zj>gD(W5%_l89Dd6>7m^0pRWSt7wyrtl{4&Ss^ zr1c%6VuxOkClF)DZF>Ot7SxdKh?>n%42yQQ#mb``YioL!>J!}}BkX}RyL6kdA?9GOr*X(4)S4VTUJFbOA|t}ol;@0Vv@ zVgMD-WD?HHwNng4;Bf$-QWYFlr~s=dJj?Zr6SBWPXYK5C9ox2tA26O}#5H@mPZk>U z6gp4NrGo8D3!L@@F~qSmp&EKq8tqc4O{Jc!1qSZ*)3RHmKI1ZLp<3wCH1RgwLaUlH zhUGS`s_)k6O0ibiN{8K;UuG!AlWI<0oO8^VIt~@~irVE61`h2rEIqc33j>7JWM6EZ zC>s`D`ZV%dSg-}U>K1&sajw$FK3EVPrUXd?I-kjNTTNTM3NMcJ23?i1b+p^R&VItY z`6I%I<)UenrPWNO%LX%f-2ayCr}mZS+)v;;!`-4(^*P$3&cr*4ZtYA9bWNCX!}IEt z_Lu#XpoM!uIQkgwfp{p7pY0c~4SF3`oU`!Zs`+3{&>%f-f z8!hGB^Iq@m$pq!c7?wfQ6QxT@JO(YgKjxoJvG;B!`3@0981-PSdLMlKIHNDlQosR7 zIyYfm%K2K`_&{Lks`Dwma9aGak%>e}wsk0Rx%-#EcvTvK@>~LbKnjNqg%b*0?Ca2= zx+nn2ae5L&+nAsi4^XX%^SsQ9@{z&9J(5!dc9Gf9bxpvf-JzfdX;*l7000=uRl}fR zagG+2G0%&VZihgxB_+i-`#h+<3j#Q?31+yntKT&0E;?ur87(g)894ja6GahG^RmCsL|yq6nS85ZKfzN~ENBkcc32UUz-g&D zxhrEN-jd}Wru<0~y61zY3r;UlyBKu>z4}0%HTm`jxKD8fKtj`!sFk!*_+?i?k#vMf znRmpJ=o~XdxNj2!5kY1JAKr(Q3e|o0I-AsF+Nt0Q=4vhNYMukU9uBe+lC4PmVf}7~ zDGL_PpH@=d`(`m4{-Cmm~Y*!s|j z#zlIuBmx@ln>5SdJJTA6<)lUQW39fqQ(arhz{pJGAmGB|l<4SjmbZewg@!S+ZX)XI zTQikb&RNvdcWICo1NVtN`LpdL!<NsM>Rcm%^v6w~|ZEl7xxAT8etixEo9$*F!;kVVC)VC9^=s4^%&?*NuHbb=Z!cGkQ=fgyzh5a-4O*W_Sq@FD&?GQN|DJ0ykf@Uba<)!C&PE4w#Jf> z^3fLOg=cusMmtv(RY#7o3U{mEVNVkJddL#lrt{7W{Rw+=q|hSi$%f%pv7nbJ!rv!& zo9J|cS`7JGy1eTB;pRK$*#3w*;<#g+^_CdEQxRnTCQN7J^d zbq#CW6rrx%)4~I+o&rY5!4x9i&WY(q<*y%3N;rlmH?ehHpx3FhEU)8ej4x$tOzF5p3Q3(43U zg|}k&?G*+h&-DY@N$7hE$x4+L91j|1i^!%jrhp5L9+q2bvi3lx7(@GXp9q}PCf$Tp zpxuwC)y>BoGc?E`~(H|0kq|u zn!Q+_TK1C3_J&_1G9}+SG-vI-%K-KlPN%e905|(@?-nGt)ah?$UJR=jf2a9)2?-_VcNzx~rLCMu%9DaZbk*9Og2nzQg%?WQRhFa~616<&Ptn z56OpjI*t}tf;n4okM`n)Z=f2d7l(qIv`47+7Wq zh5^+Jdl2XFc(3G&m|2`f;1xJX!fL`xJdBp0@neyupF%YIRt9 zmHNhBH#o+@G)z2(;o{Rg$uut=LRtb#{0*Cmc&vR#&dG}UtRHxznpq4puW={AKuPNT zezmi0?nw^Su&fjxi5#B4*ShS;rUr(Y$m+&mq-PyKO-*8N*OA$Vr;v46hg`NH8M+F5 z0|2scMcS>rnD=4%uxGgCE8i~R?@|4Hx~linW*_Rh6?n94v*uwdN@%xFrND8ia z^g>ia^b#Z8G^6^0CqVg9waN$N-IR$r7vO-)HI9a@%1is@QCO3&8K$e_aMhh=#km3h zll=;>^-{p2^1@MRNRggggT(ST0|=b~C|E3BAarW1J2XGhwEix`LXLh-t3@kweBtxY zNWoKRp36pR)C5T*sO$+`6|65l+1jL#YN?8fFOy#bxx;iYUtA8ItysarNyEw9?Goxq@Aj=! zvHM2?_Icq|l=8<)sdFtkV@b+wE z&JoHY!Hs>few{dTqPh#n~4!EsY)4H-FJmgEI@_unFLNScD zrwOe(H-Nv@3~om|Rt@I=8M^+nPIlx-AJ0`z=kp`;<&3bhB!NQ<*N@RwCm#slW`q^78fJ}=44ueU3F#+YoB}Dh%JgE zbxb+X8)E?d>OIFjl=}YIErRl*d!ixk=NdkdfuG&)N&?^ckp8F>O@FQWVX?G?`q8|O zI0y0_^$IcHgw^~EtpH2s`eO81j1%;_GK@9#BEe)6**#2d1% zbr~t_^g#VOlS*w^@G-D*#0J@ScgOXX1xM9Y5mW^Mf1s>yZ58a$ zG2U^q)<>MIzN0-l1RpM=EX#$mh6SK%=xD3=T0RwMVna723ZcEFkjnjt4@xi{?sE5A z>gkzjWf^mkxY*uFIK@O0Lp`CHa7*$E_JU1J3LTMz8!9G8jz#vnw*XJ*XBfj*gvcGw zzYV|5Q5+!&j|{cIQhqqB)bdme`Ck%Q%C~mIaOkLUR*F(g!{LA##bEgp+o}l~IT~dJ zAPx$#9m^Y35=|prE&Ku-iLG_o+FL~z#V>5Z36?0jEQF2{KWJ|br&&}~MHKTXKAl+@ z(!P;2C%2gf?O)He7E&|dcJ=bEk3_06%iOLZC;@;x6g5Oc=+{Tg}3s~AD|`VlA$z4 zC5b%Wy|6`|;~J={h*%;VIUrUD3n+(|qZ9I+7XT2G@N$A!+C$xd7Eo(jIGFCJwUZ8L zYXzp$=U3%Yb&`hK*edwAK(&3;bS!=BErqSK)6_QatjL!b8_);^6-F=9w1k5xEsU^1b3zX1@Q-l4Aj-q#n#Eq z76AwT!h~2L+}*%*bVxh!AN_N5QdRvYJlyq97La^!dO@5xxjDEv9UVFU8sX|D>wyIM zvqS$j!c_-(9L}i)bw#+lSVCnzpl~<(zd~49{xjam-NoT|I#!mPPzR_Z($y8&D)--- zlvh;M_-Di~3ao7%oqk6l$^M(Cn=R~LWc{sgzbwDg`D;gz;s3<_oA*C*|Lu(QQdJd^ zL0G!~>YkzunC@5mB31}XTPu;@MN1(*9t(Z}VGs{LL=eOWgIa)uExEWrd_uxP+?L1< zH%v(IFHnkbS2qaU68Z}Y3C>}Q#1Y~bhVb%PT7s;Ec=$kk0uUaM1s5MLh+kMp5NaXB zZzU+i`xgjx7h9w%Ar61-)h{S3Bov>eAP>Zfj|T)5LJETq0_6h#V zprr-mHzxd=9;N|5NUz-0@_1aM9 zKc4=W1P->pO+esp;fg>k|7gS&;sLe#-4HVF50j-01a1vQ&hS4a^^bbne=}S>yq0_d zf-nfkLYUtI#3zg#IAJT85Xch7&u0a(;(}Om!~Tx$ih#L!LR_Fy*2qke*&rqKI~yR& zpJcNB-QUv&`im(p9&Qjf7l?~bhlfjqM_7bUkdE{JK0QGJs2~?F3Z-l{DKxRE=&GD)c8;G{x{O&6%gSO_(ytToWGW=e+;G==l?g{e>nVQjYHD% zM;&tQL@vdg|6Ggzngvfm!riHS+%hS z;=X)tF8$8psJ(r;pIw9zy0ek0kGR!K_Ej6ZyjnW-&CbOM3?qIu0z*ed5uzcOJ@Bx} zz*J0B3J=tJ`BOR-8u1z%;3Sg$3C}Q9@Qx`&GO?g`#f+e=?5QT{u~=MhuWU(K%WiXN za@N?kG#AB(Z{K*V48wrfpZM;{g#xkjlHVVA&=X<g+=Hdse2GuC2sNSdZTI1Jv zt4=umOuK${<6gV|>f&f+<3l8gLh%GJ{66in1(y;LZYchn=mD$u35WFj{UA=xH?6JI z$y&HM+Yo`wCj_zm8}Ck+vcnR-d0ujhh`c;Ib6;%rtlsicOk8zomUI-L7K$ww`LWTD zJd(&nP&7}NRMGDVRjl|^zx7}fiFp_&gjUjw_AePgc9y5%+(hz%@Mn~ZpC zRC(+r_R)%G)!Xz!>?yVfLwc`8cW)GVH`j1ZLY)D-IR)l`6){2ho2rl2@?S(mMY+Yq z+Bk9N56)ZqqF!U$H5fIuo6{w9jbsWU^FB|Wot>pH;UJgMoNS*C$e%m!o#4C2Y1%gH zIyIUDj_T+TuWxGF=n21nHJ1IFCG39r*47pP(B9tOvW+y5TPVNTxIEp~AjL>fu%fRl z!-(CGJfqP;lCPY`!O!>A=-IR0eChB{3ky*O?0T*QJzRN+m5xg9DnD^rIXOA}{B>ls zdjguq0-2cq4aN3oHrnHufM7<&_Y=kH?xN1xTojYj)6>Vz-J32ic<|BMzjzt;A!fqH z%hIDsJny`yV{s3or%9?u?#G%ROrudKZYOkX{J6~R9HJuqJY2o3{+#{r1NmSlRBUDO z3m{PQiT<_%Z@IupzeDe~qodJvR$TJwt#%APic0R-2O^IB{DG zEwSz~qHs-Lo0_yk$yr&GG(J0-k}9&ivzUuU%#>f1POjPzPF~9%{I#gTA;f3n7A<8g zd%6Dk%IzxxcE4NMGyINaKloLqeVEqK_a)lFyUyZbtRfAT0%+ab*x~S{U&kI4Gie}X zf2O);|BT9_2Px-`jg6MWU7W{x$gD~VOoV9UglGh!sn~~JBV+OK@LDRMQ!YL=EPTSk zk%fhYVF@o~=!AT}vzT`HMH;YUf;-Z;y*0qPmAkvP!^6XTAqL9IcpPRO(SG7_k&!q% z_e6}0jip?gv-0l+uN;Lh9LVueRqp2i019j=JbcZuu>=4~WQ{LJ!@|MG?b8J#q>7K4 zyM?{KF#vX2@K!7|JZg_uSAm$AnCG(HK4^YQtR+2bvmWVd<8S1VYMvblY0Ba~JUR*= z+AK3`&bGpcltGP@u^6L|QD6wN?2la=NMb&tOm6S^w#2HZrx!m0Bf`hjL~LWM2@%M_ zXX2rhb#-+}?H8+Me+H}T>VEPQzh69Ic-F^5h%HGN8b-u^u3`Gl2}N65n_rYVOU?QP z&Dd#)8>cD0Mzu+^1x5HH+|c8_sq(T7_6K+_%`_h~1rcnG2YnmP6s$ppW@i0^gD*Mg zlNA_nvunk3@3RlV?eZPldy3U_fiKrnV-fV-4g%yx_i?23^~qn4vnW|^s(yJn*R*o9g6;XT@?wfz zXE(jUs1lh|B7<&ySCGcrHlOQebg96FgRm((j2 Date: Sun, 24 Oct 2021 19:31:14 +0200 Subject: [PATCH 083/355] [calculation] Fix bug in second degree list controller --- .../second_degree_list_controller.cpp | 206 ++++++++++++++---- .../second_degree_list_controller.h | 1 + 2 files changed, 165 insertions(+), 42 deletions(-) diff --git a/apps/calculation/additional_outputs/second_degree_list_controller.cpp b/apps/calculation/additional_outputs/second_degree_list_controller.cpp index e4cb2a0c7f0..26ad391ae6a 100644 --- a/apps/calculation/additional_outputs/second_degree_list_controller.cpp +++ b/apps/calculation/additional_outputs/second_degree_list_controller.cpp @@ -29,6 +29,12 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { Context * context = App::app()->localContext(); Preferences * preferences = Preferences::sharedPreferences(); + Poincare::ExpressionNode::ReductionContext reductionContext = Poincare::ExpressionNode::ReductionContext(context, + preferences->complexFormat(), preferences->angleUnit(), + GlobalPreferences::sharedGlobalPreferences()->unitFormat(), + ExpressionNode::ReductionTarget::SystemForApproximation, + ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, + Poincare::ExpressionNode::UnitConversion::Default); PoincareHelpers::Reduce(&m_expression, context, ExpressionNode::ReductionTarget::SystemForAnalysis); @@ -46,60 +52,108 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { Expression a = polynomialCoefficients[2]; Expression b = polynomialCoefficients[1]; Expression c = polynomialCoefficients[0]; - - bool aIsNotOne = !(a.type() == ExpressionNode::Type::Rational && static_cast(a).isOne()); - + Expression delta = Subtraction::Builder(Power::Builder(b.clone(), Rational::Builder(2)), Multiplication::Builder(Rational::Builder(4), a.clone(), c.clone())); PoincareHelpers::Simplify(&delta, context, ExpressionNode::ReductionTarget::SystemForApproximation); - Expression alpha = Opposite::Builder(Division::Builder(b.clone(), Multiplication::Builder(Rational::Builder(2), a.clone()))); - PoincareHelpers::Simplify(&alpha, context, ExpressionNode::ReductionTarget::User); + // Alpha is -b/2a, but because after we use -α, we immediately store -α=-(-b/2a)=b/2a. + Expression minusAlpha = Division::Builder(b.clone(), Multiplication::Builder(Rational::Builder(2), a.clone())); + PoincareHelpers::Reduce(&minusAlpha, context, ExpressionNode::ReductionTarget::SystemForApproximation); + + // Same thing for β + Expression minusBeta = Division::Builder(delta.clone(), Multiplication::Builder(Rational::Builder(4), a.clone())); + PoincareHelpers::Reduce(&minusBeta, context, ExpressionNode::ReductionTarget::SystemForApproximation); + + enum MultiplicationTypeForA { + Nothing, + Minus, + Parenthesis, + Normal + }; + + MultiplicationTypeForA multiplicationTypeForA; + + if (a.type() == ExpressionNode::Type::Rational && static_cast(a).isOne()) { + multiplicationTypeForA = MultiplicationTypeForA::Nothing; + } + else if(a.type() == ExpressionNode::Type::Rational && static_cast(a).isMinusOne()){ + multiplicationTypeForA = MultiplicationTypeForA::Minus; + } + else if (a.type() == ExpressionNode::Type::Addition) { + multiplicationTypeForA = MultiplicationTypeForA::Parenthesis; + } + else { + multiplicationTypeForA = MultiplicationTypeForA::Normal; + } - Expression beta = Opposite::Builder(Division::Builder(delta.clone(), Multiplication::Builder(Rational::Builder(4), a.clone()))); - PoincareHelpers::Simplify(&beta, context, ExpressionNode::ReductionTarget::User); + PoincareHelpers::Simplify(&a, context, ExpressionNode::ReductionTarget::User); /* - * Because when can't apply reduce or simplify to keep the canonised - * we must beautify the expression manually + * Because when can't apply reduce or simplify to keep the + * canonized form we must beautify the expression manually */ - Expression canonised; - if (alpha.type() == ExpressionNode::Type::Opposite) { - canonised = Addition::Builder(Symbol::Builder("x", strlen("x")), alpha.childAtIndex(0).clone()); + Expression xMinusAlphaPowerTwo; + Expression alpha = getOppositeIfExists(minusAlpha, &reductionContext); + + if (alpha.isUninitialized()) { + PoincareHelpers::Simplify(&minusAlpha, context, ExpressionNode::ReductionTarget::User); + xMinusAlphaPowerTwo = Power::Builder(Parenthesis::Builder(Addition::Builder(Symbol::Builder("x", strlen("x")), minusAlpha)), Rational::Builder(2)); } else { - canonised = Subtraction::Builder(Symbol::Builder("x", strlen("x")), alpha.clone()); + PoincareHelpers::Simplify(&alpha, context, ExpressionNode::ReductionTarget::User); + xMinusAlphaPowerTwo = Power::Builder(Parenthesis::Builder(Subtraction::Builder(Symbol::Builder("x", strlen("x")), alpha)), Rational::Builder(2)); } - canonised = Power::Builder(Parenthesis::Builder(canonised.clone()), Rational::Builder(2)); - if (aIsNotOne) { - canonised = Multiplication::Builder(a.clone(), canonised.clone()); + + Expression xMinusAlphaPowerTwoWithFactor; + + switch (multiplicationTypeForA) + { + case MultiplicationTypeForA::Nothing: + xMinusAlphaPowerTwoWithFactor = xMinusAlphaPowerTwo; + break; + case MultiplicationTypeForA::Minus: + xMinusAlphaPowerTwoWithFactor = Multiplication::Builder(a.clone(), xMinusAlphaPowerTwo); + break; + case MultiplicationTypeForA::Parenthesis: + xMinusAlphaPowerTwoWithFactor = Multiplication::Builder(Parenthesis::Builder(a.clone()), xMinusAlphaPowerTwo); + break; + case MultiplicationTypeForA::Normal: + xMinusAlphaPowerTwoWithFactor = Multiplication::Builder(a.clone(), xMinusAlphaPowerTwo); + break; + default: + assert(false); + break; } - if (beta.type() == ExpressionNode::Type::Opposite) { - canonised = Subtraction::Builder(canonised.clone(), beta.childAtIndex(0).clone()); + + Expression canonized; + Expression beta = getOppositeIfExists(minusBeta, &reductionContext); + if (beta.isUninitialized()) { + PoincareHelpers::Simplify(&minusBeta, context, ExpressionNode::ReductionTarget::User); + canonized = Subtraction::Builder(xMinusAlphaPowerTwoWithFactor, minusBeta); } else { - canonised = Addition::Builder(canonised.clone(), beta.clone()); + PoincareHelpers::Simplify(&beta, context, ExpressionNode::ReductionTarget::User); + canonized = Addition::Builder(xMinusAlphaPowerTwoWithFactor, beta); } - Expression x0; Expression x1; - if (delta.nullStatus(context) == ExpressionNode::NullStatus::Null) { // x0 = x1 = -b/(2a) - x0 = Division::Builder(Opposite::Builder(b), Multiplication::Builder(Rational::Builder(2), a)); + x0 = Division::Builder(Opposite::Builder(b.clone()), Multiplication::Builder(Rational::Builder(2), a.clone())); m_numberOfSolutions = 1; - PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::User); + PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::SystemForApproximation); } else { // x0 = (-b-sqrt(delta))/(2a) x0 = Division::Builder(Subtraction::Builder(Opposite::Builder(b.clone()), SquareRoot::Builder(delta.clone())), Multiplication::Builder(Rational::Builder(2), a.clone())); // x1 = (-b+sqrt(delta))/(2a) - x1 = Division::Builder(Addition::Builder(Opposite::Builder(b), SquareRoot::Builder(delta.clone())), Multiplication::Builder(Rational::Builder(2), a)); + x1 = Division::Builder(Addition::Builder(Opposite::Builder(b.clone()), SquareRoot::Builder(delta.clone())), Multiplication::Builder(Rational::Builder(2), a.clone())); m_numberOfSolutions = 2; - PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::User); - PoincareHelpers::Simplify(&x1, context, ExpressionNode::ReductionTarget::User); + PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::SystemForApproximation); + PoincareHelpers::Simplify(&x1, context, ExpressionNode::ReductionTarget::SystemForApproximation); if (x0.type() == ExpressionNode::Type::Unreal) { assert(x1.type() == ExpressionNode::Type::Unreal); m_numberOfSolutions = 0; @@ -109,40 +163,87 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { Expression factorized; if (m_numberOfSolutions == 2) { - if (x0.type() == ExpressionNode::Type::Opposite) { - factorized = Parenthesis::Builder(Addition::Builder(Symbol::Builder("x", strlen("x")), x0.childAtIndex(0).clone())); + Expression firstFactor; + Expression secondFactor; + + Expression x0Opposite = getOppositeIfExists(x0, &reductionContext); + if (x0Opposite.isUninitialized()) { + PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::User); + firstFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0); } else { - factorized = Parenthesis::Builder(Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0.clone())); + PoincareHelpers::Simplify(&x0Opposite, context, ExpressionNode::ReductionTarget::User); + firstFactor = Addition::Builder(Symbol::Builder("x", strlen("x")), x0Opposite); } - - if (x1.type() == ExpressionNode::Type::Opposite) { - factorized = Multiplication::Builder(factorized.clone(), Parenthesis::Builder(Addition::Builder(Symbol::Builder("x", strlen("x")), x1.childAtIndex(0).clone()))); + if (x0.type() == ExpressionNode::Type::Opposite) { + factorized = Parenthesis::Builder(Addition::Builder(Symbol::Builder("x", strlen("x")), x0.childAtIndex(0).clone())); + } + + Expression x1Opposite = getOppositeIfExists(x1, &reductionContext); + if (x1Opposite.isUninitialized()) { + PoincareHelpers::Simplify(&x1, context, ExpressionNode::ReductionTarget::User); + secondFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x1); } else { - factorized = Multiplication::Builder(factorized.clone(), Parenthesis::Builder(Subtraction::Builder(Symbol::Builder("x", strlen("x")), x1.clone()))); + PoincareHelpers::Simplify(&x1Opposite, context, ExpressionNode::ReductionTarget::User); + secondFactor = Addition::Builder(Symbol::Builder("x", strlen("x")), x1Opposite); } - if (aIsNotOne) { - factorized = Multiplication::Builder(a.clone(), factorized.clone()); + Expression solutionProduct = Multiplication::Builder(Parenthesis::Builder(firstFactor), Parenthesis::Builder(secondFactor)); + switch (multiplicationTypeForA) + { + case MultiplicationTypeForA::Nothing: + factorized = solutionProduct; + break; + case MultiplicationTypeForA::Minus: + factorized = Multiplication::Builder(a.clone(), solutionProduct); + break; + case MultiplicationTypeForA::Parenthesis: + factorized = Multiplication::Builder(Parenthesis::Builder(a.clone()), solutionProduct); + break; + case MultiplicationTypeForA::Normal: + factorized = Multiplication::Builder(a.clone(), solutionProduct); + break; + default: + assert(false); + break; } } else if (m_numberOfSolutions == 1) { - if (x0.type() == ExpressionNode::Type::Opposite) { - factorized = Power::Builder(Parenthesis::Builder(Addition::Builder(Symbol::Builder("x", strlen("x")), x0.childAtIndex(0).clone())), Rational::Builder(2)); + Expression x0Opposite = getOppositeIfExists(x0, &reductionContext); + Expression factor; + if (x0Opposite.isUninitialized()) { + PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::User); + factor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0); } else { - factorized = Power::Builder(Parenthesis::Builder(Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0.clone())), Rational::Builder(2)); + PoincareHelpers::Simplify(&x0Opposite, context, ExpressionNode::ReductionTarget::User); + factor = Addition::Builder(Symbol::Builder("x", strlen("x")), x0Opposite); } - - if (aIsNotOne) { - factorized = Multiplication::Builder(a.clone(), factorized.clone()); + Expression solutionProduct = Power::Builder(Parenthesis::Builder(factor), Rational::Builder(2)); + switch (multiplicationTypeForA) + { + case MultiplicationTypeForA::Nothing: + factorized = solutionProduct; + break; + case MultiplicationTypeForA::Minus: + factorized = Multiplication::Builder(a.clone(), solutionProduct); + break; + case MultiplicationTypeForA::Parenthesis: + factorized = Multiplication::Builder(Parenthesis::Builder(a.clone()), solutionProduct); + break; + case MultiplicationTypeForA::Normal: + factorized = Multiplication::Builder(a.clone(), solutionProduct); + break; + default: + assert(false); + break; } } PoincareHelpers::Simplify(&delta, context, ExpressionNode::ReductionTarget::User); - m_layouts[0] = PoincareHelpers::CreateLayout(canonised); + m_layouts[0] = PoincareHelpers::CreateLayout(canonized); if (m_numberOfSolutions > 0) { m_layouts[1] = PoincareHelpers::CreateLayout(factorized); m_layouts[2] = PoincareHelpers::CreateLayout(delta); @@ -156,6 +257,27 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { } } +Expression SecondDegreeListController::getOppositeIfExists(Expression e, Poincare::ExpressionNode::ReductionContext * reductionContext) { + if (e.isNumber() && e.sign(reductionContext->context()) == ExpressionNode::Sign::Negative) { + Number n = static_cast(e); + return std::move(n.setSign(ExpressionNode::Sign::Positive)); + } + else if (e.type() == ExpressionNode::Type::Multiplication && e.numberOfChildren() > 0 && e.childAtIndex(0).isNumber() && e.childAtIndex(0).sign(reductionContext->context()) == ExpressionNode::Sign::Negative) { + Multiplication m = static_cast(e); + if (m.childAtIndex(0).type() == ExpressionNode::Type::Rational && static_cast(e).isMinusOne()) { + // The negative numeral factor is -1, we just remove it + m.removeChildAtIndexInPlace(0); + } else { + Expression firstChild = m.childAtIndex(0); + Number n = static_cast(firstChild); + m.childAtIndex(0).setChildrenInPlace(n.setSign(ExpressionNode::Sign::Positive)); + } + PoincareHelpers::Simplify(&m, reductionContext->context(), ExpressionNode::ReductionTarget::User); + return std::move(m); + } + return Expression(); +} + I18n::Message SecondDegreeListController::messageAtIndex(int index) { if (m_numberOfSolutions > 0) { if (index == 0) { diff --git a/apps/calculation/additional_outputs/second_degree_list_controller.h b/apps/calculation/additional_outputs/second_degree_list_controller.h index 52300d97218..89812b71043 100644 --- a/apps/calculation/additional_outputs/second_degree_list_controller.h +++ b/apps/calculation/additional_outputs/second_degree_list_controller.h @@ -14,6 +14,7 @@ class SecondDegreeListController : public ExpressionsListController { void setExpression(Poincare::Expression e) override; private: + Poincare::Expression getOppositeIfExists(Poincare::Expression e, Poincare::ExpressionNode::ReductionContext * reductionContext); I18n::Message messageAtIndex(int index) override; int m_numberOfSolutions; }; From e4961563e9470866c8fad8510fd8b941e0f33cdb Mon Sep 17 00:00:00 2001 From: Mino1289 <68814671+Mino1289@users.noreply.github.com> Date: Mon, 25 Oct 2021 16:27:21 +0200 Subject: [PATCH 084/355] [apps/code] Remove base python script and add mathsup.py (#50) --- apps/code/script_store.cpp | 6 +-- apps/code/script_template.cpp | 95 +---------------------------------- apps/code/script_template.h | 4 -- python/test/basics.cpp | 6 +-- 4 files changed, 5 insertions(+), 106 deletions(-) diff --git a/apps/code/script_store.cpp b/apps/code/script_store.cpp index ebc35882753..ad790811b53 100644 --- a/apps/code/script_store.cpp +++ b/apps/code/script_store.cpp @@ -8,11 +8,9 @@ bool ScriptStore::ScriptNameIsFree(const char * baseName) { return ScriptBaseNamed(baseName).isNull(); } +// Here we add "base" script ScriptStore::ScriptStore() { - addScriptFromTemplate(ScriptTemplate::Squares()); - addScriptFromTemplate(ScriptTemplate::Parabola()); - addScriptFromTemplate(ScriptTemplate::Mandelbrot()); - addScriptFromTemplate(ScriptTemplate::Polynomial()); + } void ScriptStore::deleteAllScripts() { diff --git a/apps/code/script_template.cpp b/apps/code/script_template.cpp index 8b59c912af1..b6e3b72eaee 100644 --- a/apps/code/script_template.cpp +++ b/apps/code/script_template.cpp @@ -3,105 +3,12 @@ namespace Code { constexpr ScriptTemplate emptyScriptTemplate(".py", "\x01" R"(from math import * +from mathsup import * )"); -constexpr ScriptTemplate squaresScriptTemplate("squares.py", "\x01" R"(from math import * -from turtle import * -def squares(angle=0.5): - reset() - L=330 - speed(10) - penup() - goto(-L/2,-L/2) - pendown() - for i in range(660): - forward(L) - left(90+angle) - L=L-L*sin(angle*pi/180) - hideturtle())"); - -constexpr ScriptTemplate mandelbrotScriptTemplate("mandelbrot.py", "\x01" R"(# This script draws a Mandelbrot fractal set -# N_iteration: degree of precision -import kandinsky -def mandelbrot(N_iteration): - for x in range(320): - for y in range(222): -# Compute the mandelbrot sequence for the point c = (c_r, c_i) with start value z = (z_r, z_i) - z = complex(0,0) -# Rescale to fit the drawing screen 320x222 - c = complex(3.5*x/319-2.5, -2.5*y/221+1.25) - i = 0 - while (i < N_iteration) and abs(z) < 2: - i = i + 1 - z = z*z+c -# Choose the color of the dot from the Mandelbrot sequence - rgb = int(255*i/N_iteration) - col = kandinsky.color(int(rgb*0.82),int(rgb*0.13),int(rgb*0.18)) -# Draw a pixel colored in 'col' at position (x,y) - kandinsky.set_pixel(x,y,col))"); - -constexpr ScriptTemplate polynomialScriptTemplate("polynomial.py", "\x01" R"(from math import * -# roots(a,b,c) computes the solutions of the equation a*x**2+b*x+c=0 -def roots(a,b,c): - delta = b*b-4*a*c - if delta == 0: - return -b/(2*a) - elif delta > 0: - x_1 = (-b-sqrt(delta))/(2*a) - x_2 = (-b+sqrt(delta))/(2*a) - return x_1, x_2 - else: - return None)"); - -constexpr ScriptTemplate parabolaScriptTemplate("parabola.py", "\x01" R"(from matplotlib.pyplot import * -from math import * - -g=9.81 - -def x(t,v_0,alpha): - return v_0*cos(alpha)*t -def y(t,v_0,alpha,h_0): - return -0.5*g*t**2+v_0*sin(alpha)*t+h_0 - -def vx(v_0,alpha): - return v_0*cos(alpha) -def vy(t,v_0,alpha): - return -g*t+v_0*sin(alpha) - -def t_max(v_0,alpha,h_0): - return (v_0*sin(alpha)+sqrt((v_0**2)*(sin(alpha)**2)+2*g*h_0))/g - -def simulation(v_0=15,alpha=pi/4,h_0=2): - tMax=t_max(v_0,alpha,h_0) - accuracy=1/10**(floor(log10(tMax))-1) - T_MAX=floor(tMax*accuracy)+1 - X=[x(t/accuracy,v_0,alpha) for t in range(T_MAX)] - Y=[y(t/accuracy,v_0,alpha,h_0) for t in range(T_MAX)] - VX=[vx(v_0,alpha) for t in range(T_MAX)] - VY=[vy(t/accuracy,v_0,alpha) for t in range(T_MAX)] - for i in range(T_MAX): - arrow(X[i],Y[i],VX[i]/accuracy,VY[i]/accuracy) - grid() - show())"); const ScriptTemplate * ScriptTemplate::Empty() { return &emptyScriptTemplate; } -const ScriptTemplate * ScriptTemplate::Squares() { - return &squaresScriptTemplate; -} - -const ScriptTemplate * ScriptTemplate::Mandelbrot() { - return &mandelbrotScriptTemplate; -} - -const ScriptTemplate * ScriptTemplate::Polynomial() { - return &polynomialScriptTemplate; -} - -const ScriptTemplate * ScriptTemplate::Parabola() { - return ¶bolaScriptTemplate; -} - } diff --git a/apps/code/script_template.h b/apps/code/script_template.h index ec32e7052a5..6eac56d34b1 100644 --- a/apps/code/script_template.h +++ b/apps/code/script_template.h @@ -9,10 +9,6 @@ class ScriptTemplate { public: constexpr ScriptTemplate(const char * name, const char * value) : m_name(name), m_value(value) {} static const ScriptTemplate * Empty(); - static const ScriptTemplate * Squares(); - static const ScriptTemplate * Mandelbrot(); - static const ScriptTemplate * Polynomial(); - static const ScriptTemplate * Parabola(); const char * name() const { return m_name; } const char * content() const { return m_value + Script::StatusSize(); } const char * value() const { return m_value; } diff --git a/python/test/basics.cpp b/python/test/basics.cpp index 296b519af8a..84a7a2275fc 100644 --- a/python/test/basics.cpp +++ b/python/test/basics.cpp @@ -7,9 +7,7 @@ QUIZ_CASE(python_basics) { deinit_environment(); } +// "base" scripts to test. QUIZ_CASE(python_template) { - assert_script_execution_succeeds(Code::ScriptTemplate::Squares()->content()); - assert_script_execution_succeeds(Code::ScriptTemplate::Mandelbrot()->content()); - assert_script_execution_succeeds(Code::ScriptTemplate::Polynomial()->content()); - assert_script_execution_succeeds(Code::ScriptTemplate::Parabola()->content()); + } From 89e51166e705ab34beef6c6f6eb08a1d65d5f421 Mon Sep 17 00:00:00 2001 From: lolocomotive <49951010+lolocomotive@users.noreply.github.com> Date: Wed, 27 Oct 2021 10:10:14 +0200 Subject: [PATCH 085/355] Make battery background use appropriate colors (#59) --- apps/battery_view.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/battery_view.cpp b/apps/battery_view.cpp index 28ab8d9794e..1026f496e2d 100644 --- a/apps/battery_view.cpp +++ b/apps/battery_view.cpp @@ -75,13 +75,13 @@ void BatteryView::drawRect(KDContext * ctx, KDRect rect) const { assert(!m_isPlugged); // Low: Quite empty battery ctx->fillRect(KDRect(batteryInsideX, 0, 2*k_elementWidth, k_batteryHeight), Palette::BatteryLow); - ctx->fillRect(KDRect(3*k_elementWidth+k_separatorThickness, 0, k_batteryWidth-5*k_elementWidth-2*k_separatorThickness, k_batteryHeight), Palette::BatteryInCharge); + ctx->fillRect(KDRect(3*k_elementWidth+k_separatorThickness, 0, k_batteryWidth-5*k_elementWidth-2*k_separatorThickness, k_batteryHeight), KDColor::blend(Palette::Toolbar, Palette::Battery, 128)); } else if (m_chargeState == Ion::Battery::Charge::SOMEWHERE_INBETWEEN) { assert(!m_isPlugged); // Middle: Half full battery constexpr KDCoordinate middleChargeWidth = batteryInsideWidth/2; ctx->fillRect(KDRect(batteryInsideX, 0, middleChargeWidth, k_batteryHeight), Palette::Battery); - ctx->fillRect(KDRect(batteryInsideX+middleChargeWidth, 0, middleChargeWidth, k_batteryHeight), Palette::BatteryInCharge); + ctx->fillRect(KDRect(batteryInsideX+middleChargeWidth, 0, middleChargeWidth, k_batteryHeight), KDColor::blend(Palette::Toolbar, Palette::Battery, 128)); } else { assert(m_chargeState == Ion::Battery::Charge::FULL); // Full but not plugged: Full battery From 8e1b311be04ae58d5080674e44e8322ef5bb8aa5 Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Wed, 27 Oct 2021 19:29:53 +0200 Subject: [PATCH 086/355] [apps/settings] Fix gamma selector (#60) --- apps/settings/sub_menu/accessibility_controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/settings/sub_menu/accessibility_controller.cpp b/apps/settings/sub_menu/accessibility_controller.cpp index 10c824df2dd..ca6b5b4a0da 100644 --- a/apps/settings/sub_menu/accessibility_controller.cpp +++ b/apps/settings/sub_menu/accessibility_controller.cpp @@ -25,7 +25,7 @@ bool AccessibilityController::handleEvent(Ion::Events::Event event) { int redGamma, greenGamma, blueGamma; KDIonContext::sharedContext()->gamma.gamma(redGamma, greenGamma, blueGamma); - if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { + if ((event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) && (selectedRow() <= 2)) { if (selectedRow() == 0) { invertEnabled = !invertEnabled; } From c4ed8f84d3d008ba30794991915d6c7edfd46555 Mon Sep 17 00:00:00 2001 From: Laporte <40714786+Laporte12974@users.noreply.github.com> Date: Wed, 27 Oct 2021 19:30:35 +0200 Subject: [PATCH 087/355] Custom themes script (#46) --- themes/UpsilonIconsVAR_PrettyPrinted.svg | 491 +++++++++++++++++++++++ themes/logocolors.json | 10 + themes/script.sh | 77 ++++ 3 files changed, 578 insertions(+) create mode 100644 themes/UpsilonIconsVAR_PrettyPrinted.svg create mode 100644 themes/logocolors.json create mode 100644 themes/script.sh diff --git a/themes/UpsilonIconsVAR_PrettyPrinted.svg b/themes/UpsilonIconsVAR_PrettyPrinted.svg new file mode 100644 index 00000000000..dde7d1356a2 --- /dev/null +++ b/themes/UpsilonIconsVAR_PrettyPrinted.svg @@ -0,0 +1,491 @@ + + + + + + + + + + + + =+÷- + + + + + + + + + Xcas + + + + + + + + + + + + + + + + + + + x=? + y=? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Upsilon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 016215 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/themes/logocolors.json b/themes/logocolors.json new file mode 100644 index 00000000000..d6a7bea90db --- /dev/null +++ b/themes/logocolors.json @@ -0,0 +1,10 @@ +{ + "MainColor": "#BB2244", + "UnknownColor1": "#FFFFFF", + "RegressionLineGrey": "#4D4D4D", + "UnknownColor2": "#CCCCCC", + "LogoBackgroundColor": "#2F2F2F", + "SecondaryColor": "#888888", + "StatisticsSelectedBG": "#998899", + "BackgroundColor": "#FFFFFF" +} diff --git a/themes/script.sh b/themes/script.sh new file mode 100644 index 00000000000..0212ba618fb --- /dev/null +++ b/themes/script.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +# Necessary packages: jq, inkscape + +rm UpsilonIconsCUSTOM.svg +cp UpsilonIconsVAR_PrettyPrinted.svg UpsilonIconsCUSTOM.svg +file=UpsilonIconsCUSTOM.svg +k=1 + +mkdir themes/local/custom_theme +mkdir themes/local/custom_theme/apps +mkdir themes/local/custom_theme/probability +dir=./themes/local/custom_theme + + +#Checks if jq is installed then assigns returned variables to $k or uses command line args +if [ $(which jq | wc -l) -ge 1 ]; then + hex=$(cat logocolors.json | jq -r '.[]') + for param in $hex; do + echo "COLORID$k => $param" + sed -i 's%COLORID'$k'%'$param'%g' $file + k=$(($k+1)) + done +else + for param in $@; do + echo "COLORID$k => $param" + sed -i 's%COLORID'$k'%'$param'%g' $file + k=$(($k+1)) + done +fi + +if [ $(which inkscape | wc -l) -ge 1 ]; then + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/calculation_icon.png --export-area=0:0:55:56 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/rpn_icon.png --export-area=80:0:135:56 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/graph_icon.png --export-area=160:0:215:56 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/code_icon.png --export-area=0:87:55:143 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/stat_icon.png --export-area=80:87:135:143 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/probability_icon.png --export-area=160:87:215:143 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/solver_icon.png --export-area=0:173:55:229 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/atomic_icon.png --export-area=80:173:135:229 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/sequence_icon.png --export-area=160:173:215:229 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/graph_icon.png --export-area=160:0:215:56 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/graph_icon.png --export-area=0:258:55:314 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/regression_icon.png --export-area=160:0:215:56 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/settings_icon.png --export-area=80:258:135:314 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/external_icon.png --export-area=160:258:215:314 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/xcas_icon.png --export-area=80:342:135:397 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/binomial_icon.png --export-area=273:0:308:19 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/chi_squared_icon.png --export-area=273:29:308:48 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/exponential_icon.png --export-area=273:58:308:77 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/fisher_icon.png --export-area=273:87:308:106 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/geometric_icon.png --export-area=273:116:308:135 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/normal_icon.png --export-area=273:145:308:164 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/poisson_icon.png --export-area=273:174:308:193 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/student_icon.png --export-area=273:203:308:222 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/uniform_icon.png --export-area=273:232:308:251 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/calcul1_icon.png --export-area=273:261:312:284 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/calcul2_icon.png --export-area=273:294:312:317 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/calcul3_icon.png --export-area=273:327:312:350 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/calcul4_icon.png --export-area=273:360:312:383 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_binomial_icon.png --export-area=327:0:362:19 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_chi_squared_icon.png --export-area=327:29:362:48 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_exponential_icon.png --export-area=327:58:362:77 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_fisher_icon.png --export-area=327:87:362:106 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_geometric_icon.png --export-area=327:116:362:135 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_normal_icon.png --export-area=327:145:362:164 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_poisson_icon.png --export-area=327:174:362:193 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_student_icon.png --export-area=327:203:362:222 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_uniform_icon.png --export-area=327:232:362:251 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_calcul1_icon.png --export-area=327:261:366:284 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_calcul2_icon.png --export-area=327:294:366:317 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_calcul3_icon.png --export-area=327:327:366:350 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_calcul4_icon.png --export-area=327:360:366:383 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/exam_icon.png --export-area=273:393:291:402 + inkscape ./UpsilonIconsCUSTOM.svg -o $dir/logo_icon.png --export-area=0:447:115:479 +fi + From f6e937a9277cae22e91f3a4b30288da7251bdfb0 Mon Sep 17 00:00:00 2001 From: Laury Date: Wed, 27 Oct 2021 19:29:09 +0200 Subject: [PATCH 088/355] [settings] Bug fix in code options controller --- apps/settings/sub_menu/code_options_controller.cpp | 13 ++++++------- apps/settings/sub_menu/code_options_controller.h | 7 +++---- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/apps/settings/sub_menu/code_options_controller.cpp b/apps/settings/sub_menu/code_options_controller.cpp index 1aff0745663..ee8d296f650 100644 --- a/apps/settings/sub_menu/code_options_controller.cpp +++ b/apps/settings/sub_menu/code_options_controller.cpp @@ -10,12 +10,8 @@ CodeOptionsController::CodeOptionsController(Responder * parentResponder) : GenericSubController(parentResponder), m_preferencesController(this) { - for (int i = 0; i < k_totalNumberOfCell; i++) { - m_cells[i].setMessageFont(KDFont::LargeFont); - } - for (int i = 0; i < k_totalNumberOfSwitchCells; i++) { - m_switchCells[i].setMessageFont(KDFont::LargeFont); - } + m_chevronCell.setMessageFont(KDFont::LargeFont); + m_switchCell.setMessageFont(KDFont::LargeFont); } bool CodeOptionsController::handleEvent(Ion::Events::Event event) { @@ -44,7 +40,10 @@ bool CodeOptionsController::handleEvent(Ion::Events::Event event) { HighlightCell * CodeOptionsController::reusableCell(int index, int type) { assert(type == 0); assert(index >= 0 && index < k_totalNumberOfCell); - return &m_cells[index]; + if (index == 0) { + return &m_chevronCell; + } + return &m_switchCell; } int CodeOptionsController::reusableCellCount(int type) { diff --git a/apps/settings/sub_menu/code_options_controller.h b/apps/settings/sub_menu/code_options_controller.h index 44bea0a3d5b..d3a82e336b9 100644 --- a/apps/settings/sub_menu/code_options_controller.h +++ b/apps/settings/sub_menu/code_options_controller.h @@ -14,11 +14,10 @@ class CodeOptionsController : public GenericSubController { int reusableCellCount(int type) override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; private: - constexpr static int k_totalNumberOfCell = 1; - constexpr static int k_totalNumberOfSwitchCells = 1; + constexpr static int k_totalNumberOfCell = 2; PreferencesController m_preferencesController; - MessageTableCellWithChevronAndMessage m_cells[k_totalNumberOfCell]; - MessageTableCellWithSwitch m_switchCells[k_totalNumberOfCell]; + MessageTableCellWithChevronAndMessage m_chevronCell; + MessageTableCellWithSwitch m_switchCell; }; } From 0a1b3bcaee9a15a3e4c8f1a8e9780ec510f82da4 Mon Sep 17 00:00:00 2001 From: Laury Date: Thu, 28 Oct 2021 14:38:32 +0200 Subject: [PATCH 089/355] [theme] Added upsilon light theme --- themes/README.md | 2 + themes/UpsilonIconsVAR_PrettyPrinted.svg | 491 ----------------- themes/default_icons.svg | 503 ++++++++++++++++++ themes/logocolors.json | 17 +- themes/script.sh | 112 ++-- themes/themes/local/upsilon_light.json | 150 ++++++ .../local/upsilon_light/apps/atomic_icon.png | Bin 0 -> 2706 bytes .../upsilon_light/apps/calculation_icon.png | Bin 0 -> 1114 bytes .../local/upsilon_light/apps/code_icon.png | Bin 0 -> 1253 bytes .../upsilon_light/apps/external_icon.png | Bin 0 -> 1367 bytes .../local/upsilon_light/apps/graph_icon.png | Bin 0 -> 851 bytes .../upsilon_light/apps/probability_icon.png | Bin 0 -> 1194 bytes .../local/upsilon_light/apps/reader_icon.png | Bin 0 -> 1617 bytes .../upsilon_light/apps/regression_icon.png | Bin 0 -> 997 bytes .../local/upsilon_light/apps/rpn_icon.png | Bin 0 -> 1972 bytes .../upsilon_light/apps/sequence_icon.png | Bin 0 -> 617 bytes .../upsilon_light/apps/settings_icon.png | Bin 0 -> 750 bytes .../local/upsilon_light/apps/solver_icon.png | Bin 0 -> 2036 bytes .../local/upsilon_light/apps/stat_icon.png | Bin 0 -> 581 bytes .../themes/local/upsilon_light/exam_icon.png | Bin 0 -> 424 bytes .../themes/local/upsilon_light/logo_icon.png | Bin 0 -> 2708 bytes .../probability/binomial_icon.png | Bin 0 -> 225 bytes .../probability/calcul1_icon.png | Bin 0 -> 676 bytes .../probability/calcul2_icon.png | Bin 0 -> 672 bytes .../probability/calcul3_icon.png | Bin 0 -> 670 bytes .../probability/calcul4_icon.png | Bin 0 -> 767 bytes .../probability/chi_squared_icon.png | Bin 0 -> 487 bytes .../probability/exponential_icon.png | Bin 0 -> 479 bytes .../upsilon_light/probability/fisher_icon.png | Bin 0 -> 491 bytes .../probability/focused_binomial_icon.png | Bin 0 -> 228 bytes .../probability/focused_calcul1_icon.png | Bin 0 -> 639 bytes .../probability/focused_calcul2_icon.png | Bin 0 -> 675 bytes .../probability/focused_calcul3_icon.png | Bin 0 -> 633 bytes .../probability/focused_calcul4_icon.png | Bin 0 -> 701 bytes .../probability/focused_chi_squared_icon.png | Bin 0 -> 477 bytes .../probability/focused_exponential_icon.png | Bin 0 -> 472 bytes .../probability/focused_fisher_icon.png | Bin 0 -> 473 bytes .../probability/focused_geometric_icon.png | Bin 0 -> 221 bytes .../probability/focused_normal_icon.png | Bin 0 -> 516 bytes .../probability/focused_poisson_icon.png | Bin 0 -> 234 bytes .../probability/focused_student_icon.png | Bin 0 -> 491 bytes .../probability/focused_uniform_icon.png | Bin 0 -> 198 bytes .../probability/geometric_icon.png | Bin 0 -> 224 bytes .../upsilon_light/probability/normal_icon.png | Bin 0 -> 524 bytes .../probability/poisson_icon.png | Bin 0 -> 224 bytes .../probability/student_icon.png | Bin 0 -> 520 bytes .../probability/uniform_icon.png | Bin 0 -> 197 bytes 47 files changed, 720 insertions(+), 555 deletions(-) delete mode 100644 themes/UpsilonIconsVAR_PrettyPrinted.svg create mode 100644 themes/default_icons.svg create mode 100644 themes/themes/local/upsilon_light.json create mode 100644 themes/themes/local/upsilon_light/apps/atomic_icon.png create mode 100644 themes/themes/local/upsilon_light/apps/calculation_icon.png create mode 100644 themes/themes/local/upsilon_light/apps/code_icon.png create mode 100644 themes/themes/local/upsilon_light/apps/external_icon.png create mode 100644 themes/themes/local/upsilon_light/apps/graph_icon.png create mode 100644 themes/themes/local/upsilon_light/apps/probability_icon.png create mode 100644 themes/themes/local/upsilon_light/apps/reader_icon.png create mode 100644 themes/themes/local/upsilon_light/apps/regression_icon.png create mode 100644 themes/themes/local/upsilon_light/apps/rpn_icon.png create mode 100644 themes/themes/local/upsilon_light/apps/sequence_icon.png create mode 100644 themes/themes/local/upsilon_light/apps/settings_icon.png create mode 100644 themes/themes/local/upsilon_light/apps/solver_icon.png create mode 100644 themes/themes/local/upsilon_light/apps/stat_icon.png create mode 100644 themes/themes/local/upsilon_light/exam_icon.png create mode 100644 themes/themes/local/upsilon_light/logo_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/binomial_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/calcul1_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/calcul2_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/calcul3_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/calcul4_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/chi_squared_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/exponential_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/fisher_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/focused_binomial_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/focused_calcul1_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/focused_calcul2_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/focused_calcul3_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/focused_calcul4_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/focused_chi_squared_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/focused_exponential_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/focused_fisher_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/focused_geometric_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/focused_normal_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/focused_poisson_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/focused_student_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/focused_uniform_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/geometric_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/normal_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/poisson_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/student_icon.png create mode 100644 themes/themes/local/upsilon_light/probability/uniform_icon.png diff --git a/themes/README.md b/themes/README.md index cdfc9f8bd5e..2cf71797872 100644 --- a/themes/README.md +++ b/themes/README.md @@ -40,6 +40,8 @@ Example: make THEME_REPO=https://github.com/Omega-Numworks/Omega-Theme-Example.git THEME_NAME=omega_blue ``` +> You can use `./themes/script.sh your_theme_name` to build the icons of your theme from the colors of `themes/logocolors.json`. + ## License Omega-Themes is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). NumWorks is a registered trademark. diff --git a/themes/UpsilonIconsVAR_PrettyPrinted.svg b/themes/UpsilonIconsVAR_PrettyPrinted.svg deleted file mode 100644 index dde7d1356a2..00000000000 --- a/themes/UpsilonIconsVAR_PrettyPrinted.svg +++ /dev/null @@ -1,491 +0,0 @@ - - - - - - - - - - - - =+÷- - - - - - - - - - Xcas - - - - - - - - - - - - - - - - - - - x=? - y=? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Upsilon - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 016215 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/themes/default_icons.svg b/themes/default_icons.svg new file mode 100644 index 00000000000..4080526c676 --- /dev/null +++ b/themes/default_icons.svg @@ -0,0 +1,503 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + = + + + ÷ + - + + + + + + + + + + + + + X + cas + + + + + + + + + + + + + + + + + + + + + + + + + + + x=? + + y + = + ? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Upsilon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 16 + 215 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/themes/logocolors.json b/themes/logocolors.json index d6a7bea90db..c090022844c 100644 --- a/themes/logocolors.json +++ b/themes/logocolors.json @@ -1,10 +1,11 @@ { - "MainColor": "#BB2244", - "UnknownColor1": "#FFFFFF", - "RegressionLineGrey": "#4D4D4D", - "UnknownColor2": "#CCCCCC", - "LogoBackgroundColor": "#2F2F2F", - "SecondaryColor": "#888888", - "StatisticsSelectedBG": "#998899", - "BackgroundColor": "#FFFFFF" + "COLOR_MAIN": "#7EA2CE", + "COLOR_ICON_SECONDARY": "#4A4A4A", + "COLOR_DRAWINGS": "#F2F2F2", + "COLOR_REGRESSION_LINE": "#D4D4D4", + "COLOR_ICON_BORDER": "#E6E6E6", + "COLOR_ICON_BACKGROUND": "#F2F2F2", + "COLOR_FOCUSED_STATS_ICONS_BACKGROUND": "#E0E0E0", + "COLOR_STATS_ICONS_BACKGROUND": "#FFFFFF", + "COLOR_ICON_CROPPED": "#FFFFFF" } diff --git a/themes/script.sh b/themes/script.sh index 0212ba618fb..6b681805f02 100644 --- a/themes/script.sh +++ b/themes/script.sh @@ -2,24 +2,23 @@ # Necessary packages: jq, inkscape -rm UpsilonIconsCUSTOM.svg -cp UpsilonIconsVAR_PrettyPrinted.svg UpsilonIconsCUSTOM.svg -file=UpsilonIconsCUSTOM.svg -k=1 +cd themes +cp default_icons.svg generated_icons.svg +file=generated_icons.svg -mkdir themes/local/custom_theme -mkdir themes/local/custom_theme/apps -mkdir themes/local/custom_theme/probability -dir=./themes/local/custom_theme +mkdir -p themes/local/$1 +mkdir -p themes/local/$1/apps +mkdir -p themes/local/$1/probability +dir=./themes/local/$1 #Checks if jq is installed then assigns returned variables to $k or uses command line args if [ $(which jq | wc -l) -ge 1 ]; then - hex=$(cat logocolors.json | jq -r '.[]') - for param in $hex; do - echo "COLORID$k => $param" - sed -i 's%COLORID'$k'%'$param'%g' $file - k=$(($k+1)) + hex=$(jq -r 'to_entries[] | (.key)' logocolors.json | tr -d '\r') + for key in $hex; do + value=$(jq -r '.'$key'' logocolors.json) + echo "$key => $value" + sed -i 's%'$key'%'$value'%g' $file done else for param in $@; do @@ -30,48 +29,49 @@ else fi if [ $(which inkscape | wc -l) -ge 1 ]; then - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/calculation_icon.png --export-area=0:0:55:56 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/rpn_icon.png --export-area=80:0:135:56 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/graph_icon.png --export-area=160:0:215:56 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/code_icon.png --export-area=0:87:55:143 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/stat_icon.png --export-area=80:87:135:143 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/probability_icon.png --export-area=160:87:215:143 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/solver_icon.png --export-area=0:173:55:229 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/atomic_icon.png --export-area=80:173:135:229 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/sequence_icon.png --export-area=160:173:215:229 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/graph_icon.png --export-area=160:0:215:56 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/graph_icon.png --export-area=0:258:55:314 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/regression_icon.png --export-area=160:0:215:56 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/settings_icon.png --export-area=80:258:135:314 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/external_icon.png --export-area=160:258:215:314 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/apps/xcas_icon.png --export-area=80:342:135:397 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/binomial_icon.png --export-area=273:0:308:19 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/chi_squared_icon.png --export-area=273:29:308:48 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/exponential_icon.png --export-area=273:58:308:77 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/fisher_icon.png --export-area=273:87:308:106 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/geometric_icon.png --export-area=273:116:308:135 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/normal_icon.png --export-area=273:145:308:164 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/poisson_icon.png --export-area=273:174:308:193 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/student_icon.png --export-area=273:203:308:222 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/uniform_icon.png --export-area=273:232:308:251 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/calcul1_icon.png --export-area=273:261:312:284 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/calcul2_icon.png --export-area=273:294:312:317 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/calcul3_icon.png --export-area=273:327:312:350 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/calcul4_icon.png --export-area=273:360:312:383 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_binomial_icon.png --export-area=327:0:362:19 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_chi_squared_icon.png --export-area=327:29:362:48 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_exponential_icon.png --export-area=327:58:362:77 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_fisher_icon.png --export-area=327:87:362:106 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_geometric_icon.png --export-area=327:116:362:135 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_normal_icon.png --export-area=327:145:362:164 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_poisson_icon.png --export-area=327:174:362:193 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_student_icon.png --export-area=327:203:362:222 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_uniform_icon.png --export-area=327:232:362:251 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_calcul1_icon.png --export-area=327:261:366:284 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_calcul2_icon.png --export-area=327:294:366:317 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_calcul3_icon.png --export-area=327:327:366:350 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/probability/focused_calcul4_icon.png --export-area=327:360:366:383 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/exam_icon.png --export-area=273:393:291:402 - inkscape ./UpsilonIconsCUSTOM.svg -o $dir/logo_icon.png --export-area=0:447:115:479 + inkscape ./generated_icons.svg -o $dir/apps/calculation_icon.png --export-area=0:0:55:56 + inkscape ./generated_icons.svg -o $dir/apps/rpn_icon.png --export-area=80:0:135:56 + inkscape ./generated_icons.svg -o $dir/apps/graph_icon.png --export-area=160:0:215:56 + inkscape ./generated_icons.svg -o $dir/apps/code_icon.png --export-area=0:87:55:143 + inkscape ./generated_icons.svg -o $dir/apps/stat_icon.png --export-area=80:87:135:143 + inkscape ./generated_icons.svg -o $dir/apps/probability_icon.png --export-area=160:87:215:143 + inkscape ./generated_icons.svg -o $dir/apps/solver_icon.png --export-area=0:173:55:229 + inkscape ./generated_icons.svg -o $dir/apps/atomic_icon.png --export-area=80:173:135:229 + inkscape ./generated_icons.svg -o $dir/apps/sequence_icon.png --export-area=160:173:215:229 + inkscape ./generated_icons.svg -o $dir/apps/graph_icon.png --export-area=160:0:215:56 + inkscape ./generated_icons.svg -o $dir/apps/regression_icon.png --export-area=0:258:55:314 + inkscape ./generated_icons.svg -o $dir/apps/settings_icon.png --export-area=80:258:135:314 + inkscape ./generated_icons.svg -o $dir/apps/external_icon.png --export-area=160:258:215:314 +# inkscape ./generated_icons.svg -o $dir/apps/xcas_icon.png --export-area=80:342:135:398 + inkscape ./generated_icons.svg -o $dir/apps/reader_icon.png --export-area=160:342:215:398 + inkscape ./generated_icons.svg -o $dir/probability/binomial_icon.png --export-area=273:0:308:19 + inkscape ./generated_icons.svg -o $dir/probability/chi_squared_icon.png --export-area=273:29:308:48 + inkscape ./generated_icons.svg -o $dir/probability/exponential_icon.png --export-area=273:58:308:77 + inkscape ./generated_icons.svg -o $dir/probability/fisher_icon.png --export-area=273:87:308:106 + inkscape ./generated_icons.svg -o $dir/probability/geometric_icon.png --export-area=273:116:308:135 + inkscape ./generated_icons.svg -o $dir/probability/normal_icon.png --export-area=273:145:308:164 + inkscape ./generated_icons.svg -o $dir/probability/poisson_icon.png --export-area=273:174:308:193 + inkscape ./generated_icons.svg -o $dir/probability/student_icon.png --export-area=273:203:308:222 + inkscape ./generated_icons.svg -o $dir/probability/uniform_icon.png --export-area=273:232:308:251 + inkscape ./generated_icons.svg -o $dir/probability/calcul1_icon.png --export-area=273:261:312:284 + inkscape ./generated_icons.svg -o $dir/probability/calcul2_icon.png --export-area=273:294:312:317 + inkscape ./generated_icons.svg -o $dir/probability/calcul3_icon.png --export-area=273:327:312:350 + inkscape ./generated_icons.svg -o $dir/probability/calcul4_icon.png --export-area=273:360:312:383 + inkscape ./generated_icons.svg -o $dir/probability/focused_binomial_icon.png --export-area=327:0:362:19 + inkscape ./generated_icons.svg -o $dir/probability/focused_chi_squared_icon.png --export-area=327:29:362:48 + inkscape ./generated_icons.svg -o $dir/probability/focused_exponential_icon.png --export-area=327:58:362:77 + inkscape ./generated_icons.svg -o $dir/probability/focused_fisher_icon.png --export-area=327:87:362:106 + inkscape ./generated_icons.svg -o $dir/probability/focused_geometric_icon.png --export-area=327:116:362:135 + inkscape ./generated_icons.svg -o $dir/probability/focused_normal_icon.png --export-area=327:145:362:164 + inkscape ./generated_icons.svg -o $dir/probability/focused_poisson_icon.png --export-area=327:174:362:193 + inkscape ./generated_icons.svg -o $dir/probability/focused_student_icon.png --export-area=327:203:362:222 + inkscape ./generated_icons.svg -o $dir/probability/focused_uniform_icon.png --export-area=327:232:362:251 + inkscape ./generated_icons.svg -o $dir/probability/focused_calcul1_icon.png --export-area=327:261:366:284 + inkscape ./generated_icons.svg -o $dir/probability/focused_calcul2_icon.png --export-area=327:294:366:317 + inkscape ./generated_icons.svg -o $dir/probability/focused_calcul3_icon.png --export-area=327:327:366:350 + inkscape ./generated_icons.svg -o $dir/probability/focused_calcul4_icon.png --export-area=327:360:366:383 + inkscape ./generated_icons.svg -o $dir/exam_icon.png --export-area=273:393:291:402 + inkscape ./generated_icons.svg -o $dir/logo_icon.png --export-area=0:447:115:479 fi +rm generated_icons.svg diff --git a/themes/themes/local/upsilon_light.json b/themes/themes/local/upsilon_light.json new file mode 100644 index 00000000000..68aaf856272 --- /dev/null +++ b/themes/themes/local/upsilon_light.json @@ -0,0 +1,150 @@ +{ + "name": "Upsilon Light", + "icons": "upsilon_light", + "colors": { + "PrimaryText": "000000", + "SecondaryText": "6e6e6e", + "AccentText": "00857f", + "ApproximateSignText": "595959", + "ApproximateExpressionText": "595959", + "Background": { + "Hard": "ffffff", + "Apps": "fafafa", + "AppsSecondary": "f0f0f0" + }, + "Toolbar": { + "": "7EA2CE", + "Text": "ffffff" + }, + "ExpressionInput": { + "Background": "e0e0e0", + "Border": "d9d9d9" + }, + "Grid": { + "PrimaryLine": "d9d9d9", + "SecondaryLine": "f5f5f5" + }, + "Battery": { + "": "ffffff", + "InCharge": "179e1f", + "Low": "992321" + }, + "ScrollBar": { + "Foreground": "4a4a4a", + "Background": "d9d9d9" + }, + "Control": { + "": "ffad83", + "Enabled": "ff8b52", + "Disabled": "9e9e9e" + }, + "Calculation": { + "BackgroundOdd": "fafafa", + "BackgroundEven": "ffffff", + "EmptyBox": "c4c4c4", + "EmptyBoxNeeded": "ffad83", + "TrigoAndComplexForeground": "ff000c" + }, + "Code": { + "Background": "ffffff", + "BackgroundSelected": "e0e0e0", + "Text": "000000", + "Comment": "999988", + "Number": "009999", + "Keyword": "ff000c", + "Operator": "d73a49", + "String": "032f62", + "GutterViewBackground": "E4E6E7" + }, + "Probability": { + "Curve": "ff8b52", + "CellBorder": "ececec", + "HistogramBar": "d9d9d9" + }, + "Statistics": { + "Box": "ffad83", + "BoxVerticalLine": "d9d9d9", + "Selected": "ffad83", + "NotSelected": "f5f5f5" + }, + "Graph": { + "Tangent": "595959" + }, + "SubMenu": { + "Background": "e0e0e0", + "Border": "fafafa", + "Text": "000000" + }, + "Toolbox": { + "HeaderBackground": "4a4a4a", + "HeaderText": "ffffff", + "HeaderBorder": "4a4a4a", + "Background": "000000" + }, + "List": { + "CellBackground": "ffffff", + "CellBackgroundSelected": "e0e0e0", + "CellBorder": "ededef" + }, + "Button": { + "Background": "e6e6e6", + "BackgroundSelected": "c9c9c9", + "BackgroundSelectedHighContrast": "00b2b0", + "Border": "adadad", + "RowBorder": "d9d9d9", + "BorderOut": "f5f5f5", + "Shadow": "003833", + "Text": "000000" + }, + "Tab": { + "Background": "4a4a4a", + "BackgroundSelected": "757575", + "BackgroundActive": "fafafa", + "BackgroundSelectedAndActive": "e3e3e3", + "Text": "ffffff", + "TextActive": "000000" + }, + "SubTab": { + "Background": "e0e0e0", + "BackgroundSelected": "d1d1d1", + "Text": "000000" + }, + "Banner": { + "FirstBackground": "4a4a4a", + "FirstBorder": "4a4a4a", + "FirstText": "ffffff", + "FirstVariantBackground": "4a4a4a", + "FirstVariantBorder": "fafafa", + "FirstVariantText": "ffffff", + "SecondBackground": "e0e0e0", + "SecondBorder": "fafafa", + "SecondText": "000000" + }, + "Home": { + "Background": "ffffff", + "CellBackground": "ffffff", + "CellBackgroundActive": "4a4a4a", + "CellText": "000000", + "CellTextActive": "ffffff", + "CellTextExternal": "008f87", + "CellTextExternalActive": "6fe6df" + }, + "Atom": { + "Unknown": "eeeeee", + "Text": "000000", + "AlkaliMetal": "CC9E7E", + "AlkaliEarthMetal": "D69477", + "Lanthanide": "A5DDAD", + "Actinide": "96D481", + "TransitionMetal": "99C6E7", + "PostTransitionMetal": "D69877", + "Metalloid": "D6B071", + "Halogen": "84ABE3", + "ReactiveNonmetal": "DBC377", + "NobleGas": "8FC2E6", + "TableLines": "323532", + "Highlight": "000000", + "Background": "d9d9d9" + } + } +} \ No newline at end of file diff --git a/themes/themes/local/upsilon_light/apps/atomic_icon.png b/themes/themes/local/upsilon_light/apps/atomic_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..531b003a5487fea8b95c8432646652a8885be9a6 GIT binary patch literal 2706 zcmW-jcQ~Be7RE=3Xd|LUOU#KLf-o4pi_z!kHKUs_N{CJpa-$tVM51IkN;n}*hD0wB zMmda$&cV3h7(&$133uoIvA_L%-}60tueJ8?eb-L4w!8~v6=a1#AW)=<;qPEp1p~p% z1fEVA=o~PygqS#mK_DF6zXn}j@L%5Gqd>TkW4KMQZ+N7~10P6aWTYG>C?L$sBg988 z_<>*HrlueS!YYC^)U)+}GGBlVwDA$?eN-u5i3|6d4-ZrzsmvLq1#f2f&tb(y`> z8fHn3*8YL|&ksaiRTeQj$-&LvK z+zfhJff1$b1`*wE55sxI;^X7TCnpza$9Z?Wm^hLdD#wtfrhh}HIH1sxuU~azV>Q4Z z|1K{RA~u{iTer05BUt9==L<-_t(e3R#1NHK$o)j=`S`f)T#%}dkIzzV4IaPp`3ZyJ z&1@8TBSND!V&kT=@_xmJps?`x1=aEmLQjgvb91g)^Wu!Qx3?Fvw~89ZY4jCo#rg*a zuMn@@>+vLrO@ahzJAEQ!)6>klDAZLe%^k*>nVGnQ8NcxG@a-`x9hAF!?V!s1qO-Fz z#F5~6skyoNUn;dO-(iOiavl16D|-Fr%wWud2FwC#HQajI7nRH)9-G_np||M2jQ{kWY)qOF5N4i1+wweGVrj63?75brc?eFh{A~1+5PZ7NOdT~N41AqTL8c>iB3NnSbcEqWDj|vEox%cR&vT<3qUr{tu z*+_qhL9%~9fSJ0^FzqwLl~rYn&ZguWG0cw_5>By0jtF6DS*EuB7`!Oq1as@wEyqvy zc?&8Cv4@*am0I{hM`%>iSU|x!thVZweA_m?1Kxdk#;;hME0vR(5rhr)%=d>Eto8xE zq|&6)6052lYwUf=MHa@!OacM|(}5}noAc=;-?EAdi4nGD0d@`!N*e+{WoBl^)_Hy+ z(|K{OVdLb+&(1_0$~OoNy+d0eOUla!j|%7TRZ6kyoNY5eq0pnaj-D_*&ad!{moIrh zlqObIl?@HA0BbD|*BwFLzV!kqo@_A|r&kz%fAm5}JlGh(mu>R2JNXK2>fR&qqs9Kg z!S3(jyVG8o9*P#STY0u86BNoYU~F}BlgZfiAqFE&BoYzA9PY6j%K9aoMn*<(W#y|J z$*CfwmL6vbS{vWGIcCLc0j+km6es>AVL#O#PoxL_4 zs5(y8ttI?-J=e&SiOI>{W7u%uj1OI*KS1_c-l^hxLW2 zx{+@*A~%Jz+RlLl%j)X5^f-?|4c}E)FID3^^f?)C$;jj!h%adltaT6QH#W&Q)ItuAVF$S2udcwD)KyMf(rh$ZP)s-kD`yC- zudR84T|i6ADlR71y0roiDr;>m+?Z{Yyx$#gb>(;k_oDDgQ0Q2t)#X1cEB(f4y}iZ_ z*jeM?VEIdzF4@}IW#{G^+1c@e7vMGxLGuL&`4rJ%CEoZ!mbdq&48*ufK7GoymNL}S zqgx>tuYfMN+@CE9Oyq^QS=9c719zWOsZ^c!0N2UsDQI|bRn;*H#nRN2wW?}yC+Y~m z6a@$b7T1PpmB1y4cYh-Ma5gX2#wiC(eh8eY3!S>5lc1Sh;g%j%t(WO$^P&W@cX(*y z;$r6KC+XzmG=$T5T~}AfGLHpk?!_T8AJn+n-6zc*so|-QeWoE0*@)1}Kipu~<23FO zXZswfXlr-9`IHxr-;@CDVkWQ`T|bj0_T7F~>1~!zK4z@KL!pqA`t%@37cNNjB*4-Z zQyTwRVCxvW)Vq=Cz*i<0lC1w5xC)g@0b5ky54tcZsk)U`3L8}9OZTz;E`CN^YZpVh zw#V=~y+YYNG`+%GDVPXDEAWG5&4D6^unQOTnFzu;HCDX~+$SMOBTK_teRSgg046*z A7ytkO literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/apps/calculation_icon.png b/themes/themes/local/upsilon_light/apps/calculation_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8930c03da7f628aaf4fb05f45358e964e2a81543 GIT binary patch literal 1114 zcmV-g1f~0lP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H11LsLZ zK~!jg?V8O`<3LIPvTW^R{IaNYJ;;-6@ z6YZ5-_q3N()k<6{w7W%w21v--;EdOv9)eLeAto_#;-vW<=he)-k7qm|@4Q4xDbaxA zI515ULI`-Chqyy2g`z0Xbse1Z4{XF2C#6KWT!s(=oO3Y7z!>YPBc^FW*Y%#Z>AEgl z*M$%Qs;XjodK!cf0N3KAlqi)-AcR0w)t+|VNEG54v27cK5EzC5LI@}Tl*{GFh4lkf zRUxHBrBVR^P{(mltJQ{Hn2)Neux%Sc2vE~Bp(x6red;NSqQJ5&P$5Lm7mh*07=zkU%M`f53< zjKvKrBP}iwWPJ7J9bUbywv?Y%1puIW>EPEFM=kcOk;1bWtfGP9E-oFsDP6U-JFdLd zp5*b^Hv`2@Ya9!Y(kHI`X`R^}JaB%bvlWC5ho_*X=N1gJ|=@p*;(K^+AW^|69zx%YKw*J2h${^5^?AF=h zqpO{}96NCjCKUYe)of4N8c)k+<})gk1c9E4-N1i9DDM^*rhtVWdlCU7JuS!LhLw>P zmjD1EMBh5>XJ36^%lG#7`qEE6pRbGSSKLwvf!*ERzVwyP=STaW8;cuO#^Q#RP+P&< z(<}Hd%Grl180{>k(QiVD`|V{Bmo*mv@XPm~A)Smzw5yp|T*wv|7qPRm({;V`^K)!( zZ@0CFyR1G;!4~zAW9rcJVp&$8Jy6`?$tC_S z1~+gUnJJ}yTknu!u^7p*EKuBW*+${;qU%OK|48dfTvP;#Qx%5E5J4a<8H;r)SX}P$ z6mpNFAAw9JgO!z)uIt&hjs5-ow)Rk;_|L-&yuW^|_;O(qoZauBlg(zav9Zy0y|c5k z-iv#2aDKCiKgmuYCNEYk#Ny{D6be{hU+=o!U6~l6gR(WJK5{&9|FgH060YmsyPakd zq{D~2M!tPA_$T2YQmIruYmX2T$yXaBv$M0Hl!7XXGIHXUmzTjg2i0}Gud&3aNvG3T zU0sEyX`r0*n;gYqhY*6z%}p4F0Yy>jVFo`hc$fvI(`jsNZ6TM-VP<9q!1Y$(=LJ_P z75K@ei9`aF#yiBKsw(E@=CHK1gtfIb7>02p3~0zs_Yp$CvMe}`1JCpB+$aYLAq13C gaL%D=TFZs^KZ15VxzhZA$^ZZW07*qoM6N<$f|L&%{Qv*} literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/apps/code_icon.png b/themes/themes/local/upsilon_light/apps/code_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bfc44ae94d86eec780209457e3afcaeb6c276156 GIT binary patch literal 1253 zcmVpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H11ae74 zK~!jg-I`5n8%G#`pV`%ZNUNw$EVm&^X+WeHyNJ-#y_Qla1()<}-y6s+zaZq|Kagv{ zC*N`iHu#cLC^P}#f(~ghlpcx$aT7U`ZQ0tL{p!J$>%>lWtsT%>&qYEzdU>DG?z}Uj z5pf*H9KrW}y4@~PN}?zt?`WEaX_}PFWemf3>ze4x#c@o#-9}1@VHoJTj;`xNN%%B` zVMrK;NGY*xo3XJmgb=`ga&a8<`t@ss5ZJaoVYlzK`d5XI`w2ZQHo6iMBp4KBe334iv+(ET*QWn46nZHn=H~TU}jcX=y2Q zoQ;hQwzs#rbLWnNtT?E1S$X#CnTjv~tgWr#x~`H~O5}ETcMsp+zI~g;#YL`Pzdlf` zAPCsm*-?>}8o8s)%*;?Km8jS2szaX|CC}VZ?%%&py213-MxD^)xx&XXrwL~VHDGKgJYkUtOABEQpHdrSFKi4wX0MrsbhBCfCrEN zKJnU>YLVF+pKz}D;q(*bqA23gqenCv4SKy^>f}zs^Sn&o@AIP76XlN1KF%5=T?%q3r)tTQJGrY@uTrU0 z@MQK4Vdc}WK@mbQJw45>Tekq%*veLRK#|(jPQc+)NumfpN_}NwoBvBHaq*rr(UNev)t_LER{-y&YsWX z<|{T@J@T^Wlv!?SY6^gr#!Fsp%ghO>B=cxlEEd`K!rY00qEg_8`TQR*cXduBXI8Fg zCa0d*Leui;f9+CoJxv@fYqc7io101!Dp@*)o~^SiL-6^fGBel465A%q&Ck!XwYA0i z`g*3M0KWO6%Jiq_GRH|vqTKlSIQQ<|!*$&gcY-fmxWJxFSG9Xy$o9UU*iHbIl9AXh ziPS~hw(a*XMPzcq08gH`c=EiJ*zTi}!O!1+mNvEblF!jX5kr?Vbiw51GLEf&^)+<4 zZ|f8MICGJozW(qw-5?BIZr=;vdR2*%d!cCR=$ar*v)}d(zy7hs`iouK-Tw!rqNS$K zl|(TM8nuhgK;Mml){cCuVRkU^@L3n^=YRhEdElJ`+?`Gb*L5*XlR}}8+bxEp zL{UT#1o*y>Wm$(}a73<;l#;!@J$&CMiX!rSWKjr#rfC?4f#W#GPW%1~tDBn9CG_Nr P00000NkvXXu0mjfp^tgr literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/apps/external_icon.png b/themes/themes/local/upsilon_light/apps/external_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4c88358b6bc9ecd2ae1e063334f5571c9ddec56a GIT binary patch literal 1367 zcmV-d1*rOoP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H11msCX zK~!jg?V3Mq6IUF7zjyz9#3!&5JJb!31XV;#Q5r;5u`xv*+Aeeev9ZCz#FmADk%dw2KV z@7)tY5Cm+(^E_!K(MitD-q@!^m` zRaHFCLkWUlDCK3*!1g0S` zXxp~@J9q#7{m5bfl}d#>ckXOEzmpii%X*6kzdpxt<3%UZa)m-cx{_kC$m`dyyT?() zLUto4CD(4ZyOUB`LOB_+khp?<9SYpG&kw)JiG{qZxA>{>uT-AC$}Jb`)SL2RA;lL> zRx8cO>H8{Iske4t#(!?)w0)I3JfiQMXs0p`MIV~`DmOcI1Va@lr|)ym zX?!*R392G5IFeBL`fN^WN+u;N|uCB&@EK8*l4vFz#J257=`~H` z`t|F~&(C+y6$%Az-@e`R#=MJVS=_vNbITWZCt?MwOG`_U#b6^$x_9qhw|9Ks=kDFR zk;TG>E-fv|HdRh82m+oyeJWi^tyZI4E(1_5m#Nq5(q%k(@}%eE+Y2eVPN&mzL1-t! z-flD+vGO~PL#NY`$}`mKmIrc!%7NUVav&F?X6*>L*_&8UuUfpu;+O=zBv)RaH z%*@Pi@#4kE=|f_LJihPq`0-;_R#rav3+u#*6U@!ckw|P0I334fd3pJRk%eiRoIH7w z+1c4`k^D~Vt9b5X6DukhBo5>T6-D+%9TbER8*;L7(tgMH{f)g}hG7hyT)W*y*L4)j zvT)t#|GWkP&+|wolPJ2bclRg`1CHa6NF*=}W8-TX?h77t!La@5bQ*YP19z=f!*Lu8 z!$8wC6lMR96u$4%ZnyC~k3=HT6@yK3A+GDvXf*IV58wBtm(%)*5CTO}&~=?;GP!NH Z@Ba~SF!(FpC}#iw002ovPDHLkV1iurm9zi= literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/apps/graph_icon.png b/themes/themes/local/upsilon_light/apps/graph_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..87ee2696d4a7a4393ecba8b7cac4abb6b91367ba GIT binary patch literal 851 zcmV-Z1FZasP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10^mtR zK~!jg?V8VS(=Z&zKX#ThQghg}H{jHl;l9V<5&BXH4oF-;2ni}Np)CRd8*CG8QU9>C zOYFqvP%@2iSlcCS673`Pl(hbS*<2X2uvz5TBkYyQJmZ6lw_kDJEcM(DWf90&T42MI65cs~o<+$q; z$1y?(_V@P@LLdRg<8kf90DRxaTFYcI0YGZ4Ns^@YVmZF=6UQ-1Da2?rvc?!Z&)asc z!ca;fr9>*FYB6!Fg5x-7t&!H+M#>pu7IIC*TDzDno63V+U3riL=ytnwyWJ9_6wl7G zH-=$YW{y&Lcr5Emqve(!b7gROl`@zn^d|{}X~HbdNK!+h7oR7OAKmNsH!e4vCWPk^ z;n^kAd0J{)8lB`tzc)6wcamu{OLq8WowP zaMqT46OOo2)qEUt){>hg8HXpoi_TIwYsvM#Owd^kPF+|1g0Ys5-!IF^QaCHiee2Je zZTW?GSyq-i`caQwgEi#7{H*S}cAJ&u(i%)8IgS2nuhDYi;^M*vL2&1d2L}fmh;uic zPG_kDnJ9{u@3~H=bDvCGVd5(NYZ^j`gpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H11UE@U zK~!jg?V3+dBR3d_fA(Z1vm~uxQKePY9(rqUz4X$LfYcAbN8vkgKwLR;1yq`xO2r}~ z)GpGBYzq}-vlKxH3mK9j|2kuj?H=f$E!`3_b~Ys9xh8(+ePoaQdTgSUQg{c>Ia;k2 z7-Nu9;-Nz+h2uELX0xzu`(MA2SWYR0dc6+D7;M{yWm&K+Yaj{tf)D~i2r$Orx-Q1Y z$3X}I@Q<8Q3e{>Agb=u{JK%Ja69fSWA()(;1R(?z0P6Mn(2D_p>$*@%!S{Ut0Ogz` z48x%pi*a2SK@fm322!ill#~*V;|w}4V=%@*DFtPW4aLOK3zlVpa}KJM8c8`R<+a=> zqLjLxEk~6{xnbo|ZdiGg8&*>N2&$CA?(Qzu*49uemC$T9F+Dwvsi`TveB)zt?-EZO z3!gr9@%0xUCn}ak)mLf%*A)lu9LpVTia$=yW;&fRIZFsn7}{ysorxbkWAM z2^Zge^9)ZMvz3z3VcF?)Ff%iQ{r!CeL4a=6j{?9Vh11Ibi_cFGMrL>ZM&)K_XK{6P zg(!-8YA2*ZI~4f-#aol{%>ENQLuyfTN3!sg%sgEiNu12m-AbERuNs zn`bB^Lvoc$1)gUnsYZvR-r`1L8Ida#3J^k=2nDHt-%pxGVi}R!+1W|_uH8mRg+I^R zMq+8p2_bNLdTJt<8@vXCG_Z-*mODE;Ln>vq7NCT{H}KwR%blN}YbS9VLIJ;{Kg_k| zE-o&>xtZB5CXxz%`vLUa$;pYa$s|Sy1+T%h<7mqrA0KNc@jkp}s2xLFu2QLJC-FXN zgYLO%wQ77w#zYcm{-qaNORiWfg3|tM<|z~oH5YTO%+}Y}wTtEdg9(LSkMzp0#B#px zqg*a)J-i#2jS#IM`Or)(x3RI&w;>rrD6n^&y!R4i7lt8LS62~+`qTAY@JM0%pbnPs z^Ft!Jd_ModZaPS%@bb-7qP9KDd7g({F4y;7i{V0H`*khRve1*9QVMf(bH;1QZU_nd zu>BUj?t*%fTV7s9xm-53>%K2iDtOHhzZ}$hYIkqBt*tHOa=E^(Yu(_H!i$4Cc8?qP zZga0)W}#5P!omU=)35h*h=jsVuRK_k;H%F*>iYjX<)oBYT3SLrpC7nbOe7?JELKr# za(w&cQ;<6j8SWgGo12?hSy?e6Ha0c}07%>V3qOw}e*U9|y)IwLNTpIy*=$z(fe(#V zt2Nr^bCldQXona=$hF*uUbp&2dPx5?>lW1NGTCT5jf{a zr_(oLz&mm=j4`y^ZE(&ZrR;iWKS&56pp?S4ZDcZ;|J?Td8_Bpf1fJe&EdT%j07*qo IM6N<$f+as6D*ylh literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/apps/reader_icon.png b/themes/themes/local/upsilon_light/apps/reader_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..aaeb533de06fabaf9d5f5e2eee1671a8e4872466 GIT binary patch literal 1617 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 TyFiuq00000NkvXXu0mjf;%nl( literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/apps/rpn_icon.png b/themes/themes/local/upsilon_light/apps/rpn_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..00ff3e8f62ab7fd88932643ae8240e1249ce3212 GIT binary patch literal 1972 zcmV;l2TS;gP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H12SG_h zK~!jg-I_~mQ|T4Qf8VuzeeKx9j`Jb`@@NP{T2zSC4y{_kBNGS+RRKYTPU~Z!(Z6 ziOpZ~=J=d*zn{+cy5IeTVHgHKlBQ{ljEtbFD!Q(d>5?P~S(b6RT-a>3l>fwAIKwa) z7#KiRRctmJ7K;Uo#WE`mejg+3@Zfubn0+}!Ca z7KudC0>`z<@-Iv6RESh#bQCzG$g|?vI6c|?+7EKaXwpGfNYge zWQldPg^Y~GdDa_A@wHei=(;`*mkoLuis5!zaVgfM&81kGP96Xf5oqW}y>H5zM+P-OEOWepra&3redK863hjv`5d@|G^#C3zYUWMN+@}_Lb zeyohETt^_xgJ)sWHSPM>LB0(1^T|9nwUr*a-;DD4-B*+DP%@@+Ls5;kZ$muqjbgQk zwEw$;QJ%gW=FfjDWuDIgKuMm$xtm>l|2o1#ljR^|D)+^={e&YL+gFsD&Vb+bL@4su zlVAXOE*pQ_P-zMV8C1DH)Z~$yVPFhUHRPLu_vj_@u<&?vqVy_)fj*Z@dG1Hi=fy;BtVAA^pFxg2-*1-Lu z*hz3uB{ZxiZEuED!jU-h3mm2~Su^Hp=DT^+HO%LCU-8>&FG9dq-wjagwNqV^V;Yl@ za21743ML%1NBQlEd5K*jKI~`*Rx~C4#MFukw}EHvNG=9zt72&CrKm{ zDZcsn`5ZWKfCmpAq`__4w26j>hP3>L49!Qo0uO4zk)7nd(z=F+8004!d- zn1cro5(osie*JpVx2md&ef##|^Z873Ra8{a-rml|ix*SuZ$I!))CV7PH8nNpy3W05vr=N!!Ma8#!|12#+2;qPMpeMMTO-su&ckW#5c00jfkkzYKvth#q zs;jG~eD;itxrv{q*RNlX&*!79tu1M5ZEc-0M|pWU0Q>jv=f#T`xZQ4=o13|H>lUX^ zpH6!Q%)(p2iWMu^zI{6_EiD8Bfp?CRB#A&E!1?p%dH(!40BhH-W%usgJbwI`J9qA+ z#mp#NQ&SULw{GRag$rD{a^<~Z+uPe|Z*NbrwYIj>*x1OjWy@v{P6$DBb2IDKt>fIe zbKJObW7>HpcEA4qekOZmn~@!g-|wfszMc~&PD~qY@7}!}J9aF^Cd)GO=FOwKyW2EI z`p_5JmM>qj-zaK?WxOVMY zim$`rKnQ_V2tiR%(UjYvpQF0EIsh6P8q#(bPo6yC(4j-`l;_^PdlVNJvu)cpwrtsg zEXxcJ4|DwZaYCU`if>U-5t1a0UuX?gDf{;qO397vLR?@uHWrh7t%!;!ooI#Eui zlhV>sbX}*bt1D&we5C&aFTD1zF&zJJP zCnjFKS5=irB!Z@C=(=vYXUh}`A&?{qo6SZ}PEOiw-~R&R#nAPH9!+2X0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10rp8m zK~!jg?V3TZn=lkb?=!Kn1cOA`LI*7853`UifCY3hT}}oaXA&teAd>S8b_bNN-&Xob zS`uQ*XKD-GqlYd79(nIQK47gy*L7fKIOp){2qB=9LRprOQvP;ElbrV+P1AsxA*Do? zWyrE@k-+yb31YZzu!MH$3uwvwY+(9&*0nl zAD^Coefx6P^P-+yk_(k27b;I}%WG{oH&IV6$%RUi3zb>h^4V>8ji@J=O#Z#EkMe=KmfZ3|-zlu}r&RuJO#zZA|n^nDL&E%H2n z7Xv=XO)xXg=QFIeaL(bCR~Cr~LI_AHQ53~r+rB>mzJAL2aYw1J00000NkvXXu0mjf D{{RX2 literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/apps/settings_icon.png b/themes/themes/local/upsilon_light/apps/settings_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a574aee05de12cc814111af9006579ee3b66a41c GIT binary patch literal 750 zcmVpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10((hB zK~!jg?V8U@+&~=1KQqb9rfdY)J-5eF_0Wq@sI;%(t9a`(^yaJd6$)NjFFmvsJPKY! zH(;c3CYkABn?-G*+9b^%^11D12)_@LnfWsOg!kTa3u`TDnqrK>IY-x#Qlhmcj$@Qk zcbdvA`IEQi>o52!f!Mf*(Us6cj~)F@`9LSYKa92m$<(^WJlQevS}=D2iGx z_ut8~3?T&b`5Ykx5@4}d^j-`giXy!CBuN5*wAONSbJKgVk|>JEvJ7Jk;_~v+JLk|^ zw_R6l7-Nu9B8@S<*f<_T5Cm9jk>2}3$~otjazn&>zuYZ{$|%=YM!CKcR((NUcn*&* z_dJ_-vojyZ@q-2*9UXCS za6q2ttN&i^y+b`&yED%IxZ=x)_q7%ph9Ud=`|RxOJZOBHro&!ykD9qZ@$u`$>QgLv z^?b>#&`lG)=3ox!uhT0KT1G0kHjSjn^+Ws=l@sD$iV38T^_X<@(B?x^6zF|Km_s*AtHxG{d+qcewi-7&6~_*EB8#JJAPT-^+Q&5d$>Wzces@0M-ejx{dV z=IvPH@vE-eu`zz#qwzz$t=qBx62IEK9UJ3UUm4~4$|%=YWZAwqG=vaKxuPiAuBBF- zbIaZ@t@YsK@;pZ=g^c4EV@%ui)QYv1>2!)zO09Ynhd`EPOePbw*2~?p>WF-c->If9v-B2}=KE>~B>N_W%F@07*qoM6N<$f(c<_i~s-t literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/apps/solver_icon.png b/themes/themes/local/upsilon_light/apps/solver_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a3c1f6346e38353d641c5ff34c3ca595b618eb2d GIT binary patch literal 2036 zcmVpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H12Z2dM zK~!jg?V3wWR9PCwfA>{YKw-UjJFSg6GTNg+aL~rcLt``!VKF_E7!za0xS5TzGbS!O zMqRjZuaU&r7#5n0Gs!TCJ%FO4cI((aK&2IV$)l>M!du*`d(SM8YG@eX7W9B*eo1Za zIrZiLuXE4&zH{nGLI}Ym;_*1qXcWURuq=z^jx5VK91h%WH=3qRX_F?*2_XmugBXT^ zrfDdOf}$u(BG?Y5X=0ithGF1xx$t_uNRkA6G$({$U|;}Al5n|PODs2aVzC&KBoPP% zkR%BiAQ%jO^2PvME*C-wLZJ`yWf|EpjAgfQSV9od6C_MAcjp;U6oq&^jw*y$Hs;<9>U{J45bwidn3h12Am8Vr za&v%RWTnkJj-n`7mW9;Q(<9t&_s=MKe!Lsfxz^f)BthAFFIlUc433QP@KqQ~2!8iv zK5Mhn=N&H^jiUaHdTr7AY#7rLeD!4kg}D|NX`7s0pqD)sWB23 zJ;N~qnT|=u##xh{M%F4P1K}~^roD>E*2s+|EdKn>J3fq=d{t3Ep4XXdyY*y{CvQe5 z-{_eu#*k zprp{l$Es@H*q(Er=cH4z&cnB_!`y8TQc~#QuMgkj)MRRZlZX6K!M@TQ_Lb(?4L^n3 z&xg6!5hTy&r0UmMw!fd!f>QZKfVTrW_d0@f4eE@>64aIFu_|rx)~A2H3~~GUF!??g zzu#Garq~=dQZ%P2GQTa$u>mels#_j6kM?KkivG?RnRrPQtV(DfFal z3{o^_T7thk=tl@xo1Mms?kMYWBYd$YGufx4(8K3Bv*#tJHnVU`2t4T;ez!Jb`+P~M zLTPzAL|c21ieLFDTkqxaKi*;_ERO76gC~7?)CJLK)P53wH>7i~BSenZ$@YytoSMwu zl5C8G#mxu(SR%DzcQWlgQU3nj0B_#Uo+szRmbtNy1_1V#=Ag*qlWu)p2Ae<2q&wr}*|N!Q5nIH$Dj-jdDgfD1*I5CYo?cQP9b z+;}rI@_h^2KrfqJ!D5rrocomczqwD96>Uz1@zbJ}$jTqNm2_q~fngXcd*oKMx#dy9 zPa+bD(ACw&$jAtOzn^vM)}gAY$y_$(Ow**Hp@F8RCQS3k&9BerNim-m*DyHVlKNrY5{zFDFl) zBt3n62cMgpi_hod?Afzizkc18Ik35)^z`)5+1W`v9-r1`Sr(n0o%Hwj+Y$iK-QA7H z<6-;u?MY)36c-nxX&QZfeYWG;8o67yZgJzr4UQi_PDMqHBT%}A2O z&Ye2}aJ$`PWMs_xnX7n+L?T?cZ~;k@sI9HFBNebYa{2lBY}l}Y$B!Qq4u|o0JOJpr z&i(uMDK0K15C{P9_19n97BiV}ILw(dXXx$iWF#bxW zr>C=R+qPuCYuBzJ<~Ru}E-t31sA$^rJ9qBz>eVZ1YirrRf4^OcNo<=-OG~4?yquPn z7W(@7P!xsd&!4kr&z>oBsIjqeUa@8}*WTU^z^+}p?8=)$N_noTs*0AD7TVg{P*oKn z1XWd4Q~Lb3)o(Hx85uJ^U%h%YJ9g|y&fio?sX`G#aN@)XGBY!AI2?pRq2#KzTuAx! zOOhliD=T^Z`ZX_Jyr8Ter%ut)(XpU7DQ|fuUPB>7GPf-jy}iA3c6Orc z3p-9vxwiCry|`R1ii(QJ%Ci5j=WHB3dK6vP$;-=IP#iVY*Xi!vyXd;k-o1MlBw!{M zY6`}ujw(qKrfJUW7LjS1w6?aQ>pG2%jpXL$Qd(NNU?Gc*Wm(9wjOuVW=50<<6fRx5 zL@XA=;c!r2Uyp3_`o#Z&L?VHvX-I})h{3@@=9>eGRTZDlH=YV6?ghtUv8C3wASU$td_LeO7q~;AP;x%0s=6%a z(@(K1i$o$pJRZmCbS90#By$rOhQa9QDDik4%d+OI?3WTrl8|K?P19!F{Qd_&X6Fp9 SsPkI@0000I7~8 literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/apps/stat_icon.png b/themes/themes/local/upsilon_light/apps/stat_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..af1c46cefff189a06cf27410f7c713e4156cb21f GIT binary patch literal 581 zcmV-L0=oT)P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10n$lC zK~!jg?V2%;+b|GC|4=4HKq_qb3OP^;7d}Xiu?KPCA_q$C#(z@7=dg)iHP76-g|Ui2WCbH0k4iy3dR`Jbq%fcAKz$_6A?5`17?QS8bwi{ zD2hb_e}!QfFbo5j8P-~?*J}_FfZuW=g7f(dB7(Jc;kb|EoC6VIyWN6_pa7t0n&`y< zz*-9tLEE+f0Oh^M?RJY^Y{FU#=NyRAWB{rq+J?2qr?o{}Ny&1Jb*$#SuhqHO2kv&ehr`TG(sD`vmRT-VvRtfWxmd|^ zv6AItCCkN1mWvfSola8M_0lalhUTP50C^rnl(phFDgfMo$j4{c{^?eVm zHB?>KVCJRs%!>CORaHT0tv@=7Dd3z#S(Y%yjJM^~3yxiI%D>rc0Q^%SBs1oV9m?>)-0{15{^$xSdbuGcHP_Xr{2l`D%x1f>+T)~KrLpSJHin8CgO TM+A-@00000NkvXXu0mjf0tf?E literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/exam_icon.png b/themes/themes/local/upsilon_light/exam_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..764b0a02557784aec1db649760e34f42588ecd42 GIT binary patch literal 424 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 T`jS~dn;AS^{an^LB{Ts5bh}P@ literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/probability/calcul1_icon.png b/themes/themes/local/upsilon_light/probability/calcul1_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6047ccc88cafe60e12f6e0876dfb7fe295bcf02a GIT binary patch literal 676 zcmV;V0$crwP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10x?NM zK~zYI&6hoI(@+$KU*{7Xkv369p`kEEiA2y!-LcM1WQF>-+jN2!IL`)Y|eKH)l-=ESv z@Y(q|iwhGh@+FBnz9+aZW&6bQ$vD22W$O6UaH66?R5V-9)kG8xMMI-$?>kJ?aO?Hv zksdZ2z|3R>>pxmlIjG?{e%Fz2Zh9OXe}ZtjVPbP&`>@m8^cZSl2fA@^;n;}^j^lTs z6KBcBBO%1jgizjgHq(Qmf-AV(9#R)$uEzira9dK>Q85ha_LT{2Y`4M7U035=!5PFv zX1NBpO;+So0QXaq$SiNdaJU>?!5PHD`&<>gthyunqr2D7A{6l9L#_&gID0!saNVAP zbe6+fO`!q<03XBP`GZSP`v%gVIHpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10xd~I zK~zYI?U!Fq(@+?PpLT1v!Rpu&5!@sp7&W?JOuX^N8%4eH3-}rF1IUeDaHB|~&WM0G z`7_55)@F2D*K=Ag3?xWcyLQRE@ORn1@A*CH`<~PHDAj7!=O2|iYJh%94r38l`n=m} zvvtq|U~W?9{_Gf9LK!Wb=fkT?i+xL+KKoL0Xxfrz^OsO#@Wn3cr`_%uuoKlSL1nia zYwcKYuJrl1-MR4nhwU~!5q-bKf?KOOxbk9iSHjo26M6kuaGQ-_mDKIiB3UEnMOA_)Zp88GO`G6@`Vefg?PYKDTh( z;4j!0&h!5?|H~DNY+7ZZY{Z(;w1mQwvcZ_Hu()EfUU%qM<#Y2~*Y!9&mh3fMzBPJe zQ!0;c8(hf^WiBP5Dm=WIrEUq{ZrQxAcDO#NQ_O1=vWYXje9m!k#UhtUFr7>Cv}{nw zCWZp_x2AGQp5M!Hcr5vT;IR6mgVm9|c#uDhQT|_WBI-AcV%w9(erQer0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10xL;G zK~zYI&6iJa5>XVz&rmvmr4-NuDCQ3DK; z{UnNXREpQzMWlW-zyY|j;6rpZ1jporrMi~i9HBa1yvd?ilW=b>jE+k2akqxpW)4)d z)-jgh-lr>&6@o|6Wn2t<|E}k&ZG1>qM+YZ&DH5p)?%q5HC*S`G-=)gvbm?f|ws&g? z&$^SN}_~~L>`118wd=+NeV%1GYh3l zasT!rY~1j(?Kq_i4aDANAuGf0+<|aZ(~-=GSdV{!*UjU>oh9&g>+@+&XyI8rjbug~ zI974JW4fde2z42m(gAk9@54LABYHiA`GDI{@Z;y7m_XgGNnDNia6aTg(C_-Em;2zJ zzTCkXj}wuA8<*#O2>M-2VXSbydh+nv@o?j$vHrpN4UmH3Z!6)+Qvd(}07*qoM6N<$ Ef(hz30RR91 literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/probability/calcul4_icon.png b/themes/themes/local/upsilon_light/probability/calcul4_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5759f44c327b619c96be7acc37b2265a47ff8dc4 GIT binary patch literal 767 zcmVpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10*pyS zK~zYI&6i706Hy$+&rI8C>0{cdgqEu5gBBAgVwA)Lg9(i!x^Uq}6MQ8u{0x2$^$WOQ zrwhCEfhN|-hNgm+1lmF&Fl}{O=zHiqT&Y1Uopzvbw)fm~f4TRcd;gb^Wm(1v6?xnM z2T6MvA$&u`+vpY+*9riD5f6i@i!SJO8dbT3+G7pBA&=8{+Z6&9889Fg+MNWBq?PNnn13>o{K(Tr9<5EYRC@eAH)0ES2v# zzhk)Eb{)k^14C|GQ_ry5hV3$sA3OE#!Yyy^z}IhqPSZR>ry=2WTCluX>@HlAEy3@i zTlxYn8cDW`a1BAiMy>+C%hJ*tI89?cTR}sRx&xPFOR&>=SWQ$*uhm3>u~0~5O5K5r zCOC|E7{&gehe0&a{*}@;+!j|wI$Or2z$wLZ#sa-q&6L6Bt49YXh!Vnc>zEjEz^GT= zGnfooOavVW&uu^umDiqPI6;*0GLnXY(qeq5Pw@i3b!E5@l$OM+NE(8utk+NkSLFrF z%qGF_ikP1Cfh5}RG)RKL^rR29T>lBn^m-`oS?L`g<8!Qpv$2{to@yP*M4+Rk4* zy-tIBp#jiF9iD~bh$c9QQuAHhS2$H(0GqENu~o#vY5_JAg*#Wha68N$L+x2mL*mxB z8*AAz-b6R?KE`6y%fRoV;k5VcspWlePhZE;OB>*Im~lTe0H?j@P@w*m$6?0f8|T30 xYxtfnVD>AEpT#pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10dz@3 zK~zYI?bJ(40#O*o@xS9Znaw0qQ+SIqsSLW(br%)1=-WkKqGfGbw2L4mYEhI5fngNk z6y1~xMm?>9S)|mI>7r+MIPU|$#W}A_2q9>aNR0;A^N*>Pe@wMB6-Krna%Pde(>&LA zF9;!s1~le}yXfs!{{-Xu*faYnlbxe$K3h7mpvLkmC9mmGsW|Z^`(MWakDrcWOm3va=CsNlgyZ6`{3%^MZ@o9btXbAsQn&h zeJdqypWd96fzhbW(nJVdRep?-ok9QvaB}g$S?-a^SSJf(0or{Y=NQLLTgnx|;rSiM z*?Xo34d&toy4unh%tx$pg+$sUnK6k5G^Pg(;yr%c($^k(UH+&Lki998y(yrzdKl@` z8H}{k+pX4w)n{SYQh2pUq)ifO6OSwr>h#fLw9)1FqH79%pF)e`Mv|9^ dxqR`{`*-7Ub7>VvD!~8%002ovPDHLkV1iJ*+(-Zb literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/probability/exponential_icon.png b/themes/themes/local/upsilon_light/probability/exponential_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e0032db683ac87925c6a414f5a60ff2382742a11 GIT binary patch literal 479 zcmV<50U-W~P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10c=S` zK~zYI&D2Xv0#O*o@xP;^<892$0<$8d+C_aqKS+xLi9SIQU5FNKB9I6YL#FbUPEcOP zm{P}S(aj=B0v(^_%Q^5nFAs-!B5A&~409AB0MwqEj6wu}tlF^J0lZ@_ZYx$hfUeD` z72Rsf8wOzKtbl2ygf2{0ZE&tUSZVpf0BoNYEG_dlOxx(Nb$pFlwWdG9&%txkU}G=C zO+sXNUX%hS&fD8N_}7NE4Fwd`L9yg1shAz0L$}1Vi6gqR01FYBkn9>RraNuJL@8+~C5>IcD>;}6dzlUUn4a)3HRi_cvJVII zGf&TL()k+ceC>1QcZ*C0C4xQ|vRfqJbs~F2{2mdXB;pi>!C-!irnk`a7SQ`MeFYA2 V0~9B+_K^Sp002ovPDHLkV1g{_$6Wvb literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/probability/fisher_icon.png b/themes/themes/local/upsilon_light/probability/fisher_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ccbdd5834db5fdcc0513f4aa0e589fb1a739a374 GIT binary patch literal 491 zcmVpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10eDG7 zK~zYI&DKjx0#O*o@xRj;Q)*?PWQ!spi24A1(`Z$+Y8iAPkRaMb5e3ph^HO=4taNM~ zZ)09gt4zq~rjF0@eGi8p?>PsKO$Z?vl1NqqIy1bOozn!HfhaADPOgo~7EFTiJoir} z?lL87F_%$|_j!cV1#2;}2cumKr*un>N&+w{b+QGsT{J3n;txe@Fp1~ScjV;ykwMQ! z0?25UPmNTzOfa4wJVxz;0oXrJkJe+f!D447$@uH*PcND%zBehQYVzu0kF#wzUeW%Q)7506{ek|j4Luu h#g0RfvCAL7m^Xnbqrliat8@SW002ovPDHLkV1i~4+6e#v literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/probability/focused_binomial_icon.png b/themes/themes/local/upsilon_light/probability/focused_binomial_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..581bde13bc6ca244f7c2d70d3a325c5e3c49b4b0 GIT binary patch literal 228 zcmeAS@N?(olHy`uVBq!ia0vp^%0Mj4!3HFy7H|0iq&N#aB8wRq_zr_GK#zs~w8FIe09k~Ms_m&`IRacaBAusV5#tnHCm=UsY~q8Lkl&tN+w_<|wLo*~v* Vdud9nCNI!(22WQ%mvv4FO#s&FPaXgO literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/probability/focused_calcul1_icon.png b/themes/themes/local/upsilon_light/probability/focused_calcul1_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e3110c512fe2dda586567b67c78b7c4b0da14db8 GIT binary patch literal 639 zcmV-_0)YLAP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10t`t+ zK~zYI?Uzk&(?Af0-?hC?9cV(+q@|RkG~%O|svMv<1iyiw$`JtvloJvJq@*gTlsHb4 zRCR39B#ysr4iyLiZtZo{a^Su0&dj5k+1Xtp%W{Z+RNQp~TqI!;PI|hFj~x@^xrcm~ z$9^@7%@vy3Z|ck`nuE8^8Fa%#buEMYYZ<&dn4zaR*9&L6A!;oHL3rM_%S#+~%S(8^ za}%|e0ozO0&Z*#d4glzWevd&*B)Ko@Re1Hj(Wv#K^S5XBO~{UB6_+5*2*%$sS_WLe|f}G zjt9>Hqp8Odh7E2oj#ny<5R?ld`rp_}%LLa=Fks6C3PrmjIBn)ZpZT%(Zx$$~mJcm~ z%?~Apt9O$PR>~7R+Q^{ZohJ>I46dU(P#Esay|RSUi3eFtW*ZxBIQCF$8HvFFKokgG z>@1^pWZ>j1{>hC6*HIn3ZcO3E7#~r(vm)Tx!y97 Z^&QQF*xj6Rq&)xt002ovPDHLkV1l>KAzlCg literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/probability/focused_calcul2_icon.png b/themes/themes/local/upsilon_light/probability/focused_calcul2_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..92921687160596ba18ce7e476a73730c9255142a GIT binary patch literal 675 zcmV;U0$lxxP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10x(HL zK~zYI?Uze$(@+qFk85AIc{GWm08L6;6#6uo z7_vzoo9ii*r1+_0JJ-}5ysDbeEe7TRy2SQ>T@yn$ z-Z;mCt2S-)sdwtPf#c&_+m4eyI-KkI_;D}{jQPGl1oNW17a6XlIk276T^q@EyjZgr z8BXp{hyoWFBMKbq>Wtw$-$z?_P|A_OxMGf=sZ&JlY$Q0f=Ry=XNGU!rE}!Bd3LH9y zJ3F}j6Bo-;uxleNO9buXXswL|*U%_d^5NjFO9Xq`jNmlWg=Kr;oy!v#HiKre>BH?b zY?O=h5cuFt9f|N9%Efu?H2TwptE&{M;o{zE;#@^GRuVYsxlmL(9k_!N7uz)pTjdnQ zkgkm+3LKtP7V*Aj;jkNib3?(^Rf;!X4LmNVQ3^S0f2$-3cyv33*Hr_u8hq@H6nN?C z4H!PieP|}bv%8B}%Er5+trbLw0*AM|7UUy}%^OK%5@-J_C#re=AE3jo1EoXJF&HYv z1#DH)Am?o5St{lToNi#+;euo+xx@ef002ov JPDHLkV1j^(DqR2o literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/probability/focused_calcul3_icon.png b/themes/themes/local/upsilon_light/probability/focused_calcul3_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..df632bc2af106d59aec0e9a219460e8666908bec GIT binary patch literal 633 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 P00000NkvXXu0mjfZgtR9 literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/probability/focused_geometric_icon.png b/themes/themes/local/upsilon_light/probability/focused_geometric_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5718a0a7ab54cf8a45928a2479f462b25611aee5 GIT binary patch literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^%0Mj4!3HFy7H|0iq&N#aB8wRq_zr_GZ6NA_>1IMGQWDWpr OW$<+Mb6Mw<&;$S*eMolz literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/probability/focused_normal_icon.png b/themes/themes/local/upsilon_light/probability/focused_normal_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..19573d834676c8364162f32da5a69e0a9e53e451 GIT binary patch literal 516 zcmV+f0{i`mP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10g*{W zK~zYIy_HK(!ax*-Pa71G5LUht6Boq9g$uX(cl{?CB`ypmm?%)N6++8PsBM`Jv@fNSzZG;FYLprJB@-&HylReerkWdXQSOf)&{H?!9 zjK|6l;fUkvETwd&t`gMjM|hb6nTKrl$J>En9LO6!merO6$Qwh%Yzr_YJA_MCXL3o5 zN{en5hH;K!BNWJLaB4Ho@l+k+ zt*O?u%T%xk0?Kf6z7Ji~*2A>i2zevGR}lXz$V6p_qU%Sv&ZTjbolUP*svq1V47NK) ztvy1=i#7tWNC%k&a)-M(&h8+S(vi4*=x;6gygFVqbNL@E8{3C^-anH70000_|91J zLVA{>Grz+H!zojwm&`KW;?y>eVR`ZjQQIXmtamuKg(*7q*!uiXdC558ZK{-p`-0}m d_Irj53?j?*mLB2z`U&U+22WQ%mvv4FO#oIORC@pb literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/probability/focused_student_icon.png b/themes/themes/local/upsilon_light/probability/focused_student_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..15c9e7be9507b806f20aadaa9f53c130dcaa15ee GIT binary patch literal 491 zcmVpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10eDG7 zK~zYI#g@%a!Y~-d9~dA8L!!oDLOl6#G4bj@&XxNTFL;nd9Wsf?H-ot`*7jXJ7!Kez z_5tBJzH9sZ+NbTB(RE!Sg|vYSwY9+GbqTkXLYkqFP19MB#8cY=6B4RsgHft^rGse= z864&T060l}4B6pe^zpiRXL2AIP7?jOSgZAB96Uu-iD7y(4)WHG$@{Ax07m%M2Xp`W zUSWo&zv*Uldxzm9F|qtjCl)2%H;G_IrhpgV=Shk2PqNiX1k(?QXr*4ls1m_^j>GSh zZQpkW^EKr#Yzl2-*o3YT|A@zeVI-lp7LnSj+9LKwjtnD6qW)sRbi!9H!X-5 zK#^b!n^0?*v2WLJgQM0kx5m0sChR@q3W;WSiRX?Jc$A|^$pQ~o$GEQ+QOISY!$<&A zOF$biy!G8wKfX=n72xi&h?`0Q=O?-UFt6`zX)Xy+K96NTs7Js;7Be hlyf*MDJT~e`~n!u)lpzpx={cC002ovPDHLkV1iy@)NcR) literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/probability/focused_uniform_icon.png b/themes/themes/local/upsilon_light/probability/focused_uniform_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9a2b1ebc8ceca230a0ef80fda61f6521da2ff04f GIT binary patch literal 198 zcmeAS@N?(olHy`uVBq!ia0vp^%0Mj4!3HFy7H|0iq&N#aB8wRq_zr_G2w?$1;V6Ksq3At$UN+JfXF7WsR jCY>!>pFT3ilrb^n7V@Q^_P=HXG@ilJ)z4*}Q$iB}|2aeh literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/probability/geometric_icon.png b/themes/themes/local/upsilon_light/probability/geometric_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..82bd78447225678d98a97cdef184009bbb09b30b GIT binary patch literal 224 zcmeAS@N?(olHy`uVBq!ia0vp^%0Mj4!3HFy7H|0iq&N#aB8wRq_zr_GrRu?8UZH^o?p^q zmS{bu!CWOKwVLNe;EUp!(}J!VZ=LY#Z<<1@b-uO#vy+qfjkQ(p$E>_x!qlIqC8FpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10hvie zK~zYIz1Cez!cZ6o@c)E}N`y?zz>uJ>0)y}(=pyJc=qvSM`T)^I*j?I%MGdtXTA42` zM^o8!&NnvOP8Z=trM9zX|I5R34nLlqJsU#^A+Q-XFX1XzhuRy!Ge3jfO+O+VKD)<( zv5Sdks)%vokM;en73_rrwlFR`n~HC3#795?AjXL(iS7Tv7$!lKtzcX`34pVk3f>L+ zhErpb&k`h6n^+^aHKdF3bTK9&NaX64Y7^NSOv>v>g~{>?l#Xtxw%O6~@Y00X~6((JfK|7HM`P}Cy#w3LL5e<_n$d)g%CCpv10kt=< zg;9G0WJ*nEFn!G!IreFC!HMYW#@{dm!09z_yDP)Ax;jo0{Me~&2@~T)JXYvt#kk~! z7A`Z@i8Uj?+9n}5X9e6o(ai}f*3jguQq31!!0{ab O0000n33XbYEM(woFX>Yn@!Ir^6!ESY4l=-ZuVVDPbA(Ov(x Rr3+{=gQu&X%Q~loCIBcVOC$gQ literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/probability/student_icon.png b/themes/themes/local/upsilon_light/probability/student_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f0b0999a34eb64fb373ba9800ee5e83e5b1ac8cf GIT binary patch literal 520 zcmV+j0{8uiP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10hLKa zK~zYI#g@%W0znwYAGI>Gh?OJ-A;=CLf+#u!(Y0;nKgLHXrSPC!NkTfq;o>BLqh!R(R`7dSY!C}K*#=bI z#C3*)YM6_I$(1DZ6$1dE875xX_TpgjweHCHo$K0o&KO1~7Ro|@#K!V6bYjgPhUH}t zbM#0{KAa!KaJnDG>Po;l3j(z@&b0000< KMNUMnLSTaP-sP+qk0;3&2->M9$QXV~nR}wKW jwTxm>qCbBy=vgzqN#x_YWFegZG@ZfI)z4*}Q$iB}^#4Mt literal 0 HcmV?d00001 From fbe542e5bfc24266a217a407bc0b016109634cf4 Mon Sep 17 00:00:00 2001 From: Laury Date: Thu, 28 Oct 2021 14:41:33 +0200 Subject: [PATCH 090/355] [python] Fix bug in draw_string --- python/port/mod/kandinsky/modkandinsky_table.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/port/mod/kandinsky/modkandinsky_table.c b/python/port/mod/kandinsky/modkandinsky_table.c index ce962e4c1b6..902338c5f3b 100644 --- a/python/port/mod/kandinsky/modkandinsky_table.c +++ b/python/port/mod/kandinsky/modkandinsky_table.c @@ -3,7 +3,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_color_obj, 1, 3, modkandinsky_color); STATIC MP_DEFINE_CONST_FUN_OBJ_2(modkandinsky_get_pixel_obj, modkandinsky_get_pixel); STATIC MP_DEFINE_CONST_FUN_OBJ_3(modkandinsky_set_pixel_obj, modkandinsky_set_pixel); -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_draw_string_obj, 3, 5, modkandinsky_draw_string); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_draw_string_obj, 3, 6, modkandinsky_draw_string); STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_draw_line_obj, 5, 5, modkandinsky_draw_line); STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_draw_circle_obj, 4, 4, modkandinsky_draw_circle); STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_fill_rect_obj, 5, 5, modkandinsky_fill_rect); From f2f44f0f6f4f939be9cf525129546cdf8aabb66e Mon Sep 17 00:00:00 2001 From: lolocomotive <49951010+lolocomotive@users.noreply.github.com> Date: Thu, 28 Oct 2021 19:13:21 +0200 Subject: [PATCH 091/355] Make use theme colors instead of hard-coded ones (#65) --- apps/code/toolbox_ion_keys.cpp | 8 ++++---- escher/src/modal_view_empty_controller.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/code/toolbox_ion_keys.cpp b/apps/code/toolbox_ion_keys.cpp index b3e67f63f04..1387f9badff 100644 --- a/apps/code/toolbox_ion_keys.cpp +++ b/apps/code/toolbox_ion_keys.cpp @@ -37,10 +37,10 @@ namespace Code { } void toolboxIonKeys::toolboxIonView::drawRect(KDContext * ctx, KDRect rect) const { - ctx->fillRect(rect, Palette::GrayBright); - ctx->strokeRect(rect, Palette::GrayDark); - ctx->drawString(I18n::translate(I18n::Message::PressAKey),KDPoint(rect.left()+80, rect.top()+20)); - + ctx->fillRect(rect, Palette::WallScreen); + ctx->strokeRect(rect, Palette::ListCellBorder); + ctx->drawString(I18n::translate(I18n::Message::PressAKey),KDPoint(rect.left()+80, rect.top()+20),KDFont::LargeFont,Palette::PrimaryText,Palette::WallScreen); + } View * toolboxIonKeys::view(){ diff --git a/escher/src/modal_view_empty_controller.cpp b/escher/src/modal_view_empty_controller.cpp index 99757cfda28..f8f7056dfa9 100644 --- a/escher/src/modal_view_empty_controller.cpp +++ b/escher/src/modal_view_empty_controller.cpp @@ -30,7 +30,7 @@ void ModalViewEmptyController::ModalViewEmptyView::setMessages(I18n::Message * m void ModalViewEmptyController::ModalViewEmptyView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(bounds(), k_backgroundColor); - drawBorderOfRect(ctx, bounds(), Palette::GrayBright); + drawBorderOfRect(ctx, bounds(), Palette::ListCellBorder); } int ModalViewEmptyController::ModalViewEmptyView::numberOfSubviews() const { From 5dce215165d36f0dc230a626d3a525ce5a90ce4c Mon Sep 17 00:00:00 2001 From: Mino1289 <68814671+Mino1289@users.noreply.github.com> Date: Thu, 28 Oct 2021 19:15:26 +0200 Subject: [PATCH 092/355] [build/config.mak] Update of base theme (#64) --- build/config.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/config.mak b/build/config.mak index 4ca2f781eec..222cc244db7 100644 --- a/build/config.mak +++ b/build/config.mak @@ -14,6 +14,6 @@ EPSILON_I18N ?= en fr nl pt it de es hu EPSILON_COUNTRIES ?= WW CA DE ES FR GB IT NL PT US EPSILON_GETOPT ?= 0 ESCHER_LOG_EVENTS_BINARY ?= 0 -THEME_NAME ?= omega_light +THEME_NAME ?= upsilon_light THEME_REPO ?= local INCLUDE_ULAB ?= 1 From 58ef8cb95df061319bd50423c3c3b1fcb2d17095 Mon Sep 17 00:00:00 2001 From: Mino1289 <68814671+Mino1289@users.noreply.github.com> Date: Thu, 28 Oct 2021 19:15:53 +0200 Subject: [PATCH 093/355] [apps/code] Remove import of mathsup (residue of #50) (#63) --- apps/code/script_template.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/code/script_template.cpp b/apps/code/script_template.cpp index b6e3b72eaee..c5bde929ea4 100644 --- a/apps/code/script_template.cpp +++ b/apps/code/script_template.cpp @@ -3,7 +3,6 @@ namespace Code { constexpr ScriptTemplate emptyScriptTemplate(".py", "\x01" R"(from math import * -from mathsup import * )"); From 473b0bbfcb0dd55f95fd72c6e9c5d7f476a44473 Mon Sep 17 00:00:00 2001 From: Mino1289 <68814671+Mino1289@users.noreply.github.com> Date: Thu, 28 Oct 2021 22:20:00 +0200 Subject: [PATCH 094/355] [apps/settings|code] Add a Flag to remove Code option in settings if code app is not compiled (#67) --- apps/code/Makefile | 2 ++ apps/settings/main_controller.cpp | 2 ++ apps/settings/main_controller_prompt_beta.cpp | 2 ++ apps/settings/main_controller_prompt_none.cpp | 3 ++- apps/settings/main_controller_prompt_update.cpp | 3 ++- apps/settings/sub_menu/code_options_controller.cpp | 5 ++++- 6 files changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/code/Makefile b/apps/code/Makefile index 23b6a320780..af50725cd59 100644 --- a/apps/code/Makefile +++ b/apps/code/Makefile @@ -1,6 +1,8 @@ apps += Code::App app_headers += apps/code/app.h +SFLAGS += -DHAS_CODE + app_code_src = $(addprefix apps/code/,\ app.cpp \ console_controller.cpp \ diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index 2b74f96fa7c..01145af4e1b 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -21,7 +21,9 @@ constexpr SettingsMessageTree s_accessibilityChildren[6] = {SettingsMessageTree( constexpr SettingsMessageTree s_contributorsChildren[23] = {SettingsMessageTree(I18n::Message::Developers), SettingsMessageTree(I18n::Message::QuentinGuidee), SettingsMessageTree(I18n::Message::JoachimLeFournis), SettingsMessageTree(I18n::Message::MaximeFriess), SettingsMessageTree(I18n::Message::JeanBaptisteBoric), SettingsMessageTree(I18n::Message::SandraSimmons), SettingsMessageTree(I18n::Message::David), SettingsMessageTree(I18n::Message::DamienNicolet), SettingsMessageTree(I18n::Message::EvannDreumont), SettingsMessageTree(I18n::Message::SzaboLevente), SettingsMessageTree(I18n::Message::VenceslasDuet), SettingsMessageTree(I18n::Message::CharlotteThomas), SettingsMessageTree(I18n::Message::AntoninLoubiere), SettingsMessageTree(I18n::Message::CyprienMejat), SettingsMessageTree(I18n::Message::BetaTesters), SettingsMessageTree(I18n::Message::TimeoArnouts), SettingsMessageTree(I18n::Message::JulieC), SettingsMessageTree(I18n::Message::LelahelHideux), SettingsMessageTree(I18n::Message::Madil), SettingsMessageTree(I18n::Message::HilaireLeRoux), SettingsMessageTree(I18n::Message::HectorNussbaumer), SettingsMessageTree(I18n::Message::RaphaelDyda), SettingsMessageTree(I18n::Message::ThibautC)}; // Code Settings +#ifdef HAS_CODE constexpr SettingsMessageTree s_codeChildren[2] = {SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Autocomplete)}; +#endif constexpr SettingsMessageTree s_modelFontChildren[2] = {SettingsMessageTree(I18n::Message::LargeFont), SettingsMessageTree(I18n::Message::SmallFont)}; diff --git a/apps/settings/main_controller_prompt_beta.cpp b/apps/settings/main_controller_prompt_beta.cpp index ab9a457bdbd..1b5f8814320 100644 --- a/apps/settings/main_controller_prompt_beta.cpp +++ b/apps/settings/main_controller_prompt_beta.cpp @@ -13,7 +13,9 @@ constexpr SettingsMessageTree s_modelMenu[] = SettingsMessageTree(I18n::Message::Language), SettingsMessageTree(I18n::Message::Country), SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), +#ifdef HAS_CODE SettingsMessageTree(I18n::Message::CodeApp, s_codeChildren), +#endif SettingsMessageTree(I18n::Message::BetaPopUp), SettingsMessageTree(I18n::Message::About, s_modelAboutChildren), SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren)}; diff --git a/apps/settings/main_controller_prompt_none.cpp b/apps/settings/main_controller_prompt_none.cpp index 78224036310..3cccfd1ae77 100644 --- a/apps/settings/main_controller_prompt_none.cpp +++ b/apps/settings/main_controller_prompt_none.cpp @@ -13,8 +13,9 @@ constexpr SettingsMessageTree s_modelMenu[] = SettingsMessageTree(I18n::Message::Language), SettingsMessageTree(I18n::Message::Country), SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), +#ifdef HAS_CODE SettingsMessageTree(I18n::Message::CodeApp, s_codeChildren), - SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), +#endif SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; constexpr SettingsMessageTree s_model = SettingsMessageTree(I18n::Message::SettingsApp, s_modelMenu); diff --git a/apps/settings/main_controller_prompt_update.cpp b/apps/settings/main_controller_prompt_update.cpp index 22f24059e83..ff2056bc9e8 100644 --- a/apps/settings/main_controller_prompt_update.cpp +++ b/apps/settings/main_controller_prompt_update.cpp @@ -13,8 +13,9 @@ constexpr SettingsMessageTree s_modelMenu[] = SettingsMessageTree(I18n::Message::Language), SettingsMessageTree(I18n::Message::Country), SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), +#ifdef HAS_CODE SettingsMessageTree(I18n::Message::CodeApp, s_codeChildren), - SettingsMessageTree(I18n::Message::UpdatePopUp), +#endif SettingsMessageTree(I18n::Message::UpdatePopUp), SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; diff --git a/apps/settings/sub_menu/code_options_controller.cpp b/apps/settings/sub_menu/code_options_controller.cpp index ee8d296f650..1501982706f 100644 --- a/apps/settings/sub_menu/code_options_controller.cpp +++ b/apps/settings/sub_menu/code_options_controller.cpp @@ -61,11 +61,14 @@ void CodeOptionsController::willDisplayCellForIndex(HighlightCell * cell, int in GlobalPreferences::sharedGlobalPreferences()->font() == KDFont::LargeFont ? myTextCell->setSubtitle(I18n::Message::LargeFont) : myTextCell->setSubtitle(I18n::Message::SmallFont); - } else if (thisLabel == I18n::Message::Autocomplete) { + } +#ifdef HAS_CODE + else if (thisLabel == I18n::Message::Autocomplete) { MessageTableCellWithSwitch * mySwitchCell = (MessageTableCellWithSwitch *)cell; SwitchView * mySwitch = (SwitchView *)mySwitchCell->accessoryView(); mySwitch->setState(GlobalPreferences::sharedGlobalPreferences()->autocomplete()); } +#endif } } From 41b2b64d800142aa3ed2cee8a18c490dfa3d5124 Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Thu, 28 Oct 2021 22:23:10 +0200 Subject: [PATCH 095/355] Fix somes Upsilon name in somes files (#61) --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- .github/ISSUE_TEMPLATE/omega-beta-only---bug-report.md | 6 +++--- .github/ISSUE_TEMPLATE/problems-during-installation.md | 4 ++-- README.fr.md | 2 +- README.md | 2 +- ion/src/simulator/3ds/assets/app.rsf | 4 ++-- ion/src/simulator/ios/Info.plist | 2 +- ion/src/simulator/shared/store_script.cpp | 2 +- ion/src/simulator/shared/window.cpp | 4 ++-- ion/src/simulator/web/simulator.html.inc | 2 +- python/port/mod/os/modos_table.c | 2 +- 12 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d6e0dd68643..e683ad5e2e3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -24,4 +24,4 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - - Omega Version: [go to settings > about > Omega Version and type the version here] + - Upsilon Version: [go to settings > about > Upsilon Version and type the version here] diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 5e45c503273..340a7684f13 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,6 +1,6 @@ --- name: Feature request -about: Suggest an idea for Omega +about: Suggest an idea for Upsilon title: '' labels: 'Status: Triage, Type: Feature' assignees: '' diff --git a/.github/ISSUE_TEMPLATE/omega-beta-only---bug-report.md b/.github/ISSUE_TEMPLATE/omega-beta-only---bug-report.md index f97b2b86f12..9af063f3df1 100644 --- a/.github/ISSUE_TEMPLATE/omega-beta-only---bug-report.md +++ b/.github/ISSUE_TEMPLATE/omega-beta-only---bug-report.md @@ -1,6 +1,6 @@ --- -name: OMEGA BETA ONLY - Bug report -about: Omega 1.21 is not working like it should? Let us know! +name: UPSILON BETA ONLY - Bug report +about: Upsilon 1.21 is not working like it should? Let us know! title: "[BETA-1.21] …" labels: 'Status: Triage, Type: Bug' assignees: '' @@ -24,5 +24,5 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - - Omega Version: [go to settings > about > Omega Version and type the version here] + - Upsilon Version: [go to settings > about > Upsilon Version and type the version here] - Discord username: ..........#.... diff --git a/.github/ISSUE_TEMPLATE/problems-during-installation.md b/.github/ISSUE_TEMPLATE/problems-during-installation.md index baab16e6417..329de3b67af 100644 --- a/.github/ISSUE_TEMPLATE/problems-during-installation.md +++ b/.github/ISSUE_TEMPLATE/problems-during-installation.md @@ -1,6 +1,6 @@ --- name: Problems during installation -about: Need help to install Omega? +about: Need help to install Upsilon? title: '' labels: 'Status: Triage, Type: Installation issue' assignees: '' @@ -16,4 +16,4 @@ Copy/paste the logs here (If you have some) ``` **Environment** - - Omega Version: {go to settings > about > Omega Version and type the version here} + - Upsilon Version: {go to settings > about > Upsilon Version and type the version here} diff --git a/README.fr.md b/README.fr.md index 92dc638fd7d..8a9f26150a6 100644 --- a/README.fr.md +++ b/README.fr.md @@ -126,7 +126,7 @@ Vous aurez besoin de devkitPro et de devkitARM disponible dans votre `$PATH` (in ```bash git clone --recursive https://github.com/Lauryy06/Upsilon.git cd Upsilon -git checkout --recursive omega-dev +git checkout --recursive upsilon-dev make PLATFORM=simulator TARGET=3ds -j ``` diff --git a/README.md b/README.md index adf8b58a572..8369661fd19 100644 --- a/README.md +++ b/README.md @@ -240,7 +240,7 @@ You need devkitPro and devkitARM installed and in your path (instructions [here] ```bash git clone --recursive https://github.com/Lauryy06/Upsilon.git cd Upsilon -git checkout --recursive omega-dev +git checkout --recursive upsilon-dev make PLATFORM=simulator TARGET=3ds -j ``` You can then put epsilon.3dsx on a SD card to run it from the HBC or use 3dslink to launch it over the network: diff --git a/ion/src/simulator/3ds/assets/app.rsf b/ion/src/simulator/3ds/assets/app.rsf index 8ebf29bc2a6..8e1a5900dc1 100644 --- a/ion/src/simulator/3ds/assets/app.rsf +++ b/ion/src/simulator/3ds/assets/app.rsf @@ -1,7 +1,7 @@ BasicInfo: - Title : Omega + Title : Upsilon CompanyCode : "00" - ProductCode : CTR-E-OMEGA + ProductCode : CTR-E-Upsilon Logo : Homebrew # Nintendo / Licensed / Distributed / iQue / iQueForSystem RomFs: diff --git a/ion/src/simulator/ios/Info.plist b/ion/src/simulator/ios/Info.plist index 244f1c4d3a7..c3376034ece 100644 --- a/ion/src/simulator/ios/Info.plist +++ b/ion/src/simulator/ios/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable Epsilon CFBundleIdentifier - io.github.omega.simulator + io.github.upsilon.simulator CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/ion/src/simulator/shared/store_script.cpp b/ion/src/simulator/shared/store_script.cpp index 8a5cea839cd..80187ef5dae 100644 --- a/ion/src/simulator/shared/store_script.cpp +++ b/ion/src/simulator/shared/store_script.cpp @@ -16,7 +16,7 @@ static char* pref_path = nullptr; static char* file_buffer = nullptr; void loadPython(Args * arguments) { - pref_path = SDL_GetPrefPath("io.github.omega", "omega-simulator"); + pref_path = SDL_GetPrefPath("io.github.upsilon", "upsilon-simulator"); std::string path(pref_path); printf("Loading from %s\n", (path + "python.dat").c_str()); diff --git a/ion/src/simulator/shared/window.cpp b/ion/src/simulator/shared/window.cpp index e9b04bdafca..0c3156160a6 100644 --- a/ion/src/simulator/shared/window.cpp +++ b/ion/src/simulator/shared/window.cpp @@ -36,7 +36,7 @@ void init(bool screen_only, bool fullscreen, bool unresizable) { if (screen_only) { sScreenOnly = true; sWindow = SDL_CreateWindow( - "Omega", + "Upsilon", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Ion::Display::Width, Ion::Display::Height, @@ -48,7 +48,7 @@ void init(bool screen_only, bool fullscreen, bool unresizable) { ); } else { sWindow = SDL_CreateWindow( - "Omega", + "Upsilon", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 458, 888, diff --git a/ion/src/simulator/web/simulator.html.inc b/ion/src/simulator/web/simulator.html.inc index dbfd95c89c6..1c52b8ff5e4 100644 --- a/ion/src/simulator/web/simulator.html.inc +++ b/ion/src/simulator/web/simulator.html.inc @@ -4,7 +4,7 @@ - Omega + Upsilon + + + +
+

LaTeX Parser

+ +

In the reader app, you can read a txt file. You can also read a txt file with LaTeX expression inside of it.

+

All the symbols you can use are listed here :

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CommandOutputCommandOutput
MathExpressions
\frac{ab}{cd} + + + + + a + b + + + c + d + + + + \frac{ab}{cd} + + \sqrt[n]{x} + + + + x + n + + + \sqrt[n]{x} + +
MathSymbols
\times + + + × + + \times + + \div + + + ÷ + + \div + +
\forall + + + + + \forall + + \exists + + + + + \exists + +
\partial + + + + + \partial + + \pm + + + ± + + \pm + +
\infty + + + + + \infty + + \approx + + + + + \approx + +
\neq + + + + + \neq + + \equiv + + + + + \equiv + +
\leq + + + + + \leq + + \geq + + + + + \geq + +
SimpleArrowsDoubleArrows
\leftarrow + + + + + \leftarrow + + \Leftarrow + + + + + \Leftarrow + +
\rightarrow + + + + + \rightarrow + + \Rightarrow + + + + + \Rightarrow + +
\uparrow + + + + + \uparrow + + \Uparrow + + + + + \Uparrow + +
\downarrow + + + + + \downarrow + + \Downarrow + + + + + \Downarrow + +
\leftrightarrow + + + + + \leftrightarrow + +
\updownarrow + + + + + \updownarrow + +
Greek CapitalLettersGreek SmallLetters
\Alpha + + + A + + \Alpha + + \alpha + + + α + + \alpha + +
\Beta + + + B + + \Beta + + \beta + + + β + + \beta + +
\Gamma + + + Γ + + \Gamma + + \gamma + + + γ + + \gamma + +
\Delta + + + Δ + + \Delta + + \delta + + + δ + + \delta + +
\Epsilon + + + E + + \Epsilon + + \epsilon + + + ϵ + + \epsilon + +
\Zeta + + + Z + + \Zeta + + \zeta + + + ζ + + \zeta + +
\Eta + + + H + + \Eta + + \eta + + + η + + \eta + +
\Theta + + + Θ + + \Theta + + \theta + + + θ + + \theta + +
\Iota + + + I + + \Iota + + \iota + + + ι + + \iota + +
\Kappa + + + K + + \Kappa + + \kappa + + + κ + + \kappa + +
\Lambda + + + Λ + + \Lambda + + \lambda + + + λ + + \lambda + +
\Mu + + + M + + \Mu + + \mu + + + μ + + \mu + +
\Nu + + + N + + \Nu + + \nu + + + ν + + \nu + +
\Xi + + + Ξ + + \Xi + + \xi + + + ξ + + \xi + +
\Omicron + + + O + + \Omicron + +
\Pi + + + Π + + \Pi + + \pi + + + π + + \pi + +
\Rho + + + P + + \Rho + + \rho + + + ρ + + \rho + +
\Sigma + + + Σ + + \Sigma + + \sigma + + + σ + + \sigma + +
\Tau + + + T + + \Tau + + \tau + + + τ + + \tau + +
\Upsilon + + + Υ + + \Upsilon + + \upsilon + + + υ + + \upsilon + +
\Phi + + + Φ + + \Phi + + \phi + + + ϕ + + \phi + +
\Chi + + + X + + \Chi + + \chi + + + χ + + \chi + +
\Psi + + + Ψ + + \Psi + + \psi + + + ψ + + \psi + +
\Omega + + + Ω + + \Omega + + \omega + + + ω + + \omega + +
+ +
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/reader/TexParser.md b/apps/reader/TexParser.md new file mode 100644 index 00000000000..b530498cd35 --- /dev/null +++ b/apps/reader/TexParser.md @@ -0,0 +1,51 @@ +# LaTeX Parser + +In the reader app, you can read a txt file. You can also read a txt file with LaTeX expression inside of it. + +All the symbols you can use are listed here : + +|Command|Output||Command|Output| +|--:|:--:|--:|--:|:--:| +|Math|Expressions|||| +|`\frac{ab}{cd}`|$\frac{ab}{cd}$||`\sqrt[n]{x}`|$\sqrt[n]{x}$| +|Math|Symbols||| +|`\times`|$\times$||`\div`|$\div$| +|`\forall`|$\forall$||`\exists`|$\exists$| +|`\partial`|$\partial$||`\pm`|$\pm$| +|`\infty`|$\infty$||`\approx`|$\approx$| +|`\neq`|$\neq$||`\equiv`|$\equiv$| +|`\leq`|$\leq$||`\geq`|$\geq$| +|Simple|Arrows||Double|Arrows| +|`\leftarrow`|$\leftarrow$||`\Leftarrow`|$\Leftarrow$| +|`\rightarrow`|$\rightarrow$||`\Rightarrow`|$\Rightarrow$| +|`\uparrow`|$\uparrow$||`\Uparrow`|$\Uparrow$| +|`\downarrow`|$\downarrow$||`\Downarrow`|$\Downarrow$| +|`\leftrightarrow`|$\leftrightarrow$|||| +|`\updownarrow`|$\updownarrow$|||| +|Greek Capital|Letters||Greek Small|Letters| +|`\Alpha`|$\Alpha$||`\alpha`|$\alpha$| +|`\Beta`|$\Beta$||`\beta`|$\beta$| +|`\Gamma`|$\Gamma$||`\gamma`|$\gamma$| +|`\Delta`|$\Delta$||`\delta`|$\delta$| +|`\Epsilon`|$\Epsilon$||`\epsilon`|$\epsilon$| +|`\Zeta`|$\Zeta$||`\zeta`|$\zeta$| +|`\Eta`|$\Eta$||`\eta`|$\eta$| +|`\Theta`|$\Theta$||`\theta`|$\theta$| +|`\Iota`|$\Iota$||`\iota`|$\iota$| +|`\Kappa`|$\Kappa$||`\kappa`|$\kappa$| +|`\Lambda`|$\Lambda$||`\lambda`|$\lambda$| +|`\Mu`|$\Mu$||`\mu`|$\mu$| +|`\Nu`|$\Nu$||`\nu`|$\nu$| +|`\Xi`|$\Xi$||`\xi`|$\xi$| +|`\Omicron`|$\Omicron$||| +|`\Pi`|$\Pi$||`\pi`|$\pi$| +|`\Rho`|$\Rho$||`\rho`|$\rho$| +|`\Sigma`|$\Sigma$||`\sigma`|$\sigma$| +|`\Tau`|$\Tau$||`\tau`|$\tau$| +|`\Upsilon`|$\Upsilon$||`\upsilon`|$\upsilon$| +|`\Phi`|$\Phi$||`\phi`|$\phi$| +|`\Chi`|$\Chi$||`\chi`|$\chi$| +|`\Psi`|$\Psi$||`\psi`|$\psi$| +|`\Omega`|$\Omega$||`\omega`|$\omega$| + + diff --git a/apps/reader/tex_parser.cpp b/apps/reader/tex_parser.cpp new file mode 100644 index 00000000000..f2b69afeea5 --- /dev/null +++ b/apps/reader/tex_parser.cpp @@ -0,0 +1,268 @@ +#include "tex_parser.h" +#include + +namespace Reader { + + // List of available Symbols + static constexpr char const * k_SymbolsCommands[] = { + "times", "div", "forall", "partial", "exists", "pm", "approx", "infty", "neq", "equiv", "leq", "geq", + "leftarrow", "uparrow", "rightarrow", "downarrow", "leftrightarrow", "updownarrow", "Leftarrow", "Uparrow", "Rightarrow", "Downarrow", + "Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta", "Eta", "Theta", "Iota", "Kappa", "Lambda", + "Mu", "Nu", "Xi", "Omicron", "Pi", "Rho", "Sigma", "Tau", "Upsilon", "Phi", "Chi", "Psi","Omega", + "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", + "mu", "nu", "xi", "omicron", "pi", "rho", "sigma", "tau", "upsilon", "phi", "chi", "psi", "omega", + "sim", + }; + + // List of the available Symbol's CodePoints in the same order of the Symbol's list + static constexpr uint32_t const k_SymbolsCodePoints[] = { + 0xd7, 0xf7, 0x2200, 0x2202, 0x2203, 0xb1, 0x2248, 0x221e, 0x2260, 0x2261, 0x2264, 0x2265, + 0x2190, 0x2191, 0x2192, 0x2193, 0x2194, 0x2195, 0x21d0, 0x21d1, 0x21d2, 0x21d3, + 0x391, 0x392, 0x393, 0x394, 0x395, 0x396, 0x397, 0x398, 0x399, 0x39a, 0x39b, + 0x39c, 0x39d, 0x39e, 0x39f, 0x3a0, 0x3a1, 0x3a3, 0x3a4, 0x3a5, 0x3a6, 0x3a7, 0x3a8, 0x3a9, + 0x3b1, 0x3b2, 0x3b3, 0x3b4, 0x3b5, 0x3b6, 0x3b7, 0x3b8, 0x3b9, 0x3ba, 0x3bb, + 0x3bc, 0x3bd, 0x3be, 0x3bf, 0x3c0, 0x3c1, 0x3c3, 0x3c4, 0x3c5, 0x3c6, 0x3c7, 0x3c8, 0x3c9, + 0x7e, + }; + + // List of available Function Commands that don't require a specific handling + static constexpr char const * k_FunctionCommands[] = { + "arcos", "arcsin", "arctan", "arg", "cos", "cosh", "cot", "coth", + "csc", "deg", "det", "dim", "exp", "gcd", "hom", "inf", + "ker", "lg", "lim", "liminf", "limsup", "ln", "log", "max", + "min", "Pr", "sec", "sin", "sinh", "sup", "tan", "tanh" + }; + +TexParser::TexParser(const char * text, const char * endOfText) : + m_text(text), + m_endOfText(endOfText), + m_hasError(false) +{ + +} + +Layout TexParser::getLayout() { + Layout layout = popText(0); + + if (m_hasError) { + return CodePointLayout::Builder(CodePoint(0xfffd)); + } + + return layout; +} + +Layout TexParser::popBlock() { + while (*m_text == ' ') { + m_text ++; + } + + if (*m_text == '{') { + m_text ++; + return popText('}'); + } + + if (*m_text == '\\') { + m_text ++; + return popCommand(); + } + + if (m_text >= m_endOfText) { + m_hasError = true; + } + + UTF8Decoder decoder(m_text); + m_text ++; + return CodePointLayout::Builder(decoder.nextCodePoint()); +} + +Layout TexParser::popText(char stop) { + HorizontalLayout layout = HorizontalLayout::Builder(); + const char * start = m_text; + + while (m_text < m_endOfText && *m_text != stop) { + switch (*m_text) { + // TODO: Factorize this code + case '\\': + if (start != m_text) { + layout.addOrMergeChildAtIndex(LayoutHelper::String(start, m_text - start), layout.numberOfChildren(), false); + } + m_text ++; + layout.addOrMergeChildAtIndex(popCommand(), layout.numberOfChildren(), false); + start = m_text; + break; + case ' ': + if (start != m_text) { + layout.addOrMergeChildAtIndex(LayoutHelper::String(start, m_text - start), layout.numberOfChildren(), false); + } + m_text ++; + start = m_text; + break; + case '^': + if (start != m_text) { + layout.addOrMergeChildAtIndex(LayoutHelper::String(start, m_text - start), layout.numberOfChildren(), false); + } + m_text ++; + layout.addOrMergeChildAtIndex(VerticalOffsetLayout::Builder(popBlock(), VerticalOffsetLayoutNode::Position::Superscript), layout.numberOfChildren(), false); + start = m_text; + break; + case '_': + if (start != m_text) { + layout.addOrMergeChildAtIndex(LayoutHelper::String(start, m_text - start), layout.numberOfChildren(), false); + } + m_text ++; + layout.addOrMergeChildAtIndex(VerticalOffsetLayout::Builder(popBlock(), VerticalOffsetLayoutNode::Position::Subscript), layout.numberOfChildren(), false); + start = m_text; + break; + default: + m_text ++; + } + } + + if (start != m_text) { + layout.addOrMergeChildAtIndex(LayoutHelper::String(start, m_text - start), layout.numberOfChildren(), false); + } + + m_text ++; + + if (layout.numberOfChildren() == 1) { + return layout.squashUnaryHierarchyInPlace(); + } + + return layout; +} + +Layout TexParser::popCommand() { + // TODO: Factorize this code + if (strncmp(k_ceilCommand, m_text, strlen(k_ceilCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_ceilCommand)))) { + m_text += strlen(k_ceilCommand); + return popCeilCommand(); + } + } + if (strncmp(k_floorCommand, m_text, strlen(k_floorCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_floorCommand)))) { + m_text += strlen(k_floorCommand); + return popFloorCommand(); + } + } + if (strncmp(k_fracCommand, m_text, strlen(k_fracCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_fracCommand)))) { + m_text += strlen(k_fracCommand); + return popFracCommand(); + } + } + if (strncmp(k_leftCommand, m_text, strlen(k_leftCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_leftCommand)))) { + m_text += strlen(k_leftCommand); + return popLeftCommand(); + } + } + if (strncmp(k_rightCommand, m_text, strlen(k_rightCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_rightCommand)))) { + m_text += strlen(k_rightCommand); + return popRightCommand(); + } + } + if (strncmp(k_sqrtCommand, m_text, strlen(k_sqrtCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_sqrtCommand)))) { + m_text += strlen(k_sqrtCommand); + return popSqrtCommand(); + } + } + + if (strncmp(k_spaceCommand, m_text, strlen(k_spaceCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_spaceCommand)))) { + m_text += strlen(k_spaceCommand); + return popSpaceCommand(); + } + } + if (strncmp(k_overrightArrowCommand, m_text, strlen(k_overrightArrowCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_overrightArrowCommand)))) { + m_text += strlen(k_overrightArrowCommand); + return popOverrightarrowCommand(); + } + } + + for (int i = 0; i < k_NumberOfSymbols; i++) { + if (strncmp(k_SymbolsCommands[i], m_text, strlen(k_SymbolsCommands[i])) == 0) { + if (isCommandEnded(*(m_text + strlen(k_SymbolsCommands[i])))) { + m_text += strlen(k_SymbolsCommands[i]); + return popSymbolCommand(i); + } + } + } + + for (int i = 0; i < k_NumberOfFunctionCommands; i++) { + if (strncmp(k_FunctionCommands[i], m_text, strlen(k_FunctionCommands[i])) == 0) { + if (isCommandEnded(*(m_text + strlen(k_FunctionCommands[i])))) { + m_text += strlen(k_FunctionCommands[i]); + return LayoutHelper::String(k_FunctionCommands[i], strlen(k_FunctionCommands[i])); + } + } + } + + m_hasError = true; + return EmptyLayout::Builder(); +} + +// Expressions +Layout TexParser::popCeilCommand() { + Layout ceil = popBlock(); + return CeilingLayout::Builder(ceil); +} + +Layout TexParser::popFloorCommand() { + Layout floor = popBlock(); + return FloorLayout::Builder(floor); +} + +Layout TexParser::popFracCommand() { + Layout numerator = popBlock(); + Layout denominator = popBlock(); + FractionLayout l = FractionLayout::Builder(numerator, denominator); + return l; +} + +Layout TexParser::popLeftCommand() { + m_text++; + return LeftParenthesisLayout::Builder(); +} + +Layout TexParser::popRightCommand() { + m_text++; + return RightParenthesisLayout::Builder(); +} + +Layout TexParser::popSqrtCommand() { + while (*m_text == ' ') { + m_text ++; + } + if (*m_text == '[') { + m_text ++; + Layout rootFactor = popText(']'); + Layout belowRoot = popBlock(); + return NthRootLayout::Builder(belowRoot, rootFactor); + } + else { + return NthRootLayout::Builder(popBlock()); + } +} + +Layout TexParser::popSpaceCommand() { + return LayoutHelper::String(" ", 1); +} + +Layout TexParser::popOverrightarrowCommand() { + return VectorLayout::Builder(popBlock()); +} + +Layout TexParser::popSymbolCommand(int SymbolIndex) { + uint32_t codePoint = k_SymbolsCodePoints[SymbolIndex]; + return CodePointLayout::Builder(codePoint); +} + +inline bool TexParser::isCommandEnded(char c) const { + return !(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z'); +} + +} \ No newline at end of file diff --git a/apps/reader/tex_parser.h b/apps/reader/tex_parser.h new file mode 100644 index 00000000000..82e2c1f06ba --- /dev/null +++ b/apps/reader/tex_parser.h @@ -0,0 +1,57 @@ +#ifndef __TEX_PARSER_H__ +#define __TEX_PARSER_H__ + +#include +#include +#include + +using namespace Poincare; + +namespace Reader +{ +/// @brief Class used in the WordWrapTextView class to parse a Tex expression +class TexParser { +public: + TexParser(const char * text, const char * endOfText); + Layout getLayout(); +private: + Layout popBlock(); + Layout popText(char stop); + Layout popCommand(); + + // Expressions + Layout popCeilCommand(); + Layout popFloorCommand(); + Layout popFracCommand(); + Layout popLeftCommand(); + Layout popRightCommand(); + Layout popSqrtCommand(); + Layout popSpaceCommand(); + Layout popOverrightarrowCommand(); + + //Symbols + Layout popSymbolCommand(int SymbolIndex); + + const char * m_text; + const char * m_endOfText; + bool m_hasError; + + inline bool isCommandEnded(char c) const; + + // Expressions that require specific handling + static constexpr char const * k_ceilCommand = "ceil"; + static constexpr char const * k_floorCommand = "floor"; + static constexpr char const * k_fracCommand = "frac"; + static constexpr char const * k_leftCommand = "left"; + static constexpr char const * k_rightCommand = "right"; + static constexpr char const * k_sqrtCommand = "sqrt"; + static constexpr char const * k_spaceCommand = "space"; + static constexpr char const * k_overrightArrowCommand = "overrightarrow"; + + static constexpr int const k_NumberOfSymbols = 71; + static constexpr int const k_NumberOfFunctionCommands = 32; +}; + +} + +#endif diff --git a/apps/reader/utility.cpp b/apps/reader/utility.cpp index d941039cd7f..8ed4cd4f015 100644 --- a/apps/reader/utility.cpp +++ b/apps/reader/utility.cpp @@ -117,7 +117,7 @@ const char * EndOfPrintableWord(const char * word, const char * end) { UTF8Decoder decoder(word); CodePoint codePoint = decoder.nextCodePoint(); const char * result = word; - while (codePoint != '\n' && codePoint != ' ' && codePoint != '%') { + while (codePoint != '\n' && codePoint != ' ' && codePoint != '%' && codePoint != '$') { result = decoder.stringPosition(); if (result >= end) { break; @@ -127,4 +127,21 @@ const char * EndOfPrintableWord(const char * word, const char * end) { return result; } +const char * StartOfPrintableWord(const char * word, const char * start) { + if (word == start) { + return word; + } + UTF8Decoder decoder(start, word); + CodePoint codePoint = decoder.previousCodePoint(); + const char * result = word; + while (codePoint != '\n' && codePoint != ' ' && codePoint != '%' && codePoint != '$') { + result = decoder.stringPosition(); + if (result <= start) { + break; + } + codePoint = decoder.previousCodePoint(); + } + return result; +} + } \ No newline at end of file diff --git a/apps/reader/utility.h b/apps/reader/utility.h index 55a6e8c3338..61aabbf2cf6 100644 --- a/apps/reader/utility.h +++ b/apps/reader/utility.h @@ -12,6 +12,7 @@ bool stringEndsWith(const char* str, const char* end); int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize); void stringNCopy(char* dest, int max, const char* src, int len); const char * EndOfPrintableWord(const char * word, const char * end); +const char * StartOfPrintableWord(const char * word, const char * start); } #endif \ No newline at end of file diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index acbcbd8ef56..c116e620e85 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -1,6 +1,7 @@ #include "word_wrap_view.h" #include "utility.h" +#include "tex_parser.h" #include #include "../shared/poincare_helpers.h" #include @@ -40,16 +41,17 @@ void WordWrapTextView::previousPage() { const int charWidth = m_font->glyphSize().width(); const int charHeight = m_font->glyphSize().height(); - const char * endOfWord = text() + m_pageOffset - 1; - const char * startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); + const char * endOfFile = text() + m_length; + const char * endOfWord = text() + m_pageOffset; + const char * startOfWord = StartOfPrintableWord(endOfWord, text()); KDSize textSize = KDSizeZero; KDPoint textEndPosition(m_frame.width() - k_margin, m_frame.height() - k_margin); while(startOfWord>=text()) { - startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); - endOfWord = UTF8Helper::EndOfWord(startOfWord); + startOfWord = StartOfPrintableWord(endOfWord-1, text()); + //endOfWord = EndOfPrintableWord(startOfWord, endOfFile); if (*startOfWord == '%') { if (updateTextColorBackward(startOfWord)) { @@ -57,20 +59,28 @@ void WordWrapTextView::previousPage() { continue; } } - if (*startOfWord == '$' && *(endOfWord-1) == '$') { - const int wordMaxLength = 128; - char word[wordMaxLength]; - stringNCopy(word, wordMaxLength, startOfWord + 1, endOfWord-startOfWord-2); - Poincare::Expression expr = Poincare::Expression::Parse(word, nullptr); - if (expr.isUninitialized()) { - expr = Poincare::Undefined::Builder(); + + if (*endOfWord == '$') { + startOfWord = endOfWord - 1; + while (*startOfWord != '$') { + if (startOfWord < text()) { + break; // File isn't rightly formated + } + startOfWord --; } - Poincare::Layout layout = Shared::PoincareHelpers::CreateLayout(expr); - textSize = layout.layoutSize(); - + startOfWord --; + + TexParser parser = TexParser(startOfWord + 1, endOfWord - 2); + Poincare::Layout layout = parser.getLayout(); + textSize = layout.layoutSize(); } else { - textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + if (*startOfWord == '\\' || *(startOfWord + 1) == '$') { + textSize = m_font->stringSizeUntil(startOfWord + 1, endOfWord); + } + else { + textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + } } KDPoint textStartPosition = KDPoint(textEndPosition.x()-textSize.width(), textEndPosition.y()); @@ -111,7 +121,7 @@ void WordWrapTextView::previousPage() { m_pageOffset = 0; } else { - m_pageOffset = UTF8Helper::EndOfWord(startOfWord) - text() + 1; + m_pageOffset = EndOfPrintableWord(startOfWord, endOfFile) - text() + 1; } markRectAsDirty(bounds()); } @@ -121,7 +131,12 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { const char * endOfFile = text() + m_length; const char * startOfWord = text() + m_pageOffset; - const char * endOfWord = EndOfPrintableWord(startOfWord, endOfFile); + const char * endOfWord; + + if (*startOfWord != '$') { + endOfWord = EndOfPrintableWord(startOfWord, endOfFile); + } // Else we don't need to update endOfWord + KDPoint textPosition(k_margin, k_margin); const int wordMaxLength = 128; @@ -154,18 +169,26 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { continue; } } - - if (*startOfWord == '$' && *(endOfWord-1) == '$') { // Look for expression - stringNCopy(word, wordMaxLength, startOfWord + 1, endOfWord-startOfWord-2); - Poincare::Expression expr = Poincare::Expression::Parse(word, nullptr); - if (expr.isUninitialized()) { - expr = Poincare::Undefined::Builder(); + + if (*startOfWord == '$') { // Look for expression + endOfWord = startOfWord + 1; + while (*endOfWord != '$') { + if (endOfWord > endOfFile) { + break; // If we are here, it's bad... + } + endOfWord ++; } - layout = Shared::PoincareHelpers::CreateLayout(expr); + endOfWord ++; + + TexParser parser = TexParser(startOfWord + 1, endOfWord - 1); + layout = parser.getLayout(); textSize = layout.layoutSize(); toDraw = ToDraw::Expression; } else { + if (*startOfWord == '\\' || *(startOfWord + 1) == '$') { + startOfWord ++; + } textSize = m_font->stringSizeUntil(startOfWord, endOfWord); stringNCopy(word, wordMaxLength, startOfWord, endOfWord-startOfWord); toDraw = ToDraw::Text; @@ -187,7 +210,7 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { } if (toDraw == ToDraw::Expression) { - layout.draw(ctx, textPosition, m_textColor); + layout.draw(ctx, textPosition, m_textColor, m_backgroundColor); } else if (toDraw == ToDraw::Text) { ctx->drawString(word, textPosition, m_font, m_textColor, m_backgroundColor); @@ -224,7 +247,10 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { } textPosition = nextTextPosition; - endOfWord = EndOfPrintableWord(startOfWord+1, endOfFile); + + if (*startOfWord != '$') { + endOfWord = EndOfPrintableWord(startOfWord+1, endOfFile); + } // Else we don't need to update endOfWord } m_nextPageOffset = startOfWord - text(); diff --git a/build/targets.device.mak b/build/targets.device.mak index eb3ad0a8f56..97293a6cdbe 100644 --- a/build/targets.device.mak +++ b/build/targets.device.mak @@ -39,7 +39,7 @@ openocd: # The flasher target is defined here because otherwise $(%_src) has not been # fully filled -flasher_src = $(ion_src) $(ion_device_flasher_src) $(liba_src) $(kandinsky_src) +flasher_src = $(ion_src) $(ion_device_flasher_src) $(liba_src) $(simple_kandinsky_src) $(BUILD_DIR)/flasher.light.$(EXE): $(call flavored_object_for,$(flasher_src),light usbxip) $(BUILD_DIR)/flasher.verbose.$(EXE): $(call flavored_object_for,$(flasher_src),usbxip) $(BUILD_DIR)/flasher.verbose.flash.$(EXE): $(call flavored_object_for,$(flasher_src)) @@ -48,7 +48,7 @@ $(BUILD_DIR)/flasher.%.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/shared/ram.ld $(BUILD_DIR)/flasher.%.flash.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/$(MODEL)/internal_flash.ld #TODO Do not build all apps... Put elsewhere? -bench_src = $(ion_src) $(liba_src) $(kandinsky_src) $(poincare_src) $(libaxx_src) $(app_shared_src) $(ion_device_bench_src) +bench_src = $(ion_src) $(liba_src) $(default_kandinsky_src) $(poincare_src) $(libaxx_src) $(app_shared_src) $(ion_device_bench_src) $(BUILD_DIR)/bench.ram.$(EXE): $(call flavored_object_for,$(bench_src),consoleuart usbxip) $(BUILD_DIR)/bench.ram.$(EXE): LDFLAGS += -Lion/src/$(PLATFORM)/bench $(BUILD_DIR)/bench.ram.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/shared/ram.ld diff --git a/build/targets.device.n0110.mak b/build/targets.device.n0110.mak index 77bc1bf8ce8..5b1f9acda15 100644 --- a/build/targets.device.n0110.mak +++ b/build/targets.device.n0110.mak @@ -1,7 +1,7 @@ HANDY_TARGETS += test.external_flash.write test.external_flash.read $(BUILD_DIR)/test.external_flash.%.$(EXE): LDSCRIPT = ion/test/device/n0110/external_flash_tests.ld -test_external_flash_src = $(ion_src) $(liba_src) $(libaxx_src) $(kandinsky_src) $(poincare_src) $(ion_device_dfu_relogated_src) $(runner_src) +test_external_flash_src = $(ion_src) $(liba_src) $(libaxx_src) $(default_kandinsky_src) $(poincare_src) $(ion_device_dfu_relogated_src) $(runner_src) $(BUILD_DIR)/test.external_flash.read.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_external_flash_read_symbols.o $(call object_for,$(test_external_flash_src) $(test_ion_external_flash_read_src)) $(BUILD_DIR)/test.external_flash.write.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_external_flash_write_symbols.o $(call object_for,$(test_external_flash_src) $(test_ion_external_flash_write_src)) diff --git a/build/targets.mak b/build/targets.mak index b570237f235..aa082ad908e 100644 --- a/build/targets.mak +++ b/build/targets.mak @@ -7,7 +7,7 @@ HANDY_TARGETS_EXTENSIONS ?= # Epsilon base target -base_src = $(ion_src) $(liba_src) $(kandinsky_src) $(escher_src) $(libaxx_src) $(poincare_src) $(python_src) +base_src = $(ion_src) $(liba_src) $(default_kandinsky_src) $(escher_src) $(libaxx_src) $(poincare_src) $(python_src) epsilon_src = $(base_src) $(apps_src) diff --git a/kandinsky/Makefile b/kandinsky/Makefile index 16cfdc66802..88cb6885af2 100644 --- a/kandinsky/Makefile +++ b/kandinsky/Makefile @@ -21,10 +21,8 @@ kandinsky_src += $(addprefix kandinsky/src/,\ rect.cpp \ ) -kandinsky_src += $(addprefix kandinsky/fonts/, \ - LargeFont.ttf \ - SmallFont.ttf \ -) +simple_kandinsky_src := $(kandinsky_src) +default_kandinsky_src := $(kandinsky_src) tests_src += $(addprefix kandinsky/test/,\ color.cpp\ @@ -53,16 +51,53 @@ $(eval $(call rule_for, \ RASTERIZER := $(BUILD_DIR)/kandinsky/fonts/rasterizer -# Define a rasterizing recipe. Parameters : font name, size, packed_width, packed_height +# Define a rasterizing recipe. Parameters : font source, font name, size, packed_width, packed_height define raster_font $(call rule_for, \ RASTER, \ - kandinsky/fonts/$(1).cpp, \ + kandinsky/fonts/$(2).cpp, \ kandinsky/fonts/$(1).ttf $$(RASTERIZER), \ - $$(RASTERIZER) $$< $(2) $(2) $(3) $(4) $(1) $$@ $(if $(HAS_LIBPNG),$$(basename $$@).png), \ + $$(RASTERIZER) $$< $(3) $(4) $(4) $(5) $(6) $(1) $$@ $(if $(HAS_LIBPNG),$$(basename $$@).png), \ global \ ) endef -$(eval $(call raster_font,SmallFont,12,7,14)) -$(eval $(call raster_font,LargeFont,16,10,18)) +ifdef HAS_READER + +kandinsky_src += $(addprefix kandinsky/fonts/, \ + LargeFontExtended.ttf \ + SmallFontExtended.ttf \ + LargeFontSimple.ttf \ + SmallFontSimple.ttf \ +) + +default_kandinsky_src += $(addprefix kandinsky/fonts/, \ + LargeFontExtended.ttf \ + SmallFontExtended.ttf \ +) + +simple_kandinsky_src += $(addprefix kandinsky/fonts/, \ + LargeFontSimple.ttf \ + SmallFontSimple.ttf \ +) + +$(eval $(call raster_font,SmallFont,SmallFontExtended,1,12,7,14)) +$(eval $(call raster_font,LargeFont,LargeFontExtended,1,16,10,18)) + +$(eval $(call raster_font,SmallFont,SmallFontSimple,0,12,7,14)) +$(eval $(call raster_font,LargeFont,LargeFontSimple,0,16,10,18)) + +else + +kandinsky_src += $(addprefix kandinsky/fonts/, \ + LargeFont.ttf \ + SmallFont.ttf \ +) + +simple_kandinsky_src = $(kandinsky_src) +default_kandinsky_src = $(kandinsky_src) + +$(eval $(call raster_font,SmallFont,SmallFontSimple,0,12,7,14)) +$(eval $(call raster_font,LargeFont,LargeFontSimple,0,16,10,18)) + +endif diff --git a/kandinsky/fonts/code_points.h b/kandinsky/fonts/code_points.h index 8671637f900..0c1509b8b70 100644 --- a/kandinsky/fonts/code_points.h +++ b/kandinsky/fonts/code_points.h @@ -8,7 +8,7 @@ /* This array lists the code points that are rasterized by rasterizer.c. We put * most characters from the LATIN charset, and some mathematical characters. */ -uint32_t CodePoints[] = { +uint32_t SimpleCodePoints[] = { 0x20, // // SPACE 0x21, // ! // EXCLAMATION MARK 0x22, // " // QUOTATION MARK @@ -153,6 +153,206 @@ uint32_t CodePoints[] = { 0x1d422, // 𝐢 // MATHEMATICAL BOLD SMALL I" }; -int NumberOfCodePoints = sizeof(CodePoints)/sizeof(CodePoints[0]); +uint32_t ExtendedCodePoints[] = { + 0x20, // // SPACE + 0x21, // ! // EXCLAMATION MARK + 0x22, // " // QUOTATION MARK + 0x23, // # // NUMBER SIGN + 0x24, // $ // DOLLAR SIGN + 0x25, // % // PERCENT SIGN + 0x26, // & // AMPERSAND + 0x27, // ' // APOSTROPHE + 0x28, // ( // LEFT PARENTHESIS + 0x29, // ) // RIGHT PARENTHESIS + 0x2a, // * // ASTERISK + 0x2b, // + // PLUS SIGN + 0x2c, // , // COMMA + 0x2d, // - // HYPHEN-MINUS + 0x2e, // . // FULL STOP + 0x2f, // / // SOLIDUS + 0x30, // 0 // DIGIT ZERO + 0x31, // 1 // DIGIT ONE + 0x32, // 2 // DIGIT TWO + 0x33, // 3 // DIGIT THREE + 0x34, // 4 // DIGIT FOUR + 0x35, // 5 // DIGIT FIVE + 0x36, // 6 // DIGIT SIX + 0x37, // 7 // DIGIT SEVEN + 0x38, // 8 // DIGIT EIGHT + 0x39, // 9 // DIGIT NINE + 0x3a, // : // COLON + 0x3b, // ; // SEMICOLON + 0x3c, // < // LESS-THAN SIGN + 0x3d, // = // EQUALS SIGN + 0x3e, // > // GREATER-THAN SIGN + 0x3f, // ? // QUESTION MARK + 0x40, // @ // COMMERCIAL AT + 0x41, // A // LATIN CAPITAL LETTER A + 0x42, // B // LATIN CAPITAL LETTER B + 0x43, // C // LATIN CAPITAL LETTER C + 0x44, // D // LATIN CAPITAL LETTER D + 0x45, // E // LATIN CAPITAL LETTER E + 0x46, // F // LATIN CAPITAL LETTER F + 0x47, // G // LATIN CAPITAL LETTER G + 0x48, // H // LATIN CAPITAL LETTER H + 0x49, // I // LATIN CAPITAL LETTER I + 0x4a, // J // LATIN CAPITAL LETTER J + 0x4b, // K // LATIN CAPITAL LETTER K + 0x4c, // L // LATIN CAPITAL LETTER L + 0x4d, // M // LATIN CAPITAL LETTER M + 0x4e, // N // LATIN CAPITAL LETTER N + 0x4f, // O // LATIN CAPITAL LETTER O + 0x50, // P // LATIN CAPITAL LETTER P + 0x51, // Q // LATIN CAPITAL LETTER Q + 0x52, // R // LATIN CAPITAL LETTER R + 0x53, // S // LATIN CAPITAL LETTER S + 0x54, // T // LATIN CAPITAL LETTER T + 0x55, // U // LATIN CAPITAL LETTER U + 0x56, // V // LATIN CAPITAL LETTER V + 0x57, // W // LATIN CAPITAL LETTER W + 0x58, // X // LATIN CAPITAL LETTER X + 0x59, // Y // LATIN CAPITAL LETTER Y + 0x5a, // Z // LATIN CAPITAL LETTER Z + 0x5b, // [ // LEFT SQUARE BRACKET + 0x5c, // \ // REVERSE SOLIDUS + 0x5d, // ] // RIGHT SQUARE BRACKET + 0x5e, // ^ // CIRCUMFLEX ACCENT + 0x5f, // _ // LOW LINE + 0x60, // ` // GRAVE ACCENT + 0x61, // a // LATIN SMALL LETTER A + 0x62, // b // LATIN SMALL LETTER B + 0x63, // c // LATIN SMALL LETTER C + 0x64, // d // LATIN SMALL LETTER D + 0x65, // e // LATIN SMALL LETTER E + 0x66, // f // LATIN SMALL LETTER F + 0x67, // g // LATIN SMALL LETTER G + 0x68, // h // LATIN SMALL LETTER H + 0x69, // i // LATIN SMALL LETTER I + 0x6a, // j // LATIN SMALL LETTER J + 0x6b, // k // LATIN SMALL LETTER K + 0x6c, // l // LATIN SMALL LETTER L + 0x6d, // m // LATIN SMALL LETTER M + 0x6e, // n // LATIN SMALL LETTER N + 0x6f, // o // LATIN SMALL LETTER O + 0x70, // p // LATIN SMALL LETTER P + 0x71, // q // LATIN SMALL LETTER Q + 0x72, // r // LATIN SMALL LETTER R + 0x73, // s // LATIN SMALL LETTER S + 0x74, // t // LATIN SMALL LETTER T + 0x75, // u // LATIN SMALL LETTER U + 0x76, // v // LATIN SMALL LETTER V + 0x77, // w // LATIN SMALL LETTER W + 0x78, // x // LATIN SMALL LETTER X + 0x79, // y // LATIN SMALL LETTER Y + 0x7a, // z // LATIN SMALL LETTER Z + 0x7b, // { // LEFT CURLY BRACKET + 0x7c, // | // VERTICAL LINE + 0x7d, // } // RIGHT CURLY BRACKET + 0x7e, // ~ // TILDE + + 0xb0, // ° // DEGREE SIGN + 0xb1, // ± // PLUS OR MINUS + 0xb7, // · // MIDDLE DOT + + 0xc6, // Æ // LATIN CAPITAL LETTER AE + 0xd0, // Ð // LATIN CAPITAL LETTER ETH + 0xd7, // × // MULTIPLICATION SIGN + 0xd8, // Ø // LATIN CAPITAL LETTER O WITH STROKE + 0xde, // Þ // LATIN CAPITAL LETTER THORN + 0xdf, // ß // LATIN SMALL LETTER SHARP S + 0xe6, // æ // LATIN SMALL LETTER AE + 0xf0, // ð // LATIN SMALL LETTER ETH + 0xf7, // ÷ // DIVISION SIGN + 0xf8, // ø // LATIN SMALL LETTER O WITH STROKE + 0xfe, // þ // LATIN SMALL LETTER THORN + + 0x300, // ̀ // COMBINING GRAVE ACCENT + 0x301, // ́ // COMBINING ACUTE ACCENT + 0x302, // ̂ // COMBINING CIRCUMFLEX ACCENT + 0x303, // ̃ // COMBINING TILDE + 0x305, // ̅ // COMBINING OVERLINE + 0x308, // ̈ // COMBINING DIAERESIS + 0x30a, // ̊ // COMBINING RING ABOVE + 0x30b, // ˝// COMBINING DOUBLE ACUTE ACCENT + 0x327, // ̧ // COMBINING CEDILLA + + 0x391, // Α // GREEK CAPITAL LETTER ALPHA + 0x392, // Β // GREEK CAPITAL LETTER BETA + 0x393, // Γ // GREEK CAPITAL LETTER GAMMA + 0x394, // Δ // GREEK CAPITAL LETTER DELTA + 0x395, // Ε // GREEK CAPITAL LETTER EPSILON + 0x396, // Ζ // GREEK CAPITAL LETTER ZETA + 0x397, // Η // GREEK CAPITAL LETTER ETA + 0x398, // Θ // GREEK CAPITAL LETTER THETA + 0x399, // Ι // GREEK CAPITAL LETTER IOTA + 0x39a, // Κ // GREEK CAPITAL LETTER KAPPA + 0x39b, // Λ // GREEK CAPITAL LETTER LAMBDA + 0x39c, // Μ // GREEK CAPITAL LETTER MU + 0x39d, // Ν // GREEK CAPITAL LETTER NU + 0x39e, // Ξ // GREEK CAPITAL LETTER KSI + 0x39f, // Ο // GREEK CAPITAL LETTER OMICRON + 0x3a0, // Π // GREEK CAPITAL LETTER PI + 0x3a1, // Ρ // GREEK CAPITAL LETTER RHO + 0x3a3, // Σ // GREEK CAPITAL LETTER SIGMA + 0x3a4, // Τ // GREEK CAPITAL LETTER TAU + 0x3a5, // Υ // GREEK CAPITAL LETTER UPSILON + 0x3a6, // Φ // GREEK CAPITAL LETTER PHI + 0x3a7, // Χ // GREEK CAPITAL LETTER KHI + 0x3a8, // Ψ // GREEK CAPITAL LETTER PSI + 0x3a9, // Ω // GREEK CAPITAL LETTER OMEGA + 0x3b1, // α // GREEK SMALL LETTER ALPHA + 0x3b2, // β // GREEK SMALL LETTER BETA + 0x3b3, // γ // GREEK SMALL LETTER GAMMA + 0x3b4, // δ // GREEK SMALL LETTER DELTA + 0x3b5, // ε // GREEK SMALL LETTER EPSILON + 0x3b6, // ζ // GREEK SMALL LETTER ZETA + 0x3b7, // η // GREEK SMALL LETTER ETA + 0x3b8, // θ // GREEK SMALL LETTER THETA + 0x3b9, // ι // GREEK SMALL LETTER IOTA + 0x3ba, // κ // GREEK SMALL LETTER KAPPA + 0x3bb, // λ // GREEK SMALL LETTER LAMBDA + 0x3bc, // μ // GREEK SMALL LETTER MU + 0x3bd, // ν // GREEK SMALL LETTER NU + 0x3be, // ξ // GREEK SMALL LETTER KSI + 0x3bf, // ο // GREEK SMALL LETTER OMICRON + 0x3c0, // π // GREEK SMALL LETTER PI + 0x3c1, // ρ // GREEK SMALL LETTER RHO + 0x3c3, // σ // GREEK SMALL LETTER SIGMA + 0x3c4, // τ // GREEK SMALL LETTER TAU + 0x3c5, // υ // GREEK SMALL LETTER UPSILON + 0x3c6, // φ // GREEK SMALL LETTER PHI + 0x3c7, // χ // GREEK SMALL LETTER KHI + 0x3c8, // ψ // GREEK SMALL LETTER PSI + 0x3c9, // ω // GREEK SMALL LETTER OMEGA + 0x1d07, // ᴇ // LATIN LETTER SMALL CAPITAL E + 0x212f, // ℯ // SCRIPT SMALL E + 0x2190, // ← // BACKWARD ARROW (leftarrow) + 0x2191, // ↑ // TOP ARROW (uparrow) + 0x2192, // → // FORWARD ARROW (rightarrow) + 0x2193, // ↓ // BOTTOM ARROW (downarrow) + 0x2194, // ↔ // BACKWARD FORWARD ARROW (leftrightarrow) + 0x2195, // ↕ // TOP BOTTOM ARROW (updownarrow) + 0x21d0, // ⇐ // DOUBLE BACKWARD ARROW (Leftarrow) + 0x21d1, // ⇑ // DOUBLE TOP ARROW (Uparrow) + 0x21d2, // ⇒ // DOUBLE FORWARD ARROW (Rightarrow) + 0x21d3, // ⇓ // DOUBLE BOTTOM ARROW (Downarrow) + 0x2200, // ∀ // FORALL + 0x2202, // ∂ // PARTIAL + 0x2203, // ∃ // EXIST + 0x2211, // ∑ // N-ARY SUMMATION + 0x221a, // √ // SQUARE ROOT + 0x221e, // ∞ // INFINITY + 0x222b, // ∫ // INTEGRAL + 0x2248, // ≈ // ALMOST EQUAL TO + 0x2260, // ≠ // NOT EQUAL TO + 0x2261, // ≡ // IS CONGRUENT TO + 0x2264, // ≤ // LESS-THAN OR EQUAL TO + 0x2265, // ≥ // GREATER-THAN OR EQUAL TO + 0xFFFD, // � // REPLACEMENT CHARACTER + 0x1d422, // 𝐢 // MATHEMATICAL BOLD SMALL I" +}; + +int NumberOfSimpleCodePoints = sizeof(SimpleCodePoints)/sizeof(SimpleCodePoints[0]); +int NumberOfExtendedCodePoints = sizeof(ExtendedCodePoints)/sizeof(ExtendedCodePoints[0]); #endif diff --git a/kandinsky/fonts/rasterizer.c b/kandinsky/fonts/rasterizer.c index 5c953f2de36..7e4e8435940 100644 --- a/kandinsky/fonts/rasterizer.c +++ b/kandinsky/fonts/rasterizer.c @@ -16,6 +16,7 @@ #include FT_FREETYPE_H #include "code_points.h" + #include "../../ion/src/external/lz4/lz4hc.h" @@ -49,17 +50,18 @@ int main(int argc, char * argv[]) { FT_Face face; image_t bitmap_image; - int expectedNumberOfArguments = 8; + int expectedNumberOfArguments = 9; #ifdef GENERATE_PNG - expectedNumberOfArguments = 9; + expectedNumberOfArguments = 10; #endif if (argc != expectedNumberOfArguments) { #ifdef GENERATE_PNG - fprintf(stderr, "Usage: %s font_file glyph_width glyph_height font_name output_cpp output_png\n", argv[0]); + fprintf(stderr, "Usage: %s font_file extended glyph_width glyph_height font_name output_cpp output_png\n", argv[0]); #else - fprintf(stderr, "Usage: %s font_file glyph_width glyph_height font_name output_cpp\n", argv[0]); + fprintf(stderr, "Usage: %s font_file extended glyph_width glyph_height font_name output_cpp\n", argv[0]); #endif fprintf(stderr, " font_file: Path of the font file to load\n"); + fprintf(stderr, " extended: 0 if simple set of code points, else 1\n"); fprintf(stderr, " glyph_width: Width of bitmap glyphs, in pixels\n"); fprintf(stderr, " glyph_height: Height of bitmap glyphs, in pixels\n"); fprintf(stderr, " packed_glyph_width: Minimal glyph width in pixels. Pass 0 if unsure.\n"); @@ -73,16 +75,20 @@ int main(int argc, char * argv[]) { } char * font_file = argv[1]; - int requested_glyph_width = atoi(argv[2]); - int requested_glyph_height = atoi(argv[3]); - int packed_glyph_width = atoi(argv[4]); - int packed_glyph_height = atoi(argv[5]); - char * font_name = argv[6]; - char * output_cpp = argv[7]; + int extended_set = atoi(argv[2]); + int requested_glyph_width = atoi(argv[3]); + int requested_glyph_height = atoi(argv[4]); + int packed_glyph_width = atoi(argv[5]); + int packed_glyph_height = atoi(argv[6]); + char * font_name = argv[7]; + char * output_cpp = argv[8]; #ifdef GENERATE_PNG - char * output_png = argv[8]; + char * output_png = argv[9]; #endif + uint32_t * CodePoints = extended_set ? ExtendedCodePoints : SimpleCodePoints; + int NumberOfCodePoints = extended_set ? NumberOfExtendedCodePoints : NumberOfSimpleCodePoints; + ENSURE(!FT_Init_FreeType(&library), "Initializing library"); // 0 means we're picking the first face in the provided file diff --git a/kandinsky/src/font.cpp b/kandinsky/src/font.cpp index 25d82e93a0b..a1d217521c1 100644 --- a/kandinsky/src/font.cpp +++ b/kandinsky/src/font.cpp @@ -155,7 +155,7 @@ KDFont::GlyphIndex KDFont::indexForCodePoint(CodePoint c) const { return endPair->glyphIndex(); } NoMatchingGlyph: - assert(CodePoints[IndexForReplacementCharacterCodePoint] == 0xFFFD); + assert(SimpleCodePoints[IndexForReplacementCharacterCodePoint] == 0xFFFD); return IndexForReplacementCharacterCodePoint; #endif } diff --git a/poincare/Makefile b/poincare/Makefile index eca0b7ed8d8..380f200ab23 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -26,6 +26,7 @@ poincare_src += $(addprefix poincare/src/,\ right_square_bracket_layout.cpp \ sequence_layout.cpp \ sum_layout.cpp \ + vector_layout.cpp \ vertical_offset_layout.cpp \ ) diff --git a/poincare/include/poincare/layout_cursor.h b/poincare/include/poincare/layout_cursor.h index de552e7983b..b8d5c2f5851 100644 --- a/poincare/include/poincare/layout_cursor.h +++ b/poincare/include/poincare/layout_cursor.h @@ -23,6 +23,7 @@ class LayoutCursor final { friend class MatrixLayoutNode; friend class NthRootLayoutNode; friend class SequenceLayoutNode; + friend class VectorLayoutNode; friend class VerticalOffsetLayoutNode; public: constexpr static KDCoordinate k_cursorWidth = 1; diff --git a/poincare/include/poincare/layout_node.h b/poincare/include/poincare/layout_node.h index e2aaf7a899b..d033788aa04 100644 --- a/poincare/include/poincare/layout_node.h +++ b/poincare/include/poincare/layout_node.h @@ -40,6 +40,7 @@ class LayoutNode : public TreeNode { RightParenthesisLayout, RightSquareBracketLayout, SumLayout, + VectorLayout, VectorNormLayout, VerticalOffsetLayout }; diff --git a/poincare/include/poincare/vector_layout.h b/poincare/include/poincare/vector_layout.h new file mode 100644 index 00000000000..3d3a3e676f4 --- /dev/null +++ b/poincare/include/poincare/vector_layout.h @@ -0,0 +1,53 @@ +#ifndef POINCARE_VECTOR_LAYOUT_NODE_H +#define POINCARE_VECTOR_LAYOUT_NODE_H + +#include +#include +#include + +namespace Poincare { + +class VectorLayoutNode final : public LayoutNode { +public: + // Layout + Type type() const override { return Type::VectorLayout; } + + // SerializationHelperInterface + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { + return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, "vector", true, 0); + } + + virtual void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool forSelection = false); + virtual void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool forSelection = false); + + // TreeNode + size_t size() const override { return sizeof(VectorLayoutNode); } + int numberOfChildren() const override { return 1; } +#if POINCARE_TREE_LOG + void logNodeName(std::ostream & stream) const override { + stream << "VectorLayout"; + } +#endif + + constexpr static KDCoordinate k_arrowWidth = 5; + constexpr static KDCoordinate k_arrowHeight = 9; +protected: + virtual KDSize computeSize(); + virtual KDCoordinate computeBaseline(); + virtual KDPoint positionOfChild(LayoutNode * child); +private: + virtual void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor, Layout * selectionStart = nullptr, Layout * selectionEnd = nullptr, KDColor selectionColor = KDColorRed); + constexpr static KDCoordinate k_sideMargin = 2; + constexpr static KDCoordinate k_topMargin = 1; + constexpr static KDCoordinate k_arrowLineHeight = 1; // k_arrowHeight - k_arrowLineHeight must be even +}; + +class VectorLayout final : public Layout { +public: + static VectorLayout Builder(Layout child) { return TreeHandle::FixedArityBuilder({child}); } + VectorLayout() = delete; +}; + +} + +#endif diff --git a/poincare/include/poincare_layouts.h b/poincare/include/poincare_layouts.h index 72cb49ac657..f8a4319af31 100644 --- a/poincare/include/poincare_layouts.h +++ b/poincare/include/poincare_layouts.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include diff --git a/poincare/src/tree_handle.cpp b/poincare/src/tree_handle.cpp index ea171237109..3c1a6cdfd27 100644 --- a/poincare/src/tree_handle.cpp +++ b/poincare/src/tree_handle.cpp @@ -375,6 +375,7 @@ template VectorCross TreeHandle::FixedArityBuilder template VectorDot TreeHandle::FixedArityBuilder(const Tuple &); template VectorNorm TreeHandle::FixedArityBuilder(const Tuple &); template VectorNormLayout TreeHandle::FixedArityBuilder(const Tuple &); +template VectorLayout TreeHandle::FixedArityBuilder(const Tuple &); template MatrixLayout TreeHandle::NAryBuilder(const Tuple &); } diff --git a/poincare/src/vector_layout.cpp b/poincare/src/vector_layout.cpp new file mode 100644 index 00000000000..5d9b816f77a --- /dev/null +++ b/poincare/src/vector_layout.cpp @@ -0,0 +1,78 @@ +#include + +namespace Poincare +{ + const uint8_t arrowMask[VectorLayoutNode::k_arrowHeight][VectorLayoutNode::k_arrowWidth] = { + {0xff, 0xf7, 0xff, 0xff, 0xff}, + {0xf3, 0x2c, 0xd9, 0xff, 0xff}, + {0xff, 0x93, 0x46, 0xfb, 0xff}, + {0xff, 0xfb, 0x46, 0x93, 0xff}, + {0x13, 0x13, 0x13, 0x13, 0xf0}, + {0xff, 0xfb, 0x46, 0x93, 0xff}, + {0xff, 0x93, 0x46, 0xfb, 0xff}, + {0xf3, 0x2c, 0xd9, 0xff, 0xff}, + {0xff, 0xf7, 0xff, 0xff, 0xff} + }; + void VectorLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool forSelection) { + if (cursor->layoutNode() == childAtIndex(0) + && cursor->position() == LayoutCursor::Position::Left) + { + // Case: Left of the operand. Go Left of the brackets. + cursor->setLayout(this); + return; + } + assert(cursor->layoutNode() == this); + if (cursor->position() == LayoutCursor::Position::Right) { + // Case: Right of the brackets. Go Right of the operand. + cursor->setLayout(childAtIndex(0)); + return; + } + assert(cursor->position() == LayoutCursor::Position::Left); + // Case: Left of the brackets. Ask the parent. + LayoutNode * parentNode = parent(); + if (parentNode != nullptr) { + parentNode->moveCursorLeft(cursor, shouldRecomputeLayout); + } + } + + void VectorLayoutNode::moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool forSelection) { + if (cursor->layoutNode() == childAtIndex(0) + && cursor->position() == LayoutCursor::Position::Right) + { + // Case: Right of the operand. Go Right of the brackets. + cursor->setLayout(this); + return; + } + assert(cursor->layoutNode() == this); + if (cursor->position() == LayoutCursor::Position::Left) { + // Case: Left of the brackets. Go Left of the operand. + cursor->setLayout(childAtIndex(0)); + return; + } + assert(cursor->position() == LayoutCursor::Position::Right); + // Case: Right of the brackets. Ask the parent. + LayoutNode * parentNode = parent(); + if (parentNode != nullptr) { + parentNode->moveCursorRight(cursor, shouldRecomputeLayout); + } + } + KDSize VectorLayoutNode::computeSize() { + KDSize size = childAtIndex(0)->layoutSize(); + return KDSize(2 * k_sideMargin + size.width() + k_arrowWidth + k_sideMargin, k_topMargin + (k_arrowHeight+k_arrowLineHeight)/2 + size.height()); + } + + KDCoordinate VectorLayoutNode::computeBaseline() { + return childAtIndex(0)->baseline() + (k_arrowHeight+k_arrowLineHeight)/2 + k_arrowLineHeight + k_topMargin; + } + + KDPoint VectorLayoutNode::positionOfChild(LayoutNode * child) { + assert(child == childAtIndex(0)); + return KDPoint(k_sideMargin * 2, k_topMargin + (k_arrowHeight+k_arrowLineHeight)/2 + k_arrowLineHeight); + } + void VectorLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor, Layout * selectionStart, Layout * selectionEnd, KDColor selectionColor) { + KDColor workingBuffer[k_arrowWidth * k_arrowHeight]; + ctx->fillRect(KDRect(p.x() + k_sideMargin, p.y() + k_topMargin + (k_arrowHeight-k_arrowLineHeight)/2, 2 * k_sideMargin + childAtIndex(0)->layoutSize().width(), k_arrowLineHeight), expressionColor); + ctx->blendRectWithMask(KDRect(p.x() + 2 * k_sideMargin + childAtIndex(0)->layoutSize().width(), p.y() + k_topMargin, k_arrowWidth, k_arrowHeight), expressionColor, (const uint8_t *)arrowMask, workingBuffer); + } + +} From d5dbb8df6f3a68c7d8b726e14819977aefcc8d98 Mon Sep 17 00:00:00 2001 From: Laury Date: Wed, 15 Dec 2021 21:47:46 +0100 Subject: [PATCH 135/355] [reader] Make fonts better --- kandinsky/fonts/LargeFont.ttf | Bin 226352 -> 500892 bytes kandinsky/fonts/SmallFont.ttf | Bin 220448 -> 478012 bytes poincare/src/vector_layout.cpp | 1 + 3 files changed, 1 insertion(+) diff --git a/kandinsky/fonts/LargeFont.ttf b/kandinsky/fonts/LargeFont.ttf index ddf0ade0a6356db845e30df56a300c204921b84e..51a39d85a8dac3079b4897e2892ebadf30f93143 100644 GIT binary patch literal 500892 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

  • zKe2*8v4TIbf
  • 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 diff --git a/kandinsky/fonts/SmallFont.ttf b/kandinsky/fonts/SmallFont.ttf index 204fa77531f6613981eef46289bbcc1de034a88a..f7ee64b4e9d37de73c3dce64b744a02972947598 100644 GIT binary patch literal 478012 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 literal 220448 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

    evli;3BL!Jm-EnmvyO*foh0qoz8*n&aIIRS_pE- zJ8?2v@J#!?JC$ycw7HFPEu0pr*kkD2RcTF50{;E`@rBHx>Flds-GlfL+ctL7@ztxC zIim?L?YKRI^1tfdv zRr3gPQk%K4a-GsFeWU9rxNW#}F-?Z4&FWzj6s8pr$+6hZO&htM=TA4z_5XJi?5tC| zx?e3YQgN}|kMHaic_He@%QyREH-06|-$gD+?jea7fKq4l-B-*f3Pm(*}e=R;P&JT9EWw`w=h};now0R2a zJk5G-q95XF8vyB*HP)p&ihv=O1xqbSo>u{2iH!Z+Pl;=Je01@4ArcFBL9$~j`Ks6! zC_k#Sv5^nIbYjbF5BTuPwao6IF;i=-P+w*!Y*O{h^De z$+N&WlPf(+`~Y?_Ym$LBeG+{adDbUL?%sZ@kq@N%`@`)YhFw#ku#OmledyN5J+^p| zdxDN9r2ElnZoB9qKWDg&;eI2kF;>^8L{>#AwCk+>^=@N?)VqJeP4e;*RKfPM!R1)* zL<$f!SoF#O3k=|~#Xsh@wWH*PF8t4+A7vPRmLi{4O;~U$5`E1{3->9W-C7eDxIb0h zI^Y{UURm9sK(nRH+n_lq+|Hqt)ja+yaRQl1fAgg{aXt7x z@``4vBpkjZro_DJ{3!tq&gJgTRIr$_Y8eM5Regpv( zI&p*T2RN7e?Lu0vhL2L$N-MUj2XC}{tz}^U*OiNQaAyH`_W|oP*iS=?K;HWh%-;t{ z(M}QZ`Zf(fci;n%apV*1G=jexK8)i*%AmU~@ED%83=;C|auPs3yVqd{mWLvM(a4KF|brsw+v7c?p|ma$p58#L3&OEaz&oM zfC#?R-azWd?>Nol~@qe`0bkKt7Hh3=cB(CtL3}Ip4Ft}Iy-f%S^zwn17E zpD!%b$)AVA+XpfO5^|KfVlG{4WzenV{R!_x&}C0Vidipb#r2YMxsbbNK9ll>D(z2@ z7xowZzI^UY?dJ%Np7Qr8&NJWWaQqktl&sDC?qlHyC9IuuK zPLGUabCWs0*&D87QVut|EfUEuqE_cnCLGwk`{^(3-D?h;XJ?=IG|HC=>iOnK8@U8j z2vHAI_1(}jyR+Ah6`>e$T@DtZplRDI9UVh|kL)UCv{Bu~wuS2B(on@6s z44h7*Sw?Lv7USAKJ0hmhsw)=ViiunpZe8dGaG}F26B(rx|ZuhJ=&FuZQJG?lr^H{Hnx?XYzx z9qgx;*xJ($FcuM8f=uZ6mtkaeFTF2X$jurfP(jFC&~S?QfJH3dDH zR%Se~i6ZOne~nQ8dUGFcTxOlEjh41!m0>35y{C{vre_bmy{WUWfRES3%uTfEAqw5r zL(2PEz~L6kw~P*8v$1%?26Uj>YwOa2Q?=OE-Z&|$#u{QASXc{MA7S?uVWrTS3iywT z3nd6_pg&XNh4SqB)AZPlmn)j6=Y+d*MN{>-CzgD6Du=;C@;=xWx;L@uX&ze2V^=iM zueu87C}LQyzZ#$y>(9fAIk`)55}5f$M(<_F8=z;ul%_vKU459IFR!2~Yg zFF0I`yjVDzHW=S0PgGSqXsC3}Il;RH(Dl-Dsa=e?gxa%P4jZ%1B%PJWn6i{v8Kcx0 ztTedAy<%N;tdcrsi8pRjdY=OZju|`DwxE5I(f^g24Y8DgWl&>wwJz^7bR?{Hh8^%v z)WNXqoH-|}_YQ+iH<H2avQcFY?z;G_3$);4ipo z)6baweft6w)%V}uB6|exZ(FUu728jfJ$Q>8_oTQ*WZS8tGJio zSkPR&NR~~g+PfEtF!Zy3M4aiGQZ?h{ev_KxUHR!rtfHDGE4&Uy{w&Ob$jv3XsBI=^ zZCt0F+6EmJ7r(M$Lp6$G#b>1V+(+qNHqL8fuNzI|dhf;w?;or^uWP*|cssMfAg?9Z z5m(w&|30T1QJQu7=-FP~xP#u!G&rN;>MnRjQP@Nq&VA%9uWob`8AGGgwX_@5 z9bU3C-x?7gacvq}8Qodjw^_QOF&%hsYejV6ux&&udu}cM-G)b?#!|H>7;mC>els0CSp;ew4!uQ* zjBU)P&#AF66@Ncsyqdfuw^DzREx!)O)|J55Mtu9uPSlI)p(Cghc^!ofh$7I+eYGrt zCL7i)5rMj(6V|1>q=ET8QaKop?`-%*wLXMhAJ~4hS`WAH!{E2UyU9n}CvF-V@;hVE zbXR%EL{qzU^xfo>?Heczhj4$ug?u+ERXVqFv4*M&@$DM5Ds_4rmwc;GC^cxURxGx& zo-0*@ojbP-6u~m8(;ch&1Bm#OZ2SBBGZ`^3Ob)e$!R=k(BxCM8y&?>GM!(F!-e8do z>dvE~p@*+f&z{%P@rh_fgu&{t7+{Qasu&&+g>bNy8FKgxdh>p%4{9{-&?x zF!(;EI*0qmcE9PSj}MQZ_{g7X7hiqgD>HS^1GXWGgZdw!iY0ceIbAwDHkTy}fp(qhD!F2fAs|K3`gS_#}zA$%haF z`Ub0f#QXy?Cs%i5mD(s+fE`-^F&-_QW5i}4bWN|UA&A1`o>XUoC2Iq=INBLxsoIdV zb_?FcYK)L2XpSm3f%`1|roQ&SF2S|DRD99c1)yrH(E4zgE~}sK?1J{X?%Jpamw~;! z()ylN@KgW64!cqDAC_?VEybn0ehFN~k9hrCYry#ep5s`04#t40lR%$S!-s>*0DFPC zX^Z6!MA)R}4jjxD#4KrLAp{Bw6T)4frat5hGi@=gZ6M;yy5?dr@u7$ExdX9KIBMpe zuJo3l_?UjQZHZ#1zQy0VdjAJMw0jR({J6aC1aK3?x?$?ccKXZJB5Hm)rMO0PeFR=q2MIUjB=VWY zj_nwTCz3y|)lU`@semJ#k@B}}-5Lzn+kdF5*A9*rF~?ZjzAlu**sQ6yav$2i&*2Dr zVoulL!=Jo<{;CwQ-#hf;iGTFtskcSU0yHid3s)3-opaxxavWK#XB|{7Q<$G#Mcsy6)d5h=}1KDxm z^O$+QYzF#G>iBcf#0zLJYl{af7*Ra_>r*3S03*H`=jEWV*3nXwZ3wl!+zL4%A78$L1_$X#nh*ulCW~D==8&VQ_fTsS9JK1 z?f%!G18A>p*jHE3Yg<)jo9}@nsyb@(jgUB3C(fwa0;})IcB;44cpSYaD;L?zF*#h5 zRg@`Zbr#F{d+c5A5US{8x#s^B1;lTlhF;d4KTt}!VZq39;uo8LqO5i~H9BurtH(A! z0)xCR2b)#@j^2a1ync}ioR_L}a4Gn@M5V(&E2p@0hMSw~phB^ZxBCMHGaGxmpObN+ ztc`QuRIfaB>2*Bar<9Fv30CbuW6hmkHMCLC=E4it$L*O=Z<@E)nOVTlpWomVP?H zxSx*Id$RinY3;zvmJ8e|_4VB^r{A~x*AKmLk6nMDy&VGu&)N`TRx$JZ4^B!?r;mQ1o>ljiQE6-+=eONCQ>s5;|F8h4``n2w}3>oqefigpOg(Rh)9!yrXXGvd`(`M|4MQ$HZe-dRBY(*s!TC+WY?lFFkeC zcIwK{>6C>A;#B%m&uRNF>71^Bm(K2Y9J$i-Ib}Ph))mho|3l{=ql*o#EJqC?&G93R z$}ae@62l0a;(5W+CTlnH^)hl{6)t9Ai3j?0V0aGpz{jt z%kuYskI1b~R_)a7tPX8;QIR9LAVYr?|PfDtLu z9s9+EiCRs9XIdf6WMW6NYVn2RVJR8s_Z~)tz}Aruap<$o=c7JLAel%@k%-xyNlV+~ zajyMC!f9_B%w%Pkjv%@qVp54zWQU762`rDvq|s`oQ#BrcNB`YYcgpI zrp~%BkuW?u5)KV-xh5}ueV}sO;&casnYR7Nb#A&ptk^Q^ zTyiH`rF?+WChJ}D!vDeECCXmju-+wm6zl4Z>|OGMj!im<_?2T#*+1;eI{piu@ z=@dSu=MU_k-=0h+6Vo6|I@3VziIk`I*=F|yE=@^|b$4_M;R>|t-dd}K5o~H@eg0)T znwhEALL#Qu6mr9?ZMRP-6bETL@3#)lvaa2z>22&e-WZCxqRDvIe#X(&c<|qv&GB+Y z6j}G~NvRURcZd{q_-tOzUtg-X1g$4uubV^}0qgbntCyA~D!hewwcHozR^X1tfn)?$ zXlaFPjRR3j=_$Dwfc~YMHXyWAOwz2D5+G8D!x=Ro)~EhmjW;F{7^01;-!Q)NA>jT- z@)VtAPQ{|?=glMT!9(M$b$1NswDO+R7CJ?B%{x;6zI+zp=I+2zQdsAH6hXW8jqbpa zhgm;&`HsVW?GvcWr`a$Xxm1FHoufuHhIZv;5o0xwxoRx;P|)p?GvlUS=dF&}{-wP5 z8l$;S>UcgPr^R`f-Mq(J_()e;e2w|svc2+}w#eYzp}A%qIQivJmK%r*f|QlYVb-%* zCeThu8g~s+j7-tBiIFUt zQV<=J+KKzpQK?kY(QtV-pG#IfwMsC^V&?f=DO~Z?iasAA=JvERt_99io@=qS6aT0CS}K&~>8_aQ$Iu_9-sK%Fl{~9GW)|-cRiq2lv-zb zpVS0fMt%PT6S{mH6bGMh)KskPHA?W)Rki6|AQL~bUf z+~jHhloq0{>S@5er>iCv&!u&rewvHis3vudi#ux+sD7w>`)gFHwqvVyM{Dv8$kbdw z9(JP+9cy>ySi2(!wK=qO8VKisN9u#Frka}8Oi*);PLO5R83u#W{LAvuwazkl7nT=G zQsX9@_xeUX7;emFvs*`Qe{z2QG1ps*rBd6kZY5?iR^1@)TD` z0gExC)Iiq1tHKm_T8Fl#oT4`|6vCX%!NJKJr(F|Aw@4BlRw8996=^^avgv&j@lq*~ z*)kbL+Bae5o5Ou``EY%3W><4C6x`|ACW_VC*3oiVtcHUwS2Pz89})t2j6e;!Ehu85 zzVRHl4>{Qk&3rQdPN8?&d|hV94m&nFP1+U57`~j^lYM)47xsR0h<6%|7K0PBG&n9= zDpt^^y{YykrwsAqrEcu6zh3tF`-C2?E+}rhH{tc6SNq$#yR*x1zQ!HmUc_ziI;7bW zEu!5*ssk2vPdngCJ+iI71S5G)5b}PfG1xZ-23_i)u6B=K-a4K;G9C-}3C1&vPb}kJ z+C+VEId}|@uW?44eU#my*2^l^qd2>f)DmQh=|t&*ktgl4Odf1Z525tIvg?}R@<1rq zKE?$CmFCcBwTgk=vBASSQOxE`dj=aldA;;|a9u3~+=#8!E3-p6-#z<9djw($HS1+)EDI^0{U%9Ew`v z5r1={R(0+08qH>pPT!;Dh@sCW2K{c2exm&;>|(TxYuGMb`zxdriEkeSqc;Xy@0&^{ z%qEnTnENx?`9NRYYUenj?eloOo&n$ccO+9NoEoa<`|rkvh~^CTV`2NzIMr&LrK!2> z$_T0%lo}p({2lh8G*FXX*k7sbsn@bOhre1H*q+IhN{6z8$EB(?&>AWfGLC$CxPD@A zC>$;mO=cR=AiFi{@p>#???6fLBPLX9h(W70=|K@lGC3xN0~Y3*6U|P@zgJ|~sMQ-o zoVd#u@5GrQaXMB2#jq+1QRwkprTV79?BQZ5ot}{jxr8H^t=0F`D*F-T8XP`WA1>z| znL=TxB@NtHqv0u`G~o4G>;X^IYDb88I-~pF?7azmoaK2hs`s0bMjDMqv#;Omo1__y zG`nWBT8k}PvTQu^g0~pk7-Nhvjv^M2oqMw_w8xxdr%`&|iSEi>P{KKt|hAHBzy9*Rbd zhIU&Nm%;;btwAlSc{&MX?QiGv2ffZ{ zTZeg0H}gBx-zvW=O7HF{7F$|vrDkP6Y~W|R;YV3Pv{~aWBL2dc;g>C=qLSo_Idc8C$WsP=b&P^p?i=j$7EBqi!*s z8XnHU{n^^;{7vyO9~3ooU|u^}DEJ}Mnd1+x$K~tlELAUFPJk> zlc4KXTsAT#VDr9Yv9z^WEKOdOM*CXuYo>%cVfvc?Rc*7UX+u({1tVxqs_#U213PGQ zt!^;b*q=$SMz?H@*mscm$(r?DTT)5b~e!<$JB!0k4>EQ{y} zNwzC$cjdAZ7fzWKjhB>eXbTP2)Hg&+ix`giLS1RUFBTSCEMm~<>RuCzC6gO)&ttJQ zIg99TiA9D-BGGVkbR-&KFYE1Qo3krzvzhIBv$=a9SLhp$$K#1reT95~PH&c;E22$U zxR}pmdJ5T84?n{o^j)geuhtsKr8kAjT|~A;>KIb_GjX7?dWC#K24Z~zdHZhn0NZ+Q z^qE_WZh~N?Y<{{Ihww$>`eNl?%ma5NmPA{DR4?Urr`Sqgq}StNYIOzYwF3VtYmCC< zhG=Z$2?go(wK?n{ML(pwpx_i?T%a}8;uOu15C*N8NtJ$Of~p0b#i+pq^wfECS_ZzT zqN3JJa1o9X_x%bz9G{Z_KCFXnP=GXXThY|j3`Q5@mjc}Ildd{R`J~6rsnN= z8H1khhAefJ%14sGIb-T7l8qaE#0G{lQf2Ai54ws)ix zUN5`tZNIy(t;0SM&yOU#TrR66X!D!Qcv)M1Djrv>njNfL8W3Lj&>+3q;B=r!h}LDU z6M$p$A=M03vvq5-Ip=>H%2oJsibZJZ2*8!gFpCpyE_?IF zvdI(-CD;z>djX@tWa>g*{-bS1ll+~&6x8V~mN@26Lf_9u+(Gyps*;Qb=IT%FH@M;z zT|EZpg4gAWWSnmAM8IYDC||v5wFAFy17`P{q{R{rjsq?8#PxP-mL016dv5gUeH|8y z)q-n8d$1m7OT7dMCoEfKF_mv%F;}bt<==`#t>k*+jth0QlYn(#@i-%G4qG$L5i0gP z^jEfp<|Exx{r9cmu-$C6CNSYpbWA#01OLiyljo?O!&0kS_QVJ)JaHjYB0K-<3Vxbb zVTx93(yY-pHk$3e79HT#*v$%l+8kp%JSuwCs(P2!?Mw@XUGmt#aTE4qe+Um|ZVR}$ zo+E?mOk^KJW$byTT!lH;j{W@_s=7QX_4`4DHYZ@!xw8qDRqKA$(28yrFe*X#9XHZ&G`*2BVTWOqx~x?IW5VlkO? zx}?KKSlq?J`VEC5z7b2s_3L^{;@NH4TrRs!97$)~AewA?1pF<5xvk_nF6J5`T~X!{ zbLuUAMZd0?G_vswZVP+=ZK*9=NF%*P zxE|6QH>P{M9-a7vczxFCHn4{brP6^_Xf;6lL$v4xpCY}8yb#>Xl+|lOT!$JWD%FGNT>KV*td{Ef3`2p`hKI`>({i*Dbx3{CqhIw|zOSg6O z6+077hr^lBGM`ukOjQBEhwSue;N180tc90E^s!f8Wv>fvPj_l$1Re}gU{8D8f;23M zC`lO}Np*YVyArMw*yG@3&`F4oRMhN>`G*S=%0Z1>*X+~#J7d*$DnH4!h_i_5`YUujh)sSW9x zLc@hZ1|W^Xv0^N25t~)&NU$tK&n1r$uNem&(|ry@Zf z%2X_-X=rg68X8mSOWA9DjgLTB)0n8zQfgChRVpf*(2y+cLMOekkwb-|mOW|eDPH~l z2RHZk(}V?HPi4UVw9+Xuv{uJCDzIa4<#HvQ+{-Dp^9LOrBM*na`y=l=+R<3_8&5yY z-nwN)AjDpO{dM-%X}3GO_hme9yw1jcjyu7h7lC3xgdPKTB&i+ab~g3U0q*2GKj?@L z^9Q~;8jaz3wp&&NgY-Bls5}nmSH}4fJ`CCu09CG{=tM!NiV2p>#O6XHoO)`E7%(i2 z>_6ca4+oP{6nmiHTzQ4QCFZd=GF?4uY_g>t4wvVmpkm^Q&HYyi zIGC88U z9*afDZfk4TXiTowMh$Y#&fT-Mt;6qX)ig$<;{r7Pn(?4lDwbG1hz(^_ui{^q08K}(lDsX<?O_c{36pZM60mea!JsN3`b*%}74j%MEXF7ZsG%TGK-TjG3FeSaaA>nd#CU$Ib zsGq@yC`lW(Z-~Skw$h!`w3a@MDPl~DC{#H;SIoFe1zuikPpv{r!U|K$SX1woC~%TY z^!Ijk+U%iF>Cs~c-JW!AY&4g5iU*H9%3h51_O9#BC&?+D%y+Nr?TuMPVbAS-)Wn@T zgm2#?h!&a$`3%VagF7Po=0m|&AvP#$$a4IL??#QnnqqIjK~uCcbhFhoG}kjP5*gj@ zTy|ndp@g<{YX_2@j(hvoZ0<@jm0A?wwqu#hzUk4?SS*{pnpvfP-P_e24nYhuU|yj+ zDDFrL?g(ne(djy*inR)<|6xlU{FCyZ9K77#+UohE(a-aC1;w#!j@O{i>DQ=|B!?w@ z%_#Pcb_vRN=0e71x0A@s*v;rwD%7dey!Kn)TDG)k97c1s^gE zVDPsh9A*o?=f5`;P`7$D4Yzs!&6Y?Eh0I#viN{+iq3|o-SHgz^o$8l-?MC~<(kqWW#*z;whw)y> zLSYxb&vk_gT257=%qjJ_EOm_CJWq?14zru3N7-%Ct>RRT@|ZWE)4M^MqIrSu&)y-F z@N5@#QA83foWzv}X1Jcm<0c^lA~bZH0dX9JYH+l**{f@n{+Zn&Jq$hW*t@yS@c4$K zLw)^TTd_-Yr;)Qd)vzL^jCCI>H{e;ZI#%>h=5cKSfet!rtM)2b`;L$~DxR zT`Y-}|EvW*&Ea%63C_AL_;LnkPhJCZnpB+RGO3UcMbXkA`oaT=M9>oJO0L{iDmq^}@3bXS=P#?apMj_QpE{p_ue@mM>;ed)jPRQzAaGI+rO*FEW3@>`%sfw`LIL zt?KO61S`*X$NEBO&ww@pyVW;AK>`?AZ3i`=UFK zlFnB!w(LJ6pIkF|l#+!GdntDpyaPjC@B8nww_bf!3ck(W66Wy}zExL4>Q zRTvNc=-XbeT6(bKA7SME>sRhPHwu~h$}@B&;j2=5s6mS%{9kBc(9j^5u;rk4Sx!JS zLw=F4WY$kOEgkZTe(+%<(i1%7RcHN^}dy5^3sS_`GADcYH zN~vPcDCo=C<|NWfXSPkIb16}Jn|T$V-99W(P{yNmEz;4Ta@cFf)2~I8tM}5;?yi_6 z&>0^D`K7wIJ^7gTrOE9WFPB=oEt7V;J8UuVmiWY2p;$AX0IXx1Z~*qt7;%*aTuoW5 z4F*fh%(V~2?xI;K7Bw~Eu6nchW-Jwg5WJyS)*tj*e26fo1NbO8HA0g`V|n~Ts4T5M ze{`(9!)}j7k^z?sK{hY*KkhQE>S(vx0+QDh2=ol(9Zp*y;`13_aTrZro3Eu6(TcV4 zgj&@E)e7#fH6f>Kc*Pfnhl_p+3+#Q7d&4rhO+ehY38`#93K@1XOUP#Sdck>sa{#@g z1G-#i-D;v{nmza1q54`@yWQ??XN_i0qr-S}&|+!Pn}336op0^IWU{r@W{CfVzjH+> zXg0?pLrf&yP?xlso3-$DMXU~spffcXy#r==rIm}<@0G%&VHAZ7WJmvRd6^O zg(tOcF?Vjb#;GNQr6;m(x4~r!2S?c`E#hYUSqI65DKQ%?tTn=qkx^L33K@+C@CRa} zgMIMa5IMtG-8<}Vc1U=N_bO4mz}z3JsR~pGwGNNj67c0sahFR4E)5=Sbg;XfuIOm7 z5ODYt*=`+v?Jz=dp=X|Clk7Xlnwf+-}h_=dcFp-{YSK$S0n6SuhB@y=+(fBoJ-M|-XqM0k_c-7bXL-@qTQ zA--(aAb}@#wtV}@p2qTo@T(f!*b}f?9s|Fk*D2L5Pbx(zN?|xZ1J=SGWD~*@kfP|? z6H>7Wqi7VKK;@C5Yo3C(_e-*s~Z>)O&n&{KK|J;(ObxZXZSlB>- z&XK9%sh{RDdkOuAP1m;hncrLteC(%~!yST*?Y6|b7bCl|JC=oR6PCqjllS5+;E>$` z>P59eRV%CN^&}R^eKQ~2%jsy3sz+;ayxM~SKlbh}VU)eZdmw^_cvw{7qzY>ycFNsZ zvD97Xhk{%V^O5E()V+!Qc?o?C;tL6&lir4>5HbWh4zW zxq>_?_+fAIxciM;4r{=FEk*;YvwzMn#)qs{qY`(1hzaZo;Xgq;gpZfWo?TT2_7d4n zHk+JSfoGCo0*zL-P2gdyn{CxLS#Jzp+SsIOYBa3z->4S!%}ph5YaAYZKb$Kb6*C*X zF0-S9MNgX@4s*vd($DHO^>&xdHe~#o#S?6bG;0mv9;}ym!UNz5H+@E)a8oT$c=@S0 zo*)&3#H>T5<1~sLl#a~w&Zu-Vr5S{FzbS-guTrV`->|>ZUzPqtuMjTHl<>C#{&s`R zOPIn-q*LW1BkP+kUm-J-63ts`5yobJDEv2W)8-3gaX^Jj=Q5U%ESC9QfFUilXpgvD zL4#B8>L_m;X-CFOlil5*VPz()`ku<{GT6&Ad6&Q}*0_M@ZWngJ8s)r&w82v{Yhh9Y z#``_U1@H$Id@S3f%M`T_?->?Kv-@PaRw-IfH5mpyQyypxhDS$3p+;A=G4&E;jpsZn zY>_@$j;^IoYN4_g(lw?tHoKM2k9ZW6KKPbGfPVw~;|gjS&wp4TtCZX5m5LiK;h?(} zQquRkLVhUcjRAvk6SPWZGKB(AI~>g|dP^51h#(!qI=d^pqtg3OQ3YUmE#@7tQQO*( z|KMzIce;+Fl!aWrbxRN z2^B%g;+f$^&z#z1+5Q0j7f#P;j~nnm@c{WeRR-9Y@Rl#OFzrj6@%+;{u*8YKQ`=+B zRZ8Haq|P9nBAQY!xIKqY9u}Vc>H*|h zp{dmqvs1zm)M?eJ8f=9I#l9xxjbH8O1h{pz>R_;^A?0-FMd65wu?Dxs+3oc;xim2K zT4M%0=K)E=EPhOSnubRNz*A~;tPk34NCaxn!6oU-?Lz7+EC-7KXGOx{5G;8Af!V&< zf$AD*6*=j7ar>($4jh1bLVwre@AoQuAg}SjtJ}ppMDf6Z>ffIwyS<*TQ3jEg-WIoS z7q`z$Vk;))=N!Uw?j&AB!MA5$y`Homoc-*u^vUdfSu(Xr=zH&G_To_rybR z%H6Xs%HwDjJvBDZdqZA@c|7DU@DI*J=&Fog(T<9(uNMAgl0X~C#zK{iVMv1$o&bFKFU%Kh2 zuC<}D(cm;TmeT>3qu5(+Xw^0A8=LSmp7YgN|7^Fc169{nVIV|I(1AX#szAII?=mV7 zp7osck@U;iQ*`e2&_JcP8vG5DPd&xyYuBu6c6{~`x?6N7h<=`MopNpZ`q86br}eC# z4bEt<5`1n6SvHNj|w_ioPdhmsR-OEyy=|{YHH&(omXtE|I=P#J7}Vgl)q(+h_OU-rrK8rBi2a zk!dMg_xS8lG|R*IkzaHCAD?gh8lF?1Uu+vn55NS3F(_`DE&-&9=y*r`xNQ%^>B4)G)NoE@G7E|^s%w2q1BxB3Yx~I<7)VyhI$4Nw4^nC z^5N{>x>NGqu|p?3^~n%j9(K+k?5(?B_?X-KOWgmvc3pGjEw@~G%`V-p>#zRGE!SMP zODF8@_4|-cY#9s&0)Zh2GCcp&{2xq@7Y)}vNB)rjVu>22Z**83L$^JL_j+eX*^#B! z#5OFuCZX;m>d20>zeD$6ct9f1tBFS|aRcrNLD>)l8=DL|oy_m=N_7QGZXeRW_=k-e zPo|@7)?uWag#yVkyOms{n2$r~X2*b+0^UW4w`@an+@WgFzC^z{fxkV5zme>T!8fo- zDlC>hbcNn)(n6V61kiJ}Kdc;gl!O~w8k?FldI%@{srLI@UuyU%=Auj>iqyDK&>_F=7zm)y&iSGr4BZEJJ$06S6&|FY(K@Mn^jU!9{j3bT5@8a=(jUj~am&nhRgC&Lqd5fSD;iO* z>tb(9kF!zf&G+6D9#=~rtEIyty1{cy7`&C<4@~cx_ft>>*^c;>-+#*Q|HSW?-@8>h zDcmc)!J_95vJJHV^84SdeLu!02Rb6d%J@%7&)t1D>$|%;JawH%J9>J*isvNZ>9`dc z?k|)>7+fcm{YHw9SE-hfy_{VCs!%{GhE)q1a0L`y6uT%f8mJciC1giQ&seSSW2Vy) zrM(?WrefwVV^;+A*yle#Hbr=zQ`39*PEVq5WXBrA%H6wHjm6+ETeWKUWvdX*4M)bF zrd{J}9m0agF;_j|`UY}@1jSf*g zHZ^u|>-g%nb`xKrx^5SI(a>}`y%c$__ytf5=$I54?jR)-nf%rOI|m)TRZBYa=;$D0 zdsA_pA#98c`2A132AVw&T|GFX`QcHUqe-g?M<-d2$h@4;xS$m_!8X-!ylRF*E0C5d z6GPk_w4&X+`Byv2XFE>%(^{7Yow)a(J&Uf=r!M_JtbG*3mGKro#``U>$MQG{;wpr<(HlzhK|#HMXX6*Zj?&A1xG2mfTunVegmP?RK-tZuiAJ!$)r~ zx*YYuJ`maE>h6yIgI!(1{c6_Mb_wzqTHaL8s8l9Xd&FX_m(H5b8VxpG%HLr(f#zp0 zrw=ec*d}IBW59YA(9puzxj%-BfM}0uXhr;8pjTwf=14%*arUfqD{H!qmRn|5W0`mk zUd*2Z8HpTh;tt#rLMp*!z+^pj7R&@soGG8Jc`YopOck#i!vZv%1BcH6UC(n5ZB=))ocgrsv|8?k`Y!`t+oaGDSs|r0(Un-bA($S@md06Hn zHam9c>Q(F8YO(ynBA_oa}d$b*uU5tvo)(ooJ{gxfo>Ci|8Lq`wQ(8a*f->jA#e{vrqie zaX+1caO~V2_#3T(2#Ln+z%;LtCP@{~nJuy6x?{=~d&Dlnc=;|pt-CVa@K#R~87fNs z0U`vd<9P}ZbrYE1WdSE1o;{6N-6PP2J4pWW93tiPmdnTGfgMy*%P}lEP|DGp=qXXg zaAgd&Q2i8v?4y`@sAcaOLt-R*XjN_$5IKkBH~uPdQ!kZs+{P?A?#x4zWx^9%Ut z3>)mf>cG$n#$J^Yg2$Wd8@Q@}K#&qOdw^72vrmA~wT3hlgO0{xNSDfG(BT7O$DX=z z)y9_f`wo_V@{`iRPSW7dG@ zt;cgd!a20zUlcsSyhj2M4kKR}5ON6i1tHTBz9p%i6mr>oKEv7yyNbGS&#n{ZW-nTc z-LNi&jNB9ZJKMFfofE?S+kaewH`t?CWs$ePUD}{&dKaAtY zIdUGalKW9~VeAW#+~yih?L5sWwkr6{=fP>*Rs-3Lx$wKF%mupeh0Nvu9}6!*O152_~cC>hbUoGSy{XD%0ot~*slbLN$zKp%V8px0aA=`%Y6W~)kw#^XI( zvw75hrUnO68H3>Pc1YiL1L{A~-r;hq8?Bw4g={?G`Y%v;PM0sS8Cx4&TAMZ6YCxe{ z|G<&y(ty_oCFjl$ug|aE)l+o2+vBNSyFvlclVdYv)ZjzlHNGuaOO7&?kVnJk3_Dh% z%#H{&Q?Sa}$BM>0&o>0PGfe;WnAMg^m#-)rJh^n+SR16|mcrQfWOuTw+?#0cHg=`k zCoapuQILEX6J~V=j~D~tuCP0;RPEd879rA6@0;^_TU**3QIjDS9ZdVYEoy;VU^LgG z$Y=f#bLIWakl_h(%2Il?;)1q9TQn;!0S%wkTal1g?Ntli$>iv&P|%CK{N8Sd-**p9 z;h|s>*3AB`;{Fk2^v6FZIkvJpFNsC9T9NIq?oUOJ<+dhn-%|Q19bbNd8mgxu-dd<_ zwkgk5qyN!|LIc)9_T(f{`h{4OsQ3dPCbNd(ZOUnI|8b3}gOuGAJFn>}J>K9&#W+AR zQ^|EXbk%Ssy>K&UGX0nK6p&fkcwQA+dVgiNE~?7qRq^iVR>V%6`PBNG_iMj+{d8~1 z>+MSJ-?EwAE8DMxx$vQoMlLMT%CAo;pP5Q7mO?mfP%V)KahpKhAUF?gaix}ANvyHh z?UnjY`|sVkWu?#M*o{y<(+cO+*2^Uo!_qOso9 ziTK!;x*}*$0n|v*+bDc9n!9#H-JHp8D0X$aoMv-Jr{+1~)ZYGnvn4G3lQo~~8xDta z*(;>$x7e()NDes~4e;%7oD0HdaN$;w`auLz$dHG`hL{`In&4pGJyIyR)Fat$gWuVm z{9L0~@OiVDtjJEUyYZ%*4u0YDpF8~dJr7*I_TbJv`*&YfLP8tS-~`UI1$)v>oUaP_ z0cKI<91HR)tNITGib1se;>uT-oOZF)v$;TEaPaa%ENZewqN|1zJyY7rQhR^igJ4_G zKD8yC?(N;*ll!d$eSM<4voo`^ySpPb*6K>7hjt9Da0k4i+Y^k79Y3g@FyLn2bN2O>3vq-%3O$=wWs2?CBf>Ul!5#rG=(BOXi{N+{!|ia<>h79d zBQzq14YGG$O%rY%scdwR$}TFpWi@P&Vvo6ocI9&U{Mxk>|Js7o1@(^6sa!Ul-jWXk zwjl2cYMOLfzu2fVrPBvD@A%xrRGIz3(ViG-mE@erdJA2GG*4Hg5!W_0dM z=DpZ2_&Gm~^Df7A&&P2wh6=Tf|HjS={{o*R&p43l-R4E}U@X311WKmos- zTzMd0=;^uQihK@zfYkK_uHx}(KK@v(*8#jC>2jc^_hWZt^Ty}@?D9RkcK^v;<>@fH z?S_GoffZlexH*?|qV_Q#y@KEKGJ2kVh&VFue??w0s|EoTW1*nYe6(@n`i(Q?b(fx) z7T(|dz0D^i{RTF}Rclsv+6`Kf&)n8PWSO)Ds~+B+w9wkyno6=75^H zmk&nR7;}`63sG)tG&T?YHdHw_10(m(lWq%Efaj5F$!%PsJu@Uil{JL~o{s?EJoDmm2tTd&!7#o@z;?`HKs)9t+B$XDn&*DG`atEGlcaxyF8y?QB| zyuxsBQ>-w+YJRntdQO%6dwPng?$w!a;ELa8_k1%D583T5M|&jf^dhm}U5a-ju$tSR z4~O&$HJR;hgUXahY?&zJEoxgVTG*F_CSx@FL-BT`Cz#biwawlUFRc%SjZIpezJ|J5 zV1@~VWx{b*KF#*6tYprEq$rT=j29`O*}=gK!tY*i@xcMWDoWpUI-%GUOUTcKe=i;{ z72`=eyRJCJ@tjazS(utC0@(@QnNuUfsWhN>J?Zp{6_5h>$kHCy%}V^tDZWA3q4?xH z2h`UrqR(4i8JQgOdbL`eF5n+q>j;_6218V)>2RSg`cfpV0PRh42zz20jJHmL0;bEn1p@-RF0val&^G&Z`_Divy^*gl6^9Fu+~ zKa=a9*mu~L(3vkxtK5BlwQ>Ka>xDEn)$LNv*#XFs|D|=ix87P?xBJK=7gV=9Iy)rz zv2UoYs@EX#GiFv5;HAr`1i$#2`bF{eSG>=vPX?borDH3lCv|KS>(fcEGQae!yzgu2 zzGLm}ct3f#p#wocC`^ic1xh@kpjE0f zZ*3JVo32$QwgD_(*m`b8JveanL?qX*9nOz-->`C(`osyMa z7}v@w!}CP7ia9H*c*queq}y&c=-p0ln<%QIYLD2S_IfS0ZdbPxRn>!0(PLB_MKKnm z^b?mo<6y53_6@Hc4YfOLLEpN4qPTA&6hwo+HG`!Q!^qIs)Zv~UbZtX+ywj2HxMX6y zcMZZKbXU0jB|NOe5iZC|S~`kwx;Fb`UMk401*V&Dv%a~K527{Het&#{J%{;mI#T90 ziG95QU&#eqaUU%^W$EejlH&3!kWOC^k;0ElG70wSuMtb*ZjFnOOn*%#^My+%_I=ry z7OJ*%8$}>5n48h`&d<@#(1b34tFeb@CW1^~*t34paDF`sqsd(~m7SpZeg;ZoNt!?D zt}D6j8V7IUV2%|Pb$LaW66}EJx3Q|KF0IPuHV01?zEGpAE~n6L+0Fe@tZx78dQ?`t z#rY&D%d%gJoV29Dk@G|TtWBCqvEOm$_m}!st~`2YDb%@dps!HeRdjoF&3&$*R@d0% zV$E6v;PxLamAr76?%5IVG;3RQMx&>D($ofDszI+q(1Lg+VG$6{3(OxoMY+uQ-}ngI zDLpMPhxG0#;T}=?2;mD^f1Bj`n@G|68ckHuzDa|?ryXV4H6YSlnUO7E<{wI?+@i+= z{K}zRR`j6vVds$GJ;M7bI2^g`rI%)5(WQt_$KaC3Oz0mO3XiKdIKdZ8X6c<4=5Gx? z8}DslZC#^dn+%6HPsbBR&krv-aQXOGoVN|H-eUGR@7TU-JmKg+aMhRZ{K}3!h!x5@ z6Z)Z0uV9`x0B=}LWbh5b_D@!#gQ_V2`k(VuVuYX7*E|<@tgG#Oc7N>;@HNX3$@4m! zm0%^E`RqsFd86>(LnlK0N=4P3%S+|T>0H({wiIUP)`4OFv+RwJMhC7Q=m&OrGHh$| zG&Z(2nSy|EvcA9$>7<8*W1{$xE}gkzb+Xf9QGV(>> zHME*7E`wgHX*TLxbS+ID&2!#eSy(ie4d3zfRzUJ~U23uk$L^6%Z|h1Kj17LZzNI6% z_8Q!o3|@`#&W z_WPMb9yHn^Cl*zPVH+sb=-eH1LxI-+JH{%=u{w?)x`^HpRZ9u>uC85G`aamx<9~5W zG+L$SuCBc=zKD6>f@k%B|9J^X(~L&S^vPo=vbRA)k$*Cs0pmi=4~~EHGKbS*G&)WLEb*qHDEou7mt8Qh#L>|H8 z>_NHB3Z6THdtq0t#B-rbA=`}Z#2joYE}06p&b+HqAQZ|BVh$&P?lU~>@u|H?0ZjuD zM)0n^H~6;Q<4ak*+Ey!Y&J3ovgU6HV4%KK1t%D;OAwh5&3E|uu?D}&rxflL*_Mysz;}IL>od-HB39-0aVFr+8MJbh3Km z{`b!hLawGz!roZvjw6~+ciaU0aAfM>MBswpRbWVs;EY6*@_KD3L`b%(n18gvC52rL zhb<;{=$&SRqseO28MOJ_mznx1>$y7&FFf_s3)_k&VSC~5x4RF69nrbZ=hJiYeyKbo zI`=J*hsZfF!_A5NDhomdn-mk_tc0j?X}S!hp1LP`ywuX#qSLjufGArHE@1x|OFPA# zB?19-7&)o7HtY1QEv4hpdv=Svi6+_ZAIF`r-a)F3O>R%_6c8}pIF<9bn;P+}294Dj z|FQHCf8_>wT_n3(F;|kCaEzxMuUEP+XnVDwFHRo%QcQ3QAeDpz}Ve19UI z&23MF!ja*~isI@G{e!_!IJjbHdbG6CQWBcIdj06gks~8xR(soULKNHDCN_L--^7~s zHefYR%)1|$$+Ed#T;AipLaSiQO7_c^5#!9ewAhC390s4ug4XPi7oFgBSUdNiBPXiX zvq_kY+?bbhz_>k38DPhTh5>yc9$T?u#qbxz{XIQBg)7Aa1$-RXA`Gp#_VCbvuy?O8 zu;Q9)R}A4h_5$na*|WR1D6mJRX`xu!v$s$Xr0IG40IB{zHQ#F+Wa1F^P6t{i-9#3J z#>^MlWnAd>a_xi!99{%^g$Mx75y?>a33)Vw6dEH}u_#3>btId+d~($9A767_so&2& z+7XHBSPwL`0bMeA;K2v+4APOI-}2l_WFm6GhOa`0r1rn;f0<__Lf7S4iO2Bz1m;LK ztpRhaytp#Y$tV3!@`*Oe6QwuFlNBiUkFU+3R9(w)5Q_fbDT*${Ede%txPK6u(Dko> zG^K_f^!o4`b_m%&J5$i_k>km=pnIXk*&_7E2Owqo(X1wA5)ZP z`QV{`Qn9x9vxB@N|6O##Li}f63F19uF|Dtv#7~uWQ4a@)$dYu^o!V zFPjpif{~)Yw~%rx6u($in@FAQI_mcyt?%rbyrjFUyL z3-((pyn$Xo$UdCQLV!HSe_-0OAlKCFzXtsjmDxd;Q__IYTqN0#q?K@RXz-en!PeMn z@4Gx#@F5#0oht0=>4BT?>Z_+&8a6E4E!Je$Mze3-`rn+{xG@wkHP>tXP4zmX-QGWV zUCQAc7}$qgSU>yTK_Pklnl&B-YKJ!$dMHM6jP5*qmH#VnydJ^WCrv+@!D#9FR`3qA zroV#;$GQy$!K=30dXK&O>akv%9TvEuHFZpq-p7YLkAJ`YQdr#$*r6%sh;oy3U{zY9#P@?q-@-tAoxvXuWTI$Kqssn*F z6TYC@%S3mnr#wL9+3TCPtCAUJ!h^!=GP|VH?E1_))NQ;az5gZ3FFb_j zUKnzZMr{icQ9mi;A%1fDtWeNt>42qZ@L5h+S1#j0b6&+*p2H81Hn)*R9xBLQ-~+iY=_@uuvlX4 z`L%8B&05dMFjBjFCZD{o?XBtnD)-BVA^nVx_X=^Yw*9Pm9i?RSg?df(t6Cs;(0i*Ky=J z#_NX2=Tp;_jez*(P|`RV31bhjH*=GiIr&FlJ zG@C=g_Fyl5bXvTou~=JItIyYC5S><^!7K(`?v{UWIeabXM$@WyyGzOR`y<1sNYS<$ z$6tJLyaOHjHf7LJuw(qxsd1~edBxBT*JcK;DD^k%t!%H@p!Ej2rT=W#HY2xaD39Et zg!BOIGqeez8T$;uNOd)Y6&^)>A))L7=Gz8;89q}iG%#@RCnxv#*R_NUI!&X|d+;Ye zIp~jAnNh1@@y&@#PhTKU8)Vn=YFsd5&F481&!qn$3X&>9jW<-x+UlM9~WHwMGWmwv9)Q zjQf1RD|}Z9zM+=+7BbnnJ^+6LtF=!WrE%|L;$z?;Kn10X$1flCvzQjfF^8q>lS6m| zpLkTB_}t_Lq9wt@-#6b^i<=9?bs2n}Yzcy2ziLwwADh1V^qc#&mp}oIuHU(nxx(Sm zv2X-*@SG&Ndwbokw|Tw^=KT?#yM&Fh=(%8_Z_1Cp$-7f|^7J%|daDnAtNLur9Sf&D z66(H-G5$#zW1fw&XoQ0N`1`!al*b=Bw8XRVxN1L0Xx7LpDiD?Cm$FP0dm+Vyt1mH) z@7>qi3$RX)rnj^?ABzPo(RgGf^1_jVw(?rtlx-ZD*W&hU)?^IWA||sa_ViwI!-^rS zfod?Lka=EYf>$h}C_#FV3rSXKDtvaHZcG+FqNcAzGFdb}aUAt}udDCwUXzI+-e|M6 zZCL>p-o9cm7DXPB0ZrF>de%YIynb!5Bzk(%mtNZ4<6%$tmae+8)Qh=YG0=;-z^(*t zzJkLq=cJR30xc?3K(|SLp$Rwt^hig?58mjG-xqUsEx#O0>t2F0)$UUESag zS-?dWsFyda9%sAEiTH|0FlcA-KcMo=`exPoX==%~xnFFh%YJJg2`iMwtsctq*t z`!{bvfKsUAwhzZ@q_{nfoL7fh+2>GdEBzVlAP(v{TPGVstZo}<#tNDV&XaQ**&eCJ z?3F6A)V5%-R9Kx(b%ZRdHts0(H?h3*m%XJcx2-d6zWln8mG4g%ia;1Lhis$S>~`@= z^pvwr4GrBW?-A|>@BKh=1`^%)R5Dm`N0#sGz=M4xhk#R;MPkTj>W%%G+F@fq)3dYa zJ4N_)$gZ59bC1O0^3j>2>%R7r>v+b-jC5Hg=U$Gjy{6Cs@+vQ+Mo`Y~U>((Ky$~O& zP(HhS7TsNv9(oPu<3d8a|!V6*xFYw|bcbHAKxvM9&%$p_#kOOtZXY9kF$HjwtDuzt`+O)MWG=Xi5V zv&S3e_Jqf3Z89~!WZTfy*%{xIz2~WQk#IOVrPHxzUpTln8iS4D_YWL<^|?@Zj7v{x zYejepRK>>q5OVyUqx{T`D$9oXBOiLq+aZrM{un^N<*^XR2llrz00DtZ1C=2l=ls}Z z3A7IatnGidh}^MQZ~?SRsq}GDXuQ&UkC~MAO>?xZblyA7Ot_2OrUPv|DHd#A)~cg$ z{?eA+H^2mzu5KE66%Lw|}8!E)`oN zwC@Yh?AcR`O=9VvFF@U2Xexhl`~viU;N8wnv%kl^g}zvc@5i7+{4NW6A5uGnhq>k# ze3Waf#B*`}==J^#YV65_Y$XPtLFXMfjiLGF02We-rQ#u}*WP(&%ez0n<-Ys;(^sZb zne5e*)50_Ni6>y^89v1dSzqb?uR{NvKt^smZGj zOs$WxOTMyZHKld!SicI&aCFuB9V9dUJ@*@9uny}G$2d;D3}%+~OGR)Z37@w?9Tb2t zy~&7@ss@L)sRgLwEhoYx?(I{Wy3eWK<_svKvbtCq35CE!_!b_2Tx#9k0-SvbqQHT$5^u z5Y3o|P$L2b!PF={?q}Fs*H2hZ-+D`XbXQcw~ukol>kM?!K6u zif;xQT&{aAJgMSI1ga>ng4U>o4Y?iXhbTgTYyCV0mWC&eoIWRp9y**zd(9e86E1=VxFnl}=VOI6f`s z13hZv1NE*JmBW!zayXXC`9NdNxtxzeBJOai{@@Sz`L6*#roP(ns?VD{x7)yCE9{lL z)iIe^$eUIT>H>K@#q`;HcSogp_IcWDXS#QQ$Lko1{gnpV=V`Rv9}U+ZKThX;3-)&a zcN9862E+n#!9*5j_RL+y<9`2A33snM8(g|ye1qbM3-j&%xN^DR+r`~VU_ZR_SPT92 zHTP)7>s{dD{eBR4=Hvq3u)7=C$ZWjfYN2TL5Kj6GkY#C5*)WcZz zQz`GW63Cl;T)BYU00!QwA+)Zi9x&vtdS4!I4aTEfia6+efkiVPuYzK5lw3g+1pm*} z6uJ=w=8`8Cq*sLcN`nn9R^QgXX3g$u2F1Mm3Uq<4ClFKkaxr)E<+jOom26YVPTPvm zu2xdQ`&cbC7&bOO8c;p=h-ul#~^z4*ZgkldJm zWgft+1@Mk6x#js(pLArZ{5O0ed{+PQcTadvq8;9|G!=Pf1L9k6Iq)o!RdgQ$#P_4I zSPgiOPlcx|us)oPejKPp@R*8&4+=Hu#krey# zQr=o}~$FPD?9Y{#XsXqkmNL&&qNuQypjfQS2) z^p-=7T!%f<-!TXVx@+7Ag8Kp6;!|?{VLuTs=6TN(xZxFnO-w{ifKI6{QWa7-cj|>| zGITYMQFk$lk(_L*TB&48D|yqB?xK@V(HjYCoZ>d(ie=V#Q4#7Vt1;86$$0mt>QPG2 zT6jr`s4DT*bJ^wfDkY39$I+;#I&ki)b+a3B{R%E^VWkQkTl;D>zA^L3<Q`Gm zUxof;mdM+5fmPIQm%XBdY67W<{HH)FK%8Vw4ukbU%|}PqQdaNuhyAH&*uG&~J`@`4 zzgzH#IeQ}L)O(_lwp1XXv$nTUj8H&h9YJ+XPkRT-uBD%E&15ZBmn+^8?-b4Nqs}hu zFzT9J&h9+{fA8wfq~X%`Hhqi9ZfR}x`Um;}M%Q;~LVT_}bD7?3^?2I>C5v{9nJF9g zkMkvd7YS8ii5melwYiM#yRD zaU`8_yWOsAdIbDJuB~f;pOMV+<+AO~L|n^d+>e}DI_I9fTfxz#TprMFlyZehHeP_w zpGLfpjGq5~=F@X}R3@5;N?Ge}zE=NvR!dkp!T(&x z<7+?7*Pgua)hjc&kR7;P>F;y_`*2I83)@`2oCf;=x|C=-SfLzs-w z4SN&z3Xgt5k%Af!Iy*CS7HMUZhzn9o=>w<_i2cv*WpjAru+FPkMHh!S&TrcVqmIYV zTomrOv=WyAI`HgX+)2VDT zp4w*B?Ty6zhE_abARIOrtoAOckJAm!aadTz{VHx=uhM{sk{vQzV*@)z6Xc5T7EvYp zA}-GUY|U4G@AsaVm=u^tT=PdL4ecMexx9BxZGgAE9ca$#a`~aHTgKZv2KsOQN?)I( z|F83V9I4s~t=-rghQB{t)&75McbzPk%liik0 zZ8}(f-Iy0CWzkc*{b+mbgj}w4y1ad;$K^bG&dR=Hw!~6BmxKar^4wp|o0NQhmA&Eq zg!AkTF|Va}PIg4RtVExXbi&Md#YU+^O*J$$x}pSh1}ofmar zns);hTmcphnoM^#jM$tebGw&Cl*8{W-Dedmr$4MQ8r`d#T=lvZhfSwz*vOO%@LOBU z#<6czZUE*nG26!1&ZB~h=22S*f!YVMwRlEV%^hIf)#+H;y!zVXKWV9-JO71Knx3~w z!|WGxmq4)}fJ<=#6s(~m_&CoDVl1L(=Iwql7Wc}l9J zl21jv>f&>dRFD!tP{~|mR@FSk5*%#-E3~ahei4gf($geLJmVCKy15%Ee4H7xRDbw(J}GN`{5TtuEyf7aw^DM zggxlkjPpCsp5)yn=JlClKd)&u!Fhztk9nTsr+6wCx0_tfcUh=q#OHHlN~;(0Vty|# z3Y&f2`j^Mc0qP0CH-6NLmPCzcTGZHxu6Z@zOVhPGL;OL<=Q7Q)*@iFBx;ekEcd{11 zXC9i*A200$R%L6aSo{6C|K~jJVTPJI;<@*ee_SCZve-FQ0OhiBT@ZTHq;9D)SMd4> zZPHnjWf3c_^e2%{VJoQaQs(K%D_MAJ2muykJ#Re1{aeQ4Em~^FcQ51rUObtW^eny0@5q8~dhGbTi%}VeeEW#VFI+wJyxO%=s+#O!uQ*7M z0l$pLKcfjPY*Kq(+WFdo4yNS0Bt4H}Ml!EZ*eBn4(xXzUnm#RThY<}Zn~kkVQ`H%N zQtjIEbhRlZXj8RmK?|4a804o$ zB;C!%`Lp8Bn48OQ;~~GAH-k^UMRIbE_adUVnD-W-D`br9>$UuDH(-G0@!bCkRpHzp zlgO3NNA~d}h*R|fSH1j8XuGw3|AV#T%nJfY|7jj0#aJr%62aRc2 z#0qwvVUI0bgz5oB>7kX#_C#1U%z0z%!dAjZTXr?BGv`^4>a{tYmh@=q^Mssa>d=%i zQm-TG&s1imGt`~w+#i$5t(?ES*A&T<%RVUkSc#~R(KRos^3;MU{7R9(bZ}$9BBFg< zq5~0O8P;|ox*f1)OLig>PB;!5FfaLegg`{xlT!{jCU{fD$vgksj8$PMNWq7e$TB@^ zgMpygS~eP-VY|awMt}1#oG|6G+3!un&}OrN?C9^5O%{K!Q~Dm;5ilA|#%|cy(@~4j zWa`3qAC!VRoy8LWBBg6q=j7t&xrF~xwW#kBo6>6adTKA*g0CBEx-bbJQP)|(XYsCo z=T0uulu6kK;P)936Io(Rxj5F9(*PVzDE~xR~Ap@0&Y&Sc7_*tg%;S;;Bh$3 z574cAncm${BQ=o2fD6gSR8U$~c8J6+)!Wq4tbGjl+JIJ9qx!JC_gxem&bv|4wyWy@ z5oG>fg1@n@g}Kn>t9iv2IK9)jJXf9QYLVXnrGAta&&g>{8>!G-itVpm`|b8td$}BL z)j3;}ThZjjEvD^mQ|tVEb!paa_d`B@P;S%fw3>Kgm-Oc>Q*t`dd|T%Aiar91AIGt1 z)r*g4VIQ(bYv;6NQ!>dp@5b~4;wk*&(kuUsc{BOS@(VxrtN9b+ za#-01$j~MC0i1Z@%0D@?^m03^yj@vi5YPOSHRAEhtm<#iu=6ct7K2sdJ15}ZqrRKH zxLdWpz1sZd^Tloy@4`j(ar4REhYOF(2-{`%1Vjw?OMniOpu-q)%DPAfl99}fY6I8T zmZ?uBiN-x4pHip%lI>R0V`t7^tt|A5TWl10gU*1}>jkdRlI4JZrNL4*0!LABa|F@Flx^eTxR7SLPMf3_MU1vcixtO-f%? z;{0Z_&)~Bb1HpDjKnyt|5tR9@Q z-A{XC{nyadE7~<#%EUzd+M6+Yy?_ zMIKvnJI*~dkAaX62{MrDv9vd+So{pTV}@Jn!glBYnG+YXNvadjGU@F2J|QMW`@}W6 zW$(=5o#IO91?|?yP|7c0zf`Cy<@ZUpos7sNKz{6}?-)#~W%uVlo@Yx&XcMRp)?VeV zP{!Nxcjdh6?Ob+=KP9eD;|$}Ait`Xu{x6pC)E_N||Hxk`2NlSdh#nqtq=o#0K0$FV zE-=iILbJW9gn-SAm`t%fLwc{%iH;+Q(PE*et>hEEUf)2h&1?(%4wgI~EBc-ctQzR= z8i_|D#kA<1uc@3n*(Q3->7dpe@O7cbip>@XwG9Wv)>f;{A2OI*^(9}jt2Z8v*&Kd< zwv@8ioGw3KC)c4kUs`&de2KM-yzrXG^|gE}EA~&eAt^+3r;GViFlulc%%!M57EUF? z+9u0Ld|g|cP8UyX9zo4bDd-F&!+YKHnVOVwYFp8X-qRKFc}>QqW}Q`UNPAjQpyG3A zn;Tji4NfOggnEJ@hdn$j+8P@Ho=Rs4x|T7PvBqBuWqC1OeKy?Xd49{!1j4*PftSxx z(Z~7;t>V|HR$XG1pQ(N|TFZn#Q!%UblX)x}XUXjp?so!8%wkUiQ?NPa`=_NxsWXIK z^;~7Xl=pU?`En+>#0+(a>@J>GO*~JI1L(oiDcfyOK#m2J5!g@vFMIC-A6IqV3(r0? z(u}0hydP&qXWp8Jq|swEdRdZXS$@g#6U!K5z!>9zA&z4lLmUE*KMIsknovUd2qlD2 z>QL$snoz=}38j>#G;t{(*&5Bv z*^jl?Ua!6O-fLBw@xSy19ZlgC)4zN5?@lK(d&xB}*S>o%$Pf)!!E zN$l+3HXN9CnFHq{2K2FQ+r!+39q*8SCns zrUS!=tS(1qQzm*nV5l=+g)6K7e%^*pJ~=Yt@9OW{a^9qE@4@fDLDjiz^uf-DV5@$0 z|8EOny4Xq}@R$Ek9K25+sQGa~o+z#FMc@Gbr`mAvEc{9f;(#T&_layM0%lS@L$|V; zX3y(a3bb;_aVuj!YMJM_ocw?0r=q#Hi3*R*hgb|l8Otj5T-+2mwF2@gV_As$<~|N5 z|7lG`InS}U&n!f%pi%5<3r*baidLJQ**S?f>MbdwE~$38fh0eT%QKg}|33RFU8Ldk z$oGqnW6g>idX@GaL~16x%^7 zydkqO#+s&YT5+jmdL6p}#}GQOX#DEBAF~eOUv3)ScW9z;4^n7MPp^|_cw5E2;;J71 zWC^-5cbEJxB^b-}@ts9LNxvz6B`2NltJLv>I*7~k%@_1g&u(1ETm+88LIYz%dk4*3>Au}|8|iv#!b@7{8? zJ;&Z%;N-kpbr&h?7p(I!Np52+C?-K!cWaRi?s+VU@6Q?F<7lM;WHb~5(##4}BUhRo z*yG)S^WQm~MMt7U^&~o!q_60W!9ojx=jiAN?N^ZQCZ~m8-TtN1=|5`g$z`(ZR5#IED*3se~i## zSIK+WLmLngAQVq5d(@xHZOCWZ;6~(f7Iw-u)#ij$s@#cmm)&?_S-m49I_t7a`DWy{ z@~bY~Kf| zbhu-65Tk0sjxyb3ggI1~hQ$iWUye6+n`@^izh>Jd?XvAO#b+T-kX^fdcR99fajZNz z3uCI^7Wt87NF?%h(cakNsf`Y^A$L4iNww1%8x!}qc1c&-_L}zE776wtvw$8aU`W{QTLVXus#mVFg7+932H6wLlZ#Eco`y!FcsKF>PTQ`oS%hs|ANBbVU{q+XtGC%iA>#8}nwlKOy5{Df&`R{~ z*mCE)z5Q;M%ho#U~d#ox;;0ucyvs73!KA147vCt#7Jl4W_!LCQ*=I zS@gt0EJHSvEdX1b^jeIf7gSmUIAU9ko64+$L$*WQXrC>y%dLYO9S32?-%&nAe4hfk z?q9gvUTGj6TD;t@v=ZQaSfms$vliB9YlpM78jPRg%Z#GTx)UM%!g{&5Y%G zczGqA-#Q!r$0}9wu5z-kxMZ)FVgb@7x zHsP6%z!JtD=5od(ZWti-$U;ksJGkepIfTq_%?2kOK4cg-tSQrh#q9xgXRoBgs~rX| z7gDw8b7rfH#Ye{?2~*G{g*rP!k}0@n_6*Ob+rw=LY!^+N;t1#NkDKnA#d+mAehKbA z!zHG(QRdO{)dqMLvtN_GXBMDge1gzWCxv=)l2jMmSgV+vPxnVv^k8i#icypNUd_@| zr44QatOlPXamNi_i)#AGU2$-t+)GeJl3qEV-@rIaZGCt~e~`jB%aOoonEYs`g%?yE zh6r~5Q97)`(fpA#TA3N~5yb8+Cxq%pv{qb_=V_iAcw*v5)XR zaLfHPxI%S`-^C(-!&%(KsvwX>-pM+qCzo1pW)J++@W~*#@7l zRmesn)+WEhYM~E>>x(E>{9lM+{tWEpH5A{{t)Ad5!Zd5Kc@`MIzhPa_gtslBc0vALtrMa6{7$KUGPr8X zTv0ljWv{^5bRl<;?XsW3}=%>v0_P9Cuv^AA};GfDavR+*o!LT~IpQ+&Sgi{6H1dTxQ!nGz&G{ zKK#gdAy=m6qFW?hC9w$DPLMqAwfMY6D}{|ig=#Zp^Rbvdvy|JPHC;z;?VN^(`aV8+^*^3-hlQ+#09 zVbxieMUhltJy#>pTI}WPDkoz;>sax8RpWW?^V+Z&pVwK)QSt_3|8}r88~El$&EegO zNBzqAtY3LmZIj7%aewB8WFv5Uhwnj?-wU@4DtBdz?jt?dY}M~44XE4HY*G`!W#*Of zHD$|Mr`CvHU~8|2uZwbsDEzW`?x;M&thwkGDS^r?t_#;4;x4aRB~iKMCA=|l6Z~B@ z+Fx|%nxAZ&ZGRoZ?Ve**%`bKHTC?x&=k^zA$qM_rukr_kMuC5yP3Q33x#x#S+vgFOFL4f(U>Q1U%?5b7$vU)(`BuUv(k%`B90 z3yakrx~!6;s8VHn`SX-;1L4oAlOs?W8WGispOKp9Py;xr3CioznGiFGS;PK(}o@KaC5` zrJZWcpZJk+theG9gX86TD}OO4E-nKgmrazH2KVdE0>@<=;0xUK8q;liFN9*`#@g@}lgab;4MM-97h8;cNty zHpE(16zsESF|+fnIpjwdJ;lm?%qhMm88mD_TqFG|Y91dw*-N!(77ZcC=h}QXI!*P0 zy7`WbDD$S0)o5Yfl}D~Ns^Gj#HW>EV6&q~BS?Z(;D@I$TEJk{=GHa~3im6?WmRn`& zI*0g|pP{ArCS;p|e`~%sJLk9-(qLs)+6C&C{rqCK+*;Fi=}R`-M$Wqv#j~5G3vm<% zXiyQinjqG;3Uv{*v^%#v=EP(A(6ZgbyKP}tAlR`il|t0nE;X)fS*+OuwrhO)K(c=O zR50j>#o8sw);_gm2gR8cj7l)s@&L2M-~6H2lq3=*vo;hq8R6&>@pk|)Qtky zh>KY`Rq4cI9VK%8@N2PGG!|Lj(e3x!Bo{(eFAx3+GA);7UdATEC$+rGQjrK1 zJaYOj|Kh5ptOvQ5%W^Sa&u=fN=lyYqg=FP_6~0#<@m+J4_&y*W{;$C|F2F1f-~7gm zhHu&z86exIiVe@#rMlaZdRkE~or7MK&G9M}U$#7XVzHE8xkfqjH@b+*wKGDA2l#rW zM~iXJL*}Jb20&0eoldEhE6NrxuhqsKm41i9?<|H}6dJ=T^at(HOd568i zq55@vN{HW)^WYJ>aOe{ZcSZsIpUAvXE1j;eOiX#ybheSm0zCatSa44QgU_=3{qyH zL_au0m5>Th+(NGg((VQcA(wkhc3(Jres}k@6d3dc-MB1Zbrw-~RYNk$^0I9@Z?u^$ z+3dw^ME;u*kplk6AC?28-@gZrs%WVNuvVsq?yionN@Y_Ro)2pze%1SCbSI;yI$ZN& z?p?j(YhQJoRnZk+nj3Q}Ou|msP+b)Lq-1Z;CM7}{g+nXZeracp_H@~)YUI>3-R5w@ zR&5DoEw8~MeZM*_5yn`9*B|F5YAzUTHp_?D-zbJC$CoexJ2XRaP>5?Q>4_7Yw+I%< zPJ!yH734$+>wyWndHM#Jk5%Z1K@9 z5lp@saYV+Uh9j~K$E(uzoIixfxt)j4r7JCI(DM}uYn)tdF%k8p-CS^WLO-?Ls zdekvbaQkc$^HYO$R+Fr_c>#+oFy92uWrj7?G>tSS_;hv}dm($lGmDkI`VByGf8-XH z#NRKfQ>fO(h#PtBEJH+reL}byvKjnRyKJt6dOk^gy&mA2#F6}qGDx-i^BZwPneKQ~ zW=UL5R?3H{Z0b{7v?Az%*_@zk}L732k};Wj*NPt=nABLNXw zL*|b^;>&Q*4UZFo{n%2RkkXhc@3EAP5J7@UMpgMx3y;mVqYUx^9s@H)UZ5JsT$#k8 z77W~^2Ww$eyig7)*)d9-1kyjzs`9&h=F9ZUCLIM-suKA^Jw0$;!D)f(3gD$uSJW6Q zq0ASssg_U)6}y>dZd*89LJ>ElFJR+oSyGCt1ixxl>GTE4==r1ueeh>G9L_8G-zdYT z%2!;jV`a9GTqoFT;JXFZ*|A!1J%?~F#B0Df1dZVXVVv#fG*X2^%BH^35I}tvm^sZp z#l8S~HY9s-J(8j)=RIa4#bU~_a~fUs!N!3wV-|C>qdsU^&9+W9$?w~n8b5m9hTDXd z>;#{a1!rUdR10&U5o`pX%iY2b_O#Ioy6o58D4EwMMTk)Ri~QsZ+|T)nZ|)Y{u0*8S z>vlM7E{_d4%bD8~cZ)v$g|``BoZDb)ZfI<5Xtp&pG#uA|5T3@ZP;*1G)m(3D4)80Y z7WzO}L=mP=siChx@4;h_2tDiu;UprayUSz(EtqmR;+cLl6AydcDa+7$;aFB2)@I#p z^*P&cgWvUJ{Szd|sGU1%Z0EbFaj~Ku%MzkUAOe8oT#CQ*D<7MO&Hsksbk0I#icw@Hp zLza}=8;)m$F+$w4#_l$!&+7KX5-t}DW(jc$-fo$*9UL!YVyT@2<6`gZc3PjPe{we7 z809-D$wwWsAc#PK9HDB!2|e8GVA-Wz{l1}YZ_zi2D94tB(#pE}O1?;g3V&?2H&gD7W}C%gc~<}7X2R{q z=#kxwXEvpn_Wrjxd7|Hk(C?!>CaBVH#m*aqR5>iUG_e131ctQ}$Jr5`{8{~n)syJ= zTI$VgQzDn^-`{Ye{sqViW_TO@j$*v<{)rS5>$+dL_>f{d$uJtDH`h=Pt@~z z6KtmIRP8{QNw%%(jPEb=6?WpD=?_2zqKK)vm{dfN*R73~-6{R)yCMG~6mme$rit*so!H`y1UqrQt= zdIqLSRZi_n0C*lEILF#U;Iznqt0NdRI*?z-Zg-083xQw?kvU^xG~3r>#n++}ii*bl z2wTs-%FhbY?JC+Wfzv1fe(p=C5}N|lba&U?I`ci|AU4j z>@nDTpz*m6^RTbE-R@{K9*>4S_zBayh=*;6WHOP6)9G|Xqv>=s>Y#Vv{$a{#Y`B8! zh<;>ED3{AsZKq1Dafid>ao~r)J~ihD6N&^|s9;NwUaP)`+!_N+!@_MUOlu718$OQw zWL3^IC)B8;FrZRA*j1HSo3RRALl;G8qoGu*@*6iUA~9b`%HxEA^7^FL+uDk)4j+P^ z`<~Q4VJ7`2c|u>1ne?Ad*yM=@p>bIvj(}N9>2+zoMUqe=xd@gI5;0YTXWFN+9D2vWqlM2mz1H;zBc`}yseU$7^HCggZAB>iTi+s|D0 zt$XE_>G%I?1#4t3EFPKRcW3@LU@`JC=|Q(K%|^a5rIeB1mES=bli|}S_#}syBDmTW zT}9bR5x+e#h~FPfvlY|G8N<`YP@GcwE&M5h#R3k&a1GWzFCafjH$THykM$|{zQQ(4 z7!i2ZJTS<$ib@y|A0)Yp#|z5W2jm$0dY#4CWUFiUdYR7@t#5L~TB7bmVs&d*ChVKK ztg|cS^;m88&wK6I;PQGQ?HykBhsBVVS$BQ%ilaxb_~b6@-tky07GLHWiQ*FbhzHF8M=3+A&?_83 zAFu_;A~NGw5QC4o?r=Y!U(-{wljzBOM3rYbW6DyuO63X*< z0MGYeMwHp{IPDBc{?t#HD`6L6*CkVf*=VGHM zUmCUZDSFlJ0nxgKD@u*T3WPabHU^ zGO(e&!}rsFXlfGv{JVnJ+sQ7Mr(Kc$zVWDNT4@amE2A4av#ULl%aKkG_Y6o8YrrZ+ zmt{LzO_B>*0Jnh!AI8*zo7S`4Y0W`;rL`UH*7< zBYxUH{#(&0{Iu`A{k-!`zZu&tFvI7fmu%f?`rv~Pf~Jk4Nyd+~G5o;?x4a;KpQ?mU z047CFBu%H+qTWL zWXaAQ;ma;#*}AT-i#Iac(~mdGr#7-zk7e4i~yG zcn~nCXEZgxn8Mxud>Z&yaF4_(=|oP=!`81-2{pULrBRYAG_DwI-ucm)pm7$j2TU%k`e$pK0^?;1Ju@D^W>#tncVlHaqcD zm@=Ei;~Dw1LbEF&dorL|So!Y0LEuRhJy?Php_#1CGJ`i~|J`kTl)ox6c8n`mA! zv9~wV9`uc_zhG#Im93V)!vq$U-)J7_-;ztFT7BvEj?tZC%j^^50-H!M(RBUlH9mjL zvwGRG0}@MQrG3kmc|9Gj9-p{o&0*1mqXfiHtARh_MY;h98iByzzx-CA7Xk&pY&3eG z^82D zLsSUTKhU2B0yM&8Z@T)2HV{c(cG=;F<2$zt!uFjpXSRFYMpjpE6y(#a>3hE$lh15q zw~a-kAkW)G(`wOl%{5ZIRXWn`^X}LUGiCLW-McZy-H@Fo$j(kKH_;_`tHFZe%UexY zXvime@j_VO2nAcYI@cSLmX2N#&Sg48Api1_;egoPy{tdh?z!_@6T8AzV@KQii7URa z{rtvDcMD!I;#qyprY~GNwknl!IQ`E{?@rWRN-z)&_&@_IfM<2aRTG^%wK+_f?L2Hc z#sNyyu)HyX>4ux8r_I7nqFdy9QR!C6k_)+xVZoP7FNdCLYpL(C!%IC`6Cv=7J1TUs&-zfa8k zQfNj$B!^WVGCRqI%FpNcxh|?tJ2hU*=YISb*1yI7l|xHA+s!Rzp}Twi)=dYkNAMqG zc;dp~u5I|0U-Nl7vrE(V#%OqLe_j+B>+K5;ZNu+#94Ewk?dUg1@^}zh2PkoBHV(;s z{DZp9VIxJME_lvzqt{-0?Ol6EMn;COILv(V+u>`OX*aOaf9bw`kBg?wqKQqkwQakg zvum^T_~W;vQXALBqVlVxbEs3g4@RwP&mADsBmg)KHnG2 zcXn*Lj@f!g_Y9BjWR2@XA*|N~ccR1AFzeQ^440#Z_rEh3B9v8wB-5s=4@{3QjczrDi)^mARcQUo& z{M~yt1usfwni~C{zOL@eCsti>?m45OmbT0W`DcVPlOYJ%u@SN(4jsp14kmHHkpM#w zFT}n!LQf!lC*duf?#1>PWvZZ~E*fh20CJU%1fF6oZfa`sSxw>W4LP&P)YvHU_d_O@ z4TZY85D-!7#`@t~UQaCM^?0ycbbIc7uqS&r+nn$?gz3fyhmU493)hF_AxRLb;R2_gpy8s)Xj4_s^5l zZjkaY&_F3yTb`7MO4qP$1V=O$8yViPeq} zl`H!DLZM&u$78WX-|FaP*6v^+5a<~mW+%5?xM$t^w)Xb6_3QRrx=tC%BhwpKr;y)i z^|IZ&$5tg%spP8lTOVnW@lFoNV+8^zwR z-5zY|NCYI8$Ln!R5r?yZoog~}3vV-FVh5t8XkV87eWN{Sb2*|(ucxue?u**(J_o$~ z!Df#q67!4ARd7h;(x`!?(tZsK~O}4{kN@qsLhDUmHv1o%yT%VCQB1_A(k-D3ZdoDpbF|~a?oJj^hI39= zOTgofMcwYkCO>K|nd+@_JAAs>L72OZI zU7?}ICb!FLYxa6mOT6_SZ=~Jtce{hGfE`Y_=(u==f_Zjn5!oi}ohk6gurs$6H*2-M zkfM?!yS$$WRx?0zbzH30JVI?;@A@1*Pm2`k3xFVogMaj+iFSD8-R{;Fx9OQ$hAcm! zafB0O8EQrrw_IzYv&SFw47P^Epcr?%n?F$l9@q&D0m}Ss=DgaYtQd*X6kbKHPm&-A znFI@4n@B|;=>@YkK9z?j{B40|x6|W#_;y>kwGXbzNPkrt#NW|;YU}E zxOhy(1?2@>Vpv*~?uzxP3A+kH<#L>1&91qcAV0Bxk*<5Kj2^2DNxl;#dgkO&-@&6& zE+;)G9D(oLs;A3YkPFv=OyxiSpSGv=?iIIgeS|#hieB1QMK5WdIdV?&2`y4EtosuC z5%ZM{pcI|~cyj00y&VgM9h8_wbj9kpeu^1${r!PpINJ5WNkuQYB5o+tkd%t~JuY`z zzER~mob!xuMO1IEBmJ~4196lIg*3f%Rbx}2GZ^rAqEJojr6T)bJVcYqcxZp_BEu!> zOw2c)*epA)IzQ%;XE)dnCl;DY)69qbxh0Fy zm|&Niy+zv=U+a}0ILqhPGz?sDW}PNar^&7>&Hlpyn>`Y3L&QtF-M>5@@9j;*R({yS zr`_~Ki`3ccaVO&*k1ZH%jfPtMzECRSbjUwf3E%;{)9H)*a7ZcMf>bf4U|&)U1cULu zsOjU1f^3DtYz}4oINf0Pe>LEAIh}!!&2DLG_P9mFlPPW?VcbL$H100LM#E0*Azf|w z%vs2)`O%cF9XYkhG1CLpP$({lx>|&qFgJSFoCRJZSmAENUi~L%e{Laqs!`~Z%lD%Z z+cQ;!W*H}%QVSw;9y)Z)60drmJg-(H)*Jp*rOgPabIfoK*Q5*LaMlXtqci3&b&DF~ zzkL+6T%)Xj#>Ru5?%J?WXLtxWAp4x|Uu^*;?En>V8%`&eR0mk?wuMeOQKgb>Vz;%9 zj<({5znZeO7!@DdLQ-cJ+z(P$rxY?itcs~(^&*tih_aE<>J*Nddg78`k_VP`1514;54&(PO zOBfJryga;;EYHkoCkwyjQA>&Jy;B}$l}~$4!IR3cUVj;Wzsq>}G_vl+aH`bH66^ko z;y3=;Iu>ld@UN!-YFd7i`~1q=hkb(Mde~IJuFiV|fJc5$EW+iHPvdOBX+xK=hy8$` zm;mtULBAKqP7Ibn|1r-!f_RVTTi^2BQ+Nx1*$+I&k9$s-PAGrKU&ahOXTHxq5PGOS zjRY1^0&S4LHP1dME61Q-7D`j;Ad*e}`BG`=JbK^X_@yh(;LgTH5%j7D}C zdy{*5*dF{jY&1${`S+;LU50LUlKmOg8BDXf{-hsUU!QW9igml$$Ed!13)_V9ml}r9 z$8)@oWPcqSWY|1oC+VqtuTpdfjkAxr<$LkfE!4+e!ytQ){gu*(J8xuumh^s{U{H$< zs?fO!RKf+FfxJVII4*F~Vm3Aq!MlGJM&7~c$k|9K93iJ&iLQXxL6xk`eBa7%26=cs z&%P*6pLZVnhJ4lMJs%K#-6(A}ZIy2Hpsq$^sOwJ@i7es7?%>NPp->txFS0LwUZJ+n z%U7{)oOd3d6A3gu0h&hrB({oFn&Z?bM9-6_%PW1ryOrNV41WMYuEh&R`?^PD3NzpTZ)3fNy9H=9m%wZZyaSy>*V}mPew!jV$*5 zwEXU#^ZwWTsdKDZt;X2j&&RE`^2_$yP1FRobF`OKlc9nI5okxvXuw{`d#@r!87+c z3utfGI3em*;m_M5;jTZ~H#4uc^!Fi4MR6PrA<{v5Le!u$W= z(m%cQGuhaUtJ<2*`I@k1!;FBjpk_KvtCb* zPB<;o^&!DW+mU>G0amo;&BWO#e2eb~wf&>wqra895R0HSzO1jitJ&I_9U4!>BhjUU zt;v*8!0B~o@H2?m+|bz49u!5F$7PqCVq>F`1)Xl6&4zp^F}K-VXRL$&KjQFNUEi|y z_D<}M$76A1$BcD%twnN7Q5-c3%`Gl>XmY004!5bE znd@E7L{zdkk#K1^R`)N-Ed+2#DwzJd>q2w`1q;u?oMu>8+SNisJdt1FJ6k2|0x~)a{ za$UW5RIa-Wduzw0C)kG@A`zGK*fdlg`FTHK*nu-p-Rgc9BoUEE62DCS`w37HLO958 zDSw4-kgxpz;f3nc-EeG+A?9j`=YH$M5HYiu@`3)mryhiYsRdux*JJn5J22oMpr`(U zfpp&MsW+$SsX2usfQj7n#E8E$+>&xTEz_2+fdH09F_+g-H_ax7#cV8?bh#{Sx^0D+ z4F^*mufxQUc6P<3J|B*-_l}J8_^a~@t(x!YKEEcco=JpA|D{1 z!)=(uF*>8i$kl}37|vY+q4n2WMNu@&Wdc(L-melE!-}>N|y2JF$hG$c{XHx)9d1f@k(BB~MkMBP#^T9Xq z`@eoO+sOZ;fBHc`RNjX21jF3r0TcaDc@~=S1Fq#-d4kQY{?fScV_Yu3%NpearB|wF zL%pdv>M8A4t7kH}F`iY$@sNDs6sC-bw(fu90IS#js{`XhgKTnRY^#c6TcEBwNIp!1 z3HW~>S!_Ei+}PH`6c!D?|$+Vpb7PDs6RDteXYZ4_4)jBx*?u{ z1>j7u5Sx-;3ZvTsbTZKgr=5!DQ=LJAT@c|KKM|NK$;r)Bl0FgLpP`E#{C zMhm_Hjcen}1DdS-IE`QUA&sASr`q$WQQ=h@zpg%)8N%iA*J<>^4>|s9y8623RGaWe ze9QTd`p=Vr$3kKGah5f{Rp<{ZygS$P)TmmYc$DgM`w+ADbr#~)fk6Cl{^xvLStd0K zBvKh=p3x53jo9?21ECm17`S*zK2UeKFimV+L%H3=zJwQ|3$%wd8KZgSZ2~thh3%t+ z^uo084UVEJW%JZO%KmiJ4gh%B9}3?9I(23BaDu`C|4+-{T%dnjVYhG}p8{Q*8mzPW zr?RgK_hGtpW#{T&9lt`iDn7|{$37Qee2Ha&(@7^(a-3r-sfh6ty#;GErN0u)m0|2_a) zNR_pzhqbJKUKZ$Fp#NFp=W<5DJI2rHQ+`Rfh6og+SLl=Q$>mkCZ=Aa#N(P)$3NZ`! zfvmK?i)Fd&=|{VqMroo@o@V|VVG_#4cIW8-K1H5uCf*J*Cdt5vk}xD-Q{Oj@NdwahiA7Woe5z1#`=~mcYX4<$65V0!WYKaxr|>A( zCNB~70Rf~^IiG6uR7-i{l^V;FN?lC*y!-;~Pm$D8xV&cgIbGH`UTR;VW5r~c4S%zO zPk!WyQxw<|HS9%@>?Fy{aYS`EMU&p43C8^MVc2Pkp&EA+qV9tfQ27<}p!P!+2qz!N zD-ANpOl9aru{Eon))#8_=OHyAXOtn?wZ2G*l=_-YBYGUyGACrAXgIE=d~qb8TTX+s z@Kp^SZBA;TDSi7Tcm%^b@NTneQy1;&pWMZsxeH+6ht}omAYYnQ+j&+QZn&Zh)&f^T zCaHJM8K#I<0=x;~9pbFQL-b0#Ex`W2l1){VrKT#%&it!k2J-)WZhP}p6RnPE2iKJJgUGr0~_>w(1>!+4ShqIJ2B5Y)U}efUQ%r3^SNJ{%XHE`T%V9h&9XCvtAu~% zii#P~Cf8>qgU}kt&Ku)n!ov3yDGG>+ACd_U94TebhmBX9DL0ofh8dK8JUj||I28uK zWHW~g*u3Mk2HC@5QS1fI2V4)rqysz$gfH?bP|Kc=c`CsI79jlMZ2w*r7PJfFLg4nw z`DmBJqFCeroZIbtx#3Ec&M){m@WK~K-m(zk8Tdo{Nk!EFF$$j$R)l{pT%~}5HSmlo z?>Q`Fh!JxcX7HSw+Ayj$QSgEuXuV6Y z5WjM6hhGEQjHW0|J#nxJFQi zAo-NCKqrFa8r-O>)4=y+P>XRK(Z&?nq+v|4(%C7MoY5I!Ym`$`k(SUe^-84Au?DFc z0)+r)^h^D!1f|jl%JY@Jin#>wETB0>xZ9!Gz;%aJe}Y0+s8ffTA)`mZAVJHiOU2?2 zjl@($@`3ha5W8-|NVw7`YKd2LhK*_>2 z%1VW%naUegJCJ0C0w;PQJUU}Zj!)iGmUw`yP0p0zQGySBg8n$2sP+tc)GRNOJDT;S z$R5xL@dUb5=umH4^b?_TN`&Eet3r!lR*e_|qmD0%xFqoMPU=a;C7*X{f#Xu0@)Axk z7w!^-B+0ayS0GhpUL%0>Re&Mk2(cwyhc*}oS7|`SFG){MuN=#`VS7b23NN0lTRev2E;Cc9gh3y-b zPo&b}j*c~x+w8C6Eg*SJwZ$KKb5x_+$cx*WWNf<=V4rR&S^J>D0vX4eWOL zz_!UX9UZ7o3k6Oad<@hrug|wFlrd~yJ=xw7<`roS9qp5=w^Iws*oeLi1~A5UY5?fesK!HfJ) zPS(O-j5o#$KPdblE)4J&VE`XBI*05l`Y^dv24T}P8Yh?AdOs!9-4E4goQI3PPe}7( zJ#~Ic^|)(@>XB`a{)&E4-XAOl-XHur+-IWIBj0DKzghlN^jEBh{>UFys#n$@;GE)X zLoP$HGUYx2sH(@o5%!Yrg!fHrP0s0%(^!`FwDA=)?~Y@dnHc(`?hXFvJoH-Vz! zv*-CUxbLZ6ulxYpS$t-=o}MMuXV({SHLLgx1{=qbQV&L)u0QsVWzYWc%YjFI6Wuf5x6EJ!8+vV`a}i zt$X%q`SWGZpb<4#Xh!tU3_nqxkw*Lp-YY6lfzxnT>DgU)U-oQW>DfBG*Pfw$%u%U7 zVSBN^_L;Zj?Sg202(nX&a7VFDt~Y$q@HNA?4S#9)d&55)K4i@-!g6c{JC|L~KEr;C zeT99K{W<$P_D}5Jg$AKT=oFR-=LnYyR|~%>d|CJ-;d{d03O^Bk32A`?m%6h(h;xyz z;hVa$eOYX1zkVEFi zMl*i+>vMB{kpE)NCdwNBb$iv3`KyjU=BTZ2wWgN1F8+Z$ zj9eQ3^VhmqTT#}ua{sl?`p7!#EZ3^P@|fQf_lu%G9u4~aV*I%|Kgd=;7X)QGqD+A+ zAK-odFKtf;lEbI_pjjJ>g|A8zs!j2c$E$%Y)l80YNFCG+zWl!Lf=*8W)FykLe+G=l;9A%pPP9I4!t} z`xd8B$3?r9=xe*-8U&&WAGr6DQcsGpH>h*#_p;@FMb4#FaJ_nqcwG zn>n1LhJCm{vWMmf=RNw}c>(uEej)4^1coahxOZ86ZqZ7(jr?-SFN7bSIKj)wVfLs@ z&7rTm0q@i7-coz)Y5A2e3Adv?E46nkV1Aw*!}C&mX)}9X{_fX=&t7-kEhrb3-+ldc zYHQfAA2G2va{8&mc3Yj$D>o8g?4n<=AO8aVQJyfZ4BBJ7lsW0t3g+ZZN?CS-u4|#q z#|#JAGYWi4Y3xLuCg528ovfW=!V+FT#!j$5RA5xwOncG98w6eUbyoj1w(@K2y7$nY zg#P!J!MMNhs2~WNi!d_aa653knU4dv@NnK#`h|cI2pi=_T=?OAmdemLcqwzttrl#R z_nuG-`#7Qr&j*0#Bc(Rk(_a$4gv)@}(RELy&07tZ0oOOH<51d6_}TMc699kez3bSG z@*6BH_+H02QW!^A9fvcK>`&qohi)U0gM#Z)8G@0QGS}6q1wHa+_9%d)(yb>>5U%e6 zt{+r!%}cWfDdMS>-7CL*C+=bru5Sabj~8*Rv}yJu6%K*sdHr?vheR1)OI>#zies<# zZNNEN2Ir^%gl|@RrZTvXx&tx!b`!lf7{Qw+`HXxY$=vE9jI_H94~ z9^9imFdN-&oG)Tua?UzJJ?^nD8D6<^$r3~hj1Ld4UPeb-l3u190h&X-m%@d z7?((8GN}Yzk!ww*=^cA>c;n{bC6RDg8XeiRVPrUhDobve%=IDMN1!h^S$cPFvL}~J zx)IMXnauShQ}m9`jSGf@GwNFfem|`I|c_j zx_p6vzcbrE*wGOX#UL*AS(RKEh`%}4UTl>N6lUcfSZU`oSDD+WuA)pW-&DrOudG!| znM!ynVP;d72Ah>~K3T0zmX9N0Ha=J^%}q^~m-q)Hd$Qt#{JOpzy9z0wlv?!cPy@9= zC7YWp>><>%nJHCNV^f3tDWx8oMEkSq{RkzSQ4Oh`%*F1^lws-$)WQ=5D)s|A0f-Dy z76hMZvC#*cgZgt=n;MX_(oXN~n;m%2V0Tb{D!V{c5KjzMEcX3seO^p?Po>O`{Zt=4 zHBrOLd!>`jHfq>rvzW~moBn;MRNVqrAo-x_-}!U1{1eiA^yEgzx^k5EBiA!J(%U1k z@AQt0_9)+Ujg97dC>cl(`0akfwd@IY2k6m@z5I$m8+2~tc(QeHh=Obk;chd+P`t(J z@uv9YZTNx`4;gjvkHw=s8F+>aP$pu>w%#8$958ecGwoG4Nrrjrs zzV^0!zO4<546*Gn*(VtDy@qSpciCN>2dnT~Wu_MP-AvoEF^Z@>by9fl%m0u2#@{@6q(O_Xuk<46$>>Qt$C@8;M z4?q=dL=kR^p?m329Ni5^J>=Dkn>lqTHT_yN*?~bskG)Rxx976a;o;RcQb_(AkE~wR z6kNXi@TZqgteCiZbzgtWmMn5@jJN_rJ(pkJGZ?flTXpWatH^%~B?mY;RA7 zGC{k;Qty2~oXv(wSeR{lB2Jqqwj$8M_uZfBO0>E?!&3O-i=R5br#sWOK}6)-Y{#ZY z&plUaS&~W(cO$b=2eh0oi>Sh(aJR(>hDOrH=H_U$nf^A^*VhGuL9?aFYBq0dvWB8A zSLZqdY-A5`y0VDV^ANx6r3%MT%xe7%c&q4uvN%RQ{ zqi@9zz22+;@O}Cg-&n2d&28-y6K(BgF?9J}No4;xe$J-lE8@vyd9p12Ec$CJZ zzLS4nkD)Sj?XNS}*VWaT%odZ$bg%xy&n<0j6XV%7Lf_>fk-acFg+qjhC=?&ny+cwv zr&CnYS?S+f(5R)M4vjW8nDp=WEvJajChol>xG^RJMcxv zPS4I{9ll6jEz|`1+TbN8tp(!EYc1N zLfED3_;{A$9teiXnO6k|^1ilGR*xiLq+}6goTART*^Be77){QtH{bM3lw;BzcZ~G( zgrsn|zkh65e;=$tpC^9L z=a((b-w;x@zW?<>yig;p2vELUF#dnT=p*ytvI;2K8I{jRu@E!LGKw5W< zrc#;Yaw%o%u-V(&mM(2?vpL#LDQQ_IolGs=(W-}PTQ<9T0)ci+nRF^Pn1qxX2zF-D zCQmXsv8E%tO~K8~Y2fBX#0xRFXi4CSe%IFJ8_&D=bNdcnG`W21u6!QN0|!>LzaF@UH^q%3*A&Cc%EBf% z65cSey(V^0{(_0TDO7gX%+C>*;&JdWr!I9u(u0F!R#BXlk|a~L2?DaS_4PJuYDDz9 z#73btvE1itjkL7|f-dA0?QpwX=AiJn!)*1{=V$UdBM&^EMT&I>rej2 zwZZGY{p}~~zka=Y<*M=ZBy%*|dbXWr$jC>L)pb~Ygl%~s%Wg;w$qxe;Gmp&3!o#rZ zz&FO?$_x~uAYWmAniQ2zFiE)qX=w0*NnqII>Bz3RU~s4TZv>=6Vz1t_-dN5Uj7v>;)}63%Y3nC={O_7c&sm z3(?h{drp3RJe3lj^__XaXS@)vD8Ccsk80)X=sF~>@6Zi@mU@oG<=2@dmAb*$<2&=9 z&)9<+)WFD^HOs2J3k=rJ|{)Wocl;$RJkCCiWGt z)#_|)&AOE&cs6TX3VT`;s}en}F(kq3OJ&^bZ6$@BlK-r5XS_Y$(&Dkk+d^UFz?0uW z4m`g#A!VYOLCWM7<2mp^pYJjs#|!y4>7t4n`{faNFMEW?e!=r9v0voN*o|uJ7vPmq zBwc;%mm7QxEucw`$KA?v1`MjPUl3J)i;vw6oE??l61JFHp#$K{U<3XAi9vWaId5`O zDBUgLh|A$%znOjAd-#@{5BvNP*UC|E#On?Fe9_iiTYK0m{{!yFT%BG1%Wr=3-`00@ zx47DpE-~9NF2BvhsgACeU_^`sJA`i*CTaPF{&ypu*Sp5M0YBXY;dg!fO8x>j(lF7f z|NZjm@Db!Bh`xfC7<)-VRK3wxo_veFE^mMFWtpyWU>!F)^CU9kZHN6zS)C|nk|OxL zc?9u`A)sHrKNr&$0A74BmTWBK%|hg3+!b8&(Mr!N*8Cl3=n4GzP4JCD0bN~JFuy3?g#82y}?-7sZ5yHO#_}y-D zV59~oLteAOLncRIUtu4AHY37E{$rXugi}gWGUoBZ)5yqNc)|GN!o7uieMsT4k;bRA zvrlWsDePm{33q<*iuz37jv3*ZGD447cv=`2*>#0)`Gh^f9?%M{bq(w_$U;9Pawp!W zV0V)>0=feys0WkH#U#6$3QOe7G!NsgzKsVDh9@SF4I>e0JmhMGQ^aY<5hX-@#x<_T zYzGfY@)&ytDV0_w;*Q{j)+Vhd89TB;&WgZexCLvc=;HcL&gi~2t1u?Zb0Ue!c;~IxqIV{?pUO| zE!pbx#S)o`m}IwmebI!&ZMO|ay1GX?QfZ$UjrI-aJU&YF_Q0M!j)Mn@u5f8c5aC~^ zJASaaD#vdmT?e^NuPjqkDah~WLA0KG@w-i*LR0>}gxH9;@E3|7ckRqxA#U6V=}1}g z5T#D@cB+{>ICmJa^r5Kj$X4fMKR{woZ$q=);tyr8;D6P3#K!Kx%Ue;;sz}tt%;82U zFeuz#*lye{9&jAS??hoA-y%SN(=%qoX?+Gg?LeIBFA`0X9MDmuLT^QSG?G=NCQ`&G z71fcz^p-p*N&~71tSl^u8gfNvTcx+n9WUzT(()I5CUSQ*tP6U)UXRV@ZfUl$V8c|9 zh}~v$1(94OxWZy?vGfk~?PtO1rpEU6odY48*KF~5B@er8DBI@su3Yu`tNRBmmbl+) zztmy1dJ~@IUdd%?ur~X}Y^T)X+!+_zy!G`Ki{xFuw^>_LB1lho27 zTx)BV@U|t?F37EJ#5%NO&)JhqPG2yRirDRfP~VJf?+yqkzrVFLv#GnMwRKy#Bjjsx zA+np>oryXfSo_x5ov}8zyB_C+n1{bOYw1^- z5uDFAfwXwmo zqGMAgW3_f<&s#Az);k;z#JXDdv^;;nbij3ubdB&PWPMTY7ydTTXt&uzq3&cP>~g{x z?X@|2kyzH&91$Cv>g^Wf=Lm({hT>R`wN6Mri=|f-g|-`RaNKbRiAJNL5$laTSkHL5 z|HLo|FA-VHpbnzLekd+zhgj8b(-r=w``5b6UH}oWHs^xD!*{WP(ign}d~s0EpEn*d znazQQ24ib#y^xg(-HeqLpm0c1G$!=`nzAB$pdiC4q&*Qbw7 z-;Q5-P53f}S%aKe#|z&TZov2S?`LwHVapaa!7kXy?}2fn21A}KVP=tn^QwbMNRT97i7l4q2ozOG^U<&EhCdLoL-4;e7_V|z-pJLw8{yj^V?qr_z;SkI@qXGkPOEAz{ZV^zpAsLJL z-{ys6Gw{10H0*Jd;6T%`x`j0ha$eRUAZ!m zummkIEc+rMOVC;qUQY}o5Px>ql*blhDrYJjSs0rodU>ZrFO}9~Wdg=Ujp&wOFq6SX zPk=mV_j^22$m4dq!y&iljhY5_X9|^rsWf?y(~<`#9ztO>;0YBTUvP*jKN!2PCQlib zQwDF%$6VAOP~^)18QlV#X44 zIJXJU*F+PqGnXq7^LUy?EIB>h#$aLJf>faK^l3p>Wh71%EC$<@yehpYEzB+y_qcXR zSK9WP_SzQ2GyAG%zax5~YvV=)T-Wr~S;9(dPXD4{6%V=&KVLhj`#o0Xe zR+VH4G9J)o=^`n@twDY$D@h(PZ0U0b!os#%NaH?r2ndw#aKt62yV2)}icMA~7A6-W z5w<}YktQ1(k-KsnS$EDSYlwR+bqj4pbEQ+M1pQOIKbnpsXPvrdGN<*ejq$k9Yzs9> zZ2^m~4vR~pC^R>Rz0O#J(P^p|d{~89j3Ns!X6(Y#A+M*-Wfkh08UsSu?X7RBXV}ke zY7&LQt+ir7p+U$d)m~kSQyh!pmrDD}sLg9lvb0P}T~Mn*TQ04PpHs}+B8~b~dj$SG zZMM$VB5~8ow@rc`H5$lm}gXBtZ&!Wvsy?j}MoUI?L zRld}HL@d-MYb>{0H!CrNwl%99$-z7Ek`=a*F$LqJjteqkOeAhh4&tc)$qP*numWnteOF z-HuQ+=yJFnEp5R-&^ET9uO~RP8mWX(rK!R1>qsTM*~wYXAM!u>xc%B{z*cev+@$}? zTweY2d7>iGzW&k8o8gBV7`*6Xn;erDp3~LI9a25#l0&LL+ZEUf(itDWX5HXWIJ|PG zaB6EFd9CxE9hB%`HTo=;?E(&+8VjDrg5<6DkZd_3ZYtt zRKbt8)J!!+x)F}Uo*gi(!MG}&;Fey8kZ6=#!>;1Z`8v5nHtrj#QUiuWY= zf;{%plfrT35QVa__PjhMN$i*e`-O6%p4(vz5l)V{@Ef3MVh%zoipromU>{%oIJTxX=|5s zU$K&gH#(mQywhGI9iS-ThmBt2;xfjnR35#M_3l603(rkzcA+x}yAVm&UwP@tSA@It8s`asuyIId3&!K^L6yvlxF8+F zv;V1wYfIk15;X_)&b}#Sm=)f<8AEZznCqDdl{XXoTvWweJGD91p5)x6{>u2Z=<@^u zs`2ibyNYg33|_dm2t|?CxUGYeB(z3_9j=@XouxRU3;oww#fU6YC#0987bWRM zex{EZ_RK`tqmW6EP_&*@4_E3|6m;G!hC^M4Q+|ILWo#4PWgbf~mWb*IVeK;Zj3leM z<_;2VqV1R>?-roLJ#&yAmyB~z8|4c%2t&xZNI`!AsCr-@N95T0_u2X{e|h?eC)ih~ z-;&;WM>L^GQ3eyoRpM}U5pAqdk0`;g85eY)!dP1P32B^|(e_0kTM$p1aOA<|26HZ{ z;gH;bId&}B-n| zCAwd$RitNQ7OQL<>X{Qpy#RZ;-Y0|)Dm44nv&^zXs|h-t$$%eURCwL5IQ*5<(GbCD z9q)ax!WSqSh*$Eqo~pApi+an?c)Qjx#!PJ!ud8iW#SB1P5@>v zbe5q?%jNQWO5geoCr|EGpfBs-XVQULuXRpn6L;njNjJwt zoBXg$N%OcUm{*1e+l-*#p}S$ipaJ_$w~rkDzwEsWe3VDoK0GtK+1(_Y&1QGA=ks}!&0&*GHVH`xAwZykrVwZfDFm9f z*kVOWDOOssV#P{%vBefEt&}3giWLznR;*aDe{4~!FDh14tf*LdebLfN`_j#K-Ouim zY)%xle*gFTeI~za=XvIE&pk8G+;hH%AIlgE98jQ#vst92$ANvXnXfk||AxPJ?0?6> z#O#!MJt`Yj+LB>;i)xsSGQp>p78GT6mgQgJbv|>#px2dT_nmpXltjMSsw$w3V0YLC zo$2oybeRoej`Ww1iRhil^#^Iq)brsyl*)tl-)7U7z-hQzTFj6(M&j_~RFAjR;+^Dp z=ET;m6qk2dU)XF8+Y7Y3r_>a17)qkhijaQPnLdih8fH}%xXh*KTAV=iV9Oc<}Xc->MldnWH#${T5SAR zuv@U7%Y?C_2kUxMXud>h+OUx;`vFLt(5=SOM#2OS4&_HFZG9bm)(}%YU#@DHRGtX< z6V=yNh9VZrJxgn9?T*UyFL*Q@Y3wR5-#7KWUT-`et__7PrInR^I=k6Ylj!F0>sy+Q zreL`4ebbbhy85f@;}tf3P1Vl!U@)0n{t525`O2!Q5(6?kTzO@pnq;P6OToVY|K)Tp zEmO;}_*9GQh_mPPQgVuhR%oPtW5Spz)r>F7<7jtH4HpWhDqcRdr>0soS1)a8bvbcd zKUQpxmBUwn9WIMgwxzCQm691lTWHw|_{VZ=?y>q?sM$^8y8XXoT>R?+dggmgSt{v=8g z7M#(6?3I>ViC@>`WG(b0xRUj)?$-LG+vWBql5OruWmQlaV67-`+ECWqSP_TT09(9( zdz_68v*)B5oNj*i-Q4YLXqY{_q0#B#PdvD*6yKp*jmGsm^ zHM%~O&(+{Pitjf@XVY>xr(Q(nBe2MyjP#|b)FulZs>|eZ33Z$@>w*dHo{ZDSKBpsP zO<8R&S9Ntg&Z$g`guR}|`YGJG-{C`_w5ht%B13Hw|HFrBtPT~_^(X=luo*}wICr7DeO(Ln0JXUBj=@Co^cVm_%hu^ zic|l5j@VE3+@gr^U|HNQ_AAqGq4z-HUgy8(?-4sN$2E3kB9Kb_ts>~N*drQmNr~B9 zCd3Y2YK+A!R=rtgFxj1$Jsn9yy2l#dnN-iEYL7dy-xD)iN=m$%u-)nlRtRyAzQloz zvpRFJ%@T{@U`wWd1%)A}!Rw4VgR=ht=xmkgWHuEdE!mWy{fh9)c3p9) zw!`6!h)yDOkD|q7DlIa{CY6<%x!p>HGW>i>rjuRsnTVfV!7}R@epCmjnUeUqU#2sL z9~?@}K@IHA;ZrRejMrA$=uM2BCq#K!Z&BW9b4rg{jtqouA4&)AK^Nkqrg<8 zEz+n9ecZ~i2f5HtRAf?X@z#WO>{7Ew<1Em13UQOQq!^d9=PQ(crL7oyjSGqtex(yZ z+~yK}zPUhESQvr^1i}6;o-#ErJFSP2N#fExZR;%7+BAU@v(r^R5l=}TDlE)bn+tV% ziyQm7iKqWH`%T3$e97<5-m*hVpKsO`9>3DfosOm^hm*T?*;%MdZub!{)K`?3mp6!7 z9FO+binrHe+UXs3%hES{9ro=04LALE(5auHR+ic%G*_9rRG%UtiC>bbYQHKFRt4~o z6|kN>>C?0<;rEW@A`)uftMB1kcO+l$Pq%o8l0xC~_ zoJowklx&3dL8yfD*UV7}NU_I!sD8(j>OPsWOd+fL*Q=E`FxJo74vP#nq zc#SVlH8!N)^PW^gL*tFxyfumWSJ!Y~`bR#Cr7V_;`-7JBDQ)jHk9>B){P_z$`^YuD zo9ExWrF+i2^!5BhSFFBf_Ot-b)0FYgP#d=r1)>q_Spi<;o;?J4qvc?F`Ewf#xQu=~ zhMVCVOlWyBJWrSADX`)567h~2QW}fSXpTipN?mzH zdny_;o3VG)jN|1-6Q*kfE5{j4iLEP{IMvIcVJ4vFyKcSJo&FrZDg9j@ z&r`LCN$FXxr=BW-(zsbXveucSIRonw-B)LxrdSZj5~ z?N&ZF{en5>@|7=ZZT5KT60LJ;l5vx_q5O(?GH&gHBf7U1X`k2>EdDafy>y?$zC zwH+PXu-S{TgSi~vDAq2ZbMVucNsCH*kFz6UdCyj6w1s@PluBiwQrD7}nG}9T4`I;Y zV-z!eaWtyQ4~6G`Fj!$QmKqxy-mhr9=2PWmev7&A!G{zgPsPzMRPhP#>~Q3Vq($v71Vr^PC;!WyVA_9!;cFrR24Cs6nBAk4l}O z^MW{pKn;$CIi?&2V^k{k-E7IGPEnf3)8wqPR~wdUcrUMRm22(6p+DD!RBxFW5SHW;jHR!Uk#-${e=3^gNp{%U4EfzzIP*K(16fJ|=-QssZGF-1!vf_(jA#9 zl^S=8qA^eh5~-X9;#yUq^3Oc36t<$g8QEq+#I<@=`e)qJA1GO#p6xnvq=bJRl%My=@EWw|stfP8AYn3Dr z1}0C=2n2_#;(8JQs@lZT>QKm1T3+5$?3g?6lSi((2J+yls}A%f>y|cFRonOy2!Qu@ zOeM8)Rr4ybm`U{!Gel?uwocdvEKFbsnn!IwbK_z+E1)mK!PSNfFBB9q%wSs9C(p`kq%OWr7s>MTkmcDj{D)=vK-*-#JT%EPAbZp+I}s{ zZtmRueRXyHlPaMu$%tw+unoeWVOkYWA4CP7QoZgWo(}TV=9OC;<{<-NX8*ndy5$?2*%?=f4v z-sTyxGJR23WmP27+1Y>nWT^6}F*jg-b>@5DH*+>dIjIHd6K&Pz((;E5et#3%Qq-Lf z@P+)>;%H`73)4}U0g9w7*y51lzkcvK>+mJPCmS@k~CR&?5UGmUFC7*7x);;mL zT7=!qe$QXvI+S4Mrso&Zm_vz1r8@bKxhdEUgIl25_#Ao~?R4r;k}{I2v=qAD)Y+0U z5$utwG@2Gf<$^M`;kpUQ6M=Fns*6-JQwfD6gQSXx^~Q(F>&&GNx3jCo(AHcvP3J3x zb63FTw)w2kWTP<@=IdSMSLy;#7;B4nXhk7cw?!g;|CF+JgXxO0vaq+iDrr!BO8HPp z(p*)-6|Q`P!xq66!D6HG<7$0@+a!t$)mqITz4@r4@rr=oAgr82qgV_l&wl&%>k6N* zKH+kB-lqDvjYID>))h1B*wS~Bm8rC9AW0XwS1F|_!9N|DK0Sgz>G!u@Pb%f{;w!25 zdijz1`uc%o_^DquQ2#JGRf(E;S65b5)u4xq@_L#-%#SL(W83*3w1@3$(|^9}2Ju$D zE4|fj=hf8C?|}cO!J8j}(bf!o*tvtSVnIVcbZB|IuSoB3#i8EKVA6*IVN8GM4c6I{ zf0h30q$Ogq4;n5i98i7bG*2Uc2jC0@kaTuA910F$uHHuXGO3ifbmIW z$~M8bh9k45`~7xDg&~L&fu+8vyFM7~t#&#ip;j!Vn5`zSFI3@i3l_DMLTy|a4Z5z_ zX22GAhpTd?LuIp-)ww;TCZEA+GFUA!@boCse-J!LE55ba{R9Cluo-#Zwnv>!P0k~G z6z=nP-)&#BM$ffU9#c9FB9AH0vb{Ncz8D^&TO_YE$~|awNgj54tG&ijtI71!L%L96 zQVdF+oBJB9mEBd~)s%3%3JMImgUC~%#j$9$&F9}hUef+8_vasY}Wn>u1Q+XF7A(&w$iJgmb} z!w=UiT$8NN%hTG;MpGo(S{d*+)`vnh`wO+6sbvwjGY*g0@8tl}PWc6)mKllBrh^1O z(B<~3+#0WY`d_(8p<8xU-}Ngn(PGE2O@Z%hGe3Z|(CxUhhTC2_0cv@iPlcWlvDsF4 z_?sMVcjejwsSruL5!CCw-iAp&G=QGC$rK7t?!Z)!B^vg)8$-o9n=M{$v9?y~4Nga0 zt;?w^GM7diVr8k(=z`h^hocg+KF}9tvHPl_3D8v$_PPzqUI-0bT!m{haD3G0TW+z~ ze15alVh-3#^o0ewf^5B}?^!AzT5Fa^P4&a$COLtD1XP5K8Z&W?tF+jL!a)mQG@)=> z1A%a;u_G2sBsvY{E-Dyk<#H<_0QhE=qDU&6>iWt+prI}ts;OCcTQbErxSSR6t1a+C zWJurVTTs`%MzM@mHK>dv8L5^#J6Ky;Q%@&jQ-(&=5lfRn(bG7oy?si1Q;V*(rLK2D zeM<{U7iIkl=q%3A$*x9ZCTAo$n@YUVKt~rEOetiLvB9Lk>_p3zS2ee^>ROuGr(9Lr zIElze>oA*nH-8!O4<~A+A>{CyGG-D;yG=&NjlO`tRA4ZWz8mQmVFj;7>-93j;y%fz zu^EfC2QEqtI?nSmyV9UY)np9l_^g6bla)iQbO~-zD|Y(4?hl(%B#*&;&K%s3Jbrpu98>**|EIwlWJ}&<%VU>oJb9dVNpXz0 zC?EDCE=ZTWcUvA;kOrcxJnk4Nj-4eX4rkREacss21o9Z^KM0=i>4el1$m5rD#c?(r z2a(5=Xa9seuDTNPxOyUSO!64{bqKsk_1%)`q|ltvzd#;uct>%JDo^=E-^&4{o$~8a z^7w^ZaZK_UX*qzjWaaS~HK6aRJf0eOM|m926~`9ikT`Z$P9TnPpV5#wE-b<{eWqU1 z_binU%@+L&Be`*=e}7LS;3 zn5b{ODcIahN~@*c=h668;x-6pWrfp)O*8SXE~q&!>*|WHogBu57!+^g%8e;fJH%c_ zh3VvR+CM0*hfp^!@p6OP>YU4q^3CbvuOn3`uV?V{D0uS0U#7*ATXD4-y}X=hFYIo7 z1Kk9Z!|6%d9Ij}S{R_WrFZ1e4jK-kD$V-aMdRGy2MkRuQifSAVNd;Y2yVY+r(#Ax# zA8Fr)cq&nMr3DaaMmeYFJ#I%h38uLwVY7Bvjn*PlvD@awFt5qxEJZ&v?C}|_db`aN zEG=EN%V=;l6cic_rMjZh(r{y`3Y~JyX&H@n16J9Ki@j~stm57q#yAVc^FnVaIX zR;_m?63$)gdD*iVC-(Fd|0I)_#QP59Wf;7NGklNf0cZ9=W;<&MzQPi-6&XuRh9|e^ z+`%MHKYHq7E{Dx(vS5+|X}uqs{B*hpoy!ckXQ0aAEYW)m&Z6SdQhx|t8Nm)A&fVa1 zxPFh3!NWb`ar(o&$>(zG4Y638)!@t1`2y8mpCVD0wA7r__E}5@V+86T zJl^uYstQ$c9@Z}WS42^~QI~5^L7}a}j$NEybPSPq-$0%{f_$L7%aOme=np^A27=B! zo3;L%uW;-1*5=-;TUy)fCS;%j{ets+C(`19cBppr4dg*@wnLBpa85^Q>iap*KbRrfPTc-H^H8lZWht=V%s4ev?UOw+Fp0hsEp)W7pfxV(iZG zql0VqI}8S$(w+=ip`zQL^eSiI+bD8HEyeh=eAZTCC@HbIbj7$-!(_zwBG>`&PW5BB z98wZ9BZWa`9u4!NNSZuUCzdgc)VfeHr2O|N5;&`9_c#-Nr^8f&)wx@h4qGf>Dm5A+ zZll$zRp%E_{?Cda|I3`%J>-*$YnoeIn(Hb} z6_xcMh;4*Y{ooqpQ8#NN8JoEeXkr;#KSst5KFAj$?B6Y8yC;yb=tb|t+Ro+AKTIrR zdF7>qEN3rs9q+_i-8eEp-HG8XCm z9Qe4Dj1>)|WGr~!fxLu_CEd3eZI7L0_1Q{Cfm>@eXoUgh$I+q-Q76la;gboc~aR{;!s^eBVDRXd!38JJpflviR4@SuQS1 z&`M>IX2i3^j7paJ(h3@GH^2-4EegNF)Akc(~Fa>X$RPJ)u*RP&e z*HnCM?UWmF_nI*vzN#zM`g{#9#1a-uEa`M;uM1hy7i!*9U@=2|EcV7A?q?A9amDIP zx-;h=sRNVUUlLH$p#kVllV@`?!}QX`30zh|+i`sUsK;b>V{3v}2&>cROhP+}y&=&o zzL+R0?@ERvrKJV=MmK-?-j<)43JRjJ);52j_UqF3_)Wz9J!y}AR!-847qZtfoa}n4 z@Hm;op9)o^f>HpMdv79$FhvcPnw=&rR)hFnm5nmZch60g+nQL zNon8a%^vX$n+Uh~eR>;L<#ENbm?Qmbj%Hth`E4QTRxWd1u-jnJ78;=VxezlIl&8-k zProaDPs3{n(#aY%q)=@QYAr^2pEH@_F1yp~^$L&M>A%G*`m62M;u7x})PpYc;_rCj zdFqdY*6oS`$Uh5656`V;@(!mk>HH$Ux)=(?=@LDVh zcoXb?uHY*qdN}DcmR?qiRi-V~y zn;q#dA}@}E|MSQTtgdjJ?0`yO$Vs&&z~?N)g4cJ$cYZP?X3X#9FQ<1Oy5qS_{{0Yj z;yVaKJM&1JvW2RH%*+*yx|qgRYVPS<$Nw_5(5_Zp#|IqgI}ukee;E5qe58hQ{v0&(djW`ZEz>qP`N~OhScXsNVlj2ucfjIVjjm@Sjas62~ zd2_X+Sl8AD7Bl6Icxk{r9N|zbqsrT%P&hmz3%t4BX~>rMgGl>xphfp0NV|>35e12y zTi-GZ4+;L93H3FG8%{9&ZwmNhljG&D`f6)^_>+D`gH-!*LNQ!ZON|lkXuA$euPrTD zij1_hge5s&$d4m#Gox=qmhjj$C?!Rzp(CTlK=`;M->XyH>rjZ+y7qwotz=b3z*kNd zEs}7LG_B?@Myep|Z;TELcapn>!|f?%Z8CyfYDaMyO161T(9XLN?dh&eLmJQtg~KZv z8>nq9%56=>7HUU}i(KitJ6fuxhP8J@bBejHLVTC!kX9^UVLxm(i4q5DtUK|&b80C; z=3pQiv8l0?cX;4A-aIE_wG=t?ahQ#L3FUMM;W58XWuEz$SAMgz#?s;pHH+EdeoAg3 zF@IpQRhFAE{`PsBu;OYdi6<(t6~tUw6~&ZDpm|O!bO4qnFdM;NwHFr`p=M*Gjtf0d zqs>_Jw3u~8&|GdRbwzCUxW!mlV9=w6XZU(i(JY-zhXVX;TbcQYaG?rTdg)kof`97~ zqp`+Wq0!qdrENC*ydI_F5%G<^wcyEGpj7z+lb*DR8*Y$jK8dvM6F-%{Z>edj!&+*F zbUhAlW%O-t!JP!Y_j*mHa*xC1@<6jT|B=I30x4EwA+h#{^pO%k0e zsIB#gT1*@?s|#lOJ`ldcKg)niLHTd2wj+>R%|~itWeC@XaIcD=&@G!B_t_#`gs!wj zXbXB?*zdsCLXDDpG47!f)`sw6Z-Z57ZSXho0}EbEfA_gXi=N|?UR!oKHVadA^kw@FSpyB`9c>wbV{l)+*@pO<8D4Bn1{REt01MP)Yr)1fAi_$(^C^c&I}u37*U7dSN+QtJUX~U|!SW zwwVmohS)py#JqNw&6tlX2MhFeoz7iZt{%T*CfEb~0e)DVLVq9YzS%x$%9j~a7$$1DF!kfz)WXwbNbf~2Un525I6^lb$jtbcHU5Kq2$haV#L%5wacT{ zj&j%a#o^p>_qp5`N(1&9h#I~|bU`X$pBZ|qYqp9mJA@^=UmQrE6J1vH zq~O*J4Fk?$H{ncsvu*=6zvy8B)X#*ye8mmZAu@QH&3G}DQvrM-aL+pvTf<_Rys@yCI4)C zDK=mU()cF=*r%$IS}!yl*dVM(7_ooKSj+G9RVcgf=DY9Soi?0t^Nou>@%!{vz9N0E z!65F=q&1H4aj}Cm*~nUiq;WJFWNG0^+Ita}CId4YJ76u{-6;)+5?^STc||Jac47C< zy+?54r`1+f)7D;9js3l4?MLoy^atF|MzN->tbEp03zp5FU9WUnE9PFcx@T&81mn5( z=0&Tonppprz|JJCo?A(h8$G{};3)2tsV z6le`vr@mOt3sg#>$yb*c-OgB{30I!Gob@)BS*LY56HQ((-NuXSkP}XqR%b5tHMG&n zW19#;hl9y0M!xl~Ud?dl^Eup4yj%=mo}2D`z6QB>CuW+YfxFy(Wc##owo&hCSYTuP ziUGEX?s3lemj}V{Gn9K7k&hc?6Sd>mVLVKAHkIvT;+$M>$5b!w{V6Q8m^x>yxM|i5 zla=c% zcdS(nwpVIgZgqhnAKmJ?+~;izm`W5&s_?f&fq` zt-C53iA1leD>ZlG?r2?!N6JsvWHIvXX_UJfs;m|2LS-J~=@sJn^aaHnNp7Har|({) zyGt==t$roy^sAzpH)EfvbdDW|_w;dVlQ{Q`>L9}H6VDGeXTp(YaJqF$F$Y&mK^EXz zJKRR8Pz{Bf8}76!R0YRW2sanu7AQI;iIUabj4QBK>*%&FMdvC#F4`h_l7`Nf!PN@7 zCr#S7M5QJP6YA|*hURWE-#jMd(#hd#&~zn3*PuCZv)s@%XqUS0Op3ElygJyG;TyMj z)g3seP|=>fG}$jyv{HH$rRvn~be^Jh9`BLDbcp7`0qNW@qeHXQYUFBv-ahsi@aPl1 zAcHu%#nHiD#dMkz9*@o*mCx6VOWag;mBU_zOXpQ~MSB%i=)+aYG8g{JGP(tjzekb3 z6ViE7>2|kIb$XO;fvbq<@+G&TBM}J(L$xAKbmAhz&wg<{{f5FW%?Xb=Yngr{dp%o$ zuBfm;T*$p)jfwPfabmDdVa>LCV?vL;6D7R|Q!3Q7(hUOn+j3vT1x&6;ZL3BjO|%R> zXZYMH4iDlIQHf7k0#L<9mJ02fu z_o*K+u9oPLsb4lu$&9#i&OWM`M0&kAG#Hio8~C~vWAbP0%)<$*-A@-y+Od1r`XRl; zhMU81**0$6R_H@G++e}xU5mvUs??Y0UDzI0QsQ)BG9OK0dNu0XEsACMcC&3b=~GC* zVDB@X0u;;in+om+;CE!*l=R32BI$nl8okc!6?mxMIuYb??AydA#}$~C=6c~)!A{e0 zM2fQsaVE#a*|WgZw+m1#yT4$Q9+2Y9{qAKkJS3RaiRVuOX=Ape$0<$#V`}7BiAC|A&6QJ!b6=Xw1Nw;t*FMu?%*-I( zJ!mKQp}mpQdpYr93HvPWu^hS;>PYU(^iDL;cN9Tjs&$$BE_0vnKBjs}iuYa5=g%%M z^j#vK2lV~_ntXnKv3}qZ`8=S*ZJkp2R`b2+$H@6S<|`WiyE>&VrtH%c;ci*Yk=&Pi zs3UYeITfqnrptJfpFcr8B#L(%zURXVt+YpU28~CCd#V%FqoEm+OMLU&gwYW26qOqF zdME_a72z@uqsecynk9?UB3R7N{nkfR32_+bpwv*+rB>;RYyvMhS&bJc+7P&OHB*Ul zL(?pYT~fJGy<9m|ZkJO(5a-18Qgd$NuVrl} z&fK~Ibqkj#gKE*J1}-6oUhGr$>HD4i6Q}rv?rwcw-%y$}vWVipoHS?K>WS0+D%}=z zlk=vDQ@;;a1R;K`g^5<^IY@((LNo)AazpnMoy@s;NTi1^782}P@jO4LST#8Zyyj({n<)M+nnk@_l4zTeD{{)Y(}1$a{e%yz*y3=02ZHnt!HPkM^48 zj7If5(PrZgZ6pec4fw5PZfB{}Rsb!624hG$U*>Z?$6h>F2`>K7(`G!|*~MkuMWl8wP?E4Hak8YuozSl7v!(&aUq)g?+%l&8|^tde*t zEYvIVNIV7QZ|7mr#Gh5ry*sqJFwUrpTIccZ(OEXm6*;r${u}J07Dv$*Z+!=v`-Xa< zL&LCEwDqHhVsoj57wjMA^SN=ZznMjM+x!!JzJRuJ44>3O%6w+-%NeVYZi2gV-ueEU z%edMG&E!y;b|df6>l?2wS;i@2t{7RbzBsmM6uZY(v4K&eF^Yc|zNZ6HU27ciZI5cC zbL$%EIUYUMzpQ%4$GHpVQgv|DH8)az?Zmg5sf!IG_?@^ej>qoh)Jv|z%{|SnbaRgq zXQ(T1C+CJ_;4HP(9xJn3 z^6VRBS*-EWh7hmPfw8aNp}c91p{)%o%P7D7kWITGoBl!i+2(2MNct;=W$^*gkG@YO z+BY@6Pim{s%iycbcN_MdQ&c*8%{|n2ptkc>_}eqa-++G0ImI5x1Zd~aeDBSWHFU=} zwMk=Icy+ct=RLVv)gR7Y6*2P0ZB6Dr2*I929z3O>d5lZboRgio!3;=2!GrSPe#C@@ zDKpX+>?w>-Hlp7w>35Vy)j4D86h1ti-H^{wHZ%9XzOd zMyg+!51fz=$!gs&omT2wkD?Q&9yPUzHQ6>Xg?t-163wZcIgO>Pekq4Hhr(@Ec}BsgHJ8M2 z^`t>_5Q>{J`G5Ci(%DVfmYtaxq9YANM&(;h+kFZ7MqLeQ-DGGk?XslX+!0zSFL7z0 zX^d)0R)%$TpJ>8*6OGeoPun%@X3X~g2YZZtg?*R(jQx@Q4=>^#XtbEh=kpu+2lyv& zcH~R^+x$QI@1Xqe<*){kvszIit9- zW6sO5$s3c3vFC69b$V{E#{QdW=2pZ_oVxtx3+o363n&-6T1zD9*VO};9x z=AV6ugG`=El_!y`QE9M+Y2=HtfFgs-1m4nyTo(ABb4k2AZkL(H=f+|29hu*g%lrrm z&;9RmnK%6cLand!sZjza+#TJpFm6X53MtvEf^2YcHjm_sVe#Y|Sy9_^L z;~Gn6te>%z$NCvdXKdQX+Dt?_yB`_bb$rR8Jtwr2ByssM57Vd}MPn=yZ2G&V9)Z zQJ!&-xK|Yur7XuPUv7-K&&u4F+-F-Zt;>1#=Z4OGHX!#@8cjA+FXP#n%?qj1ocnAj zp#0BL61ln1_V>n7Jy8H$vM?M+?z1kJlS~O^Qi+Xw!y(4-F4x=&@NT9r4a(9~sscKq zsM5G|6O{W*DIMb*HEuW}>^0~gJj>q_PfMq9Fz=9^dx)e`bm)Wbdd|$XN?RDDby3`l zmwKHco zLfLAC94EiGI=x^FBj)C}e=1vMc=~s~Pyvn37cuzi;2YvO%qMF}`vFe4SXAl~HPi_E zaD^6azN4GD5LQIXcS<7Q(Z||-Vk@So83+#&`N?%*cytiK$;j1RMhT~#; zVzND2_4a)=;;!i1M?d{x9d|!uaSjd&Cpllm4Jd8D8ER!Tzr1)?1YWT`d-KB-AE;>~u|D zi=CxCQ|4T~AkGU_L?cR5DAYcqJib86OD%N6cA>suM%`G)Yk4UT zXt`U--RW19e7TaZN^e%CX^R-uO({LUqSc}C(}S|8mBj{iZAY;mS=s=7x0a^(N0qlv znq;>41C2`~xg0MH1ln8cQ`1vh*3Xzxk0aIoz`}oze}+a2iLv92v7?l+e#RM-j2RV- zrE@tpmlfA#j3vhQ_{Z9e?P-toGqy+n@A1#@*yZ2jADSmcPwU^~pWM;LzsEoS9{PBI9~EvC>JZhU8!PwaxVQ64wv@de-JZ<)Yi5^8W+QuM?1R=gv(baLuw}I0)Q+(k z9x-J*c;oDyR-?-#EdfU;67-XceLin<)9l$z&EDas^yAP6j8nLo-|e|Cx8&izoxD7y zTBB4dx97f`)!@dEws=f!b=`QQ%gSGBo;9l(%Fg}%=A0)hom8;e_4u>rK4Us253?2^ zT9A(`c7~rh1!#-db%!xah4njR^)Ds8cF=hRrajkl-fEp5Pc+q19m;bMWMa~m2x&Z;th zFi{h5S3>&(jwq@IPvczLCY(#NN$1j*;9S}Va>@*m((WL6@46+Smdi>$(vRH>Qu)y> zQJFH9$DFwRJ9BsMY<@K1#EoQShUgdVV+*KL{wn$@teo>LvTSO*Sh4SonyINDIRjcO| zYF%D~q2h{vpOgIeIm!RM=Oh)Z8F#2T6}`-aIfo?1tn%ohwAO;The%1PmL~eMm9-Qv zicI~^=#HT+U6I87vFOZM(P(8fnqCQmo7~>!7Psg8;wI=!3gAv+XQipqDfY~(s;sQ+ z*%3{PXq2a;J8(%!I5cC&j&zf`nT~;3gU#lrOs1!3j}XIL$Ta>t?4FrS@&{4^)1^a_ z9phq>e;EOjZA2vLD2W{$i_J)(>@ms&f3Pha;WHoVU*hrkBKA-`9CZxDQ>iNpeY0lF zEQ`CGxB#lCe8%KA{3V9`?>%63M9Y&d9`*9?+hgTPdyU&sYbwpx8jZdg@p7Fq5^Zj6 znpBhUdGnRl=|+zmH%T^@@nR#cmEcNfjd=IYjn@ngiXiT%Q%E;=;IMb8Isj#Hsgzoc zdzt60-o5(DkALkpv2EV&dArj~=knd0<`(jhe_s^Sq_dqW=-kv|&68o`$mbB;h=b=w zwL&4Lz5R_xKe{e`VE1nR+1Bn)c3z(TN&5Hmct`pW(!*4Z zIDLs(MEqgK2z#1n@@PPMmw)>3kc$m?-veAkkV7a`9DK%ZMy5Cbp@=P%${t`V0Jl6e ze0f2j8R!GfB1RK{sSAsg{K(Pu4SwUc13#` z({%waF;*N0K&N;;06%&y&;V=%5Key*`2(6I%Na9(H^XYw1-KixGiK@mK+^<2rB(oL zrNP2HDgl;80&!F zXNBHRn8Jhu`oe0x;h_RU~fbGCB#%3vj2FB1vvn!4>)&-us5Z7$bnbX18m7sei z?7G2A_XglRV^<-*tIjYs7k=h}=Dbsk^~8bWjLq+2Y=IZp!PwQy0r>A-&)794U@v0} zdl*~9fUT$|2-63D*CPIF!O!9(0NP6s=MseJhu{8vj4fRUTwrY3LB_5_*y|3U(jn~i zu)o2?*o_+*TMqL*c)v-@*v)H!!;GzH2H>{h9AobV@9(|H*ewmf0C0@4_XUAXjIFc+ ztAVY+8OGkfjj>fq0CuZRGInbx0Df)-FSmlm>Tci=0J^s|1E6u+1;*AS0mQfF6l1r; z&+Uj~Z9f2BJ^+6oIKkK*9RS`xxEwgk*g8DlxstKFq5x=q$O~L#Z2b!0IAb4%`C-t$ z8@$|ohOv+AV{8MSKMFecm;kta40Jc{VeI2;ftMJ&w+Gk)oM-G4aRASo`WU;<3Ty;m zzj-TT|4|L#dCLLDKA8ky-roQmVeElU0Ccw^{8sSzsUBb}aGbFRlK`F{JjvKYQD7Zo z+ram>ZH#?72<%|&VbFQ_9Alr^#MpK#0RP(&_7Tu~

    9%BHW|>jD6Mw>|$&O!tdD5 z*kj8X+X=oNXTUMWcERm)puf9|u_r+1iM0@?D}ghNJ&8EJpao7awzq?^FKz-(Gxk&u zaFMYu0s9cgm-`sop9Jv!6}TS&-LD3LJ&Zk#c%I(I*w+x}*A6gt5OfZnXYA{%8G8mi zKJyY|-#~nal)y&DzS#@F{n;&yeGBn_YX@V8LI3b6#=gCRvFE%1!ajGFvG2hDcg`?& z!~|?*?7MoP8-U&O@cTUc{2$Ocss-TpDAMsgg#F$H#$GtU*o&}#5%Ilvl(A#Yz*+#$ z-?sut%MU=~2cYpoxcw0HU(x`e_ansfqfX!mV?T}p1HfU%{<8sqc^r1f;qNDS{|Vyx z33&hMCID_fiv!ykJE3Ll=b-oVlZ?H*4uIXu=NS71^5+*Dfn$uF3<6!iY5?({+y{{T z8ODC81gZhJ{}OJ$e2KANH3JCyE71A16X*pH_bKpkss~sJYy;r_8^rw^(ErT}U=IMg zuV?|p^~w(5C~%&!-!2FC0T&rN-3j3R^hRJ8aF(&(#epth1pq#Nw--3W*zdc6gTMvG z{?G<&0`>q$0mOeM39JRc-x>J%V?O|ze>?&p{HvY7I^Z;8e?nUR1pj}+`=7Q0p!+A# zI14{#2Y{1|{Z}=B_y5`m9A)g!8sGqMk+J^`L-QU2+g_VrG*$kXv?5#ck zJp9jgbWeI2ds`16-ZavZehIy&0pKWlIz7N9=myXN%Yl8s1;)i;s0io-&^JljbKe_|tmbky_! z2N+C!tUw3jQ=$OEwj)gYcE+c|eJcER zfaWy#nFe~(*8&$ApV1BMz_lgY7)PIm&(Z>W7{3DM6IL9Fw+A@HNC6D*LArX*GCm*iEl2{Od36-n!gy~SAoBv_*MP<~ z>j2PQi06gyw@3@%dC_{{CC2*(fD?>gy9ogAi}x|U1hkeMWW2u`01rz|0Q@gQxMkq^ zI>dk7X~qZEGJZYyxqb)ZH-L{Db}@b<=-+sr@#UcT9)!IKb~jyM9DN^t^B%@mXn>W# zNygvX3!Gy77SOu|^xlWKS8f2#GLF6ue?Qz-#et2$8OCq*0`Pn5F~(Pe-fdO@{?~K? zhZw&d{%$|V_*&5U0C@huamMc$VElufjIYx$erFKa!}wkB_aQBCmhtuA<-^^K-_02R z2<$!ryN^bJml(eX_*fjc!1%^q#y<}Gk0YLY_c8tn4RDO{P2g$MX~yqEJe#|Ki;Qn^ z0$UmXBzXMf3C8aSjr$Sj0|@f~c-p#-@lV0;r$F~X(0izlar9I8w!@5n8nho?&G=`w zFur{gt|?i`_@mVT{C;)^<2#l!{+J%P$oS4+|awe-iONxr^~H!~yuzQ2d@A6S7d;0)tGT+jGRi04O1#(xaIKkjGzKfS;f#*gFqCtBbb<39y{ z2EI-VF#hwEjK3TOE--!)asLv2eg*qqgZ8PtjQ?gIaEkF)^gtT`|F6LAm2-^$HV7cT z-);mBF@9PDbO0NGBaHt}33LM&8UKAX&PQ{fwPSNAmS^)n4)CH^nHUWEqvy7jO0*L!8(s_0runjm0fDZZ;{J)k1y8xu+ z&t3rj{=6R80UQQSG5+5S@B(c>Kd>Iy0UQQSF@BB#UI6i&1HE%wfP=tk#{Z%R8h`-+ z_J4u>Ul8W68UX40Yd^3NH~_%@HQ2ufzFzACVE@`a-~{96l>qF|_X6tyr0+cJUuQrR zKt8<=``7mXuzv$|-e>@p1KWV3zy-$t76dr8MzK}?D0^p|+W)tB&6O$0% zB&4Ak;W1w$TEJ7Q34q(=<4m-{eG2HeD}i%NOx?sp2l(o^z{Ip&OiV|(8D4DfiUJ5T z^Dq;$;OC0&uc`KRd0l)M0z;a*tnw5@-gt0q2;w zwg=b)XC z#QGp`5ID!ghmSCEH~ind3xNNRfZj*e0;ieSpaoU~7n%5IH2^>NGyp4^K)*?R>@X7> zH30bBxCuDP#K$`UxPSZv6Ze9zd$%%yzLfaH4ghwW@O~e{-Uqt(ftUL(FtHh7HiM7< zXaiuk#S3g_;*(AQ{_Y38`(gh8_<3L*un#!H#8%MXx|fMhMS*QhJZJ)XfU`_I1X>Sm z0zh}09)RC%2LYt()1djV5;(xbXR3i6Ol(J7+Y!ejpz+9hU^@W+j~r*>Q3im=M>jI@ zS@_%0%fw@Fdkk)monvAr_}U2`c6I}ZZ|6zi0uzt70SNQ>OHAy7pIvJKg!^0^*apDw z=gu;*8*aNB0K~idFcVK8z9$g&i46e4?&$!)+vk(OUM8Ll0^sip;PnfqnAqC`9A)B* zodB5^nRu!i7yuB@Q|FlYQa1p<`&IylnfNloet9_ny8D{}m|ub00Vl8yILX9Ul|UR= z4;*LWX}CQNnqSibi1TaPnK;-njBmlu(PjYjzX$j4onYbx(0t)E6EA|!i<_7@2ELB< z0r2}1`2Fc>0Q{W*ou4C~mrpVAi?tb?M4p_4-N}PY{8EFt0JL$KRk-Jkc}t3$UuO!% z?_k4D{9^MPWd7oTZ+`yb`E-+N0ooqM)M)c41Vjz%0i-|PCB46i_fG-yfOc}rg6z)# zWdOMmp30i!Cfmto(>Qjr8~K;R%63=4PL7N0I!A>kH`#BlDcg@WW!`1KGHsbJ;z52U zKjePjs5poxIee~3wB>g>UiqEE?9Oo~dy0qr$tKy6AL4<`et_IB0Cxd%fi7UW=;dyh zHvtELD6k)h0m}fQvlbw?bJ_QoVDSKlJkthkLEuPy8?jRc4tlUFTcw?O=OOyC%=#8b)t9r))xX4jv&`DGA9Z{b;b)& zeImY+fE+$I45gjybL~dI=emveO#TfybQ`w1x0DTAKPI*n} z04N{GP6mB{VPFM7-!s`$crwW^J@;gvWjd4w%2PQli}6fuMu75^Xet4+TLlm;`Uc1? z2$0++JedXMHN6u(ijOc2pm?=FGeEq|1}Kg;pbL=c(0ebi9H6*}9>p!Ejp&m54FG+I zQD83QCe_d115ahmqcDl5j{ z;DM}J1QYZ2X%)~8>;;wrJAt*p2A~^A0^~0HA%FJ+_h!vwFdqhP8U^x4^krH^`ySv3 z&<_x8nJ&dae#s7RC+HW>v@^&n=n|Lmhmky_HiXJH7gQef z-VZQ<@_#o_mVKsoLIWVXPsw?nm3_|r#>k)i{m^q8K;=tx$ezl51+W_E2PiDr(RV;5 zwYlWJ2_Qdm*gNoS1Bedsum+Ihr?6yyB|!8j9;!q19aaKzJmjC;$&TnP2c87Tejz|K zfWZqeDIbUz`o^>Nl3N<@2tW8YnCk(eHx;0EkO6A}@=xL2*=MpRTI82N;WPl*lV38) zo}T425lyOtk{<2{f1UL|@&b2P>aOLn>O%X~kWjaN3QeB^j!Qw}GaM2qrybiDGr>^2b} zvi-s=ja=U3INmK&j_chpscbLB7kxXIa=)B({-b=#c|MW(7TOGwu`nh1yA9@3S?q7q2s>7=wi12hA_2V{EW|89WlDdEv9C=QuFl3yo)pCS$&%=-Wu z&)yEK0pLEk9A+4xI!bsiKy_{|&;t++iksx)I)G#reZOS4Ec;IW$&TFVdnPyHo9yVD zw*yhY4+wzCV=z3g$UZ-lWA4ZEf$Tdy(|Cxu1J?olS(DO6 zyvTl?c$Rrsh-W$Pylnox?nEOOGM)derX<@T1LXW5KG|UAolHV5@8s|A zvCj=V(KFGM-6Z<-Eo4FV5^q#SS$|Ug(tEbNa!onhKWcKcS%YVQlfVlZK15N*B)9SC zj<6qqA8Pjx03>rL9rVo-NIu;Qkh~zsew=t7?T6|Hf$H-n@bW55s?*f~)e(|cM1$hm zku|qx{E^?m)3Bp@NMV#fC+sf5)aO8p=l<-wfC*ZIPr*C^P~F-HjH+i+|Ap!w@tZB9 zA(MEyHft}tx8t2?-2uq;xB$;dfZ`^Z^?w_bpZ^G`tYvv6m&xVOQRT$99M87^O93iR zD&IHpZEIk@o&iTWN$)S>{Re<-9>Fs~&OegHx$*bleI+29l>g+n9B2b*4A2FT{Tx8{ zC!3@FFU@2vpkE76tl0G^2k#h2wl`aY0{QlE$FUKpVAs{`VI3U&J& z#@{RO{3^_Sh}Q~pE+CKFY3$ws(3qa=_XEoTnoA(N^}uRiF2d?y(zw2x=mBp5XYo$+ z4fo)A6SmUsWqYLGy=)uXk2kV8z@BB@Y@KB8Wjoo!@FQav-uL7G0RH!|C)rbM7eYLR zf6PMQ&y5os{Y+07d=={mdi-mFViv{uz|)LwkZYEF&ZB#T@rRWL#r{Feji@yj&kuH~ z7G%~QBsb|N?*dbZ8~7HMhY{{ZS{g+!MKZAxUqf5z7|Y9-yfQBnKNHL1dzE z+L#xoQT~iLjwtrCxB9VNh}?0n!k=My>6s}31rW?UtDJZPXLwhz0-Wb?G9~!HW-3^! z*&2kpi+!0Lsp{6BDS>0^8!e~zEzzvX}6f8ww6LFm3siK(Jb^o!+UjrgFrOKcGL zi%&rd?q|hLu}eH5zAg@lZ;5Yu|fF2%iyO^U}9-&XuU@iWCQ6u(sbTJalYt8$6*I^}K32b7;tKCb+N@+-=(DZin7 zPWdC{&y~Mb{!#hg%GdHvyVP#AyTomA``qR3N$#2MdG71n?{k0D{gC@n_nV$?dw%Ho zwdcP*X|LL=_qx4)Z`fPyt@YMbLtn{+PeqpYnJ5m-?>{h=4xe3q2Ov z8+s;mB=mgfSm=kLpND=QE(w>0o5OA4_Hcjry6_F*n`R#bU8c+$7eDJH>kF z9^NXpiS6PsPOX7r-?@UpwutNV~Tv3U9Zyd_^)l$B1K)!EQJg)eE z+WYSKHp=Ahr>=V%y*W<6p`Nj%l?$P{$RU&@mk>fZIAmL~mB^Nn(Z%9)k>R;DVCs5~8hzhe9& z<6j=XeEe7A*E+wCtEz(Ex33Dr?~PSE!tYby_p7U}gWqq4-|wutr)uet-`973ubD6d zem@g_Uk1M`_PC zaGSx(!OaFY9o%Ga-+JoHcOfz!?Ll4jead?7%St zM-Ch@uw-D-z=VOSf$;;C1Dg$OI@%udBXZ_0_5`R(-bW<5eH7`e4+0&ii=lVwBqm;hpt$%V&U>-%gR0YNE=O8}RHz7-h$P*;J5 z-_Mdz02pg3&}#{S0;A#k02CC~ z@V&P0w2K!{UiYPsa=5IAy9h&2X+F~p1{iq)Ly_V3Dh*; zRRn4}O&PN@>SG+4T>-E`VeFfBKnG}yagzZA`lEl%nE=cMbuTc+jky>!`p#SdfPO(= znwtQ@7=e!FHb5{I&|e09Z-PF55g7B%JOmp3WgZ7S0s2+o0MOTX6Pz$_0^S4t7Vv7o z0BG1|J^`TL6znr!1HJ(bn+*EQ`~VvDEXG3s>SG}`DcDI!Q8G! z|5T$dg87YSkVW-VpwZXW&k&fGfS(5d8}wcEs|3NEuZG{N;ctV!tNw%_SOX?Ohe_~- zc?%fw0x<6Y?*PE~GdOS3t^@{sF{zordAW1v_yG~ zX(fup9HzBcR~*E&8BxdLZ4enQ58m%^B-16LSe(Rkx!6#gi3_O=#)kL@vJ6B^v4spX zt;8s~57Sy~B|DimBkEYOu{@Q_JuyyR$8?DpBbPE=E^6g-E$P0+nPgXYt}0v`s@*R5 z4OcaFrrQ%$vleG_iJolL)ZUJCrZ1g|=MtSYRZXc>)y&|8Y}L#}Hj!C?!ddD5Oh=+> zMsiUiRW++8o=VM3boHm=nNUq_ZSCYB4>8#lx21b?`y?{iWV*M?9UCg0*55NHotdBQ z&gJ^DJ8ZxG!i5WKdi#4824yuJ=^oJ{(xMOVHOh#j=n~zCh^vr8sTCo>c0+U+RFmjL z-FA@xJqu@KQ8$5iB4xo(6}|YA{4`qWbH8zStW(su^HL%OJab4waK=oWn+0SL6DQDe z4cbg2g3X|vK*9{vUIZ#Qa~7n;L8aVTU8t1;pMj1w&@%Wx8QQgqW{jK3BWkyy#a?K# zkCT%{iveFX;=JLO3w5WV^&X75z?S*YFz`qYR%OKwc;m&4I76y(5@@i6YJyq zha2JzR2z#;#HM(gY^4|v3npN}-5hzOYB33K#@Y&RRo({g726Kqk*tA^LGOh{MELl^ zQk|$54Wbd+PezVxC$Tf$A+syI)r_8Rg}>VH4W`}Df4k$=4||He#5BB{Y=-zNJUvV7 zEoQ@abHu)4Ke4|!K>SS{h#B~Iaj=L>Bd!+5iQ~m1;ym%0I6<5)P7#-i8^tx!Vt&0Z zjurnDr(ym*i8+3mxJmpZeiT28tHiD1dGV~c4dbW-GqO{>h`xDFydYi`FNv4MEBOA? zT=9ljCSDV_V?2H<&Ju5nH^p1%hcCoe;$$%oy*MA9PoW2{fcFnU-(=7`{pcm^u8`wg zB$i-w9*VC@9VV8FE3qCNA&wG9BTM-;GCZCvlEtz_mdY}*R^(+lzBV;VjuwLwe^@NX z%5hT4b>zBuSMB<81G%Bx2rqxxL~bfKla=B>;#Yi`s!C3f6XoV|3t25E$t~qpa%;Jb z+*WQU{~~L|Z{i)fy{yIetHOBWtuLdpPS(o?*(i6AljV+bC$U-#$eraba#z_ToAH&a zRvD9Ra*EtdPL;dMJ>;I^4soa4OHPy1oIGBhAWxJh$&-;2J4K!(Od7eC9ULY@&7s-p|CHO|yW%6=l&aRYK$*bix@>;xo=X!aAyiwjHZn zTjg!Ys@);)ly}Lynn*qib#`L=vVzAN98@5>L6BV8_6$d&RVxe8gk0l5a> z3i||E)6eAR$l!e;zm#9euf@A!mHbA2E5DQ9%O8-}`$>EtJ`~HvN8&xPQvNJ|k^hmu z%HQPg@(;OI=8rMsdC&K9HmC9F>0(Dr`A#Hs`b?RY6Ij6 zH&PoTOS`GsOjWA!s!B~z6V>K;Yf-hDq_$LBsjbyEYFo7(zF}6Qw#OR*Ln^Ey%2!cU zha6*rYQ)#kCgUyTJE@)Vm9t${lWIoBw^hYdo0@_*K2KGcjY|+oQ;2KaQO06Y5FzlzJMu z)o0an>Us5odJ*~Um(?ry?%OitxnEars5jMH>TTrd-&OCa_tgjLL$w_F@0G~Ju2QSj zfLfzIR-dR()o1E+^>6is`ci$RzEUZ^rTC4JE zPz!v;Mro~$w%XH0x>%R!QeB3udW9Z^4DT2{R*yr5eO}37C+LZKbG?PG)|2#>dMmv(a`D?DEBqJag}2wW$jXQDjT~P`b)Bx)4agMlpeO4c zk*VKV@1l3rO~@R#=vE!mZF-8{O;6Rk>pk?IdM`aqPuDZ_U-e8qOYf~`>wWYby|3O+ z@2?Ngf71u*gOKMxSjTm{?$Dh&q37x@-K~>)o}RB$x<~iwwC>Y~=#0+l9PS4!&O-meS$twpQKO5J%dw_y*>^3>ofG3`Ye66{--`i zpR3Q)=j#jfh590WvA#rKsxQ-*>nm_~;VR_0uhG})>-6>d27RNxN#Cq*(YNZ`^zHf% zeW$)l->vV__v-uf{d%c>KtHG-(huuL^rQMQ{W$JTJb^6vQ~GKBjDA)>r=Qm^=oj@% z;x2Kwei`>IUd8>T`*9a$8S-QI;v0g;h$qB};%5Dtct*dj-@p#(QSq35OTUdw`@7;{ z{hodwcRD^4zu<1i3f%AbNUzeX^?+WZKh~e; z{z3n!f6_neU-WKX1u8~6U;=j zx!J;0n@MI%vz6J}Y-6@H+nK+Z8neBrH6ar=5#yVvsWbJa!8Dp3%w)5p*$H=WcEP=b zCev(MOsk2RHZ#TSW~Q3m%^qe?vzM7>rkfe&uV$v1W%f3+%|2$1+1Ko6_BRKZznKHg zLAV2Su!-YtM2G1#2{YGpnQoIb^UQpcGCii(q)neW1otYkCWkvk3(P{Z$SgKX%%SEm zbGSLe9BGa+N1J2JvF12)yg9*~XihRGn}3*7%&F!ybGkVLcR9{7XX75zIp$p4?>OIF zU@kNl;f~WK=2CN+xg2*ut~6KS{?j$)T63Mb-rQhr#66Ll%`N6u+!?vu++prCcj4aD zJ?38AC%NA&H4or!$wTI0^N4xWJcj!w|H3`1C(TplY4ePE);wpPH!qkM%}eHG^9t^% zEW`b**UcNaxAK;G+q`4m#oev<#RKL8^PyR8R+yFMBeTk^#vQLU=411T`P6)ddtd)H zUzjh=SGX7RjrrDmXTCQ-m>vRm4%?ACT0yRF^M{>9eV?QN|M;j4cU>)WWUv-P&YHsZrTlkJXnC%d!V#qMgG zY_n~#tu|)c>=e73ooaWtd*Jgsd)aArI=;#CS3A?rvU}Uvb{{*(?rZn6``ZKT-|T_* zAp3WFu#MYx+hIFx!p^l_w%aD{JUic}Y>(}=Y1?NHu^F4SIood+*oAhHU2K=wL+xSq zaC?M3(jH}xw#V3G?Q!;adxAaDo@7t9|FEanQ|)Q?bbE$9)1GC|w*R!}*mLc9_I!JR zz0h7{FSeK1OYLR$a(jin(q3h+w%6Ee?REBgdxO2v-ehmKx7b_lZT5D1hrQF@W$(84 z*n91L_I|t6K42fT57~$9Blc1In0?&-%RXVBv`^Wm?KAdS`<#8=zF=RpFWHyvEA~~p z%)Vw{w{O@t?OXP3`;L9rzGvUJAJ`A=a=XH=v>(}3cC{U_YwXAN6Z@(C%zkeFZNIQz z+OO=__8a@H{my=Gf3QE=pX|@}7yBRktNqRXZvU`rZQc%g!jqozv}f=cBM)zBEcQyg zQm@P__bR+m-e_-(H`W{Ht>dlht%r}OY~XF^ZRBn2ZQ^a}ZRS;a_-G?n#Fvi?&^|0FqVDr=e~tfua+lJm-TA8xm7-Y`u%MCusVm(TCY zBoe);cyDL2!`n07(Vt6rDTlZU^=By3;_XQ;NI7KogncH3A8#6+)jQ%WKT2o&b>1{O ztCt+t8}CbJbD4BscS6T{yL6(rt7tl#muB;(lX+=}DyDb$_jbiI{XMC8f36}ulJjPg zY#q= zpxJCeKU*-HEa-R0&dwxzyKH~(TQPg2{uTWrxuV(Jk^O|r<_zmqwr~W!-w1l~FzxM6 z7A|q9bpN68SGr_~w5fD&SC*`7pcxrzXkxyNaAU+y?@nj1-n!q}?zcbqrQs94oA72D9ekoyeP|yvb-qy zC>*7}2}h}K!gVZ{M|il7<<+sgI+j;QeG;x?d37wWj^)&`oH~|MM`Iye$MWh}UOmgJ zXFKcJ&U%(x&vNToZavGbXLpMXSww(ub$=cSPnO^oCcQDz;YT`P6ON1!0k71 z`#cB24cvYM+tt8w8VYjQu12=2k>xeAyhfJO$o4d{oJN+@$n7?AyN%p#Be&bc?KN?G zP264+`=yEXZ|3%zS>I-Ex0%~(=Jr~+y%yG^h0kkYf41=XEqp%D-7wGHa4WYLV|!zM zaaSh3Akop&UhJj<2sZ;Fwc%p777iDX>XBMrA|dinBt$(N;Tag=85rRi7~vTh32}QN@_ZyjmPJC`ez>*_t7Lm3 zm0mccQUB*oFwVAK~*Oe13#|5aD(r zth3KH^VK!NvpQ18?eT1i@NA0ktcldeY%blK&Q^3L6PZLdnRQ%gQ>w2!?#PmOZ#tJq zC6e*-SYI}Yi-apKj^+6HRK!}YsC;@)G6>#?nLVO%>GYmN7gZXYMD;((vQCz1PUPZt zO1!5hPJ;&1h2@$3aY#d+-Mbr<2?W|b;(dK_1SLJ~opH5Szna#s_Dv#Oa2lu?Njjg)L|QN8_YQL-pa z_UR0a%R1w7FpdT5J{Z;kfF3p-c)UDlub|K9|I)9V8>8;xgN`Y6Cp(hl^W`_fm)S@w z54u(!yfGSNJ_q)GZGopj>DSUk^lNEQ`#kS`p7%b_d%u<@jbBTH-mj%W@AJg>dE)!E zB;T*4N#yfF;PXP@^FrYBLg4d4;D=a#h$gKcqDkwAXwv$;Q24x1_`Fc~yioYOQ24x1 z_`Fc~yioXIn#6vX?dQ1O53~KeX!yKn_`GQNylD8mX!yKn_`GQNVVd+lFCKoF?dL_r z=S9TlMa1Vt1fTJB{pUwmJ})YMgze{8-{%m-kFfuF(eZiF@p;kldC~EC(eWeff1mC4 z*=}Bpd=6=RpZ(5@lkche4p+13vy}v`5ZF&95VSFGWi@b z`B9e7A(PJ`lg}ZO&moh~A(PJ`lg}ZO&mof^W%(RB`Mk*b95VSFGWi_J_#Ddky!iUO z`1%|o_#7hm93uFQ+)s_{pGNi%hYCK23VtK|r;+``8)%ju z4cPTJuxk(4wHJ!fIs@$d1?=8qz$}mE zFL;(m^EVWu`3KDB)BFO@_R;(T&-T&$0?+=Y`30W!qpe9OM!^g)>qqkqJoguEHNdld zwABd3Xe$8R=FM?2s<)8P92cm13mqz%Q@B)?EG$sL`WcE*h!4#A(EJU>X#N7TJ~V&9 zbN^7N51#!+^BFwrNAormqj?L=?a;gh&+Rm^-zn5bIghg@_InfimF78)vtOIoFG#F8 zJDLh|*$$fbp%~43V3tqw9z4r$X8E)Zpq%A5vwYfepq$4Utqb5;elyFbEeOh4KCK&} z7_A$?EWaf}`nItA7M9<_er#d+v~|IGY(H&Xz_b0dbpg-vDIEcx<ev7?Wc7LJljuk5_qH?Wc7Q z$Ju^b_rSCLF_uq>4V1I|7~3CX`Lr&EVr_2S1`c`q(S%s+5P6@{A~?#@+t~UxmQHCB z94F~*+{sLQX6}zjkRxM?b}%Uwl=b=t<6~# zuBFvGc+9DEZnCRCljuZJCNKp>&II7PivHeCJfMUpo)Vqysft7TvAGFS%$aPWlj?@s zY+okXlPJbORFNV8y&!n6&-2pn|hGD&c!o} zO)8m*Qxi>fULPK^!iK=9+0;aZxZz;0v$vtcw7!MnZyb|V3p zbPQ3%9S%`M9S%`M4D9+8*!5{R6s|2vWOGO+<5@7LL5O0jaEM|nU^hpA-53QXO+pk~ zf#((}wgOLH2+`UPzNrL$NCZ!qb(iP5kzpn}TQ)bjpg@;r(Ivfnfb?i+EQx0_>4mAp z++2~P`uj>9c8|Bw!Orx;UZUF5xo)ER`#N2A5(zj~jh12XtXgAZIXBqZ+n!AjgKiE) zkq9vTeHE?}NGEWpmXnF@3&v19RhC$UJv8n-B;zzpV6ig*;!4w*Tz8Pbji<_!y%=}d zL`M#f%F*#ieOcm={^WvqD$(1Ku-)nYY@z}~GnMX2cEnTIg}Zv3tq4bG69P;_1lfwx zpixZVRIYD`Y^SrLv^7I=(JeK=)QwTvnt`WojPgKey8W>U9nt`VQ7Uh8yrL7st z-BJTg11?G-6L^{wQ3{!m)MWXzH3Lt>C`wy1@Z_y156mbJ%qVTmaGd4S)(pH`>Va84 zdnL+ViPF{#NmRGg0keGCnt^Be6rzD=`4pmoXZtBc1JCj)L<7(AX=?_a?We67c($Lm zW=P_)dA7Rx z9(eA53h}_xT){^r-KdN5Viu(k565YNiP9DgJo}%vXy9p7Mt!!QmyswhBT-&PqP&bm zc^QfFG7{xwB+AQ3l$VhxFC$S3A&~@U`zeG3&-POY37+Ls2#I7l`=3Hc@a%uea)W38 zQ7x??`|BW1zEQObe?M~blMjX2Ko8@T@)>g~v*ES+-V zIJ>zhHFxB3ww?lJ9B(VivWXY=_qY~ zah%5=Mp`T1gs=wWKC*rWKGZ{xD(^=NjpXKXPV$=w_ z5FXX@%3jYiy`E=SJq5AhdJ1C0F<#kYyduYVMdnR&m^aO_TG}iDyBP%R92TpkO$;!X zQ-}oK%`8NVZC-z6|9ZDo;zxB zL%Jre<2CW(xC@a3!X2rJSHy>JvC}x_z4S`3>8VMHq@vX@jwG?>0(>ThaYGt)DB02n-WphU{uABK9t z^AQ2UD5DCH#2p7JXvk&wYZASkUU2^aL&n`CKpRN(*C0giwgG|hSGYq{(-H40ad~@3 zA&^Ysk1J3X&-NuU0aX^LggG%xx3vcJC|8&IAYk1I!>l_>9W!)vm~}^K)DImUV!e3B z;>^&9b2QAU$!6M%ll}8E@!p!aSsV<8zBsNz-Sc6mw%=pBrxqciRog;!jz(b4{#t5 zxP;_^Dt6)=5l9Q_1j2$kBrBjuR8YxD3M!GH4ieN+gs$(1XA`AyY#B1i?0ocX_hLN% z0;jl1SKGuBIM~(VU}rj&io=`alq_!AIHx!w&@mv`DFI1NAu3SQ)eH1=^+-`ilBTYr zQ#GJQ^~E!I`UiU;SADc&-PXtPWf)P(bSE{1Kj7(!JF=izdqFc0q*?p0X6-|o1r%w9 zdZZcZ6*L1`&v-Ur0)}H;dX6@Nw&2WA}Go0^y-`Sg{f#irlvw&cq1tj^5s6aDU zFVM`@Bh4I1nz@Qjvw#}mvw$6`S-=-G3ux9XAW1Wf@QmwCM+BM$1Zx(Mq#03xX0BeK znX5;dIg&JU6`f`QHA1t19jRHs7c>iK)+`_^(wR;K14-8mu0*bjy>>z!glGtOG`hUG zRMLo-&*y|(i5JDM3Swb`%y?){r5L!rxMz*(@xFrsi1zTbs z{AKjOjw>(c$M=YFD}A(a1XEY%7&1EUIg4;n2@2zI5fVD?co|RFV9JzXtwzp?$`d_9 zbSbU*bMY`*c_1j#+~U@6VjBGsOjDy{OkX0A2@j@JrAt36& z^LRtWZq-L|u)x>iiHo3ag9AP>-g08E<+RWvkaSfoBT>0=R^&?Jc2K^`;SY^9Z^npo{q0$x??dO0{^BBRF2{u0m+M*j-^jjCRwx@FWJVDo)4n z2&KYKsd2&LlnJ`EF}mlPGcwu-oC)B)nj|SCc5=tsV}zU0_Y3Y-pc|21jliYZ7DK zx$Ovy=Yv|XxF%8Dk)As@kwA07VunWCa)xr+j@2YIoa?ryA%1`X>1^!PT7B9IwjskG z{I#kClm`UlayoutNode() == childAtIndex(0) && cursor->position() == LayoutCursor::Position::Left) From ed06b4e48b70782f499f104ed4e8e4755ff75683 Mon Sep 17 00:00:00 2001 From: Laury Date: Fri, 17 Dec 2021 21:36:19 +0100 Subject: [PATCH 136/355] [python/ulab] Updated ulab --- python/Makefile | 17 +- python/port/genhdr/qstrdefs.in.h | 4 + python/port/mod/ulab/LICENSE | 21 ++ python/port/mod/ulab/ndarray.c | 243 ++---------------- python/port/mod/ulab/ndarray.h | 20 +- python/port/mod/ulab/ndarray_operators.c | 4 +- python/port/mod/ulab/ndarray_properties.c | 12 +- python/port/mod/ulab/ndarray_properties.h | 22 +- .../port/mod/ulab/numpy/{approx => }/approx.c | 34 +-- .../port/mod/ulab/numpy/{approx => }/approx.h | 4 +- .../mod/ulab/numpy/{compare => }/compare.c | 16 +- .../mod/ulab/numpy/{compare => }/compare.h | 4 +- python/port/mod/ulab/numpy/fft/fft.c | 23 +- python/port/mod/ulab/numpy/fft/fft_tools.c | 2 +- .../port/mod/ulab/numpy/{filter => }/filter.c | 12 +- .../port/mod/ulab/numpy/{filter => }/filter.h | 4 +- python/port/mod/ulab/numpy/linalg/linalg.c | 145 ++++++++++- python/port/mod/ulab/numpy/linalg/linalg.h | 1 + .../port/mod/ulab/numpy/linalg/linalg_tools.c | 2 +- .../mod/ulab/numpy/ndarray/ndarray_iter.c | 66 +++++ .../mod/ulab/numpy/ndarray/ndarray_iter.h | 36 +++ .../ulab/numpy/{numerical => }/numerical.c | 85 ++++-- .../ulab/numpy/{numerical => }/numerical.h | 8 +- python/port/mod/ulab/numpy/numpy.c | 49 +++- python/port/mod/ulab/numpy/{poly => }/poly.c | 48 ++-- python/port/mod/ulab/numpy/{poly => }/poly.h | 6 +- .../port/mod/ulab/numpy/{stats => }/stats.c | 12 +- .../port/mod/ulab/numpy/{stats => }/stats.h | 4 +- .../ulab/numpy/{transform => }/transform.c | 15 +- .../ulab/numpy/{transform => }/transform.h | 10 +- .../port/mod/ulab/numpy/{vector => }/vector.c | 85 +++--- .../port/mod/ulab/numpy/{vector => }/vector.h | 4 +- python/port/mod/ulab/scipy/linalg/linalg.c | 24 +- .../port/mod/ulab/scipy/optimize/optimize.c | 16 +- python/port/mod/ulab/scipy/scipy.c | 2 +- python/port/mod/ulab/scipy/signal/signal.c | 8 +- python/port/mod/ulab/scipy/special/special.c | 4 +- python/port/mod/ulab/ulab.c | 35 ++- python/port/mod/ulab/ulab.h | 22 +- python/port/mod/ulab/ulab_create.c | 144 +---------- python/port/mod/ulab/ulab_tools.c | 2 +- python/port/mod/ulab/user/user.c | 6 +- python/port/mod/ulab/utils/utils.c | 6 +- 43 files changed, 667 insertions(+), 620 deletions(-) create mode 100644 python/port/mod/ulab/LICENSE rename python/port/mod/ulab/numpy/{approx => }/approx.c (89%) rename python/port/mod/ulab/numpy/{approx => }/approx.h (93%) rename python/port/mod/ulab/numpy/{compare => }/compare.c (98%) rename python/port/mod/ulab/numpy/{compare => }/compare.h (99%) rename python/port/mod/ulab/numpy/{filter => }/filter.c (95%) rename python/port/mod/ulab/numpy/{filter => }/filter.h (86%) create mode 100644 python/port/mod/ulab/numpy/ndarray/ndarray_iter.c create mode 100644 python/port/mod/ulab/numpy/ndarray/ndarray_iter.h rename python/port/mod/ulab/numpy/{numerical => }/numerical.c (95%) rename python/port/mod/ulab/numpy/{numerical => }/numerical.h (99%) rename python/port/mod/ulab/numpy/{poly => }/poly.c (94%) rename python/port/mod/ulab/numpy/{poly => }/poly.h (71%) rename python/port/mod/ulab/numpy/{stats => }/stats.c (87%) rename python/port/mod/ulab/numpy/{stats => }/stats.h (84%) rename python/port/mod/ulab/numpy/{transform => }/transform.c (94%) rename python/port/mod/ulab/numpy/{transform => }/transform.h (75%) rename python/port/mod/ulab/numpy/{vector => }/vector.c (90%) rename python/port/mod/ulab/numpy/{vector => }/vector.h (99%) diff --git a/python/Makefile b/python/Makefile index d9ac8de1906..a957082c1d6 100644 --- a/python/Makefile +++ b/python/Makefile @@ -169,19 +169,20 @@ port_src += $(addprefix python/port/,\ mod/ulab/ulab_tools.c \ mod/ulab/ndarray.c \ mod/ulab/ndarray_properties.c \ - mod/ulab/numpy/approx/approx.c \ - mod/ulab/numpy/compare/compare.c \ + mod/ulab/numpy/approx.c \ + mod/ulab/numpy/compare.c \ mod/ulab/ulab_create.c \ mod/ulab/numpy/fft/fft.c \ mod/ulab/numpy/fft/fft_tools.c \ - mod/ulab/numpy/filter/filter.c \ + mod/ulab/numpy/filter.c \ mod/ulab/numpy/linalg/linalg.c \ mod/ulab/numpy/linalg/linalg_tools.c \ - mod/ulab/numpy/numerical/numerical.c \ - mod/ulab/numpy/poly/poly.c \ - mod/ulab/numpy/stats/stats.c \ - mod/ulab/numpy/transform/transform.c \ - mod/ulab/numpy/vector/vector.c \ + mod/ulab/numpy/ndarray/ndarray_iter.c \ + mod/ulab/numpy/numerical.c \ + mod/ulab/numpy/poly.c \ + mod/ulab/numpy/stats.c \ + mod/ulab/numpy/transform.c \ + mod/ulab/numpy/vector.c \ mod/ulab/numpy/numpy.c \ mod/ulab/scipy/scipy.c \ mod/ulab/user/user.c \ diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index 91c67f9e8cd..ec26ab46f6c 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -581,6 +581,10 @@ Q(listdir) #if defined(INCLUDE_ULAB) // ulab QSTRs +Q(reduced) +Q(qr) +Q(flat) +Q(flatiter) Q(threshold) Q(edgeitems) Q(inplace) diff --git a/python/port/mod/ulab/LICENSE b/python/port/mod/ulab/LICENSE new file mode 100644 index 00000000000..1d4df66d308 --- /dev/null +++ b/python/port/mod/ulab/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Zoltán Vörös + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/python/port/mod/ulab/ndarray.c b/python/port/mod/ulab/ndarray.c index e37802efe16..45e19c26255 100644 --- a/python/port/mod/ulab/ndarray.c +++ b/python/port/mod/ulab/ndarray.c @@ -16,11 +16,11 @@ #include #include #include -#include -#include -#include -#include -#include +#include "py/runtime.h" +#include "py/binary.h" +#include "py/obj.h" +#include "py/objtuple.h" +#include "py/objint.h" #include "ulab_tools.h" #include "ndarray.h" @@ -45,195 +45,6 @@ mp_uint_t ndarray_print_edgeitems = NDARRAY_PRINT_EDGEITEMS; //| possible. Numpy's documentation can be found at //| https://docs.scipy.org/doc/numpy/index.html""" //| -//| from typing import Dict -//| -//| _DType = int -//| """`ulab.int8`, `ulab.uint8`, `ulab.int16`, `ulab.uint16`, `ulab.float` or `ulab.bool`""" -//| -//| _float = float -//| """Type alias of the bulitin float""" -//| -//| _bool = bool -//| """Type alias of the bulitin bool""" -//| -//| _Index = Union[int, slice, ulab.ndarray, Tuple[Union[int, slice], ...]] -//| - -//| class ndarray: -//| """1- and 2- dimensional ndarray""" -//| -//| def __init__( -//| self, -//| values: Union[ndarray, Iterable[Union[_float, _bool, Iterable[Any]]]], -//| *, -//| dtype: _DType = ulab.float -//| ) -> None: -//| """:param sequence values: Sequence giving the initial content of the ndarray. -//| :param ~ulab._DType dtype: The type of ndarray values, `ulab.int8`, `ulab.uint8`, `ulab.int16`, `ulab.uint16`, `ulab.float` or `ulab.bool` -//| -//| The ``values`` sequence can either be another ~ulab.ndarray, sequence of numbers -//| (in which case a 1-dimensional ndarray is created), or a sequence where each -//| subsequence has the same length (in which case a 2-dimensional ndarray is -//| created). -//| -//| Passing a `ulab.ndarray` and a different dtype can be used to convert an ndarray -//| from one dtype to another. -//| -//| In many cases, it is more convenient to create an ndarray from a function -//| like `zeros` or `linspace`. -//| -//| `ulab.ndarray` implements the buffer protocol, so it can be used in many -//| places an `array.array` can be used.""" -//| ... -//| -//| shape: Tuple[int, ...] -//| """The size of the array, a tuple of length 1 or 2""" -//| -//| size: int -//| """The number of elements in the array""" -//| -//| itemsize: int -//| """The size of a single item in the array""" -//| -//| strides: Tuple[int, ...] -//| """Tuple of bytes to step in each dimension, a tuple of length 1 or 2""" -//| -//| def copy(self) -> ulab.ndarray: -//| """Return a copy of the array""" -//| ... -//| -//| def dtype(self) -> _DType: -//| """Returns the dtype of the array""" -//| ... -//| -//| def flatten(self, *, order: str = "C") -> ulab.ndarray: -//| """:param order: Whether to flatten by rows ('C') or columns ('F') -//| -//| Returns a new `ulab.ndarray` object which is always 1 dimensional. -//| If order is 'C' (the default", then the data is ordered in rows; -//| If it is 'F', then the data is ordered in columns. "C" and "F" refer -//| to the typical storage organization of the C and Fortran languages.""" -//| ... -//| -//| def reshape(self, shape: Tuple[int, ...]) -> ulab.ndarray: -//| """Returns an ndarray containing the same data with a new shape.""" -//| ... -//| -//| def sort(self, *, axis: Optional[int] = 1) -> None: -//| """:param axis: Whether to sort elements within rows (0), columns (1), or elements (None)""" -//| ... -//| -//| def tobytes(self) -> bytearray: -//| """Return the raw data bytes in the ndarray""" -//| ... -//| -//| def transpose(self) -> ulab.ndarray: -//| """Swap the rows and columns of a 2-dimensional ndarray""" -//| ... -//| -//| def __add__(self, other: Union[ndarray, _float]) -> ulab.ndarray: -//| """Adds corresponding elements of the two ndarrays, or adds a number to all -//| elements of the ndarray. If both arguments are ndarrays, their sizes must match.""" -//| ... -//| def __radd__(self, other: _float) -> ulab.ndarray: ... -//| -//| def __sub__(self, other: Union[ndarray, _float]) -> ulab.ndarray: -//| """Subtracts corresponding elements of the two ndarrays, or subtracts a number from all -//| elements of the ndarray. If both arguments are ndarrays, their sizes must match.""" -//| ... -//| def __rsub__(self, other: _float) -> ulab.ndarray: ... -//| -//| def __mul__(self, other: Union[ndarray, _float]) -> ulab.ndarray: -//| """Multiplies corresponding elements of the two ndarrays, or multiplies -//| all elements of the ndarray by a number. If both arguments are ndarrays, -//| their sizes must match.""" -//| ... -//| def __rmul__(self, other: _float) -> ulab.ndarray: ... -//| -//| def __div__(self, other: Union[ndarray, _float]) -> ulab.ndarray: -//| """Multiplies corresponding elements of the two ndarrays, or divides -//| all elements of the ndarray by a number. If both arguments are ndarrays, -//| their sizes must match.""" -//| ... -//| def __rdiv__(self, other: _float) -> ulab.ndarray: ... -//| -//| def __pow__(self, other: Union[ndarray, _float]) -> ulab.ndarray: -//| """Computes the power (x**y) of corresponding elements of the the two ndarrays, -//| or one number and one ndarray. If both arguments are ndarrays, their sizes -//| must match.""" -//| ... -//| def __rpow__(self, other: _float) -> ulab.ndarray: ... -//| -//| def __inv__(self) -> ulab.ndarray: -//| ... -//| def __neg__(self) -> ulab.ndarray: -//| ... -//| def __pos__(self) -> ulab.ndarray: -//| ... -//| def __abs__(self) -> ulab.ndarray: -//| ... -//| def __len__(self) -> int: -//| ... -//| def __lt__(self, other: Union[ndarray, _float]) -> ulab.ndarray: -//| ... -//| def __le__(self, other: Union[ndarray, _float]) -> ulab.ndarray: -//| ... -//| def __gt__(self, other: Union[ndarray, _float]) -> ulab.ndarray: -//| ... -//| def __ge__(self, other: Union[ndarray, _float]) -> ulab.ndarray: -//| ... -//| -//| def __iter__(self) -> Union[Iterator[ndarray], Iterator[_float]]: -//| ... -//| -//| def __getitem__(self, index: _Index) -> Union[ndarray, _float]: -//| """Retrieve an element of the ndarray.""" -//| ... -//| -//| def __setitem__(self, index: _Index, value: Union[ndarray, _float]) -> None: -//| """Set an element of the ndarray.""" -//| ... -//| -//| _ArrayLike = Union[ndarray, List[_float], Tuple[_float], range] -//| """`ulab.ndarray`, ``List[float]``, ``Tuple[float]`` or `range`""" -//| -//| int8: _DType -//| """Type code for signed integers in the range -128 .. 127 inclusive, like the 'b' typecode of `array.array`""" -//| -//| int16: _DType -//| """Type code for signed integers in the range -32768 .. 32767 inclusive, like the 'h' typecode of `array.array`""" -//| -//| float: _DType -//| """Type code for floating point values, like the 'f' typecode of `array.array`""" -//| -//| uint8: _DType -//| """Type code for unsigned integers in the range 0 .. 255 inclusive, like the 'H' typecode of `array.array`""" -//| -//| uint16: _DType -//| """Type code for unsigned integers in the range 0 .. 65535 inclusive, like the 'h' typecode of `array.array`""" -//| -//| bool: _DType -//| """Type code for boolean values""" -//| -//| def get_printoptions() -> Dict[str, int]: -//| """Get printing options""" -//| ... -//| -//| def set_printoptions(threshold: Optional[int] = None, edgeitems: Optional[int] = None) -> None: -//| """Set printing options""" -//| ... -//| -//| def ndinfo(array: ulab.ndarray) -> None: -//| ... -//| -//| def array( -//| values: Union[ndarray, Iterable[Union[_float, _bool, Iterable[Any]]]], -//| *, -//| dtype: _DType = ulab.float -//| ) -> ulab.ndarray: -//| """alternate constructor function for `ulab.ndarray`. Mirrors numpy.array""" -//| ... - #ifdef CIRCUITPY void ndarray_set_value(char typecode, void *p, size_t index, mp_obj_t val_in) { @@ -257,7 +68,7 @@ void ndarray_set_value(char typecode, void *p, size_t index, mp_obj_t val_in) { } #endif -#if defined(MICROPY_VERSION_MAJOR) && MICROPY_VERSION_MAJOR == 1 && MICROPY_VERSION_MINOR == 12 +#if defined(MICROPY_VERSION_MAJOR) && MICROPY_VERSION_MAJOR == 1 && MICROPY_VERSION_MINOR == 11 void mp_obj_slice_indices(mp_obj_t self_in, mp_int_t length, mp_bound_slice_t *result) { mp_obj_slice_t *self = MP_OBJ_TO_PTR(self_in); @@ -581,11 +392,13 @@ static void ndarray_print_row(const mp_print_t *print, ndarray_obj_t * ndarray, mp_print_str(print, "]"); } +#if ULAB_MAX_DIMS > 1 static void ndarray_print_bracket(const mp_print_t *print, const size_t condition, const size_t shape, const char *string) { if(condition < shape) { mp_print_str(print, string); } } +#endif void ndarray_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; @@ -596,9 +409,13 @@ void ndarray_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t ki mp_print_str(print, "[]"); if(self->ndim > 1) { mp_print_str(print, ", shape=("); + #if ULAB_MAX_DIMS > 1 for(uint8_t ndim = self->ndim; ndim > 1; ndim--) { mp_printf(MP_PYTHON_PRINTER, "%d,", self->shape[ULAB_MAX_DIMS - ndim]); } + #else + mp_printf(MP_PYTHON_PRINTER, "%d,", self->shape[0]); + #endif mp_printf(MP_PYTHON_PRINTER, "%d)", self->shape[ULAB_MAX_DIMS - 1]); } } else { @@ -1110,18 +927,6 @@ mp_obj_t ndarray_array_constructor(size_t n_args, const mp_obj_t *pos_args, mp_m } MP_DEFINE_CONST_FUN_OBJ_KW(ndarray_array_constructor_obj, 1, ndarray_array_constructor); -#ifdef CIRCUITPY -mp_obj_t ndarray_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { - (void) type; - mp_arg_check_num(n_args, kw_args, 1, 2, true); - size_t n_kw = 0; - if (kw_args != 0) { - n_kw = kw_args->used; - } - mp_map_init_fixed_table(kw_args, n_kw, args + n_args); - return ndarray_make_new_core(type, n_args, n_kw, args, kw_args); -} -#else mp_obj_t ndarray_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void) type; mp_arg_check_num(n_args, n_kw, 1, 2, true); @@ -1129,7 +934,6 @@ mp_obj_t ndarray_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); return ndarray_make_new_core(type, n_args, n_kw, args, &kw_args); } -#endif // broadcasting is used at a number of places, always include bool ndarray_can_broadcast(ndarray_obj_t *lhs, ndarray_obj_t *rhs, uint8_t *ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { @@ -1555,12 +1359,12 @@ mp_obj_t ndarray_iternext(mp_obj_t self_in) { mp_obj_t ndarray_new_ndarray_iterator(mp_obj_t ndarray, mp_obj_iter_buf_t *iter_buf) { assert(sizeof(mp_obj_ndarray_it_t) <= sizeof(mp_obj_iter_buf_t)); - mp_obj_ndarray_it_t *o = (mp_obj_ndarray_it_t*)iter_buf; - o->base.type = &mp_type_polymorph_iter; - o->iternext = ndarray_iternext; - o->ndarray = ndarray; - o->cur = 0; - return MP_OBJ_FROM_PTR(o); + mp_obj_ndarray_it_t *iter = (mp_obj_ndarray_it_t *)iter_buf; + iter->base.type = &mp_type_polymorph_iter; + iter->iternext = ndarray_iternext; + iter->ndarray = ndarray; + iter->cur = 0; + return MP_OBJ_FROM_PTR(iter); } #endif /* NDARRAY_IS_ITERABLE */ @@ -1675,12 +1479,13 @@ mp_obj_t ndarray_itemsize(mp_obj_t self_in) { #if NDARRAY_HAS_SHAPE mp_obj_t ndarray_shape(mp_obj_t self_in) { ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_obj_t *items = m_new(mp_obj_t, self->ndim); - for(uint8_t i=0; i < self->ndim; i++) { - items[self->ndim - i - 1] = mp_obj_new_int(self->shape[ULAB_MAX_DIMS - i - 1]); + uint8_t nitems = MAX(1, self->ndim); + mp_obj_t *items = m_new(mp_obj_t, nitems); + for(uint8_t i = 0; i < nitems; i++) { + items[nitems - i - 1] = mp_obj_new_int(self->shape[ULAB_MAX_DIMS - i - 1]); } - mp_obj_t tuple = mp_obj_new_tuple(self->ndim, items); - m_del(mp_obj_t, items, self->ndim); + mp_obj_t tuple = mp_obj_new_tuple(nitems, items); + m_del(mp_obj_t, items, nitems); return tuple; } #endif diff --git a/python/port/mod/ulab/ndarray.h b/python/port/mod/ulab/ndarray.h index aa7f499af01..04abd96598a 100644 --- a/python/port/mod/ulab/ndarray.h +++ b/python/port/mod/ulab/ndarray.h @@ -39,7 +39,7 @@ typedef struct _mp_obj_float_t { mp_float_t value; } mp_obj_float_t; -#if defined(MICROPY_VERSION_MAJOR) && MICROPY_VERSION_MAJOR == 1 && MICROPY_VERSION_MINOR == 12 +#if defined(MICROPY_VERSION_MAJOR) && MICROPY_VERSION_MAJOR == 1 && MICROPY_VERSION_MINOR == 11 typedef struct _mp_obj_slice_t { mp_obj_base_t base; mp_obj_t start; @@ -49,6 +49,13 @@ typedef struct _mp_obj_slice_t { #define MP_ERROR_TEXT(x) x #endif +#if !defined(MP_TYPE_FLAG_EXTENDED) +#define MP_TYPE_CALL call +#define mp_type_get_call_slot(t) t->call +#define MP_TYPE_FLAG_EXTENDED (0) +#define MP_TYPE_EXTENDED_FIELDS(...) __VA_ARGS__ +#endif + #if !CIRCUITPY #define translate(x) MP_ERROR_TEXT(x) #define ndarray_set_value(a, b, c, d) mp_binary_set_val_array(a, b, c, d) @@ -96,15 +103,14 @@ typedef struct _dtype_obj_t { void ndarray_dtype_print(const mp_print_t *, mp_obj_t , mp_print_kind_t ); -#ifdef CIRCUITPY -mp_obj_t ndarray_dtype_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args); -#else mp_obj_t ndarray_dtype_make_new(const mp_obj_type_t *, size_t , size_t , const mp_obj_t *); -#endif /* CIRCUITPY */ #endif /* ULAB_HAS_DTYPE_OBJECT */ +extern const mp_obj_type_t ndarray_flatiter_type; + mp_obj_t ndarray_new_ndarray_iterator(mp_obj_t , mp_obj_iter_buf_t *); +mp_obj_t ndarray_get_item(ndarray_obj_t *, void *); mp_float_t ndarray_get_float_value(void *, uint8_t ); mp_float_t ndarray_get_float_index(void *, uint8_t , size_t ); bool ndarray_object_is_array_like(mp_obj_t ); @@ -135,11 +141,7 @@ ndarray_obj_t *ndarray_copy_view(ndarray_obj_t *); void ndarray_copy_array(ndarray_obj_t *, ndarray_obj_t *); MP_DECLARE_CONST_FUN_OBJ_KW(ndarray_array_constructor_obj); -#ifdef CIRCUITPY -mp_obj_t ndarray_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args); -#else mp_obj_t ndarray_make_new(const mp_obj_type_t *, size_t , size_t , const mp_obj_t *); -#endif mp_obj_t ndarray_subscr(mp_obj_t , mp_obj_t , mp_obj_t ); mp_obj_t ndarray_getiter(mp_obj_t , mp_obj_iter_buf_t *); bool ndarray_can_broadcast(ndarray_obj_t *, ndarray_obj_t *, uint8_t *, size_t *, int32_t *, int32_t *); diff --git a/python/port/mod/ulab/ndarray_operators.c b/python/port/mod/ulab/ndarray_operators.c index ac238605307..465140b65b0 100644 --- a/python/port/mod/ulab/ndarray_operators.c +++ b/python/port/mod/ulab/ndarray_operators.c @@ -11,8 +11,8 @@ #include -#include -#include +#include "py/runtime.h" +#include "py/objtuple.h" #include "ndarray.h" #include "ndarray_operators.h" #include "ulab.h" diff --git a/python/port/mod/ulab/ndarray_properties.c b/python/port/mod/ulab/ndarray_properties.c index 0fcf83bca9e..4a93fb82375 100644 --- a/python/port/mod/ulab/ndarray_properties.c +++ b/python/port/mod/ulab/ndarray_properties.c @@ -14,11 +14,12 @@ #include #include #include -#include -#include +#include "py/obj.h" +#include "py/runtime.h" #include "ulab.h" #include "ndarray.h" +#include "numpy/ndarray/ndarray_iter.h" #ifndef CIRCUITPY @@ -51,6 +52,11 @@ void ndarray_properties_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { dest[0] = ndarray_dtype(self_in); break; #endif + #if NDARRAY_HAS_FLATITER + case MP_QSTR_flat: + dest[0] = ndarray_flatiter_make_new(self_in); + break; + #endif #if NDARRAY_HAS_ITEMSIZE case MP_QSTR_itemsize: dest[0] = ndarray_itemsize(self_in); @@ -83,11 +89,13 @@ void ndarray_properties_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { } else { if(dest[1]) { switch(attr) { + #if ULAB_MAX_DIMS > 1 #if NDARRAY_HAS_RESHAPE case MP_QSTR_shape: ndarray_reshape_core(self_in, dest[1], 1); break; #endif + #endif default: return; break; diff --git a/python/port/mod/ulab/ndarray_properties.h b/python/port/mod/ulab/ndarray_properties.h index 9d3b4ee9b81..28da7c06315 100644 --- a/python/port/mod/ulab/ndarray_properties.h +++ b/python/port/mod/ulab/ndarray_properties.h @@ -7,19 +7,20 @@ * The MIT License (MIT) * * Copyright (c) 2020 Jeff Epler for Adafruit Industries - * 2020 Zoltán Vörös + * 2020-2021 Zoltán Vörös */ #ifndef _NDARRAY_PROPERTIES_ #define _NDARRAY_PROPERTIES_ -#include -#include -#include -#include +#include "py/runtime.h" +#include "py/binary.h" +#include "py/obj.h" +#include "py/objarray.h" #include "ulab.h" #include "ndarray.h" +#include "numpy/ndarray/ndarray_iter.h" #if CIRCUITPY typedef struct _mp_obj_property_t { @@ -37,6 +38,16 @@ STATIC const mp_obj_property_t ndarray_dtype_obj = { }; #endif /* NDARRAY_HAS_DTYPE */ +#if NDARRAY_HAS_FLATITER +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_flatiter_make_new_obj, ndarray_flatiter_make_new); +STATIC const mp_obj_property_t ndarray_flat_obj = { + .base.type = &mp_type_property, + .proxy = {(mp_obj_t)&ndarray_flatiter_make_new_obj, + mp_const_none, + mp_const_none }, +}; +#endif /* NDARRAY_HAS_FLATITER */ + #if NDARRAY_HAS_ITEMSIZE MP_DEFINE_CONST_FUN_OBJ_1(ndarray_get_itemsize_obj, ndarray_itemsize); STATIC const mp_obj_property_t ndarray_itemsize_obj = { @@ -82,6 +93,7 @@ STATIC const mp_obj_property_t ndarray_strides_obj = { void ndarray_properties_attr(mp_obj_t , qstr , mp_obj_t *); MP_DEFINE_CONST_FUN_OBJ_1(ndarray_dtype_obj, ndarray_dtype); +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_flatiter_make_new_obj, ndarray_flatiter_make_new); MP_DEFINE_CONST_FUN_OBJ_1(ndarray_itemsize_obj, ndarray_itemsize); MP_DEFINE_CONST_FUN_OBJ_1(ndarray_shape_obj, ndarray_shape); MP_DEFINE_CONST_FUN_OBJ_1(ndarray_size_obj, ndarray_size); diff --git a/python/port/mod/ulab/numpy/approx/approx.c b/python/port/mod/ulab/numpy/approx.c similarity index 89% rename from python/port/mod/ulab/numpy/approx/approx.c rename to python/port/mod/ulab/numpy/approx.c index 706a969a799..6ed5d7c2d7e 100644 --- a/python/port/mod/ulab/numpy/approx/approx.c +++ b/python/port/mod/ulab/numpy/approx.c @@ -13,12 +13,12 @@ #include #include #include -#include -#include -#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" -#include "../../ulab.h" -#include "../../ulab_tools.h" +#include "../ulab.h" +#include "../ulab_tools.h" #include "approx.h" //| """Numerical approximation methods""" @@ -28,17 +28,17 @@ const mp_obj_float_t approx_trapz_dx = {{&mp_type_float}, MICROPY_FLOAT_CONST(1. #if ULAB_NUMPY_HAS_INTERP //| def interp( -//| x: ulab.ndarray, -//| xp: ulab.ndarray, -//| fp: ulab.ndarray, +//| x: ulab.numpy.ndarray, +//| xp: ulab.numpy.ndarray, +//| fp: ulab.numpy.ndarray, //| *, -//| left: Optional[float] = None, -//| right: Optional[float] = None -//| ) -> ulab.ndarray: +//| left: Optional[_float] = None, +//| right: Optional[_float] = None +//| ) -> ulab.numpy.ndarray: //| """ -//| :param ulab.ndarray x: The x-coordinates at which to evaluate the interpolated values. -//| :param ulab.ndarray xp: The x-coordinates of the data points, must be increasing -//| :param ulab.ndarray fp: The y-coordinates of the data points, same length as xp +//| :param ulab.numpy.ndarray x: The x-coordinates at which to evaluate the interpolated values. +//| :param ulab.numpy.ndarray xp: The x-coordinates of the data points, must be increasing +//| :param ulab.numpy.ndarray fp: The y-coordinates of the data points, same length as xp //| :param left: Value to return for ``x < xp[0]``, default is ``fp[0]``. //| :param right: Value to return for ``x > xp[-1]``, default is ``fp[-1]``. //| @@ -136,10 +136,10 @@ MP_DEFINE_CONST_FUN_OBJ_KW(approx_interp_obj, 2, approx_interp); #endif #if ULAB_NUMPY_HAS_TRAPZ -//| def trapz(y: ulab.ndarray, x: Optional[ulab.ndarray] = None, dx: float = 1.0) -> float: +//| def trapz(y: ulab.numpy.ndarray, x: Optional[ulab.numpy.ndarray] = None, dx: _float = 1.0) -> _float: //| """ -//| :param 1D ulab.ndarray y: the values of the dependent variable -//| :param 1D ulab.ndarray x: optional, the coordinates of the independent variable. Defaults to uniformly spaced values. +//| :param 1D ulab.numpy.ndarray y: the values of the dependent variable +//| :param 1D ulab.numpy.ndarray x: optional, the coordinates of the independent variable. Defaults to uniformly spaced values. //| :param float dx: the spacing between sample points, if x=None //| //| Returns the integral of y(x) using the trapezoidal rule. diff --git a/python/port/mod/ulab/numpy/approx/approx.h b/python/port/mod/ulab/numpy/approx.h similarity index 93% rename from python/port/mod/ulab/numpy/approx/approx.h rename to python/port/mod/ulab/numpy/approx.h index 7708bb78977..487a98b5878 100644 --- a/python/port/mod/ulab/numpy/approx/approx.h +++ b/python/port/mod/ulab/numpy/approx.h @@ -12,8 +12,8 @@ #ifndef _APPROX_ #define _APPROX_ -#include "../../ulab.h" -#include "../../ndarray.h" +#include "../ulab.h" +#include "../ndarray.h" #define APPROX_EPS MICROPY_FLOAT_CONST(1.0e-4) #define APPROX_NONZDELTA MICROPY_FLOAT_CONST(0.05) diff --git a/python/port/mod/ulab/numpy/compare/compare.c b/python/port/mod/ulab/numpy/compare.c similarity index 98% rename from python/port/mod/ulab/numpy/compare/compare.c rename to python/port/mod/ulab/numpy/compare.c index 73dfaa135fe..b9154569c91 100644 --- a/python/port/mod/ulab/numpy/compare/compare.c +++ b/python/port/mod/ulab/numpy/compare.c @@ -13,13 +13,13 @@ #include #include #include -#include -#include -#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" -#include "../../ulab.h" -#include "../../ndarray_operators.h" -#include "../../ulab_tools.h" +#include "../ulab.h" +#include "../ndarray_operators.h" +#include "../ulab_tools.h" #include "compare.h" static mp_obj_t compare_function(mp_obj_t x1, mp_obj_t x2, uint8_t op) { @@ -89,7 +89,7 @@ static mp_obj_t compare_function(mp_obj_t x1, mp_obj_t x2, uint8_t op) { } else if(rhs->dtype == NDARRAY_INT16) { RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, uint16_t, int16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); } else if(rhs->dtype == NDARRAY_FLOAT) { - RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, ndim, shape, op); } } else if(lhs->dtype == NDARRAY_INT16) { if(rhs->dtype == NDARRAY_UINT8) { @@ -101,7 +101,7 @@ static mp_obj_t compare_function(mp_obj_t x1, mp_obj_t x2, uint8_t op) { } else if(rhs->dtype == NDARRAY_INT16) { RUN_COMPARE_LOOP(NDARRAY_INT16, int16_t, int16_t, int16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); } else if(rhs->dtype == NDARRAY_FLOAT) { - RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, int16_t, mp_float_t, larray, lstrides, rarray, rstrides, ndim, shape, op); } } else if(lhs->dtype == NDARRAY_FLOAT) { if(rhs->dtype == NDARRAY_UINT8) { diff --git a/python/port/mod/ulab/numpy/compare/compare.h b/python/port/mod/ulab/numpy/compare.h similarity index 99% rename from python/port/mod/ulab/numpy/compare/compare.h rename to python/port/mod/ulab/numpy/compare.h index 12a559e0af7..90ceaf7cea7 100644 --- a/python/port/mod/ulab/numpy/compare/compare.h +++ b/python/port/mod/ulab/numpy/compare.h @@ -12,8 +12,8 @@ #ifndef _COMPARE_ #define _COMPARE_ -#include "../../ulab.h" -#include "../../ndarray.h" +#include "../ulab.h" +#include "../ndarray.h" enum COMPARE_FUNCTION_TYPE { COMPARE_EQUAL, diff --git a/python/port/mod/ulab/numpy/fft/fft.c b/python/port/mod/ulab/numpy/fft/fft.c index ff2373fe0a8..6f6534f497b 100644 --- a/python/port/mod/ulab/numpy/fft/fft.c +++ b/python/port/mod/ulab/numpy/fft/fft.c @@ -14,22 +14,23 @@ #include #include #include -#include -#include -#include -#include -#include +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/binary.h" +#include "py/obj.h" +#include "py/objarray.h" #include "fft.h" //| """Frequency-domain functions""" //| +//| import ulab.numpy -//| def fft(r: ulab.ndarray, c: Optional[ulab.ndarray] = None) -> Tuple[ulab.ndarray, ulab.ndarray]: +//| def fft(r: ulab.numpy.ndarray, c: Optional[ulab.numpy.ndarray] = None) -> Tuple[ulab.numpy.ndarray, ulab.numpy.ndarray]: //| """ -//| :param ulab.ndarray r: A 1-dimension array of values whose size is a power of 2 -//| :param ulab.ndarray c: An optional 1-dimension array of values whose size is a power of 2, giving the complex part of the value +//| :param ulab.numpy.ndarray r: A 1-dimension array of values whose size is a power of 2 +//| :param ulab.numpy.ndarray c: An optional 1-dimension array of values whose size is a power of 2, giving the complex part of the value //| :return tuple (r, c): The real and complex parts of the FFT //| //| Perform a Fast Fourier Transform from the time domain into the frequency domain @@ -48,10 +49,10 @@ static mp_obj_t fft_fft(size_t n_args, const mp_obj_t *args) { MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_fft_obj, 1, 2, fft_fft); -//| def ifft(r: ulab.ndarray, c: Optional[ulab.ndarray] = None) -> Tuple[ulab.ndarray, ulab.ndarray]: +//| def ifft(r: ulab.numpy.ndarray, c: Optional[ulab.numpy.ndarray] = None) -> Tuple[ulab.numpy.ndarray, ulab.numpy.ndarray]: //| """ -//| :param ulab.ndarray r: A 1-dimension array of values whose size is a power of 2 -//| :param ulab.ndarray c: An optional 1-dimension array of values whose size is a power of 2, giving the complex part of the value +//| :param ulab.numpy.ndarray r: A 1-dimension array of values whose size is a power of 2 +//| :param ulab.numpy.ndarray c: An optional 1-dimension array of values whose size is a power of 2, giving the complex part of the value //| :return tuple (r, c): The real and complex parts of the inverse FFT //| //| Perform an Inverse Fast Fourier Transform from the frequeny domain into the time domain""" diff --git a/python/port/mod/ulab/numpy/fft/fft_tools.c b/python/port/mod/ulab/numpy/fft/fft_tools.c index ab737a85051..6dd2ca47cad 100644 --- a/python/port/mod/ulab/numpy/fft/fft_tools.c +++ b/python/port/mod/ulab/numpy/fft/fft_tools.c @@ -9,7 +9,7 @@ */ #include -#include +#include "py/runtime.h" #include "../../ndarray.h" #include "../../ulab_tools.h" diff --git a/python/port/mod/ulab/numpy/filter/filter.c b/python/port/mod/ulab/numpy/filter.c similarity index 95% rename from python/port/mod/ulab/numpy/filter/filter.c rename to python/port/mod/ulab/numpy/filter.c index 9be43946b17..bf2d16cd40c 100644 --- a/python/port/mod/ulab/numpy/filter/filter.c +++ b/python/port/mod/ulab/numpy/filter.c @@ -15,12 +15,12 @@ #include #include #include -#include -#include -#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" -#include "../../ulab.h" -#include "../../scipy/signal/signal.h" +#include "../ulab.h" +#include "../scipy/signal/signal.h" #include "filter.h" #if ULAB_NUMPY_HAS_CONVOLVE @@ -57,7 +57,7 @@ mp_obj_t filter_convolve(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_a mp_float_t *outptr = (mp_float_t *)out->array; uint8_t *aarray = (uint8_t *)a->array; uint8_t *carray = (uint8_t *)c->array; - + int32_t off = len_c - 1; int32_t as = a->strides[ULAB_MAX_DIMS - 1] / a->itemsize; int32_t cs = c->strides[ULAB_MAX_DIMS - 1] / c->itemsize; diff --git a/python/port/mod/ulab/numpy/filter/filter.h b/python/port/mod/ulab/numpy/filter.h similarity index 86% rename from python/port/mod/ulab/numpy/filter/filter.h rename to python/port/mod/ulab/numpy/filter.h index 9b1ad1875bb..d6d0f17238d 100644 --- a/python/port/mod/ulab/numpy/filter/filter.h +++ b/python/port/mod/ulab/numpy/filter.h @@ -13,8 +13,8 @@ #ifndef _FILTER_ #define _FILTER_ -#include "../../ulab.h" -#include "../../ndarray.h" +#include "../ulab.h" +#include "../ndarray.h" MP_DECLARE_CONST_FUN_OBJ_KW(filter_convolve_obj); #endif diff --git a/python/port/mod/ulab/numpy/linalg/linalg.c b/python/port/mod/ulab/numpy/linalg/linalg.c index 209330f865c..596280feaad 100644 --- a/python/port/mod/ulab/numpy/linalg/linalg.c +++ b/python/port/mod/ulab/numpy/linalg/linalg.c @@ -16,9 +16,9 @@ #include #include #include -#include -#include -#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" #include "../../ulab.h" #include "../../ulab_tools.h" @@ -364,7 +364,141 @@ static mp_obj_t linalg_norm(size_t n_args, const mp_obj_t *pos_args, mp_map_t *k } MP_DEFINE_CONST_FUN_OBJ_KW(linalg_norm_obj, 1, linalg_norm); -// MP_DEFINE_CONST_FUN_OBJ_1(linalg_norm_obj, linalg_norm); + +#if ULAB_MAX_DIMS > 1 +//| def qr(m: ulab.numpy.ndarray) -> Tuple[ulab.numpy.ndarray, ulab.numpy.ndarray]: +//| """ +//| :param m: a matrix +//| :return tuple (Q, R): +//| +//| Factor the matrix a as QR, where Q is orthonormal and R is upper-triangular. +//| """ +//| ... +//| + +static mp_obj_t linalg_qr(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_mode, MP_ARG_OBJ, { .u_rom_obj = MP_ROM_QSTR(MP_QSTR_reduced) } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("operation is defined for ndarrays only")); + } + ndarray_obj_t *source = MP_OBJ_TO_PTR(args[0].u_obj); + if(source->ndim != 2) { + mp_raise_ValueError(translate("operation is defined for 2D arrays only")); + } + + size_t m = source->shape[ULAB_MAX_DIMS - 2]; // rows + size_t n = source->shape[ULAB_MAX_DIMS - 1]; // columns + + ndarray_obj_t *Q = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, m, m), NDARRAY_FLOAT); + ndarray_obj_t *R = ndarray_new_dense_ndarray(2, source->shape, NDARRAY_FLOAT); + + mp_float_t *qarray = (mp_float_t *)Q->array; + mp_float_t *rarray = (mp_float_t *)R->array; + + // simply copy the entries of source to a float array + mp_float_t (*func)(void *) = ndarray_get_float_function(source->dtype); + uint8_t *sarray = (uint8_t *)source->array; + + for(size_t i = 0; i < m; i++) { + for(size_t j = 0; j < n; j++) { + *rarray++ = func(sarray); + sarray += source->strides[ULAB_MAX_DIMS - 1]; + } + sarray -= n * source->strides[ULAB_MAX_DIMS - 1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + } + rarray -= m * n; + + // start with the unit matrix + for(size_t i = 0; i < m; i++) { + qarray[i * (m + 1)] = 1.0; + } + + for(size_t j = 0; j < n; j++) { // columns + for(size_t i = m - 1; i > j; i--) { // rows + mp_float_t c, s; + // Givens matrix: note that numpy uses a strange form of the rotation + // [[c s], + // [s -c]] + if(MICROPY_FLOAT_C_FUN(fabs)(rarray[i * n + j]) < LINALG_EPSILON) { // r[i, j] + c = (rarray[(i - 1) * n + j] >= 0.0) ? 1.0 : -1.0; // r[i-1, j] + s = 0.0; + } else if(MICROPY_FLOAT_C_FUN(fabs)(rarray[(i - 1) * n + j]) < LINALG_EPSILON) { // r[i-1, j] + c = 0.0; + s = (rarray[i * n + j] >= 0.0) ? -1.0 : 1.0; // r[i, j] + } else { + mp_float_t t, u; + if(MICROPY_FLOAT_C_FUN(fabs)(rarray[(i - 1) * n + j]) > MICROPY_FLOAT_C_FUN(fabs)(rarray[i * n + j])) { // r[i-1, j], r[i, j] + t = rarray[i * n + j] / rarray[(i - 1) * n + j]; // r[i, j]/r[i-1, j] + u = MICROPY_FLOAT_C_FUN(sqrt)(1 + t * t); + c = -1.0 / u; + s = c * t; + } else { + t = rarray[(i - 1) * n + j] / rarray[i * n + j]; // r[i-1, j]/r[i, j] + u = MICROPY_FLOAT_C_FUN(sqrt)(1 + t * t); + s = -1.0 / u; + c = s * t; + } + } + + mp_float_t r1, r2; + // update R: multiply with the rotation matrix from the left + for(size_t k = 0; k < n; k++) { + r1 = rarray[(i - 1) * n + k]; // r[i-1, k] + r2 = rarray[i * n + k]; // r[i, k] + rarray[(i - 1) * n + k] = c * r1 + s * r2; // r[i-1, k] + rarray[i * n + k] = s * r1 - c * r2; // r[i, k] + } + + // update Q: multiply with the transpose of the rotation matrix from the right + for(size_t k = 0; k < m; k++) { + r1 = qarray[k * m + (i - 1)]; + r2 = qarray[k * m + i]; + qarray[k * m + (i - 1)] = c * r1 + s * r2; + qarray[k * m + i] = s * r1 - c * r2; + } + } + } + + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + GET_STR_DATA_LEN(args[1].u_obj, mode, len); + if(memcmp(mode, "complete", 8) == 0) { + tuple->items[0] = MP_OBJ_FROM_PTR(Q); + tuple->items[1] = MP_OBJ_FROM_PTR(R); + } else if(memcmp(mode, "reduced", 7) == 0) { + size_t k = MAX(m, n) - MIN(m, n); + ndarray_obj_t *q = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, m, m - k), NDARRAY_FLOAT); + ndarray_obj_t *r = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, m - k, n), NDARRAY_FLOAT); + mp_float_t *qa = (mp_float_t *)q->array; + mp_float_t *ra = (mp_float_t *)r->array; + for(size_t i = 0; i < m; i++) { + memcpy(qa, qarray, (m - k) * q->itemsize); + qa += (m - k); + qarray += m; + } + for(size_t i = 0; i < m - k; i++) { + memcpy(ra, rarray, n * r->itemsize); + ra += n; + rarray += n; + } + tuple->items[0] = MP_OBJ_FROM_PTR(q); + tuple->items[1] = MP_OBJ_FROM_PTR(r); + } else { + mp_raise_ValueError(translate("mode must be complete, or reduced")); + } + return tuple; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(linalg_qr_obj, 1, linalg_qr); +#endif STATIC const mp_rom_map_elem_t ulab_linalg_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_linalg) }, @@ -381,6 +515,9 @@ STATIC const mp_rom_map_elem_t ulab_linalg_globals_table[] = { #if ULAB_LINALG_HAS_INV { MP_ROM_QSTR(MP_QSTR_inv), (mp_obj_t)&linalg_inv_obj }, #endif + #if ULAB_LINALG_HAS_QR + { MP_ROM_QSTR(MP_QSTR_qr), (mp_obj_t)&linalg_qr_obj }, + #endif #endif #if ULAB_LINALG_HAS_NORM { MP_ROM_QSTR(MP_QSTR_norm), (mp_obj_t)&linalg_norm_obj }, diff --git a/python/port/mod/ulab/numpy/linalg/linalg.h b/python/port/mod/ulab/numpy/linalg/linalg.h index fc1867fb8fd..cf6b0adc79f 100644 --- a/python/port/mod/ulab/numpy/linalg/linalg.h +++ b/python/port/mod/ulab/numpy/linalg/linalg.h @@ -23,4 +23,5 @@ MP_DECLARE_CONST_FUN_OBJ_1(linalg_det_obj); MP_DECLARE_CONST_FUN_OBJ_1(linalg_eig_obj); MP_DECLARE_CONST_FUN_OBJ_1(linalg_inv_obj); MP_DECLARE_CONST_FUN_OBJ_KW(linalg_norm_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(linalg_qr_obj); #endif diff --git a/python/port/mod/ulab/numpy/linalg/linalg_tools.c b/python/port/mod/ulab/numpy/linalg/linalg_tools.c index cf48f114031..5e03a50ab59 100644 --- a/python/port/mod/ulab/numpy/linalg/linalg_tools.c +++ b/python/port/mod/ulab/numpy/linalg/linalg_tools.c @@ -10,7 +10,7 @@ #include #include -#include +#include "py/runtime.h" #include "linalg_tools.h" diff --git a/python/port/mod/ulab/numpy/ndarray/ndarray_iter.c b/python/port/mod/ulab/numpy/ndarray/ndarray_iter.c new file mode 100644 index 00000000000..8704836a333 --- /dev/null +++ b/python/port/mod/ulab/numpy/ndarray/ndarray_iter.c @@ -0,0 +1,66 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Zoltán Vörös + * +*/ + +#include +#include +#include +#include +#include "py/obj.h" +#include "py/runtime.h" + +#include "ndarray_iter.h" + +#ifdef NDARRAY_HAS_FLATITER +mp_obj_t ndarray_flatiter_make_new(mp_obj_t self_in) { + ndarray_flatiter_t *flatiter = m_new_obj(ndarray_flatiter_t); + flatiter->base.type = &ndarray_flatiter_type; + flatiter->iternext = ndarray_flatiter_next; + flatiter->ndarray = MP_OBJ_TO_PTR(self_in); + flatiter->cur = 0; + return flatiter; +} + +mp_obj_t ndarray_flatiter_next(mp_obj_t self_in) { + ndarray_flatiter_t *self = MP_OBJ_TO_PTR(self_in); + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(self->ndarray); + uint8_t *array = (uint8_t *)ndarray->array; + + if(self->cur < ndarray->len) { + uint32_t remainder = self->cur; + uint8_t i = ULAB_MAX_DIMS - 1; + do { + size_t div = (remainder / ndarray->shape[i]); + array += remainder * ndarray->strides[i]; + remainder -= div * ndarray->shape[i]; + i--; + } while(i > ULAB_MAX_DIMS - ndarray->ndim); + self->cur++; + return ndarray_get_item(ndarray, array); + } + return MP_OBJ_STOP_ITERATION; +} + +mp_obj_t ndarray_new_flatiterator(mp_obj_t flatiter_in, mp_obj_iter_buf_t *iter_buf) { + assert(sizeof(ndarray_flatiter_t) <= sizeof(mp_obj_iter_buf_t)); + ndarray_flatiter_t *iter = (ndarray_flatiter_t *)iter_buf; + ndarray_flatiter_t *flatiter = MP_OBJ_TO_PTR(flatiter_in); + iter->base.type = &mp_type_polymorph_iter; + iter->iternext = ndarray_flatiter_next; + iter->ndarray = flatiter->ndarray; + iter->cur = 0; + return MP_OBJ_FROM_PTR(iter); +} + +mp_obj_t ndarray_get_flatiterator(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { + return ndarray_new_flatiterator(o_in, iter_buf); +} +#endif /* NDARRAY_HAS_FLATITER */ diff --git a/python/port/mod/ulab/numpy/ndarray/ndarray_iter.h b/python/port/mod/ulab/numpy/ndarray/ndarray_iter.h new file mode 100644 index 00000000000..b3fc48db2a7 --- /dev/null +++ b/python/port/mod/ulab/numpy/ndarray/ndarray_iter.h @@ -0,0 +1,36 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020-2021 Zoltán Vörös +*/ + +#ifndef _NDARRAY_ITER_ +#define _NDARRAY_ITER_ + +#include "py/runtime.h" +#include "py/binary.h" +#include "py/obj.h" +#include "py/objarray.h" + +#include "../../ulab.h" +#include "../../ndarray.h" + +// TODO: take simply mp_obj_ndarray_it_t from ndarray.c +typedef struct _mp_obj_ndarray_flatiter_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + mp_obj_t ndarray; + size_t cur; +} ndarray_flatiter_t; + +mp_obj_t ndarray_get_flatiterator(mp_obj_t , mp_obj_iter_buf_t *); +mp_obj_t ndarray_flatiter_make_new(mp_obj_t ); +mp_obj_t ndarray_flatiter_next(mp_obj_t ); + +#endif \ No newline at end of file diff --git a/python/port/mod/ulab/numpy/numerical/numerical.c b/python/port/mod/ulab/numpy/numerical.c similarity index 95% rename from python/port/mod/ulab/numpy/numerical/numerical.c rename to python/port/mod/ulab/numpy/numerical.c index 7ee4edfa7aa..39a5f979147 100644 --- a/python/port/mod/ulab/numpy/numerical/numerical.c +++ b/python/port/mod/ulab/numpy/numerical.c @@ -14,14 +14,14 @@ #include #include #include -#include -#include -#include -#include -#include - -#include "../../ulab.h" -#include "../../ulab_tools.h" +#include "py/obj.h" +#include "py/objint.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/misc.h" + +#include "../ulab.h" +#include "../ulab_tools.h" #include "numerical.h" enum NUMERICAL_FUNCTION_TYPE { @@ -41,7 +41,33 @@ enum NUMERICAL_FUNCTION_TYPE { //| Most of these functions take an "axis" argument, which indicates whether to //| operate over the flattened array (None), or a particular axis (integer).""" //| -//| from ulab import _ArrayLike +//| from typing import Dict +//| +//| _ArrayLike = Union[ndarray, List[_float], Tuple[_float], range] +//| +//| _DType = int +//| """`ulab.numpy.int8`, `ulab.numpy.uint8`, `ulab.numpy.int16`, `ulab.numpy.uint16`, `ulab.numpy.float` or `ulab.numpy.bool`""" +//| +//| from builtins import float as _float +//| from builtins import bool as _bool +//| +//| int8: _DType +//| """Type code for signed integers in the range -128 .. 127 inclusive, like the 'b' typecode of `array.array`""" +//| +//| int16: _DType +//| """Type code for signed integers in the range -32768 .. 32767 inclusive, like the 'h' typecode of `array.array`""" +//| +//| float: _DType +//| """Type code for floating point values, like the 'f' typecode of `array.array`""" +//| +//| uint8: _DType +//| """Type code for unsigned integers in the range 0 .. 255 inclusive, like the 'H' typecode of `array.array`""" +//| +//| uint16: _DType +//| """Type code for unsigned integers in the range 0 .. 65535 inclusive, like the 'h' typecode of `array.array`""" +//| +//| bool: _DType +//| """Type code for boolean values""" //| static void numerical_reduce_axes(ndarray_obj_t *ndarray, int8_t axis, size_t *shape, int32_t *strides) { @@ -78,8 +104,7 @@ static mp_obj_t numerical_all_any(mp_obj_t oin, mp_obj_t axis, uint8_t optype) { } // always get a float, so that we don't have to resolve the dtype later mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype); - // We set results to true here because it crash if it is NULL - ndarray_obj_t *results = mp_const_true; + ndarray_obj_t *results = NULL; uint8_t *rarray = NULL; shape_strides _shape_strides = tools_reduce_axes(ndarray, axis); if(axis != mp_const_none) { @@ -153,8 +178,16 @@ static mp_obj_t numerical_all_any(mp_obj_t oin, mp_obj_t axis, uint8_t optype) { array -= _shape_strides.strides[ULAB_MAX_DIMS - 2] * _shape_strides.shape[ULAB_MAX_DIMS - 2]; array += _shape_strides.strides[ULAB_MAX_DIMS - 3]; i++; - } while(i < _shape_strides.shape[ULAB_MAX_DIMS - 3]) + } while(i < _shape_strides.shape[ULAB_MAX_DIMS - 3]); #endif + if(axis == mp_const_none) { + // the innermost loop fell through, so return the result here + if(!anytype) { + return mp_const_false; + } else { + return mp_const_true; + } + } return results; } else if(mp_obj_is_int(oin) || mp_obj_is_float(oin)) { return mp_obj_is_true(oin) ? mp_const_true : mp_const_false; @@ -265,7 +298,7 @@ static mp_obj_t numerical_sum_mean_std_ndarray(ndarray_obj_t *ndarray, mp_obj_t if(ndarray->dtype == NDARRAY_FLOAT) { return mp_obj_new_float(M * ndarray->len); } else { - return mp_obj_new_int((int32_t)(M * ndarray->len)); + return mp_obj_new_int((int32_t)MICROPY_FLOAT_C_FUN(round)(M * ndarray->len)); } } else if(optype == NUMERICAL_MEAN) { return mp_obj_new_float(M); @@ -551,6 +584,7 @@ static mp_obj_t numerical_sort_helper(mp_obj_t oin, mp_obj_t axis, uint8_t inpla int8_t ax = 0; if(axis == mp_const_none) { // flatten the array + #if ULAB_MAX_DIMS > 1 for(uint8_t i=0; i < ULAB_MAX_DIMS - 1; i++) { ndarray->shape[i] = 0; ndarray->strides[i] = 0; @@ -558,6 +592,7 @@ static mp_obj_t numerical_sort_helper(mp_obj_t oin, mp_obj_t axis, uint8_t inpla ndarray->shape[ULAB_MAX_DIMS - 1] = ndarray->len; ndarray->strides[ULAB_MAX_DIMS - 1] = ndarray->itemsize; ndarray->ndim = 1; + #endif } else { ax = mp_obj_get_int(axis); if(ax < 0) ax += ndarray->ndim; @@ -630,7 +665,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(numerical_argmin_obj, 1, numerical_argmin); #endif #if ULAB_NUMPY_HAS_ARGSORT -//| def argsort(array: ulab.ndarray, *, axis: int = -1) -> ulab.ndarray: +//| def argsort(array: ulab.numpy.ndarray, *, axis: int = -1) -> ulab.numpy.ndarray: //| """Returns an array which gives indices into the input array from least to greatest.""" //| ... //| @@ -739,7 +774,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(numerical_argsort_obj, 1, numerical_argsort); #endif #if ULAB_NUMPY_HAS_CROSS -//| def cross(a: ulab.ndarray, b: ulab.ndarray) -> ulab.ndarray: +//| def cross(a: ulab.numpy.ndarray, b: ulab.numpy.ndarray) -> ulab.numpy.ndarray: //| """Return the cross product of two vectors of length 3""" //| ... //| @@ -817,7 +852,7 @@ MP_DEFINE_CONST_FUN_OBJ_2(numerical_cross_obj, numerical_cross); #endif /* ULAB_NUMERICAL_HAS_CROSS */ #if ULAB_NUMPY_HAS_DIFF -//| def diff(array: ulab.ndarray, *, n: int = 1, axis: int = -1) -> ulab.ndarray: +//| def diff(array: ulab.numpy.ndarray, *, n: int = 1, axis: int = -1) -> ulab.numpy.ndarray: //| """Return the numerical derivative of successive elements of the array, as //| an array. axis=None is not supported.""" //| ... @@ -898,7 +933,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(numerical_diff_obj, 1, numerical_diff); #endif #if ULAB_NUMPY_HAS_FLIP -//| def flip(array: ulab.ndarray, *, axis: Optional[int] = None) -> ulab.ndarray: +//| def flip(array: ulab.numpy.ndarray, *, axis: Optional[int] = None) -> ulab.numpy.ndarray: //| """Returns a new array that reverses the order of the elements along the //| given axis, or along all axes if axis is None.""" //| ... @@ -946,7 +981,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(numerical_flip_obj, 1, numerical_flip); #endif #if ULAB_NUMPY_HAS_MINMAX -//| def max(array: _ArrayLike, *, axis: Optional[int] = None) -> float: +//| def max(array: _ArrayLike, *, axis: Optional[int] = None) -> _float: //| """Return the maximum element of the 1D array""" //| ... //| @@ -959,7 +994,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(numerical_max_obj, 1, numerical_max); #endif #if ULAB_NUMPY_HAS_MEAN -//| def mean(array: _ArrayLike, *, axis: Optional[int] = None) -> float: +//| def mean(array: _ArrayLike, *, axis: Optional[int] = None) -> _float: //| """Return the mean element of the 1D array, as a number if axis is None, otherwise as an array.""" //| ... //| @@ -972,7 +1007,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(numerical_mean_obj, 1, numerical_mean); #endif #if ULAB_NUMPY_HAS_MEDIAN -//| def median(array: ulab.ndarray, *, axis: int = -1) -> ulab.ndarray: +//| def median(array: ulab.numpy.ndarray, *, axis: int = -1) -> ulab.numpy.ndarray: //| """Find the median value in an array along the given axis, or along all axes if axis is None.""" //| ... //| @@ -1072,7 +1107,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(numerical_median_obj, 1, numerical_median); #endif #if ULAB_NUMPY_HAS_MINMAX -//| def min(array: _ArrayLike, *, axis: Optional[int] = None) -> float: +//| def min(array: _ArrayLike, *, axis: Optional[int] = None) -> _float: //| """Return the minimum element of the 1D array""" //| ... //| @@ -1085,7 +1120,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(numerical_min_obj, 1, numerical_min); #endif #if ULAB_NUMPY_HAS_ROLL -//| def roll(array: ulab.ndarray, distance: int, *, axis: Optional[int] = None) -> None: +//| def roll(array: ulab.numpy.ndarray, distance: int, *, axis: Optional[int] = None) -> None: //| """Shift the content of a vector by the positions given as the second //| argument. If the ``axis`` keyword is supplied, the shift is applied to //| the given axis. The array is modified in place.""" @@ -1250,7 +1285,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(numerical_roll_obj, 2, numerical_roll); #endif #if ULAB_NUMPY_HAS_SORT -//| def sort(array: ulab.ndarray, *, axis: int = -1) -> ulab.ndarray: +//| def sort(array: ulab.numpy.ndarray, *, axis: int = -1) -> ulab.numpy.ndarray: //| """Sort the array along the given axis, or along all axes if axis is None. //| The array is modified in place.""" //| ... @@ -1289,7 +1324,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(numerical_sort_inplace_obj, 1, numerical_sort_inplace #endif /* NDARRAY_HAS_SORT */ #if ULAB_NUMPY_HAS_STD -//| def std(array: _ArrayLike, *, axis: Optional[int] = None, ddof: int = 0) -> float: +//| def std(array: _ArrayLike, *, axis: Optional[int] = None, ddof: int = 0) -> _float: //| """Return the standard deviation of the array, as a number if axis is None, otherwise as an array.""" //| ... //| @@ -1326,7 +1361,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(numerical_std_obj, 1, numerical_std); #endif #if ULAB_NUMPY_HAS_SUM -//| def sum(array: _ArrayLike, *, axis: Optional[int] = None) -> Union[float, int, ulab.ndarray]: +//| def sum(array: _ArrayLike, *, axis: Optional[int] = None) -> Union[_float, int, ulab.numpy.ndarray]: //| """Return the sum of the array, as a number if axis is None, otherwise as an array.""" //| ... //| diff --git a/python/port/mod/ulab/numpy/numerical/numerical.h b/python/port/mod/ulab/numpy/numerical.h similarity index 99% rename from python/port/mod/ulab/numpy/numerical/numerical.h rename to python/port/mod/ulab/numpy/numerical.h index ef7b95d74c3..8d2971cd43e 100644 --- a/python/port/mod/ulab/numpy/numerical/numerical.h +++ b/python/port/mod/ulab/numpy/numerical.h @@ -12,8 +12,8 @@ #ifndef _NUMERICAL_ #define _NUMERICAL_ -#include "../../ulab.h" -#include "../../ndarray.h" +#include "../ulab.h" +#include "../ndarray.h" // TODO: implement cumsum @@ -201,7 +201,7 @@ } while(0) #define RUN_MEAN_STD(type, array, rarray, ss, div, isStd) do {\ - RUN_MEAN_STD1(type, (array), (results), (rarray), (ss), (div), (isStd));\ + RUN_MEAN_STD1(type, (array), (rarray), (ss), (div), (isStd));\ } while(0) #define RUN_ARGMIN(ndarray, type, array, results, rarray, shape, strides, index, op) do {\ @@ -444,7 +444,7 @@ #endif #if ULAB_MAX_DIMS == 4 -#define RUN_SUM(type, array, results, rarray, shape, strides, index) do {\ +#define RUN_SUM(type, array, results, rarray, ss) do {\ size_t j = 0;\ do {\ size_t k = 0;\ diff --git a/python/port/mod/ulab/numpy/numpy.c b/python/port/mod/ulab/numpy/numpy.c index 8d8f3ab6bda..a6559ff8ac5 100644 --- a/python/port/mod/ulab/numpy/numpy.c +++ b/python/port/mod/ulab/numpy/numpy.c @@ -14,26 +14,45 @@ #include #include -#include +#include "py/runtime.h" #include "numpy.h" #include "../ulab_create.h" -#include "approx/approx.h" -#include "compare/compare.h" +#include "approx.h" +#include "compare.h" #include "fft/fft.h" -#include "filter/filter.h" +#include "filter.h" #include "linalg/linalg.h" -#include "numerical/numerical.h" -#include "stats/stats.h" -#include "transform/transform.h" -#include "poly/poly.h" -#include "vector/vector.h" +#include "numerical.h" +#include "stats.h" +#include "transform.h" +#include "poly.h" +#include "vector.h" //| """Compatibility layer for numpy""" //| //| class ndarray: ... +//| def get_printoptions() -> Dict[str, int]: +//| """Get printing options""" +//| ... +//| +//| def set_printoptions(threshold: Optional[int] = None, edgeitems: Optional[int] = None) -> None: +//| """Set printing options""" +//| ... +//| +//| def ndinfo(array: ulab.numpy.ndarray) -> None: +//| ... +//| +//| def array( +//| values: Union[ndarray, Iterable[Union[_float, _bool, Iterable[Any]]]], +//| *, +//| dtype: _DType = ulab.numpy.float +//| ) -> ulab.numpy.ndarray: +//| """alternate constructor function for `ulab.numpy.ndarray`. Mirrors numpy.array""" +//| ... + // math constants #if ULAB_NUMPY_HAS_E #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C @@ -127,7 +146,9 @@ static const mp_rom_map_elem_t ulab_numpy_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_concatenate), (mp_obj_t)&create_concatenate_obj }, #endif #if ULAB_NUMPY_HAS_DIAG - { MP_ROM_QSTR(MP_QSTR_diag), (mp_obj_t)&create_diag_obj }, + #if ULAB_MAX_DIMS > 1 + { MP_ROM_QSTR(MP_QSTR_diag), (mp_obj_t)&create_diag_obj }, + #endif #endif #if ULAB_NUMPY_HAS_EMPTY { MP_ROM_QSTR(MP_QSTR_empty), (mp_obj_t)&create_zeros_obj }, @@ -210,10 +231,14 @@ static const mp_rom_map_elem_t ulab_numpy_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR_diff), (mp_obj_t)&numerical_diff_obj }, #endif #if ULAB_NUMPY_HAS_DOT - { MP_OBJ_NEW_QSTR(MP_QSTR_dot), (mp_obj_t)&transform_dot_obj }, + #if ULAB_MAX_DIMS > 1 + { MP_OBJ_NEW_QSTR(MP_QSTR_dot), (mp_obj_t)&transform_dot_obj }, + #endif #endif #if ULAB_NUMPY_HAS_TRACE - { MP_ROM_QSTR(MP_QSTR_trace), (mp_obj_t)&stats_trace_obj }, + #if ULAB_MAX_DIMS > 1 + { MP_ROM_QSTR(MP_QSTR_trace), (mp_obj_t)&stats_trace_obj }, + #endif #endif #if ULAB_NUMPY_HAS_FLIP { MP_OBJ_NEW_QSTR(MP_QSTR_flip), (mp_obj_t)&numerical_flip_obj }, diff --git a/python/port/mod/ulab/numpy/poly/poly.c b/python/port/mod/ulab/numpy/poly.c similarity index 94% rename from python/port/mod/ulab/numpy/poly/poly.c rename to python/port/mod/ulab/numpy/poly.c index c9a07655ff9..124d3bc8697 100644 --- a/python/port/mod/ulab/numpy/poly/poly.c +++ b/python/port/mod/ulab/numpy/poly.c @@ -1,6 +1,6 @@ /* - * This file is part of the micropython-ulab project, + * This file is part of the micropython-ulab project, * * https://github.com/v923z/micropython-ulab * @@ -12,13 +12,13 @@ * 2020 Taku Fukada */ -#include -#include -#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/objarray.h" -#include "../../ulab.h" -#include "../linalg/linalg_tools.h" -#include "../../ulab_tools.h" +#include "../ulab.h" +#include "linalg/linalg_tools.h" +#include "../ulab_tools.h" #include "poly.h" #if ULAB_NUMPY_HAS_POLYFIT @@ -32,7 +32,7 @@ mp_obj_t poly_polyfit(size_t n_args, const mp_obj_t *args) { mp_float_t *x, *XT, *y, *prod; if(n_args == 2) { // only the y values are supplied - // TODO: this is actually not enough: the first argument can very well be a matrix, + // TODO: this is actually not enough: the first argument can very well be a matrix, // in which case we are between the rock and a hard place leny = (size_t)mp_obj_get_int(mp_obj_len_maybe(args[0])); deg = (uint8_t)mp_obj_get_int(args[1]); @@ -64,8 +64,8 @@ mp_obj_t poly_polyfit(size_t n_args, const mp_obj_t *args) { y = m_new(mp_float_t, leny); fill_array_iterable(y, args[1]); } - - // one could probably express X as a function of XT, + + // one could probably express X as a function of XT, // and thereby save RAM, because X is used only in the product XT = m_new(mp_float_t, (deg+1)*leny); // XT is a matrix of shape (deg+1, len) (rows, columns) for(size_t i=0; i < leny; i++) { // column index @@ -74,15 +74,15 @@ mp_obj_t poly_polyfit(size_t n_args, const mp_obj_t *args) { XT[i+j*leny] = XT[i+(j-1)*leny]*x[i]; } } - + prod = m_new(mp_float_t, (deg+1)*(deg+1)); // the product matrix is of shape (deg+1, deg+1) mp_float_t sum; for(uint8_t i=0; i < deg+1; i++) { // column index for(uint8_t j=0; j < deg+1; j++) { // row index sum = 0.0; for(size_t k=0; k < lenx; k++) { - // (j, k) * (k, i) - // Note that the second matrix is simply the transpose of the first: + // (j, k) * (k, i) + // Note that the second matrix is simply the transpose of the first: // X(k, i) = XT(i, k) = XT[k*lenx+i] sum += XT[j*lenx+k]*XT[i*lenx+k]; // X[k*(deg+1)+i]; } @@ -90,15 +90,15 @@ mp_obj_t poly_polyfit(size_t n_args, const mp_obj_t *args) { } } if(!linalg_invert_matrix(prod, deg+1)) { - // Although X was a Vandermonde matrix, whose inverse is guaranteed to exist, - // we bail out here, if prod couldn't be inverted: if the values in x are not all + // Although X was a Vandermonde matrix, whose inverse is guaranteed to exist, + // we bail out here, if prod couldn't be inverted: if the values in x are not all // distinct, prod is singular m_del(mp_float_t, XT, (deg+1)*lenx); m_del(mp_float_t, x, lenx); m_del(mp_float_t, y, lenx); m_del(mp_float_t, prod, (deg+1)*(deg+1)); mp_raise_ValueError(translate("could not invert Vandermonde matrix")); - } + } // at this point, we have the inverse of X^T * X // y is a column vector; x is free now, we can use it for storing intermediate values for(uint8_t i=0; i < deg+1; i++) { // row index @@ -110,12 +110,12 @@ mp_obj_t poly_polyfit(size_t n_args, const mp_obj_t *args) { } // XT is no longer needed m_del(mp_float_t, XT, (deg+1)*leny); - + ndarray_obj_t *beta = ndarray_new_linear_array(deg+1, NDARRAY_FLOAT); mp_float_t *betav = (mp_float_t *)beta->array; // x[0..(deg+1)] contains now the product X^T * y; we can get rid of y m_del(float, y, leny); - + // now, we calculate beta, i.e., we apply prod = (X^T * X)^(-1) on x = X^T * y; x is a column vector now for(uint8_t i=0; i < deg+1; i++) { sum = 0.0; @@ -127,7 +127,7 @@ mp_obj_t poly_polyfit(size_t n_args, const mp_obj_t *args) { m_del(mp_float_t, x, lenx); m_del(mp_float_t, prod, (deg+1)*(deg+1)); for(uint8_t i=0; i < (deg+1)/2; i++) { - // We have to reverse the array, for the leading coefficient comes first. + // We have to reverse the array, for the leading coefficient comes first. SWAP(mp_float_t, betav[i], betav[deg-i]); } return MP_OBJ_FROM_PTR(beta); @@ -147,13 +147,13 @@ mp_obj_t poly_polyval(mp_obj_t o_p, mp_obj_t o_x) { mp_float_t *p = m_new(mp_float_t, plen); mp_obj_iter_buf_t p_buf; mp_obj_t p_item, p_iterable = mp_getiter(o_p, &p_buf); - uint8_t i = 0; + uint8_t i = 0; while((p_item = mp_iternext(p_iterable)) != MP_OBJ_STOP_ITERATION) { p[i] = mp_obj_get_float(p_item); i++; } - // polynomials are going to be of type float, except, when both + // polynomials are going to be of type float, except, when both // the coefficients and the independent variable are integers ndarray_obj_t *ndarray; if(mp_obj_is_type(o_x, &ulab_ndarray_type)) { @@ -161,10 +161,10 @@ mp_obj_t poly_polyval(mp_obj_t o_p, mp_obj_t o_x) { uint8_t *sarray = (uint8_t *)source->array; ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_FLOAT); mp_float_t *array = (mp_float_t *)ndarray->array; - + mp_float_t (*func)(void *) = ndarray_get_float_function(source->dtype); - // TODO: these loops are really nothing, but the re-impplementation of + // TODO: these loops are really nothing, but the re-impplementation of // ITERATE_VECTOR from vectorise.c. We could pass a function pointer here #if ULAB_MAX_DIMS > 3 size_t i = 0; @@ -207,7 +207,7 @@ mp_obj_t poly_polyval(mp_obj_t o_p, mp_obj_t o_x) { sarray += source->strides[ULAB_MAX_DIMS - 4]; i++; } while(i < source->shape[ULAB_MAX_DIMS - 4]); - #endif + #endif } else { // o_x had better be a one-dimensional standard iterable ndarray = ndarray_new_linear_array(mp_obj_get_int(mp_obj_len_maybe(o_x)), NDARRAY_FLOAT); diff --git a/python/port/mod/ulab/numpy/poly/poly.h b/python/port/mod/ulab/numpy/poly.h similarity index 71% rename from python/port/mod/ulab/numpy/poly/poly.h rename to python/port/mod/ulab/numpy/poly.h index cf66ab193e8..59cb9f514aa 100644 --- a/python/port/mod/ulab/numpy/poly/poly.h +++ b/python/port/mod/ulab/numpy/poly.h @@ -1,6 +1,6 @@ /* - * This file is part of the micropython-ulab project, + * This file is part of the micropython-ulab project, * * https://github.com/v923z/micropython-ulab * @@ -12,8 +12,8 @@ #ifndef _POLY_ #define _POLY_ -#include "../../ulab.h" -#include "../../ndarray.h" +#include "../ulab.h" +#include "../ndarray.h" MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(poly_polyfit_obj); MP_DECLARE_CONST_FUN_OBJ_2(poly_polyval_obj); diff --git a/python/port/mod/ulab/numpy/stats/stats.c b/python/port/mod/ulab/numpy/stats.c similarity index 87% rename from python/port/mod/ulab/numpy/stats/stats.c rename to python/port/mod/ulab/numpy/stats.c index 9172b234065..a63964fea0a 100644 --- a/python/port/mod/ulab/numpy/stats/stats.c +++ b/python/port/mod/ulab/numpy/stats.c @@ -15,18 +15,18 @@ #include #include #include -#include -#include -#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" -#include "../../ulab.h" -#include "../../ulab_tools.h" +#include "../ulab.h" +#include "../ulab_tools.h" #include "stats.h" #if ULAB_MAX_DIMS > 1 #if ULAB_NUMPY_HAS_TRACE -//| def trace(m: ulab.numpy.ndarray) -> float: +//| def trace(m: ulab.numpy.ndarray) -> _float: //| """ //| :param m: a square matrix //| diff --git a/python/port/mod/ulab/numpy/stats/stats.h b/python/port/mod/ulab/numpy/stats.h similarity index 84% rename from python/port/mod/ulab/numpy/stats/stats.h rename to python/port/mod/ulab/numpy/stats.h index e5fab5f9ff8..62bba9ff464 100644 --- a/python/port/mod/ulab/numpy/stats/stats.h +++ b/python/port/mod/ulab/numpy/stats.h @@ -12,8 +12,8 @@ #ifndef _STATS_ #define _STATS_ -#include "../../ulab.h" -#include "../../ndarray.h" +#include "../ulab.h" +#include "../ndarray.h" MP_DECLARE_CONST_FUN_OBJ_1(stats_trace_obj); diff --git a/python/port/mod/ulab/numpy/transform/transform.c b/python/port/mod/ulab/numpy/transform.c similarity index 94% rename from python/port/mod/ulab/numpy/transform/transform.c rename to python/port/mod/ulab/numpy/transform.c index 71473f5c6f6..2c2d2dbddb0 100644 --- a/python/port/mod/ulab/numpy/transform/transform.c +++ b/python/port/mod/ulab/numpy/transform.c @@ -12,17 +12,17 @@ #include #include #include -#include -#include -#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" -#include "../../ulab.h" -#include "../../ulab_tools.h" +#include "../ulab.h" +#include "../ulab_tools.h" #include "transform.h" - +#if ULAB_MAX_DIMS > 1 #if ULAB_NUMPY_HAS_DOT -//| def dot(m1: ulab.numpy.ndarray, m2: ulab.numpy.ndarray) -> Union[ulab.numpy.ndarray, float]: +//| def dot(m1: ulab.numpy.ndarray, m2: ulab.numpy.ndarray) -> Union[ulab.numpy.ndarray, _float]: //| """ //| :param ~ulab.numpy.ndarray m1: a matrix, or a vector //| :param ~ulab.numpy.ndarray m2: a matrix, or a vector @@ -87,3 +87,4 @@ mp_obj_t transform_dot(mp_obj_t _m1, mp_obj_t _m2) { MP_DEFINE_CONST_FUN_OBJ_2(transform_dot_obj, transform_dot); #endif +#endif \ No newline at end of file diff --git a/python/port/mod/ulab/numpy/transform/transform.h b/python/port/mod/ulab/numpy/transform.h similarity index 75% rename from python/port/mod/ulab/numpy/transform/transform.h rename to python/port/mod/ulab/numpy/transform.h index ba4194e3bf9..f4a09b8ef81 100644 --- a/python/port/mod/ulab/numpy/transform/transform.h +++ b/python/port/mod/ulab/numpy/transform.h @@ -15,12 +15,12 @@ #include #include #include -#include -#include -#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" -#include "../../ulab.h" -#include "../../ulab_tools.h" +#include "../ulab.h" +#include "../ulab_tools.h" #include "transform.h" MP_DECLARE_CONST_FUN_OBJ_2(transform_dot_obj); diff --git a/python/port/mod/ulab/numpy/vector/vector.c b/python/port/mod/ulab/numpy/vector.c similarity index 90% rename from python/port/mod/ulab/numpy/vector/vector.c rename to python/port/mod/ulab/numpy/vector.c index bc8326c6d19..ceba255989b 100644 --- a/python/port/mod/ulab/numpy/vector/vector.c +++ b/python/port/mod/ulab/numpy/vector.c @@ -15,13 +15,13 @@ #include #include #include -#include -#include -#include -#include +#include "py/runtime.h" +#include "py/binary.h" +#include "py/obj.h" +#include "py/objarray.h" -#include "../../ulab.h" -#include "../../ulab_tools.h" +#include "../ulab.h" +#include "../ulab_tools.h" #include "vector.h" //| """Element-by-element functions @@ -30,8 +30,6 @@ //| applying the function to every element in the array. This is typically //| much more efficient than expressing the same operation as a Python loop.""" //| -//| from ulab import _DType, _ArrayLike -//| static mp_obj_t vectorise_generic_vector(mp_obj_t o_in, mp_float_t (*f)(mp_float_t)) { // Return a single value, if o_in is not iterable @@ -111,7 +109,7 @@ static mp_obj_t vectorise_generic_vector(mp_obj_t o_in, mp_float_t (*f)(mp_float } #if ULAB_NUMPY_HAS_ACOS -//| def acos(a: _ArrayLike) -> ulab.ndarray: +//| def acos(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the inverse cosine function""" //| ... //| @@ -121,7 +119,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_acos_obj, vectorise_acos); #endif #if ULAB_NUMPY_HAS_ACOSH -//| def acosh(a: _ArrayLike) -> ulab.ndarray: +//| def acosh(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the inverse hyperbolic cosine function""" //| ... //| @@ -131,7 +129,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_acosh_obj, vectorise_acosh); #endif #if ULAB_NUMPY_HAS_ASIN -//| def asin(a: _ArrayLike) -> ulab.ndarray: +//| def asin(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the inverse sine function""" //| ... //| @@ -141,7 +139,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_asin_obj, vectorise_asin); #endif #if ULAB_NUMPY_HAS_ASINH -//| def asinh(a: _ArrayLike) -> ulab.ndarray: +//| def asinh(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the inverse hyperbolic sine function""" //| ... //| @@ -151,7 +149,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_asinh_obj, vectorise_asinh); #endif #if ULAB_NUMPY_HAS_AROUND -//| def around(a: _ArrayLike, *, decimals: int = 0) -> ulab.ndarray: +//| def around(a: _ArrayLike, *, decimals: int = 0) -> ulab.numpy.ndarray: //| """Returns a new float array in which each element is rounded to //| ``decimals`` places.""" //| ... @@ -221,7 +219,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(vectorise_around_obj, 1, vectorise_around); #endif #if ULAB_NUMPY_HAS_ATAN -//| def atan(a: _ArrayLike) -> ulab.ndarray: +//| def atan(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the inverse tangent function; the return values are in the //| range [-pi/2,pi/2].""" //| ... @@ -232,7 +230,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_atan_obj, vectorise_atan); #endif #if ULAB_NUMPY_HAS_ARCTAN2 -//| def arctan2(ya: _ArrayLike, xa: _ArrayLike) -> ulab.ndarray: +//| def arctan2(ya: _ArrayLike, xa: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the inverse tangent function of y/x; the return values are in //| the range [-pi, pi].""" //| ... @@ -315,7 +313,7 @@ MP_DEFINE_CONST_FUN_OBJ_2(vectorise_arctan2_obj, vectorise_arctan2); #endif /* ULAB_VECTORISE_HAS_ARCTAN2 */ #if ULAB_NUMPY_HAS_ATANH -//| def atanh(a: _ArrayLike) -> ulab.ndarray: +//| def atanh(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the inverse hyperbolic tangent function""" //| ... //| @@ -325,7 +323,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_atanh_obj, vectorise_atanh); #endif #if ULAB_NUMPY_HAS_CEIL -//| def ceil(a: _ArrayLike) -> ulab.ndarray: +//| def ceil(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Rounds numbers up to the next whole number""" //| ... //| @@ -335,7 +333,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_ceil_obj, vectorise_ceil); #endif #if ULAB_NUMPY_HAS_COS -//| def cos(a: _ArrayLike) -> ulab.ndarray: +//| def cos(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the cosine function""" //| ... //| @@ -345,7 +343,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_cos_obj, vectorise_cos); #endif #if ULAB_NUMPY_HAS_COSH -//| def cosh(a: _ArrayLike) -> ulab.ndarray: +//| def cosh(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the hyperbolic cosine function""" //| ... //| @@ -355,7 +353,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_cosh_obj, vectorise_cosh); #endif #if ULAB_NUMPY_HAS_DEGREES -//| def degrees(a: _ArrayLike) -> ulab.ndarray: +//| def degrees(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Converts angles from radians to degrees""" //| ... //| @@ -372,7 +370,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_degrees_obj, vectorise_degrees); #endif #if ULAB_SCIPY_SPECIAL_HAS_ERF -//| def erf(a: _ArrayLike) -> ulab.ndarray: +//| def erf(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the error function, which has applications in statistics""" //| ... //| @@ -382,7 +380,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_erf_obj, vectorise_erf); #endif #if ULAB_SCIPY_SPECIAL_HAS_ERFC -//| def erfc(a: _ArrayLike) -> ulab.ndarray: +//| def erfc(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the complementary error function, which has applications in statistics""" //| ... //| @@ -392,7 +390,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_erfc_obj, vectorise_erfc); #endif #if ULAB_NUMPY_HAS_EXP -//| def exp(a: _ArrayLike) -> ulab.ndarray: +//| def exp(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the exponent function.""" //| ... //| @@ -402,7 +400,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_exp_obj, vectorise_exp); #endif #if ULAB_NUMPY_HAS_EXPM1 -//| def expm1(a: _ArrayLike) -> ulab.ndarray: +//| def expm1(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes $e^x-1$. In certain applications, using this function preserves numeric accuracy better than the `exp` function.""" //| ... //| @@ -412,7 +410,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_expm1_obj, vectorise_expm1); #endif #if ULAB_NUMPY_HAS_FLOOR -//| def floor(a: _ArrayLike) -> ulab.ndarray: +//| def floor(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Rounds numbers up to the next whole number""" //| ... //| @@ -422,7 +420,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_floor_obj, vectorise_floor); #endif #if ULAB_SCIPY_SPECIAL_HAS_GAMMA -//| def gamma(a: _ArrayLike) -> ulab.ndarray: +//| def gamma(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the gamma function""" //| ... //| @@ -432,7 +430,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_gamma_obj, vectorise_gamma); #endif #if ULAB_SCIPY_SPECIAL_HAS_GAMMALN -//| def lgamma(a: _ArrayLike) -> ulab.ndarray: +//| def lgamma(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the natural log of the gamma function""" //| ... //| @@ -442,7 +440,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_lgamma_obj, vectorise_lgamma); #endif #if ULAB_NUMPY_HAS_LOG -//| def log(a: _ArrayLike) -> ulab.ndarray: +//| def log(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the natural log""" //| ... //| @@ -452,7 +450,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log_obj, vectorise_log); #endif #if ULAB_NUMPY_HAS_LOG10 -//| def log10(a: _ArrayLike) -> ulab.ndarray: +//| def log10(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the log base 10""" //| ... //| @@ -462,7 +460,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log10_obj, vectorise_log10); #endif #if ULAB_NUMPY_HAS_LOG2 -//| def log2(a: _ArrayLike) -> ulab.ndarray: +//| def log2(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the log base 2""" //| ... //| @@ -472,7 +470,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log2_obj, vectorise_log2); #endif #if ULAB_NUMPY_HAS_RADIANS -//| def radians(a: _ArrayLike) -> ulab.ndarray: +//| def radians(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Converts angles from degrees to radians""" //| ... //| @@ -489,7 +487,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_radians_obj, vectorise_radians); #endif #if ULAB_NUMPY_HAS_SIN -//| def sin(a: _ArrayLike) -> ulab.ndarray: +//| def sin(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the sine function""" //| ... //| @@ -499,7 +497,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_sin_obj, vectorise_sin); #endif #if ULAB_NUMPY_HAS_SINH -//| def sinh(a: _ArrayLike) -> ulab.ndarray: +//| def sinh(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the hyperbolic sine""" //| ... //| @@ -509,7 +507,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_sinh_obj, vectorise_sinh); #endif #if ULAB_NUMPY_HAS_SQRT -//| def sqrt(a: _ArrayLike) -> ulab.ndarray: +//| def sqrt(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the square root""" //| ... //| @@ -519,7 +517,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_sqrt_obj, vectorise_sqrt); #endif #if ULAB_NUMPY_HAS_TAN -//| def tan(a: _ArrayLike) -> ulab.ndarray: +//| def tan(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the tangent""" //| ... //| @@ -529,7 +527,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_tan_obj, vectorise_tan); #endif #if ULAB_NUMPY_HAS_TANH -//| def tanh(a: _ArrayLike) -> ulab.ndarray: +//| def tanh(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the hyperbolic tangent""" //| ... @@ -549,7 +547,7 @@ static mp_obj_t vectorise_vectorized_function_call(mp_obj_t self_in, size_t n_ar ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, self->otypes); for(size_t i=0; i < source->len; i++) { avalue[0] = mp_binary_get_val_array(source->dtype, source->array, i); - fvalue = self->type->call(self->fun, 1, 0, avalue); + fvalue = self->type->MP_TYPE_CALL(self->fun, 1, 0, avalue); ndarray_set_value(self->otypes, ndarray->array, i, fvalue); } return MP_OBJ_FROM_PTR(ndarray); @@ -561,14 +559,14 @@ static mp_obj_t vectorise_vectorized_function_call(mp_obj_t self_in, size_t n_ar mp_obj_t iterable = mp_getiter(args[0], &iter_buf); size_t i=0; while ((avalue[0] = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { - fvalue = self->type->call(self->fun, 1, 0, avalue); + fvalue = self->type->MP_TYPE_CALL(self->fun, 1, 0, avalue); ndarray_set_value(self->otypes, ndarray->array, i, fvalue); i++; } return MP_OBJ_FROM_PTR(ndarray); } else if(mp_obj_is_int(args[0]) || mp_obj_is_float(args[0])) { ndarray_obj_t *ndarray = ndarray_new_linear_array(1, self->otypes); - fvalue = self->type->call(self->fun, 1, 0, args); + fvalue = self->type->MP_TYPE_CALL(self->fun, 1, 0, args); ndarray_set_value(self->otypes, ndarray->array, 0, fvalue); return MP_OBJ_FROM_PTR(ndarray); } else { @@ -579,15 +577,18 @@ static mp_obj_t vectorise_vectorized_function_call(mp_obj_t self_in, size_t n_ar const mp_obj_type_t vectorise_function_type = { { &mp_type_type }, + .flags = MP_TYPE_FLAG_EXTENDED, .name = MP_QSTR_, + MP_TYPE_EXTENDED_FIELDS( .call = vectorise_vectorized_function_call, + ) }; //| def vectorize( -//| f: Union[Callable[[int], float], Callable[[float], float]], +//| f: Union[Callable[[int], _float], Callable[[_float], _float]], //| *, //| otypes: Optional[_DType] = None -//| ) -> Callable[[_ArrayLike], ulab.ndarray]: +//| ) -> Callable[[_ArrayLike], ulab.numpy.ndarray]: //| """ //| :param callable f: The function to wrap //| :param otypes: List of array types that may be returned by the function. None is interpreted to mean the return value is float. @@ -605,7 +606,7 @@ static mp_obj_t vectorise_vectorize(size_t n_args, const mp_obj_t *pos_args, mp_ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); const mp_obj_type_t *type = mp_obj_get_type(args[0].u_obj); - if(type->call == NULL) { + if(mp_type_get_call_slot(type) == NULL) { mp_raise_TypeError(translate("first argument must be a callable")); } mp_obj_t _otypes = args[1].u_obj; diff --git a/python/port/mod/ulab/numpy/vector/vector.h b/python/port/mod/ulab/numpy/vector.h similarity index 99% rename from python/port/mod/ulab/numpy/vector/vector.h rename to python/port/mod/ulab/numpy/vector.h index 6b00a221b9c..dbd0b33ea7e 100644 --- a/python/port/mod/ulab/numpy/vector/vector.h +++ b/python/port/mod/ulab/numpy/vector.h @@ -12,8 +12,8 @@ #ifndef _VECTOR_ #define _VECTOR_ -#include "../../ulab.h" -#include "../../ndarray.h" +#include "../ulab.h" +#include "../ndarray.h" MP_DECLARE_CONST_FUN_OBJ_1(vectorise_acos_obj); MP_DECLARE_CONST_FUN_OBJ_1(vectorise_acosh_obj); diff --git a/python/port/mod/ulab/scipy/linalg/linalg.c b/python/port/mod/ulab/scipy/linalg/linalg.c index 0f1b933af0e..a7266a4a1bd 100644 --- a/python/port/mod/ulab/scipy/linalg/linalg.c +++ b/python/port/mod/ulab/scipy/linalg/linalg.c @@ -7,31 +7,31 @@ * The MIT License (MIT) * * Copyright (c) 2021 Vikas Udupa - * + * */ #include #include #include -#include -#include -#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" #include "../../ulab.h" #include "../../ulab_tools.h" +#include "../../numpy/linalg/linalg_tools.h" #include "linalg.h" #if ULAB_SCIPY_HAS_LINALG_MODULE //| //| import ulab.scipy +//| import ulab.numpy //| //| """Linear algebra functions""" //| #if ULAB_MAX_DIMS > 1 -#define TOLERANCE 0.0000001 - //| def solve_triangular(A: ulab.numpy.ndarray, b: ulab.numpy.ndarray, lower: bool) -> ulab.numpy.ndarray: //| """ //| :param ~ulab.numpy.ndarray A: a matrix @@ -46,9 +46,9 @@ //| static mp_obj_t solve_triangular(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - + size_t i, j; - + static const mp_arg_t allowed_args[] = { { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none} } , { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none} } , @@ -64,7 +64,7 @@ static mp_obj_t solve_triangular(size_t n_args, const mp_obj_t *pos_args, mp_map ndarray_obj_t *A = MP_OBJ_TO_PTR(args[0].u_obj); ndarray_obj_t *b = MP_OBJ_TO_PTR(args[1].u_obj); - + if(!ndarray_is_dense(A) || !ndarray_is_dense(b)) { mp_raise_TypeError(translate("input must be a dense ndarray")); } @@ -82,7 +82,7 @@ static mp_obj_t solve_triangular(size_t n_args, const mp_obj_t *pos_args, mp_map // check if input matrix A is singular for (i = 0; i < A_rows; i++) { - if (MICROPY_FLOAT_C_FUN(fabs)(get_A_ele(A_arr)) < TOLERANCE) + if (MICROPY_FLOAT_C_FUN(fabs)(get_A_ele(A_arr)) < LINALG_EPSILON) mp_raise_ValueError(translate("input matrix is singular")); A_arr += A->strides[ULAB_MAX_DIMS - 2]; A_arr += A->strides[ULAB_MAX_DIMS - 1]; @@ -92,7 +92,7 @@ static mp_obj_t solve_triangular(size_t n_args, const mp_obj_t *pos_args, mp_map ndarray_obj_t *x = ndarray_new_dense_ndarray(b->ndim, b->shape, NDARRAY_FLOAT); mp_float_t *x_arr = (mp_float_t *)x->array; - + if (mp_obj_is_true(args[2].u_obj)) { // Solve the lower triangular matrix by iterating each row of A. // Start by finding the first unknown using the first row. @@ -166,7 +166,7 @@ static mp_obj_t cho_solve(mp_obj_t _L, mp_obj_t _b) { ndarray_obj_t *L = MP_OBJ_TO_PTR(_L); ndarray_obj_t *b = MP_OBJ_TO_PTR(_b); - + if(!ndarray_is_dense(L) || !ndarray_is_dense(b)) { mp_raise_TypeError(translate("input must be a dense ndarray")); } diff --git a/python/port/mod/ulab/scipy/optimize/optimize.c b/python/port/mod/ulab/scipy/optimize/optimize.c index 9218e6936d9..c2ed6fff86b 100644 --- a/python/port/mod/ulab/scipy/optimize/optimize.c +++ b/python/port/mod/ulab/scipy/optimize/optimize.c @@ -13,9 +13,9 @@ */ #include -#include -#include -#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" #include "../../ndarray.h" #include "../../ulab.h" @@ -30,7 +30,7 @@ static mp_float_t optimize_python_call(const mp_obj_type_t *type, mp_obj_t fun, // where f is defined in python. Takes a float, returns a float. // The array of mp_obj_t type must be supplied, as must the number of parameters (a, b, c...) in nparams fargs[0] = mp_obj_new_float(x); - return mp_obj_get_float(type->call(fun, nparams+1, 0, fargs)); + return mp_obj_get_float(type->MP_TYPE_CALL(fun, nparams+1, 0, fargs)); } #if ULAB_SCIPY_OPTIMIZE_HAS_BISECT @@ -70,7 +70,7 @@ STATIC mp_obj_t optimize_bisect(size_t n_args, const mp_obj_t *pos_args, mp_map_ mp_obj_t fun = args[0].u_obj; const mp_obj_type_t *type = mp_obj_get_type(fun); - if(type->call == NULL) { + if(mp_type_get_call_slot(type) == NULL) { mp_raise_TypeError(translate("first argument must be a function")); } mp_float_t xtol = mp_obj_get_float(args[3].u_obj); @@ -140,7 +140,7 @@ STATIC mp_obj_t optimize_fmin(size_t n_args, const mp_obj_t *pos_args, mp_map_t mp_obj_t fun = args[0].u_obj; const mp_obj_type_t *type = mp_obj_get_type(fun); - if(type->call == NULL) { + if(mp_type_get_call_slot(type) == NULL) { mp_raise_TypeError(translate("first argument must be a function")); } @@ -276,7 +276,7 @@ mp_obj_t optimize_curve_fit(size_t n_args, const mp_obj_t *pos_args, mp_map_t *k mp_obj_t fun = args[0].u_obj; const mp_obj_type_t *type = mp_obj_get_type(fun); - if(type->call == NULL) { + if(mp_type_get_call_slot(type) == NULL) { mp_raise_TypeError(translate("first argument must be a function")); } @@ -365,7 +365,7 @@ static mp_obj_t optimize_newton(size_t n_args, const mp_obj_t *pos_args, mp_map_ mp_obj_t fun = args[0].u_obj; const mp_obj_type_t *type = mp_obj_get_type(fun); - if(type->call == NULL) { + if(mp_type_get_call_slot(type) == NULL) { mp_raise_TypeError(translate("first argument must be a function")); } mp_float_t x = mp_obj_get_float(args[1].u_obj); diff --git a/python/port/mod/ulab/scipy/scipy.c b/python/port/mod/ulab/scipy/scipy.c index 3e3a8280f50..c37aa4ee8ea 100644 --- a/python/port/mod/ulab/scipy/scipy.c +++ b/python/port/mod/ulab/scipy/scipy.c @@ -13,7 +13,7 @@ */ #include -#include +#include "py/runtime.h" #include "../ulab.h" #include "optimize/optimize.h" diff --git a/python/port/mod/ulab/scipy/signal/signal.c b/python/port/mod/ulab/scipy/signal/signal.c index 0dbafd2c181..cc559b5981c 100644 --- a/python/port/mod/ulab/scipy/signal/signal.c +++ b/python/port/mod/ulab/scipy/signal/signal.c @@ -14,16 +14,18 @@ #include #include -#include +#include "py/runtime.h" #include "../../ulab.h" #include "../../ndarray.h" #include "../../numpy/fft/fft_tools.h" #if ULAB_SCIPY_SIGNAL_HAS_SPECTROGRAM -//| def spectrogram(r: ulab.ndarray) -> ulab.ndarray: +//| import ulab.numpy +//| +//| def spectrogram(r: ulab.numpy.ndarray) -> ulab.numpy.ndarray: //| """ -//| :param ulab.ndarray r: A 1-dimension array of values whose size is a power of 2 +//| :param ulab.numpy.ndarray r: A 1-dimension array of values whose size is a power of 2 //| //| Computes the spectrum of the input signal. This is the absolute value of the (complex-valued) fft of the signal. //| This function is similar to scipy's ``scipy.signal.spectrogram``.""" diff --git a/python/port/mod/ulab/scipy/special/special.c b/python/port/mod/ulab/scipy/special/special.c index bd4cf87c467..82b53247d3a 100644 --- a/python/port/mod/ulab/scipy/special/special.c +++ b/python/port/mod/ulab/scipy/special/special.c @@ -13,10 +13,10 @@ */ #include -#include +#include "py/runtime.h" #include "../../ulab.h" -#include "../../numpy/vector/vector.h" +#include "../../numpy/vector.h" static const mp_rom_map_elem_t ulab_scipy_special_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_special) }, diff --git a/python/port/mod/ulab/ulab.c b/python/port/mod/ulab/ulab.c index d7a563c855f..2b9ebd73f62 100644 --- a/python/port/mod/ulab/ulab.c +++ b/python/port/mod/ulab/ulab.c @@ -14,27 +14,26 @@ #include #include #include -#include -#include -#include -#include +#include "py/runtime.h" +#include "py/binary.h" +#include "py/obj.h" +#include "py/objarray.h" #include "ulab.h" #include "ulab_create.h" #include "ndarray.h" #include "ndarray_properties.h" +#include "numpy/ndarray/ndarray_iter.h" #include "numpy/numpy.h" #include "scipy/scipy.h" -#include "numpy/fft/fft.h" -#include "numpy/linalg/linalg.h" // TODO: we should get rid of this; array.sort depends on it -#include "numpy/numerical/numerical.h" +#include "numpy/numerical.h" #include "user/user.h" #include "utils/utils.h" -#define ULAB_VERSION 3.1.0 +#define ULAB_VERSION 3.3.8 #define xstr(s) str(s) #define str(s) #s #define ULAB_VERSION_STRING xstr(ULAB_VERSION) xstr(-) xstr(ULAB_MAX_DIMS) xstr(D) @@ -70,6 +69,9 @@ STATIC const mp_rom_map_elem_t ulab_ndarray_locals_dict_table[] = { #if NDARRAY_HAS_DTYPE { MP_ROM_QSTR(MP_QSTR_dtype), MP_ROM_PTR(&ndarray_dtype_obj) }, #endif + #if NDARRAY_HAS_FLATITER + { MP_ROM_QSTR(MP_QSTR_flat), MP_ROM_PTR(&ndarray_flat_obj) }, + #endif #if NDARRAY_HAS_ITEMSIZE { MP_ROM_QSTR(MP_QSTR_itemsize), MP_ROM_PTR(&ndarray_itemsize_obj) }, #endif @@ -89,12 +91,15 @@ STATIC MP_DEFINE_CONST_DICT(ulab_ndarray_locals_dict, ulab_ndarray_locals_dict_t const mp_obj_type_t ulab_ndarray_type = { { &mp_type_type }, + .flags = MP_TYPE_FLAG_EXTENDED #if defined(MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE) && defined(MP_TYPE_FLAG_EQ_HAS_NEQ_TEST) - .flags = MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE | MP_TYPE_FLAG_EQ_HAS_NEQ_TEST, + | MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE | MP_TYPE_FLAG_EQ_HAS_NEQ_TEST, #endif .name = MP_QSTR_ndarray, .print = ndarray_print, .make_new = ndarray_make_new, + .locals_dict = (mp_obj_dict_t*)&ulab_ndarray_locals_dict, + MP_TYPE_EXTENDED_FIELDS( #if NDARRAY_IS_SLICEABLE .subscr = ndarray_subscr, #endif @@ -111,7 +116,7 @@ const mp_obj_type_t ulab_ndarray_type = { .attr = ndarray_properties_attr, #endif .buffer_p = { .get_buffer = ndarray_get_buffer, }, - .locals_dict = (mp_obj_dict_t*)&ulab_ndarray_locals_dict, + ) }; #if ULAB_HAS_DTYPE_OBJECT @@ -123,6 +128,16 @@ const mp_obj_type_t ulab_dtype_type = { }; #endif +#if NDARRAY_HAS_FLATITER +const mp_obj_type_t ndarray_flatiter_type = { + { &mp_type_type }, + .name = MP_QSTR_flatiter, + MP_TYPE_EXTENDED_FIELDS( + .getiter = ndarray_get_flatiterator, + ) +}; +#endif + STATIC const mp_map_elem_t ulab_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_ulab) }, { MP_ROM_QSTR(MP_QSTR___version__), MP_ROM_PTR(&ulab_version_obj) }, diff --git a/python/port/mod/ulab/ulab.h b/python/port/mod/ulab/ulab.h index f57db68c640..248047c8528 100644 --- a/python/port/mod/ulab/ulab.h +++ b/python/port/mod/ulab/ulab.h @@ -34,16 +34,22 @@ // Determines, whether scipy is defined in ulab. The sub-modules and functions // of scipy have to be defined separately +#ifndef ULAB_HAS_SCIPY #define ULAB_HAS_SCIPY (1) +#endif // The maximum number of dimensions the firmware should be able to support // Possible values lie between 1, and 4, inclusive +#ifndef ULAB_MAX_DIMS #define ULAB_MAX_DIMS 2 +#endif // By setting this constant to 1, iteration over array dimensions will be implemented // as a function (ndarray_rewind_array), instead of writing out the loops in macros // This reduces firmware size at the expense of speed +#ifndef ULAB_HAS_FUNCTION_ITERATOR #define ULAB_HAS_FUNCTION_ITERATOR (0) +#endif // If NDARRAY_IS_ITERABLE is 1, the ndarray object defines its own iterator function // This option saves approx. 250 bytes of flash space @@ -80,12 +86,8 @@ // 2 kB in the two-dimensional case, and around 4 kB in the four-dimensional case. #ifndef NDARRAY_BINARY_USES_FUN_POINTER -#if defined(DEVICE_N0100) -#define NDARRAY_BINARY_USES_FUN_POINTER (1) -#else #define NDARRAY_BINARY_USES_FUN_POINTER (0) #endif -#endif #ifndef NDARRAY_HAS_BINARY_OP_ADD #define NDARRAY_HAS_BINARY_OP_ADD (1) @@ -262,6 +264,12 @@ #define ULAB_NUMPY_HAS_NDINFO (1) #endif +// if this constant is set to 1, the interpreter can iterate +// over the flat array without copying any data +#ifndef NDARRAY_HAS_FLATITER +#define NDARRAY_HAS_FLATITER (1) +#endif + // frombuffer adds 600 bytes to the firmware #ifndef ULAB_NUMPY_HAS_FROMBUFFER #define ULAB_NUMPY_HAS_FROMBUFFER (1) @@ -367,6 +375,10 @@ #define ULAB_LINALG_HAS_NORM (1) #endif +#ifndef ULAB_LINALG_HAS_QR +#define ULAB_LINALG_HAS_QR (1) +#endif + // the FFT module; functions of the fft module still have // to be defined separately #ifndef ULAB_NUMPY_HAS_FFT_MODULE @@ -638,7 +650,7 @@ #endif #ifndef ULAB_HAS_UTILS_MODULE -#define ULAB_HAS_UTILS_MODULE (0) +#define ULAB_HAS_UTILS_MODULE (1) #endif #ifndef ULAB_UTILS_HAS_FROM_INT16_BUFFER diff --git a/python/port/mod/ulab/ulab_create.c b/python/port/mod/ulab/ulab_create.c index 2cb8f23252f..a93ec741b6b 100644 --- a/python/port/mod/ulab/ulab_create.c +++ b/python/port/mod/ulab/ulab_create.c @@ -14,8 +14,8 @@ #include #include #include -#include -#include +#include "py/obj.h" +#include "py/runtime.h" #include "ulab.h" #include "ulab_create.h" @@ -89,24 +89,6 @@ static ndarray_obj_t *create_linspace_arange(mp_float_t start, mp_float_t step, #endif #if ULAB_NUMPY_HAS_ARANGE -//| @overload -//| def arange(stop: _float, step: _float = 1, *, dtype: _DType = ulab.float) -> ulab.ndarray: ... -//| @overload -//| def arange(start: _float, stop: _float, step: _float = 1, *, dtype: _DType = ulab.float) -> ulab.ndarray: -//| """ -//| .. param: start -//| First value in the array, optional, defaults to 0 -//| .. param: stop -//| Final value in the array -//| .. param: step -//| Difference between consecutive elements, optional, defaults to 1.0 -//| .. param: dtype -//| Type of values in the array -//| -//| Return a new 1-D array with elements ranging from ``start`` to ``stop``, with step size ``step``.""" -//| ... -//| - mp_obj_t create_arange(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, @@ -157,17 +139,6 @@ MP_DEFINE_CONST_FUN_OBJ_KW(create_arange_obj, 1, create_arange); #endif #if ULAB_NUMPY_HAS_CONCATENATE -//| def concatenate(arrays: Tuple[ulab.ndarray], *, axis: int = 0) -> ulab.ndarray: -//| """ -//| .. param: arrays -//| tuple of ndarrays -//| .. param: axis -//| axis along which the arrays will be joined -//| -//| Join a sequence of arrays along an existing axis.""" -//| ... -//| - mp_obj_t create_concatenate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, @@ -281,17 +252,8 @@ mp_obj_t create_concatenate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *k MP_DEFINE_CONST_FUN_OBJ_KW(create_concatenate_obj, 1, create_concatenate); #endif +#if ULAB_MAX_DIMS > 1 #if ULAB_NUMPY_HAS_DIAG -//| def diag(a: ulab.ndarray, *, k: int = 0) -> ulab.ndarray: -//| """ -//| .. param: a -//| an ndarray -//| .. param: k -//| Offset of the diagonal from the main diagonal. Can be positive or negative. -//| -//| Return specified diagonals.""" -//| ... -//| mp_obj_t create_diag(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, @@ -353,14 +315,7 @@ mp_obj_t create_diag(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) MP_DEFINE_CONST_FUN_OBJ_KW(create_diag_obj, 1, create_diag); #endif /* ULAB_NUMPY_HAS_DIAG */ -#if ULAB_MAX_DIMS > 1 #if ULAB_NUMPY_HAS_EYE -//| def eye(size: int, *, M: Optional[int] = None, k: int = 0, dtype: _DType = ulab.float) -> ulab.ndarray: -//| """Return a new square array of size, with the diagonal elements set to 1 -//| and the other elements set to 0.""" -//| ... -//| - mp_obj_t create_eye(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_INT, { .u_int = 0 } }, @@ -407,19 +362,6 @@ MP_DEFINE_CONST_FUN_OBJ_KW(create_eye_obj, 1, create_eye); #endif /* ULAB_MAX_DIMS > 1 */ #if ULAB_NUMPY_HAS_FULL -//| def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[_float, _bool], *, dtype: _DType = ulab.float) -> ulab.ndarray: -//| """ -//| .. param: shape -//| Shape of the array, either an integer (for a 1-D array) or a tuple of integers (for tensors of higher rank) -//| .. param: fill_value -//| scalar, the value with which the array is filled -//| .. param: dtype -//| Type of values in the array -//| -//| Return a new array of the given shape with all elements set to 0.""" -//| ... -//| - mp_obj_t create_full(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, @@ -440,35 +382,6 @@ MP_DEFINE_CONST_FUN_OBJ_KW(create_full_obj, 0, create_full); #if ULAB_NUMPY_HAS_LINSPACE -//| def linspace( -//| start: _float, -//| stop: _float, -//| *, -//| dtype: _DType = ulab.float, -//| num: int = 50, -//| endpoint: _bool = True, -//| retstep: _bool = False -//| ) -> ulab.ndarray: -//| """ -//| .. param: start -//| First value in the array -//| .. param: stop -//| Final value in the array -//| .. param int: num -//| Count of values in the array. -//| .. param: dtype -//| Type of values in the array -//| .. param bool: endpoint -//| Whether the ``stop`` value is included. Note that even when -//| endpoint=True, the exact ``stop`` value may not be included due to the -//| inaccuracy of floating point arithmetic. -// .. param bool: retstep, -//| If True, return (`samples`, `step`), where `step` is the spacing between samples. -//| -//| Return a new 1-D array with ``num`` elements ranging from ``start`` to ``stop`` linearly.""" -//| ... -//| - mp_obj_t create_linspace(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, @@ -506,37 +419,6 @@ MP_DEFINE_CONST_FUN_OBJ_KW(create_linspace_obj, 2, create_linspace); #endif #if ULAB_NUMPY_HAS_LOGSPACE -//| def logspace( -//| start: _float, -//| stop: _float, -//| *, -//| dtype: _DType = ulab.float, -//| num: int = 50, -//| endpoint: _bool = True, -//| base: _float = 10.0 -//| ) -> ulab.ndarray: -//| """ -//| .. param: start -//| First value in the array -//| .. param: stop -//| Final value in the array -//| .. param int: num -//| Count of values in the array. Defaults to 50. -//| .. param: base -//| The base of the log space. The step size between the elements in -//| ``ln(samples) / ln(base)`` (or ``log_base(samples)``) is uniform. Defaults to 10.0. -//| .. param: dtype -//| Type of values in the array -//| .. param bool: endpoint -//| Whether the ``stop`` value is included. Note that even when -//| endpoint=True, the exact ``stop`` value may not be included due to the -//| inaccuracy of floating point arithmetic. Defaults to True. -//| -//| Return a new 1-D array with ``num`` evenly spaced elements on a log scale. -//| The sequence starts at ``base ** start``, and ends with ``base ** stop``.""" -//| ... -//| - const mp_obj_float_t create_float_const_ten = {{&mp_type_float}, MICROPY_FLOAT_CONST(10.0)}; mp_obj_t create_logspace(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { @@ -593,16 +475,6 @@ MP_DEFINE_CONST_FUN_OBJ_KW(create_logspace_obj, 2, create_logspace); #endif #if ULAB_NUMPY_HAS_ONES -//| def ones(shape: Union[int, Tuple[int, ...]], *, dtype: _DType = ulab.float) -> ulab.ndarray: -//| """ -//| .. param: shape -//| Shape of the array, either an integer (for a 1-D array) or a tuple of 2 integers (for a 2-D array) -//| .. param: dtype -//| Type of values in the array -//| -//| Return a new array of the given shape with all elements set to 1.""" -//| ... -//| mp_obj_t create_ones(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { @@ -622,16 +494,6 @@ MP_DEFINE_CONST_FUN_OBJ_KW(create_ones_obj, 0, create_ones); #endif #if ULAB_NUMPY_HAS_ZEROS -//| def zeros(shape: Union[int, Tuple[int, ...]], *, dtype: _DType = ulab.float) -> ulab.ndarray: -//| """ -//| .. param: shape -//| Shape of the array, either an integer (for a 1-D array) or a tuple of 2 integers (for a 2-D array) -//| .. param: dtype -//| Type of values in the array -//| -//| Return a new array of the given shape with all elements set to 0.""" -//| ... -//| mp_obj_t create_zeros(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { diff --git a/python/port/mod/ulab/ulab_tools.c b/python/port/mod/ulab/ulab_tools.c index dd93787bab8..acd3d8a586c 100644 --- a/python/port/mod/ulab/ulab_tools.c +++ b/python/port/mod/ulab/ulab_tools.c @@ -10,7 +10,7 @@ #include -#include +#include "py/runtime.h" #include "ulab.h" #include "ndarray.h" diff --git a/python/port/mod/ulab/user/user.c b/python/port/mod/ulab/user/user.c index be7c6d0a04b..835c091c7c2 100644 --- a/python/port/mod/ulab/user/user.c +++ b/python/port/mod/ulab/user/user.c @@ -12,9 +12,9 @@ #include #include #include -#include -#include -#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" #include "user.h" #if ULAB_HAS_USER_MODULE diff --git a/python/port/mod/ulab/utils/utils.c b/python/port/mod/ulab/utils/utils.c index 6ff787b3b42..2b7dc093c02 100644 --- a/python/port/mod/ulab/utils/utils.c +++ b/python/port/mod/ulab/utils/utils.c @@ -11,9 +11,9 @@ #include #include #include -#include -#include -#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" #include "utils.h" #if ULAB_HAS_UTILS_MODULE From 4f63100195e5769829f691570d9f99ed3b49c050 Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 18 Dec 2021 12:48:06 +0100 Subject: [PATCH 137/355] [theme] Added reader icons --- .../local/epsilon_dark/apps/reader_icon.png | Bin 0 -> 1782 bytes .../local/epsilon_light/apps/reader_icon.png | Bin 0 -> 1619 bytes .../local/omega_dark/apps/reader_icon.png | Bin 12809 -> 1750 bytes .../local/omega_light/apps/reader_icon.png | Bin 0 -> 1636 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 themes/themes/local/epsilon_dark/apps/reader_icon.png create mode 100644 themes/themes/local/epsilon_light/apps/reader_icon.png create mode 100644 themes/themes/local/omega_light/apps/reader_icon.png diff --git a/themes/themes/local/epsilon_dark/apps/reader_icon.png b/themes/themes/local/epsilon_dark/apps/reader_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..64bc2ffa4954cce62a048dee0e65a58b5e9664b7 GIT binary patch literal 1782 zcmVpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H127^gN zK~!jg-I{MmRBIl`Kj%5>%w(lr$Btv7BJMO7TGlIAfi*aCHEF&T&R> zpO-QC?>y?T{MBm#gCg0+gOssJd8LUVI7_4V~jewW^x zi^t>m{eHsXFe4))OiWBLGc&UgU@anqz-F_NmzPITQ4ubei_4cU<8U~DpUlPMaSk3l zNHiK{aBz@hGP%mKjU&r4<>loR78Vi+1aLSU2mrs|Pc$0k`Sa($NMiuWWRg%QL@XAg zrKJUctnTh^!r?H3gM+IqZ)F%57$7${m!6&;WJ!|RLZJ|olas5hYempB4Oy0nL?Vbt zB*MhR#A<6=QO3u|35UaE0nEfRvBGL4OmG&D3&QBgroPEOiIzGrM~j3Y;mq@)w( z{b-s-mJmXB6LB~koIZUTkHcl8M zpASV*=;-J$Z45v+x2mqLMhHPySC{Edj8a=$ODq=S?%lhV;^>ZCQBe`9s`Bd9t0mWk z5S%}Mo}!{6O9Ga|;c)Qe$rH<|UrK9Ek|ZQaT5?U(G@d?v%G@Ue7&BXU*?dXo7v~|?LQ~E)%(LI+n6@@+uzu^^oK9VwM%F~X5N7F z%+6^fRsC0yDRaMaW%1n~zrdC~Zz+?;dY{bD`j;_Y#q@ViQ|A1Q8?a?dWGG<)KviL2 zB*E|h9OK4-njzq!w<)6v-|9eMUU>r?LkMr4@Vjje-L|GPl$-zkfxc(6eDn2scDQrMU1klY6Itl@XDPP~MH2k! zALCX+q?OP5U%_W>&d{`UyWRMFJ}N3Iu-onF()0nNqocI9w;TS{kdiqe1b)AtrluxS z;;a&j#Y}y}Ov&7#Lx*T;YU1I;hjey!^7!#%5(#5@o)Lr)oIQIMx7*Fln>Psr0?f_L z8IGfCZo`HR?BBniU@*w3Q>QXgy0K%&4m=(Y*RNma#*G`Mv$Ou9R6h&e2nS!dS96NT*vVi4iYipylwA6AO-8J{< z(IWsF8yhWmmMVRHeJF~;wr$%i#nC&c1#7@Or(JmX?y8oo(4bs#I218p=>)Sw>Y=q^71Ot-rsYcs!nF ziJ4>Fx^-;XvIU_i%EIt}F68Cq;c~eU&CSg?ola!gz?YXbj4aDIolXuPK8&cZugB$b zQC?oY+PW+wNfPDd<&>0^;PrZ!mI23NF$M+(ev#F(%as8yDd28tX<>MHm}oS*P{6&G zjmomTP{8eUIw>hB;nJl`OAELc-_X<3!?kPI2#3RHnzrUk?nt Y1hK?~moRkt*Z=?k07*qoM6N<$f~}Qta{vGU literal 0 HcmV?d00001 diff --git a/themes/themes/local/epsilon_light/apps/reader_icon.png b/themes/themes/local/epsilon_light/apps/reader_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b5bcc06a1d0f2de51b6d41a15a9a616cef5cabf3 GIT binary patch literal 1619 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 literal 12809 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 Date: Sat, 18 Dec 2021 12:48:36 +0100 Subject: [PATCH 138/355] [calculation] Fix a bug in second degree controller --- .../second_degree_list_controller.cpp | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/apps/calculation/additional_outputs/second_degree_list_controller.cpp b/apps/calculation/additional_outputs/second_degree_list_controller.cpp index 13866e4300f..2533c12f9d6 100644 --- a/apps/calculation/additional_outputs/second_degree_list_controller.cpp +++ b/apps/calculation/additional_outputs/second_degree_list_controller.cpp @@ -113,7 +113,7 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { xMinusAlphaPowerTwoWithFactor = xMinusAlphaPowerTwo; break; case MultiplicationTypeForA::Minus: - xMinusAlphaPowerTwoWithFactor = Multiplication::Builder(a.clone(), xMinusAlphaPowerTwo); + xMinusAlphaPowerTwoWithFactor = Opposite::Builder(xMinusAlphaPowerTwo); break; case MultiplicationTypeForA::Parenthesis: xMinusAlphaPowerTwoWithFactor = Multiplication::Builder(Parenthesis::Builder(a.clone()), xMinusAlphaPowerTwo); @@ -170,15 +170,14 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { if (x0Opposite.isUninitialized()) { PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::User); if (x0.type() == ExpressionNode::Type::Addition || x0.type() == ExpressionNode::Type::Subtraction) { - x0 = Parenthesis::Builder(x0.clone()); + firstFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), Parenthesis::Builder(x0)); + } + else { + firstFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0); } - firstFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0); } else { PoincareHelpers::Simplify(&x0Opposite, context, ExpressionNode::ReductionTarget::User); - if (x0Opposite.type() == ExpressionNode::Type::Addition || x0Opposite.type() == ExpressionNode::Type::Subtraction) { - x0Opposite = Parenthesis::Builder(x0Opposite.clone()); - } firstFactor = Addition::Builder(Symbol::Builder("x", strlen("x")), x0Opposite); } if (x0.type() == ExpressionNode::Type::Opposite) { @@ -188,10 +187,12 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { Expression x1Opposite = getOppositeIfExists(x1, &reductionContext); if (x1Opposite.isUninitialized()) { PoincareHelpers::Simplify(&x1, context, ExpressionNode::ReductionTarget::User); - if (x1.type() == ExpressionNode::Type::Addition || x1.type() == ExpressionNode::Type::Subtraction) { - x1 = Parenthesis::Builder(x1.clone()); + if (x0.type() == ExpressionNode::Type::Addition || x1.type() == ExpressionNode::Type::Subtraction) { + secondFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), Parenthesis::Builder(x1)); + } + else { + secondFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x1); } - secondFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x1); } else { PoincareHelpers::Simplify(&x1Opposite, context, ExpressionNode::ReductionTarget::User); @@ -208,7 +209,7 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { factorized = solutionProduct; break; case MultiplicationTypeForA::Minus: - factorized = Multiplication::Builder(a.clone(), solutionProduct); + factorized = Opposite::Builder(solutionProduct); break; case MultiplicationTypeForA::Parenthesis: factorized = Multiplication::Builder(Parenthesis::Builder(a.clone()), solutionProduct); @@ -227,7 +228,12 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { if (x0Opposite.isUninitialized()) { PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::User); - factor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0); + if (x0.type() == ExpressionNode::Type::Addition || x1.type() == ExpressionNode::Type::Subtraction) { + factor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), Parenthesis::Builder(x0)); + } + else { + factor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0); + } } else { PoincareHelpers::Simplify(&x0Opposite, context, ExpressionNode::ReductionTarget::User); @@ -241,7 +247,7 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { factorized = solutionProduct; break; case MultiplicationTypeForA::Minus: - factorized = Multiplication::Builder(a.clone(), solutionProduct); + factorized = Opposite::Builder(solutionProduct); break; case MultiplicationTypeForA::Parenthesis: factorized = Multiplication::Builder(Parenthesis::Builder(a.clone()), solutionProduct); From fea588bd9d4bf86300b43c9d1fb4603cbe3c719e Mon Sep 17 00:00:00 2001 From: Laury Date: Sun, 19 Dec 2021 19:10:51 +0100 Subject: [PATCH 139/355] [escher/timer] Little code quality improvement --- escher/include/escher/timer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/escher/include/escher/timer.h b/escher/include/escher/timer.h index fe53301eff7..3a7e2a7ec5b 100644 --- a/escher/include/escher/timer.h +++ b/escher/include/escher/timer.h @@ -18,10 +18,10 @@ class Timer { Timer(uint32_t period); // Period is in ticks bool tick(); void reset(); - uint32_t m_period; - uint32_t m_numberOfTicksBeforeFire; protected: virtual bool fire() = 0; + uint32_t m_period; + uint32_t m_numberOfTicksBeforeFire; }; #endif From 64a90422c40173476386e6a570a8aab7e94da7f9 Mon Sep 17 00:00:00 2001 From: Laury Date: Sun, 19 Dec 2021 19:23:15 +0100 Subject: [PATCH 140/355] [settings] Removed auto usb reactivation --- .../sub_menu/usb_protection_controller.cpp | 27 ++----------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/apps/settings/sub_menu/usb_protection_controller.cpp b/apps/settings/sub_menu/usb_protection_controller.cpp index 07eddc74363..734cf95faeb 100644 --- a/apps/settings/sub_menu/usb_protection_controller.cpp +++ b/apps/settings/sub_menu/usb_protection_controller.cpp @@ -24,18 +24,8 @@ UsbInfoController::UsbInfoController(Responder *parentResponder): bool UsbInfoController::handleEvent(Ion::Events::Event event) { if ((Ion::Events::OK == event || Ion::Events::EXE == event || Ion::Events::Right == event) && selectedRow() == 0) { - if (!GlobalPreferences::sharedGlobalPreferences()->dfuUnlocked()) { - if (!GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { - Ion::LED::setColor(KDColorPurple); - Ion::LED::setBlinking(500, 0.5f); - } - GlobalPreferences::sharedGlobalPreferences()->setDfuUnlocked(true); - } else { - if (!GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { - Ion::LED::setColor(KDColorBlack); - } - GlobalPreferences::sharedGlobalPreferences()->setDfuUnlocked(false); - } + bool dfuWasUnlocked = GlobalPreferences::sharedGlobalPreferences()->dfuUnlocked(); + GlobalPreferences::sharedGlobalPreferences()->setDfuUnlocked(!dfuWasUnlocked); m_selectableTableView.reloadCellAtLocation(0, 0); return true; } @@ -49,19 +39,6 @@ bool UsbInfoController::handleEvent(Ion::Events::Event event) { return true; } - // We cannot use things like willExitResponderChain because this view can disappear due to an USB connection, - // and in this case we must keep the DFU status. - if ((event != Ion::Events::USBPlug && event != Ion::Events::USBEnumeration) && - GlobalPreferences::sharedGlobalPreferences()->dfuUnlocked()) { - GlobalPreferences::sharedGlobalPreferences()->setDfuUnlocked(false); - m_selectableTableView.reloadCellAtLocation(0, 0); - Container::activeApp()->displayWarning(I18n::Message::USBProtectionReactivated); - if (!GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { - Ion::LED::setColor(KDColorBlack); - } - return true; - } - return GenericSubController::handleEvent(event); } From 685e73c52dbe79371dbd24f9e5b4392c377776dd Mon Sep 17 00:00:00 2001 From: Laury Date: Sun, 9 Jan 2022 21:46:39 +0100 Subject: [PATCH 141/355] [reader] Make reader works ! --- apps/reader/utility.cpp | 2 +- apps/reader/word_wrap_view.cpp | 426 +++++++++++++++++++++------------ apps/reader/word_wrap_view.h | 8 + kandinsky/fonts/LargeFont.ttf | Bin 500892 -> 499852 bytes kandinsky/fonts/SmallFont.ttf | Bin 478012 -> 478448 bytes 5 files changed, 284 insertions(+), 152 deletions(-) diff --git a/apps/reader/utility.cpp b/apps/reader/utility.cpp index 8ed4cd4f015..51fdc043a3d 100644 --- a/apps/reader/utility.cpp +++ b/apps/reader/utility.cpp @@ -117,7 +117,7 @@ const char * EndOfPrintableWord(const char * word, const char * end) { UTF8Decoder decoder(word); CodePoint codePoint = decoder.nextCodePoint(); const char * result = word; - while (codePoint != '\n' && codePoint != ' ' && codePoint != '%' && codePoint != '$') { + while (codePoint != '\n' && codePoint != ' ' && codePoint != '%' && codePoint != '$' && codePoint != '\\') { result = decoder.stringPosition(); if (result >= end) { break; diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index c116e620e85..f3a6d890be6 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -14,15 +14,20 @@ WordWrapTextView::WordWrapTextView() : m_pageOffset(0), m_nextPageOffset(0), m_length(0), - m_isRichTextFile(false) // Value isn't important, it will change when the file is loaded + m_isRichTextFile(false), // Value isn't important, it will change when the file is loaded + m_lastPagesOffsetsIndex(0) { - + for (int i = 0; i < k_lastOffsetsBufferSize; i++) { + m_lastPagesOffsets[i] = -1; // -1 Means : no informations + } } void WordWrapTextView::nextPage() { if(m_nextPageOffset >= m_length) { return; } + m_lastPagesOffsets[m_lastPagesOffsetsIndex] = m_pageOffset; + m_lastPagesOffsetsIndex = (m_lastPagesOffsetsIndex + 1) % k_lastOffsetsBufferSize; m_pageOffset = m_nextPageOffset; markRectAsDirty(bounds()); } @@ -38,223 +43,342 @@ void WordWrapTextView::previousPage() { return; } + /* We check if we have available data in our buffer */ + int offsetToCheck = (m_lastPagesOffsetsIndex + k_lastOffsetsBufferSize - 1) % k_lastOffsetsBufferSize; + if (m_lastPagesOffsets[offsetToCheck] != -1) { + m_lastPagesOffsetsIndex = offsetToCheck; + m_pageOffset = m_lastPagesOffsets[offsetToCheck]; + m_lastPagesOffsets[offsetToCheck] = -1; + markRectAsDirty(bounds()); + return; + } + const int charWidth = m_font->glyphSize().width(); const int charHeight = m_font->glyphSize().height(); - const char * endOfFile = text() + m_length; - const char * endOfWord = text() + m_pageOffset; - const char * startOfWord = StartOfPrintableWord(endOfWord, text()); + const char * endOfWord = text() + m_pageOffset - 1; + + KDCoordinate baseline = charHeight; - KDSize textSize = KDSizeZero; + KDPoint textBottomEndPosition = KDPoint(m_frame.width() - k_margin, m_frame.height() - k_margin); + KDCoordinate lineHeight = charHeight; - KDPoint textEndPosition(m_frame.width() - k_margin, m_frame.height() - k_margin); + while(endOfWord >= text()) { + // 1. Skip whitespaces and line jumps + while(endOfWord >= text() && (*endOfWord == ' ' || *endOfWord == '\n')) { + if(*endOfWord == '\n') { + textBottomEndPosition = KDPoint(m_frame.width() - k_margin, textBottomEndPosition.y() - lineHeight); + lineHeight = charHeight; + // We check if we must change page + if (textBottomEndPosition.y() - lineHeight <= k_margin) { + break; + } + } else { + textBottomEndPosition = KDPoint(textBottomEndPosition.x() - charWidth, textBottomEndPosition.y()); + } + endOfWord--; + } - while(startOfWord>=text()) { - startOfWord = StartOfPrintableWord(endOfWord-1, text()); - //endOfWord = EndOfPrintableWord(startOfWord, endOfFile); + // 3. If word is a color change + if (*endOfWord == '%' && *(endOfWord - 1) != '\\') { + const char * startOfWord = endOfWord - 2; + while (*startOfWord != '%') { + startOfWord--; + } - if (*startOfWord == '%') { if (updateTextColorBackward(startOfWord)) { - endOfWord = startOfWord - 1; + endOfWord = startOfWord - 1; // Update next endOfWord continue; + } else { + // TODO: print error } } - if (*endOfWord == '$') { - startOfWord = endOfWord - 1; - while (*startOfWord != '$') { - if (startOfWord < text()) { + KDSize textSize = KDSizeZero; + + // 4. If word is a mathematical expression + if (*endOfWord == '$' && *(endOfWord - 1) != '\\') { + // We go to the end of the expression + 1 + const char * expressionStart = --endOfWord; + while (*expressionStart != '$') { + if (expressionStart < text()) { break; // File isn't rightly formated } - startOfWord --; + expressionStart ++; } - startOfWord --; - TexParser parser = TexParser(startOfWord + 1, endOfWord - 2); - Poincare::Layout layout = parser.getLayout(); - textSize = layout.layoutSize(); - } - else { - if (*startOfWord == '\\' || *(startOfWord + 1) == '$') { - textSize = m_font->stringSizeUntil(startOfWord + 1, endOfWord); - } - else { - textSize = m_font->stringSizeUntil(startOfWord, endOfWord); - } - } - KDPoint textStartPosition = KDPoint(textEndPosition.x()-textSize.width(), textEndPosition.y()); + TexParser parser = TexParser(expressionStart, endOfWord); + Layout layout = parser.getLayout(); - if(textStartPosition.x() < k_margin) { - textEndPosition = KDPoint(m_frame.width() - k_margin, textEndPosition.y() - charHeight); - textStartPosition = KDPoint(textEndPosition.x() - textSize.width(), textEndPosition.y()); - } - if(textEndPosition.y() - textSize.height() < k_margin) { - break; - } + KDCoordinate layoutBaseline = layout.baseline(); - --startOfWord; - while(startOfWord >= text() && (*startOfWord == ' ' || *startOfWord == '\n')) { - if(*startOfWord == ' ') { - textStartPosition = KDPoint(textStartPosition.x() - charWidth, textStartPosition.y()); - } - else { - textStartPosition = KDPoint(m_frame.width() - k_margin, textStartPosition.y() - charHeight); + // We check if we must change baseline + if (layoutBaseline > baseline) { + baseline = layoutBaseline; } - --startOfWord; + + KDSize layoutSize = layout.layoutSize(); + textSize = KDSize(layoutSize.width(), layoutSize.height() + baseline - layoutBaseline); + + endOfWord = expressionStart; } - if(textStartPosition.y() < k_margin) { // If out of page, quit - break; + // 5. Else it's text + else { + // We go to the start of the word + const char * startOfWord = StartOfPrintableWord(endOfWord, text()); + + textSize = m_font->stringSizeUntil(startOfWord, endOfWord + 1); + + endOfWord = startOfWord; } - if(textStartPosition.y() != textEndPosition.y()) { // If line changed, x is at start of line - textStartPosition = KDPoint(m_frame.width() - k_margin, textStartPosition.y()); + // 6. We check if we must change line + if (textBottomEndPosition.x() - textSize.width() <= k_margin) { + textBottomEndPosition = KDPoint(m_frame.width() - k_margin, textBottomEndPosition.y() - lineHeight); + lineHeight = 0; + // We will check if we must change page below } - if(textStartPosition.x() < k_margin) { // Go to line if left overflow - textStartPosition = KDPoint(m_frame.width() - k_margin, textStartPosition.y() - charHeight); + textBottomEndPosition = KDPoint(textBottomEndPosition.x() - textSize.width(), textBottomEndPosition.y()); + + // 7. We update height of the line if needed + if (textSize.height() > lineHeight) { + lineHeight = textSize.height(); + // We check if we must change page + if (textBottomEndPosition.y() - lineHeight <= k_margin) { + break; + } } - textEndPosition = textStartPosition; - endOfWord = startOfWord + 1; - } - if(startOfWord + 1 == text()) { - m_pageOffset = 0; + endOfWord -= 1; } - else { - m_pageOffset = EndOfPrintableWord(startOfWord, endOfFile) - text() + 1; + + if (endOfWord + 1 == text()) { + m_pageOffset = 0; + } else { + m_pageOffset = endOfWord - text(); } + + // Because we ask for a redraw, m_endTextPosition must auto update at the bottom of drawRect... markRectAsDirty(bounds()); } void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(KDRect(0, 0, bounds().width(), bounds().height()), m_backgroundColor); + enum class ToDraw { + Text, + Expression + }; + + bool endOfPage = false; + const char * endOfFile = text() + m_length; const char * startOfWord = text() + m_pageOffset; - const char * endOfWord; - - if (*startOfWord != '$') { - endOfWord = EndOfPrintableWord(startOfWord, endOfFile); - } // Else we don't need to update endOfWord - KDPoint textPosition(k_margin, k_margin); + const int charWidth = m_font->glyphSize().width(); + const int charHeight = m_font->glyphSize().height(); - const int wordMaxLength = 128; + const int wordMaxLength = (m_frame.width() - 2*k_margin ) / charWidth; char word[wordMaxLength]; - Poincare::Layout layout; + Layout layout; - enum class ToDraw { - Text, - Expression, - Nothing - }; + KDPoint textPosition = KDPoint(k_margin, k_margin); + + while (!endOfPage && startOfWord < endOfFile) { + // We process line by line - ToDraw toDraw = ToDraw::Text; + const char * firstReadIndex = startOfWord; - const int charWidth = m_font->glyphSize().width(); - const int charHeight = m_font->glyphSize().height(); + // 1. We compute the size of what we are going to draw and the baseline + KDSize lineSize = KDSize(0, charHeight); + KDCoordinate baseline = charHeight / 2; + + while (firstReadIndex < endOfFile) { + + KDSize textSize = KDSizeZero; + + // 1.1. And we check if we are at the end of the line + if(*firstReadIndex == '\n') { + break; + } + + // 1.2. Check if we are in a color change + if (*firstReadIndex == '%') { // We assume each '%' non-escaped is announcing a color change // TODO : check file is rightly formated + // We go to the end of the color change + 1 + do { + firstReadIndex ++; + } while (*firstReadIndex != '%'); + firstReadIndex ++; + continue; + } - int nextLineOffset = charHeight; + // 1.3. Check if we are in a math expression + if (*firstReadIndex == '$') { + // We go to the end of the expression + 1 + const char * expressionStart = ++firstReadIndex; + while (*firstReadIndex != '$') { + if (firstReadIndex > endOfFile) { + break; // File isn't rightly formated + } + firstReadIndex ++; + } - KDSize textSize = KDSizeZero; + TexParser parser = TexParser(expressionStart, firstReadIndex); + Layout layout = parser.getLayout(); + KDCoordinate layoutBaseline = layout.baseline(); + // We check if we must change baseline + if (layoutBaseline > baseline) { + baseline = layoutBaseline; + } - while(startOfWord < endOfFile) { + KDSize layoutSize = layout.layoutSize(); + textSize = KDSize(layoutSize.width(), layoutSize.height() + baseline - layoutBaseline); - if (*startOfWord == '%') { // Look for color keyword (ex '%bl%') - if (updateTextColorForward(startOfWord)) { - startOfWord = endOfWord + 1; - endOfWord = EndOfPrintableWord(startOfWord, endOfFile); - continue; + firstReadIndex ++; } - } - if (*startOfWord == '$') { // Look for expression - endOfWord = startOfWord + 1; - while (*endOfWord != '$') { - if (endOfWord > endOfFile) { - break; // If we are here, it's bad... + // 1.4. Else it's text + else { + if ((*firstReadIndex == '\\' && *(firstReadIndex + 1) == '$') || (*firstReadIndex == '\\' && *(firstReadIndex + 1) == '%')) { // We escape '$' and '%' if needed + firstReadIndex ++; } - endOfWord ++; + + const char * endOfWord = EndOfPrintableWord(firstReadIndex + 1, endOfFile); + + textSize = m_font->stringSizeUntil(firstReadIndex, endOfWord); + + firstReadIndex = endOfWord; } - endOfWord ++; - TexParser parser = TexParser(startOfWord + 1, endOfWord - 1); - layout = parser.getLayout(); - textSize = layout.layoutSize(); - toDraw = ToDraw::Expression; - } - else { - if (*startOfWord == '\\' || *(startOfWord + 1) == '$') { - startOfWord ++; + // 1.5. We update size + int newWidth = lineSize.width() + textSize.width(); + // We check if the new text fit on the line + if (newWidth > m_frame.width() - 2 * k_margin) { + break; } - textSize = m_font->stringSizeUntil(startOfWord, endOfWord); - stringNCopy(word, wordMaxLength, startOfWord, endOfWord-startOfWord); - toDraw = ToDraw::Text; - } - KDPoint nextTextPosition = KDPoint(textPosition.x()+textSize.width(), textPosition.y()); - - if(nextTextPosition.x() > m_frame.width() - k_margin) { // Right overflow - textPosition = KDPoint(k_margin, textPosition.y() + nextLineOffset); - nextTextPosition = KDPoint(k_margin + textSize.width(), textPosition.y()); - nextLineOffset = charHeight; - } - if (nextLineOffset < textSize.height()) { - nextLineOffset = textSize.height(); + int newHeight; + if (lineSize.height() > textSize.height()) { + newHeight = lineSize.height(); + } else { + newHeight = textSize.height(); + // We check if all the content can be displayed + if (textPosition.y() + newHeight > bounds().height() - k_margin) { + endOfPage = true; + break; + } + } + lineSize = KDSize(lineSize.width() + textSize.width(), newHeight); + + // 1.6. We go to the next word + while (*firstReadIndex == ' ') { + lineSize = KDSize(lineSize.width() + charWidth, lineSize.height()); + ++firstReadIndex; + } } - if(textPosition.y() + textSize.height() > m_frame.height() - k_margin) { // Bottom overflow + if (endOfPage) { break; } - if (toDraw == ToDraw::Expression) { - layout.draw(ctx, textPosition, m_textColor, m_backgroundColor); - } - else if (toDraw == ToDraw::Text) { - ctx->drawString(word, textPosition, m_font, m_textColor, m_backgroundColor); - } + // 2. And now... we read the line again to draw it ! + while (startOfWord < endOfFile) { - while(*endOfWord == ' ' || *endOfWord == '\n') { - if(*endOfWord == ' ') { - nextTextPosition = KDPoint(nextTextPosition.x() + charWidth, nextTextPosition.y()); + //2.1. We check if we are at the end of the line + if (*startOfWord == '\n') { + startOfWord++; + textPosition = KDPoint(k_margin, textPosition.y() + lineSize.height()); + break; + // We aren't supposed to be at the end of the page, else the loop on top would have stopped drawing } - else { - nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + nextLineOffset); - nextLineOffset = charHeight; + + + const char * endOfWord; + + // 2.2. Check if we are in a color change + if (*startOfWord == '%') { + if (updateTextColorForward(startOfWord)) { + startOfWord += 2; // We can add at least 2 ('%' + the color first char) + while (*startOfWord != '%') { + startOfWord ++; + } + startOfWord ++; + continue; + } + else { + // TODO: Print exception + } } - ++endOfWord; - } - //We must change value of startOfWord now to avoid having - //two times the same word if the break below is used - startOfWord = endOfWord; + // 2.3. Check what we are going to draw and his size - if (endOfWord >= endOfFile) { - break; - } + KDSize textSize = KDSizeZero; + ToDraw toDraw; - if(nextTextPosition.y() + textSize.height() > m_frame.height() - k_margin) { // If out of page, quit - break; - } - if(nextTextPosition.y() != textPosition.y()) { // If line changed, x is at start of line - nextTextPosition = KDPoint(k_margin, nextTextPosition.y()); - } - if(nextTextPosition.x() > m_frame.width() - k_margin) { // Go to line if right overflow - nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + nextLineOffset); - nextLineOffset = charHeight; - } + // 2.3.1. Check if we are in a math expression + if (*startOfWord == '$') { + endOfWord = startOfWord + 1; + while (*endOfWord != '$') { + if (endOfWord > endOfFile) { + break; // File isn't rightly formated + } + endOfWord ++; + } + endOfWord ++; - textPosition = nextTextPosition; + TexParser parser = TexParser(startOfWord + 1, endOfWord - 1); + layout = parser.getLayout(); + textSize = layout.layoutSize(); - if (*startOfWord != '$') { - endOfWord = EndOfPrintableWord(startOfWord+1, endOfFile); - } // Else we don't need to update endOfWord - } + toDraw = ToDraw::Expression; + } + // 2.3.2 Else it's text + else { + if ((*startOfWord == '\\' && *(startOfWord + 1) == '$') || (*startOfWord == '\\' && *(startOfWord + 1) == '%')) { + startOfWord ++; + } + endOfWord = EndOfPrintableWord(startOfWord + 1, endOfFile); + textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + stringNCopy(word, wordMaxLength, startOfWord, endOfWord-startOfWord); + toDraw = ToDraw::Text; + } + + // 2.4 We decide where to draw and if we must change line + KDPoint endTextPosition = KDPoint(textPosition.x() + textSize.width(), textPosition.y()); + + // 2.4.1. Check if we need to go to the next line + if(endTextPosition.x() > m_frame.width() - k_margin) { + textPosition = KDPoint(k_margin, textPosition.y() + lineSize.height()); + break; + } + + // 2.5. Now we draw ! + if (toDraw == ToDraw::Expression) { + KDPoint position = KDPoint(textPosition.x(), textPosition.y() + baseline - layout.baseline()); + layout.draw(ctx, position, m_textColor, m_backgroundColor); + } + else { + KDPoint position = KDPoint(textPosition.x(), textPosition.y() + baseline - charHeight / 2); + ctx->drawString(word, position, m_font, m_textColor, m_backgroundColor); + } + + // 2.6. Update the position + textPosition = endTextPosition; + + // 2.7. And we go to the next word + while (*endOfWord == ' ') { + endOfWord++; + textPosition = KDPoint(textPosition.x() + charWidth, textPosition.y()); + } + startOfWord = endOfWord; + } + } m_nextPageOffset = startOfWord - text(); -}; +} BookSave WordWrapTextView::getBookSave() const { return { @@ -270,9 +394,9 @@ void WordWrapTextView::setBookSave(BookSave save) { bool WordWrapTextView::updateTextColorForward(const char * colorStart) const { - if (*(colorStart + 1) == '\\') { + if (*(colorStart + 1) == '\\' && (*(colorStart + 3) == '%' || *(colorStart + 4) == '%')) { m_textColor = Palette::PrimaryText; - return (*(colorStart + 3) == '%' || *(colorStart + 4) == '%'); + return true; } int keySize = 1; diff --git a/apps/reader/word_wrap_view.h b/apps/reader/word_wrap_view.h index a3f3c87ebba..fca07759ea8 100644 --- a/apps/reader/word_wrap_view.h +++ b/apps/reader/word_wrap_view.h @@ -25,11 +25,19 @@ class WordWrapTextView : public PointerTextView { bool updateTextColorForward(const char * colorStart) const; bool updateTextColorBackward(const char * colorStart) const; static const int k_margin = 10; + static const int k_lastOffsetsBufferSize = 10; int m_pageOffset; mutable int m_nextPageOffset; int m_length; bool m_isRichTextFile; mutable KDColor m_textColor; + /* + * Beacause the text that we draw can be of different sizes, we can't + * exactly know where the last page starts. + * So we store into a buffer (a cyclic stack) the offsets of the last pages. + */ + int m_lastPagesOffsets[k_lastOffsetsBufferSize]; + int m_lastPagesOffsetsIndex; }; } diff --git a/kandinsky/fonts/LargeFont.ttf b/kandinsky/fonts/LargeFont.ttf index 51a39d85a8dac3079b4897e2892ebadf30f93143..5446c81345e0052f09de201de4310080a84dcd42 100644 GIT binary patch delta 110605 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

    fWBJv%+<$ELFrcc~`OpUj1O_N#-AgpqdTxcaJN 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 KX7{qfReu2m@wYPo delta 111864 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 R7W&0PzYjhss@Rje{|^f!J3s&c diff --git a/kandinsky/fonts/SmallFont.ttf b/kandinsky/fonts/SmallFont.ttf index f7ee64b4e9d37de73c3dce64b744a02972947598..9df34301658a08187473ec46b3a9b60a3fe7f1d6 100644 GIT binary patch delta 17811 zcma)k4SbZvwfC8MK6aCAlHKfXc9Z?cW;e-hzBk!yHecj}B#@900t5&cAP7j5AW;#q zQi7IRq-epQ4=QTx|hCfeTjzV~+} z|I9oyXU?26b7tnud7gOeUdQ--jy=gqtrv($JG-HAR?fVft)yu^wAYu-*6&@Av7mKO zty@+I@Fi;pg^9CTZrcdJZJQ5*tF%vw)4KZ0~Wwep8izn^yV2O>1>{UA~q`+$^4{xJxl z&}ROv0M|y!??FfRgW*1^;D@P^-$!kHfC^C;?WmLHAg>Br0DK4N0iFQV@@0V94=_1_ z^ie=n@R^#p#d6DDGV^~Y51#3p^e40u&#M8IznZH2s`xbRUb0WoawxbAGOPUN44gCM z3!QZ3Gy0_G9PzqI4JPb!(n05hXZ~2|=yCsigydYpJU4cZkhjqG9C<0Odg&;|kw`JQ zC>!|XLfbRC{fqcB-S;14CLa6GE#|8&<|_{kGs}SUfUK-KEw;1y1J{1o^2~P*T>*)j0&WMbSX8ORmsiYt3l<+f+7?AO;Q zZ;mpZGStK+W!V`yRvB9>Xq545P!_+Qbux=mYh!K0N7pm9Px;dfZWy>6idBYIGmX;n zw4jSIQ$~cNtc)L)7Cc+c=T|WIKy^>DGI=E!SwE9>nMo0vQ8H69Xh+EkLsS)(^&`a2 zju0P1Ua2z(t;1QBe4dxRqJ(F0hw{4249dhfi&uIw<=INg!{R>W@WbLw!#`ckyNVOb zCNJ?T_@iW}GOA*CE7cd#=8fNrI-!Efm5ZPA`N9arDf*dWThffe5y}K+pi2SW35CiP zT0RR;nSdTb2uMrLDhK`{HuaZ-qFil(vP$GD3;ji*0v0SO4OI()VkbA7WKDwLaTjn) zaWxNBqpCpkxYa_l$;rLJbaSUMUT4x}RkW8jt*cLqH|jExEuCMH_2<-Fk1a+Qr%6o7 zC`z+r7*or)lb_Zt6FuHOrp61Myit=E# zH9jFJR8!~7yrRAI(}mb6NU`tdH^kLVPVycsFdbzp$LC=%x*z1R+5RGnF<|s~3z#>7 zC9=BrulRGH#@puewRyFSANYEW<;Dw}H($8XGMs!L-^(;D;L^e$P=0bhUt#HBABUH) ztHN_R@ur0f!{flOJ359}b3x)VD9b@v@E~7ru^7?~9-xc8*&F^U!k9OIJv==0AfL_W zW=wv>H}IcQ3QVDpPDc!T6oRUTz(y&DW{c4lWUZ7tI-ep2fxu5!7m z{2q;tUNj2u!?@4_tlqFXC!39t$!4X;i0so&b%~ zpo4zQU!=-Eg9TLUu^0k|>ga>21vAwzEYSDHT7Ax5r_U1Gt1nnk&;F(>!$;(_+)&z)Bbf`Df|^UJRE&lvf6PVEIt%NhSddAeT&iV1nA%iWq4MH{U>$gU zm*Ji`Ayyj2%*;@}T*c+f` zYO0F@dTw=wH`?-Y=jCPRq}oKSIX^#ClA9;Uv8Uwl(S`Gq4T;*+*kp57qQ2Z_pHY2% zM@6+WEhDRS*S6B;?y~Y!TXt?ycY3!c-R@9FlT$B-F+MVkA$s$hK^$PGjY&3gM$A_S z_0^57P_O*1Oq?^2wpbDaC{c7bvRVBjsIA2isCmoTsQLmFgU+RaVvA-<$H;hu z%wDKFH8iCd9L@Cbl!n-MFbF+fGe&LUx`ld2u-Mn=%g)X13^jHY_zMabR_0~<4TZ(o zEvtg1X=w#QOGNeG>K%4(Wtywbl44GtR%fRtEM>386VYp z(_|~V6wx}Je)x^nieX$b_9soLoj_O!n045P8=6_Cw42sc17p!bTXiAFp-zLm4td>F zUR?$WYCDvhkT*@`Eoi5frJ^i#%1|`22D?m&H(&{rUKJUzWV68&G?q+RMjr;HGt=V~ zog()a6g15CxSibQsPtQ1k*VMBE(kx(dY0LH>a8hxxhuogWo*}cZ&qw9X1E(OOc7lz z^iRuV)!(@>M#Sl*POV-kO5v@xQK)Q+6sX#WkWieG(4uVL%S}}c5G0SWHAT5PeTE^k z+T|LF2+;Yc=E$aGYfP*Lw#LfrIQzTMtsLprNFHkDOj+>*o}U!)6B^Z1zmcTsrE1sL zy(i6;tmv$9;OLhvPHZoJYK9b};G&D9CY*-qa}8A}dqYB-qT9>s3z{Kg7Mf*YWc|oz zBcDB$&qY3WDxZ&he&N8ee>5p~@8p@880Tb5ve}f#zBR(qV2L+`GR#EO7?oX*$!U)8 z)3Q_GjB%l;U|ujUCpD$O>360D0yCDDg_IZfaz{z{?OA?ZmJloza{XD$e7@9_(xUcl zJ+ZfJ?x?JAItvSWBI|56hT}RG#?I5omMMc8?vU;3DA<*~*NA3i@KaHv{K>;&akz`h z;3HB?ygC^oc_RCcNl7giTU?`L!Tk6mMILB8t$pPf2CbA5l}F~v&HetOOpB^T7EVU> zFjo(dKyk9AnuSV30Rc*5b(IG2h|@Dz02-X|Ox>dcIQDCpdhYWsJbSY9;*y&K<%`Pg zo|!AFN?TJg6pXDeNEAhkiKOM58ILasY^`yocG+Iq~vblwu7`S{W$~?caFe4@Ca8wpLZ8;?=mD)Hh4jxX|PA=v1`P<}x z|1)=LajNs0SE&5{05itQ6pGlWMX%I7$>vx`asFB;U0tf9Xrvy#qm)Eh%0nB(<|Ne< z&7!2pn5pX&-q8G&ys1AP$`%C6=K9SY-jGM1Qo5vOaV=xEX1^`pl~g|`&Kl>ndTgOQ zTS0+4zsVN+7(WnLRO9gkYn{t$;M0RaPvoS==xdw`L44k{pp?tKT#j59W=9JSdLJfjSgwxA_vdoC0s^jNIsT+%0 z3P#*JUdTcgrvTfiVFCDgSQ8pkvPIRDWijS1Yw}uG8ePHsjQITe1nF9>-jf^jBr#E7 zoImDBDN3o_b^Df_6kl?NZ^6 zl|iSF9H+WR(HnwAd1SWGVu1L?Q|mm5sxXNI$G@J1Q#%ud#Zmb&L7KCp@I>s92~}Qp z$g^PX40ep!fL%jyXruZ`QNu5Vf2$gKbOod_;afXbeJJkF39SuzJWjjCZ_S()a$74_ zH5V;!%Iofov&5yBIE(AtjW<_0Kb(I>vM=P$!nB&*dAuVb$!gMD+(x}4A-TS&s-@7| zF}uK3wWwt7cRkuft+gg6E#H}#YK#fLZ|SPD2eVVGIpr>AagIklepuwtU5L$~oGpvk z_rN<_tpgwKinWYV@c)@X9H`3VDyD7-X{ZcFhQb@!5X>VK#GIC4PRj~0ewxVmDXV`C zZ(kiG4QNNnJ(Y`|W5qa~Gd&=woO^J?{ zM3W;a#^|l`WmTKZC6271N1v6ZpW`%JtJ0l}VO(}kHo>hvjoJS^GA^UI*x4d$-3B$M z9WON>HDesjGnBXA@O*s4*fKdN0NGWMWRo2ZEsBuS&!8Z+Vo3o6r!+g9e^ncz(H zvWIMC*LId#yhXM|XF{SYAuUH|FvVE6n_`k;42hz^1uk+)=CPhopuMbebMNvD#uhI0 zty+IwC?}MjBny%tXymwTk3QXLi^Y0pv1yST&t!-4WUgRPT6RNK z30mnRs>V*`psInS$iWqh)bP#BBx1En5I3?-GfTBFy-l-hDRtdEZ-c2uQzBwSBy4eg|*GX5^H1jsU>EQEU{5s zqe>zIn33j21ejiQB^cczjCc`@OU;K=ccThNta{KkDR;ieTa>*&!4x-#ypEaKGiNR|n%1$gqj9b{n=9FqW6qXDUJ_(6 zaoQx=RaIa&n60%QTX~A(pBAlNFEDKk+!c+zs3}l>wX1ZABV@sn)rh)MhuP}x%XUw%U`{PK|5~tujpSw5D}8?v9JrzC6#qiG2@ap6Om+w@ zIxJxyhD~)td;{lJFOp-iEa4xw|GPA24MH{FcqGx&O^Uc7(PgPRZ!MV(E6U4PG-PJh zuc)Y4S)aiut1KfaDWfbavno1Miq?(~{KnxnYZlU`di zZ2c9lXYs24H4nn3P#&vLo-e{OeIQwkRmQvd92`PcOsXR?$uR?W9c?i~?0kr<-jVV# zv(cSL>1%*JsJB6NZ&84h) zSsKcoX=AqP0(Wt`UY8c|W|p`T9%E10D$4`L1Vc)8u)NF?T-BVXPaI`APG?4Gox@!% z7uucvYS*ywB>w}8xx{B7bm^v7l$ZCj0_7N%Qza-yV+1-!#)W=8(T``2kv$d`=qKE3>ASi6ZRck{n>l^I3Y1k(#g7H zsjl=TkKS!ydtPEp;-Z|8yreN!&~kXhoYhsag1K<37S6q|&8QPNb_o`_Ma_n%O@7v? zv_B#`lrQg;bjm7OXvWQ3U6AUIUj&(^?*h|1e-_GO&eqw~NR1kufv*^YJ3T z0h@u_fqQ{Rfu8{{0rAW0BzhH@_W`~<8qDPADF$36XfGAkDKFm28zF>xaHL|?)DEYw zU}`>k?eZ5H)g9P^*ka_~5pkWPyIFMhy=sK3G#CO(vb{WS?7fwuIu`#kp0D>4DOEcgZ?`N_txP^;H$Co3eujUrvr8F)h)j zhX+=giq{BK0fS!3H^FXQl*?M9CVl8_?jDH!ZY)C8$r$LE$VTM)CYSzAE_W&i8_1dp zdzmB0(kLt<>Yb?e9Ww^M0;zf=scG_8!f7dx_T@w(I;=QWNTTYS7d$Nt!$Ca=CTu;Et2 zEw?0X-w^1m&(jQE(s~NY?2gg`w^r*eD0SFFg&wVHRE?7{^?Vc8;s#_@H>2bVHJU1f zEVvySHF2!*wO6iu^6nddcYek5NAG1#%%2s$5Pl&%>Sf8{zo-n_FrW)Cpat~yz~Nqp z64!xf_VBZ!ez^s;agm`+oyuX@lBR|&dYX@tZz>KdV?bgtT(K~$B;?g~r$=5LD0Tg* zr+Nwwid6VGJ}^}yq|un@3>I}zl1&b#`6jps1Nzpo5Ms2=n470(qSzy1u_a}>dmLU% zjL90eR%c1o7qr(s_MoP?zBPT9>b6*|rPjez=P2WX)dTlre9r$(iqi>E0+3~(JwQ_|+ZpR9^r!*e6&qC_(zX=-HOh^#{imL|c7 zB&rlcvl&}sZtV0RwP{KNtunr}xLka6pnqG$z)}a(xhZ2T_V`z9G7U&yb{UN}Y zgMADs8y5*BitTBP(M*svf~-*u-vl%=A{)`$y?X2Uc8sQD1gifF3yio(xIL-YC`1Ub zm!Z(hbT{L=FY3%m$+iS3#+YVFT$CIrH2CU^vw}&6Qg20*B|#4Qt@HKCE^|zxH74N- zjW#h(r)3grbU7*=@fqp3zy|d03dG}*iyigb+Hw}v=h%!!TTM|~wPW74dUvJCURqY^ zcF#_Ym9=pRekI@6p71DMSHMwf2g=>1zTeGzecwP zo_gDsIrLwnTM@Y;0j?>zX0xvc^lui(^=e3`UWwl-bh3J--7B{V1vFcg$SlmDvVrus zn!k~%U^XTx1Cx{ilUGgMi@bL#KfRHv<ngX)|H)j3rRUSUp#>iUdQ5#>cAwYX-f8|`co`f z-kL1C!yp>$cCR-r&16h%wjdCSmnJJ}s%Fk@@MK$)-D&f#vfHm}@wkiptLM~I)a&bO z8{7KI%92ev+2-UFYjMu}mZr)%kyGG1le2gQ`;hYKCgr%EITZMh1|{_~MB&sJt@^$M z=bkMRzQ&kA_33j?hrIfZNll~I?Pxw4w16{!`wrp}P|(5DR|2Z`k>N;oumD~c;h*}# zM1$c#%65kq&2F6KoSEge!=+@RvtKKEXVx^8wacN(hJwtQtjT@FM?YWF3s+_>KC$ zi4Z#aj$`VsqHbM26iyqz@$A6-(iY{8VzCaMVFqd(>IGKSDDo<(l9@dSoK8=*I^77c zdZCpXg<*=Vw5OtK)~rptDm?jDRab?|yUT43U0hYFJ65NSVR5nMKylBeiV7zJu&bBn z<{M+)K{<2;EBmrE@;_XO7+`3m~BiqryHJNE5grnW({9B%I~&^ zC+y*$sIlfbpiIC-rcwdRh`wIVo6Tbpozx+etor*DIa=T=tc z=2@-5lBG*af>x_eNOOiltxdJ%PKU!$QQI`9Ea-AdhdAuhQ52|OT|Hw)_3HXS5j0&k z*~Y%Wmo7G1F+Eqth*JvlOHmRx^SK?8I<}HGGs_Ff)FcaUI;KXRolG)@KZs|pME5Up zE9053u%U6WzHdoKZl2-D{A<=W-I}svW^<>}k+QREc2k~p#x?8vcip;t^>Ej_JdMXq zV8>{{CoEYkQEC5DuqZX-vPm&~CK*#Sk~ogL7ZJATNvD3h;6=ss z(I9{zR#~Ryvtd2zp>0rKCA!s@V;=SXZ&u$We)E#d*s|)Gu3xY>CK{?Y zRL@9B@fBnw$2hdw#F!+v%VjpRYjxp=_!hf;LRTDE*IbZqGKttY1<_#edaEPG@F;BO zbD<4KU5v8q1HL>?y|z2}Aa=EPm7!;_%)8!`bTP?T;i_feFc{%;l^PqHi@>cF)--)T zoLNm|P1*Sc z)eEX;>}$N?hQ^j`UvjcL*=0&t=hqL-RTa~m*ed*WogUcPl@Z}Ei-3q037dONU zDy0y8Hzy5W{*m}`!vFB)$}qlM`8US4s&~_7rTblc9D{cehWIR%OBThbZ}`m0_!IJ6 zW*HuOL_8oT^Xbzy;x)KbLKg=7_e0z`yylQNj))wc!3K1EU{8f-xc^D~guQ%hIQ}*9E;jt&8F5fb zn0_;*p84*=fx5e`%KA%!Zg|C8;-hR>Iw$^>$#tr7b;?t>L3#7LVx>6H`-I^hyI_!` z^D(^T1M$45=#oT(`o)40{}sgA^^tgWvc^354!?nCEB|1UBhAcw4Zmf_!VSM|_|nR( zb#&xO~q`;@I*s#_i*!xh0j zwMmFmdzg$=#2Jj;Lj95=CKuZnUI^h+4TQMZjPM5yBR#{7c#FZzbZ zVu_M@C%c4$p-9=TmmINLRadP#n4Jzvb<`T3I1Ti~1XGy|v+e@B8()-dg{@cURp;*1oas`0=`p;UT0TUX2*YU)gbJ zkr>ee+XjYSR>LsN=n+DaIg_z-;RiH!L=d)wpJHqLYz>kvf?(H#ABc3=O@(Znz3|`g z2=#lJ6wI^;9^(zhZ`&0b*;!>#o%=E7 zMzT)Lq#l9eh_fN!lvd{t(QQng#^c2_YP_L<(VQM+|1cdsY&s>LiYA|gU2L1_{;xl| z-}Lm;ru%sk(ytS?!3~Cn(}uS|l5%oX!&}Rigy)4q?5XhMYoq43d|@E4a5*ecrJuIJ zM{_{(wjBrU$UcQBubw(|*GklT_=H-REqfP9v?OR~mrLvUMZ)Z=1Lg9IA$=U{% z9e%B0ZGV(|_yW%Ay5;}W1tn1Hl-6nlT|I*Zq!R2G0V%B182F}fQzdXv5@8Yw;YXx+SWm=LQUx&_CJxlAip$o+VQz-^t3t-FbOzFnqEThOfvP>FW4p zI26k9fu0wy@AfDw+6095Z)36W5bOx*s8XdNwMV5=h7M!vOH$rhD7GTfTcNP^@?57H z*sXvRYLG*Xl&f!Se8{WeU%b`M2(vkOt!c!|<>(6-^}DZxe|eW*fY0<~jZuA#rv|^H zm;3kX^!5d14NHnmF*d)~G1s3vr!>u4zOul*Zq?DA!OWu7HV=Q}OVw9=N`tOX63dBz0C?CM;zA^cHly2TqzGv|6N*)`44DO5kF zGvXt;hhn6~IhJ7h^glxj>k7Ze9N{DE1=jKY!v9$K^%`{w=S=S834FVf)X)9OaZ%9f zd4fB<&BeEWt>tIG&fpWn*Tzb3Y8+S6VmLI_u<+Za=32(fDs@!x5sQ+37rvenFj|Qi zEsoTnhFmmLpNSCC=qpb)m5Y9s!(kWFH+8Y!H=$2Ff!QoB2nNo4qRxB52wVf$^7U&C=mDcr@bP_`b#tGScA46%N7E5rpDTVY|J z^vv-4eNuH??BR$$_*A7(x%D8k#-^xLDG{oot&$bik*svj7u$wgw@ME-D0lrq(h5~n zqP%=iS~lGFq9oMenBC@sxh*Xf;+6GWzMT^dk8`=6u{#F(?sq7&S_FeuR{6qJRemqY ztCUeu_JT|~C(8e4imK&3>0haKs(do;;@%)1-;1tnLJl}!3{#|JBLpuPX-R(qo4iepk z{2qAZcl(I;p~1Z%-hYAUfr~^3%7`Aq^TVJWY$bYR4bi_f0V6~Q4-!3!^iU_!qNBG>0EBsG zC(*kHiOwG)`T%`h5Q+YRb|0bqK9KD7+QRZaXm?R!lfXOza{iT_C0@Ar{+74Cfb16p88g6EnGpnL(4%K`hlo%m#oh zvtK6W)B)#+rS)&ZFJxQ?h^3bhb8jW)87GFI0Lw&!%tOSy?ZkXd#IhTR;W)2um(6w3?XUu2n71E zpV+_cAolN_#2!0H>~YjTd4kxH{ltc}#GX1y?5GczAT|uzk*&mj4k4aLN5{7ldvS=^ z3DExv5N=vKi2W7#8|c2cNNl2&*grCe zeWe4A6T7^J*w=%^!r(F4MZBNJh_e#nd?#_SjJPyJTvJ6{s|U^#kGVuVu9LV9X~J&e ziJNdl)Dk!Nfm6hj@`xLK#7%fk-b&n}1yF7UQq~YpMQYzp+%ZAi*$yC08w8NLc2hr3 z-%s4VkGKcO05Z|Qn+F^vo{cmcg5~TWj=L4lyNnopAMqkE4CsIc;w3%6S>i$PDeWL0 zDgibDXdgO9yvzhN0T8b25b<&mC$MjbDVfB z0Vjb8;&o_Tx0Cox7cfY?9?uPUZa77}Q3pWFSw3J3@!6o6eFERZqthk`+k~>_4B#B` zIjEb{-;H0#0d(8~CauV~>48Dw^VSe=2g7!B(7ppWL3}>6nUC`MmxwRO0FDx0*ax6p zhZUG0zNi5>Lwqsn7k2mg#5-F7jK(t5_b)@`vU9|*=mstm??S~22(ki< zx>3H`1>kuNp4Woe+Wo|@vI2*RuPY;d^_D51gKL_Iug?Qc6YmA>wJN}4Bhc3koFIN3 zI=y~7@f$#U!v)O$jlIM-A0vL#MdIH9!&{CM-#SA4yL*UlN8N2mcR+yegV_&Ie@6-N zU1x~zHW9y52cZ70LE=9``5rv)IYs>Llf?Hyv3o8OR}K-sZxiwRj}kwypZG&-0OSYl z#DBa7^M4S9kM$6Lyq)+H=ZGKi6aNXA{d5a3M*OKA#Gh^eP7^=cP5jyO#DCUGeE2Z& z=PnT+?IZqk2z_i1@#jJJ0(c#_5`Pi-Uo;W_WjlcS6F%aj`PU%C8PL8ChOdtke*l@Wgj`S(zM{vh!W28mxFpb5a}^nZAO_(!Pt*hTykF#HsqjB9~& z#6N?^pLY?z1Sa^CF#h+W#3xYqCFs6_CSQT(GDhO^CE{Q2BObOBpA;ka2Z7F#z)q7O z)RG`Jksuu>K|4r7Eb3#sNr>wpA$|w-3kg0F5+_J7pfKqY2__v0W-V}(gyd5sSW$1? zPeRHe5>mm$zJmnE1rnSmNl5Dg4g;4-aBU|cok;MYeFn<2z|RYQ*|j9(WRQ@HvOLu1 z9UvjUgoJ`|68t0mBotYJJ`#!{S@A&<0((d(0dWZ$2hnLM7=&B^8kR$l$}J>R`2Yx4 z4JI`Zpbq(&J4tBp;}7gWGYk3I;M0VwwUTfT>XdF0?z59{ zKZJe&d=8-Q;VmTm7@ZtENWx>FKZJIVA1C1nPEmYZ+{#6N)$Kix#a5Ttv$ zgoLAqNqDw{gkb_kNEkugbEq3_0xpv9^L->7gMiO>lW-i&u-t@SqV5FR!fzB_8YAH( zI{ppvzr9StDLn}<*OKs`LnL5T39sP!)e_AAs~1Rk?Gy=Twv+J28WR2l#&4b@;cayI z4(i^slJGt{{-B413%w-#1^5V^UffB-CtFFt(JPFj{b$Jk6||Qi$QPi+Y7xF1C*dp9 zf89YsI0HCA!sHl_Uf6H#HK8YF z!G^OWUaJMR18B7IGKqbC0P@%EA@PR&ByK`_BZR&29EqDpNW7_s#4X6H*s2HG0cg1u zyuND%I!N56gN7GL-0lL<`F6DX-Z+W3XOOtFi^Mz5_LH~^6}!twyvs-8kBG!QV7B)> zi34CTaEZiwx=FnE0*Ut_y?>0v2dYRs&_LpYO(Z_FgT#l?*&rDI81x5+NPNss;^QcP z0(=kW0ciVE@Ep2K;?rpVYzc|O=0}c)GI5hjEoy3QMmCf9wFj^fk15Z8wQ$AlMlQ@doPuR08ZI5$A(= z7X6*w4uD_(*$EQgMuWHe01VZ+F%sVa;d{{bJUTuz_|XXxFLnbLNc2Y9!#Bneh?*RsZvm|~7n$NlcwE653iJ#X3p#S_diGOtgp#AF* zfVP)90OT(r|2O3ShWy`<|DqQ2|HVGw0*QY|2Y=rJoFZ`o3?{%}0)$_RKqoK=fbJhv zz#iZXiC>}9uTXxO0MK7X=U=1j>q8`lUBG@4C-uNi;1WsH4IC$lxqzJjtW)Ccz#uSA zlHdpS11J|!E*>LE(gT}-vn0u=m-msRA)o^|N|F}LwA+CTB*lO(<}^vMwZJKo;(Wk9 zl5}=p3viyK_+F9{z%$`EN&Sg?NJ>OQJsRlIv3{H+1M&uxCp7?Qlyn$4N0QM5Gyz+I zApp!wR-gme37i1X-i$Ic+M4@-1Heh(5=qG}pcMemWbjNrNBxooOf6t)0aFW@TENtK zk)+HClJZ5s53B+91E)wTAfO7^1AuWMo(oF=2vB$eKs$dYup78QQW5wUbpvNeD$W2P zQ1KW^fm&ckKYopqRDy~U5S4&I5DbHBfI*Ty?&t0VwNiCuu2&mgxa7TDBb+A?XUxcA;)L%9d{e@VxvqNh@?Dt%R`Mr=nPO zf~3{pb(IM~{knrBt@n|%;T%aD2TAJNiuu2;mn1j~(xx3GZO$O+CJ^3yoTRNF*j5ID z36gGGL(-1(B*7n$cJ`693(vd3=uXsOx0LR#B?*3jbT7*GizMA&LehcVBt3MLq`^ZZ z{kVywe*^8mkC5~j_&&amq$iOc>Bjv3)I`!#86-UoMo(`7(9ts;z-f|>4w3XTNcppI zl7>+?0?nR7`Onb_uG-SE<0SpZF_NAK^A|vO+(gogdB7f$enG%al75Nu6TKw;N(Feo zg!I?#B%Rz%(r@}O|Gz~ir@#<*a_K)o{ChC@!wKLLNw4GqXGl7|A3*x59ymqP9}kf9 zS|>?opz-S?B>f2*o<;dveI%WmAn6_OdJlZhgV*~I@&h|be+JE;kCJrZEJ+{s0HFIw z9o|oBfjuO_fsj7O^QB`X{mn|!7l%mtyOyMheI)&3gru)}NxBTd!k0;!>?WC;NEVw& zmb(DNL1PH$1acMM~O6aoZdjPTg7>jJv&IwumfjE&e{%~B-wkA zA0RpBEXjF?NzR8ba9HFbEpUkXQt{#$?pCoz3L6TQ)1%`n0BzIeZ4w6^p0lP_F-AD3T(Dv*Ez^AAGBFR^~fOcRH z$=8$tAh_lLfV%Zn02=mYkh}qmHlXa<<0SW)fKFgPFh=rqc)o58aGB)m_mO;qA2>ns zre2b7MBV12B;SO(n-7wFO9RPU!E>7r^S@mr`BqfkHbL?ZRDS;w$+v^xb_jDjH2gsx zaDn8Vhe*C-C&|0o0nq=@1Z)A$kh~kucY^*d2=gP*?6CsqV9zO%_v(O?B;UQA+=CavMHp%8jZo8XJLdXS@5J-Rk0m40KP*jwNXsMu! zii!#p8|sUSik44NX^Vm;Myyy-P^pa;6_qMgtf**Xi;4=%|2OX@i0$*W{l4#c{*qs2 z-kCFJ&di)SbLPBn9K6%H`wr)Zq=Xhf5gEreG&bc=%3n!_@h5K2&0}4+Psy4x{)V&pp7B1*Y_TP9L5sO8A`63jkXJV$}xdG2vi!NQZ{P}P0T7u_e zM2_7WZaqLEou8LnzIePvE z8iTZLsJ@EdOwD{9UB+*rEBRVFkFP;nQFbGB^G!%^q6vEaO|)L8LEDht_&!>JG+EF0 zfa#62oc|y|w_d&)9SMdvP&L=65sW87km9o)33^d4%GQD3O>{A6w(ES>(rlfF7{sbG z-y#1XzYW54(9YVJ>1xY$l*E5d9z2tthR%Eqf#>t}v=F=&0A0vmjJ|95Fzt1eKGcRo z-Z|*6RrJ+!#4CVng9en3gcm0xaw)2iO?ubdGe7$}$`Fn2NJe3)>Ki$h(cK+m^Ke3(D?%Nlmo!X~Z zw8dY@u{$dtjh?3UU%)Iqr-IR1XBRVQZLh&_4rrZ_MKbphJB&Hd z9y3d=z3+_Vle}z|w&i2Vsl9uz9Bu5Uc%4+MyHDQOTi?ZBFN?1_^A&I5|3!(It1@=2 zc3F&))U%_(puO}2i;XIwVx6`5j_c(X3`gzx+mRL<>my%rAEf|3^hpCivG&{>y!nn> zR3j&|7N5r_g#6{f8s;lY=e9%>_hc4vrL2Yr{6*Yb#5|d*!6exd)43GLuvNub-OJj? zcGaaj>lThh>SRWLL!Q}~=t)T|NKVO1O!3%^33&|*n3qi|o>tg0d(?SXj~cb&{My>{ zSH4v-WkGIwLw#kU$zqPTRn(7h<#tZ3keSrjsI}e6uZS59<(tay!hBU}uinYMJ*(y# zwA4G4=!TL~pVepe_;UhYKg7aMF*BH0JRALC^Iv8$iJ6=xXVA%5!PEkk-*x%|D3MWO z1?>gXwoQ|y(CJlX>P^3k-_GJ&(Mcsbk;niI9N*J+9MgU`ue48N?}RUAQ^TF?bKc(6 z6#lHSG5lOpQ*Xg$F7udqXqJa&2D(r*>G>>Z(LTGAM{7&(<;8ZNCBxzY=CN01g#RoA z;O(ckQ*YnBd@P@kbLKdo%YRKyD0Gkx4ynIMUu4!#*I8eCb#-6E}ElUW+HPe)gcR z1_W4D4|A}JSv46MwKFT&6jnL2R%9!Y^;YFrtvOZRsmOUnE|(=vR|>|?s|e0(Dv+m5 zlM5Q>RaDFyTcAwe%x+OK{iCz98vTUmnk{_U(K)JK_+yLJHkaXdz?6xS>C z4o3PZz~|PdQWP@DKps#Dj00u@7Xd#7ZUOED9tNHQUIOCgf*aTl&!vLA>#&eOAr~Y- zKPV|4z{C}cPVrEJhZ5xDv~^Xg5K9y`ps=C1q?85i1}nCdFaSc2d}T>47N}uC9|~=W zN&57HNKoW>t=NM(o=g*LMmk8Lb~P;E&taL3v!}PRv`GcQDdj1dwX>_r+S55#q^Tne zMq`W;Z#2d!3D%6HjMA7Tk|D{ORhU=pwVK?%Y*!%HA#sDk)BbAzG+f9sTsfm>`h(|< zFU-#4lErNa=d|V+Z8Es=y66~}o5d&jOvzbQW3$|~rLNS%3TIMbl4K%Imto=R^@SV5 z#{aNv^?l4?8z~lRWMr{il%s1`oMzd|CnUQt8=07mOiWP@@*d=tp8D)j}j?N-?pu8q8t8H{a zes*NO!ps_Q=lWf7c{w#n$qw0+YEO2?%bc|^u9!2@QZo|ND67*}#5 zv?%1GL>}ca$j2aWN8XA$k=jsZ8&WmN;?ME=?87II#S^f?HtEL=Ggw$@!|ds&WqAhY z8FPKbwK?W-?OaL7aK@*bJe5iMm`{JrHrfmAqj;Xv=^L9Lc3sSt*+*n0aV54gE&K!q zcOLY>rw`6fH~%m=HjP=M6y5iLY&~tCw1i-KqV-d{kD?){3PII~pz#dnUZOg*0qiQ2 zKVV@T3-&2fd0KvYaf-Wc+@!I(Nar;rP5$MS>@tg~HfQE|W)?F%4?+YWgo|1@<`8<+ zM`lb6HFlsgp^ed`JzJX_Z6%utsh5CBjxLwYk(ASuoxU=aVXKA%H_?WGK@bR zr3q-2uG5JUJ7z>IeLnIz$mby6h`GANFpHSHVw~A9 z*y+s4hGtdx8Ym8?!^)U=NLUxKi^@^zWlEfskd%~@<*=q3SpKX`9-rXI&aqqE2Ijnv zi~rQ-rh@7mi_IQ2ImP9!nKQCrN@jMltjIjAfW@XbQc}$c($DO(nIRPq@n&gCT9hR! zDj~{OIK8lWOlGh-r(&TmF{O4zW5s#R`Kk7_^vOl}ZTXq?nV1><3}D|<0X4GcwQHxz z&YmsTTC}&nX0b*qRLF!G>bZS$D>G?rcPkS@>6nv|!q7KiQdq>$GZ=a{Z0H#bJ%gcV zF!T(Dp25&F7Fk|Jly>{bx4*2NOU2TpM5hf3fb}Fj;l{eK_{@ z>_TnFZ&;GGpH!;Wb={1ESU3z0?GFjO#VdR&2W9EAWX1&eFaf?|><#yjgJH!|U~haL z%bRM%g(Q!xI&lnk9k%b#lN(Lb&@>ID!bx-MG@>*Er7=h=5BIFUN%&!Qi+*+rXIhOH zdLD7Aq2M;;E$MKyb%h_YbY2{=`pK!E4c#gJN&eiXKzd0&=XO zWghE{hG=V4bV8IZcaqa$FZX4aIb$E@_m{QTcpOEe^ZlVo>5L_w-%>m==(5@wFFU`C z*QR zvg{e9)v1kZ>XWOlu8&SjNyxQ1ic@mNIi1O#+F8MpnT`4Bz9#tCo-qM;%7~E}6Wi?B zC9Lg5CMi75ol}&OQJ5N&Zf~+>NYR#fr`KdoHzk#4xEl+C9W`!u^;BPBOLbv-!Ze*9T*{8W)^XMV6SRflXh7dcWR#na0^bQOnpkCw0VQPB^CAqDw~egOUL+y zZ|Xwcg?v!U*veWJaV4+r`Te6UAsPG1t520U1X7IqV z@R*e0D~!0YVV5ui+iI{zMa3y9IA$7slY`E{l=F(XRC7%YyVd4uZmKU$x8{^*nOyO< z!W@Uos>Ug6jJDWVM~p#HjEb!A$|z4oTX59XOFBKAH8mwwj%jIY&+}Gz?M6wFGfT6uETe(zo$ETDgUjfN4>|N<~ZP1oH@x? zaS0Y-g}&rHzrWJav+0J&aUC-0qk#$Y!*~ci;1Fzh5F$T(!1|F-fWQeDgvbxOt-e4o z;P(c-*zIC9eJ~11s#6#^@naWj2Mecf3Q-U1;KWT49tx9Sd#AM<6kImB_q=lNjKX+p zMb`-5`lfl&*$wWbJPWsHr_CGMM+nc}gGGgzy#dHcN$ zmYA$b-W0zxwZu0vtDtxJZ}}3Y-?qncqEuMDS8w9UAscQe!s^8%wF%Q)nlWUPVaSA0 zmV!RWE7UDFHZM3PM>fhWv7@_Sj#Elkd1+~lIU`s8q`O#&Qrwnl{fi{2aOT`j z|M+W0R$pEVwOKy2!=|3yVL7lHqSrE*4?W{VTBe&1FD5c_p~4E(4@JaUwQu+EmS}OP zCQ&QsdX^p*w<|Xu>-Al@T2sT+yZ2#vAavJ$?OJ@ZKN z2v4FTr#LA&+7e~4qQ60#NG9^UC#;!3Y#b7sb1J+)Z88)OLtz@h( zqq@u+$CzVmNn;V`W1GzB1*wh#w-q;9d7e?Vk4(&+gDzt^IA@j9_1M&u>3$(Uv^V;}v}$YS;BiV|$9bR8?$0#jJHdp^O(H zqO&*qNT|;{6y_VA95%XQcAY1)ZgyqmoVrX8(%kAin=N-#F49C>Zgq^eaegp3uQ69o z=QnzrT>ggatcHNg6==xHZt%Mz_Jq!ac_Tu;PWrRH55;At_rAS6m&FO^tp-D}Q7#+Z zv+6ph*7Btkt-Y~SZbi7)Ov(BhtJ-C^vX(>`oII?*IHdBh>iS;QI&YT7>27um21jCh z3L7(k6!|2?hR*T8L^uEZdWwUD@c)8eQg8UH*CxQEzA;f1O2r#r^CH z7|+|ZBhSkYOFZ02VP1q=DD0l9Z7D(!2x^ZouwIjdnuvkXU2zFQ25BBMRvAkKR4Ro0!Af7n~XxWYARWEJ(HVQ+kKNQsjxB%i~zQ#e(6NE zfO&>iK>N^)VE5EdnoSTS8euuHPg5vCdo4#A7Xu}NOsX#JwTbGuP&OWO@tBLb7x~Eb z0;m7Ky1V`8>b3*Y{EOQe`{G~S!qU&Zg~7Y5hT4qdOQBH+pfYpMk;|Q0YJp_Yo^EGx z+BenQRElB!e~F{I*tv03-{i`4XT=mhYiEJUmCp3aDL!N~eHmtRhA(p(^OR+n<1>67 z_V5F0!I%!8uVZw+dd)Rz{+JoQvYBHFl?Og%SE!z{Q5hMd%CbT#+=4PhW=EBIRI!eY zXBrSVCTLW$zK;Ev3!z=&!h^u1ZvO06DQFG)BYHNN%QgvU3}efen1i%)N606*_{*h`_odyn70n;h8~Jw2s~(J96vqgSq3e7mmD3yl$wXmky5l3 z-R4tjP3VBGlp;+T(0+3RI~$N5#YB`xT=osRpQFcVhr-af9Sp^&SsLyD zrOA>!RgrjXTA>@E=&X{oSk+`RO-XSl`zDpMhwqcKM@=r-os@4&Dzq2PE=n&ia4I}- z`hYPvSnu{!_ku6iooG&VnUz>awxcOE&lw+|Tr{CEZ*qQWN_DYoWM<){=ElsD;EER9 zG7>Dw?)XHnDY@8z=$IupG0|g+iOKd=Saa*ED_nV=(M=OZ8l3*c>OBt*s^f->1* zEEZ_*ZN#0S?-gdz?)jx`FzGSvWL%+zL6fvcEXo8$xIcpkVnr(K z1>wAoMU99aX*I>HHIx7oEg~Z+Xqo~y(5i=qFfXte!E^HBsQ$-T+k>9ul*}xLBkQ5z z%x6Ciye&ydPftlsPfs37fBla^ILSO@4YwfIf**y&&dyh|OU#?=qJL$G|G^IHp2$!{ zwSZkUWIymqLYfev>Ez)9_BqeB;m9mLhBLw;Su_wUFT=x#p!9% zV@4w8nxrAhB2G^eMi-4mjn-bf7pJH2R)%A6B43QEVyrht_^+d%KRQ8~6=U80&&&Cv z)f8__{xP8G`%y&mM@tBAQa6kG8jfezo*l0C*a_x{YZjwzj_d}5cD#U1vSMp>t&WXa=o1Ni_!kMBVsN2Q%iVpr_YpX zirg}FpYq}}ZQKP%*H44G*7WD%%!s_66)&ih2LQ3zkxxZFRYXz(C{T;F@E+de#uKWB zr$IeShBD+c4{P^7f-toxFNReRseb^#wi%A0h!+Eq9}#~W$!rCT9 z8J6wr*6=>g%;CX@xz_airtt4TD6VcZ^sAeL3Ry^ZTX!7b7H#br9?O`x7)($Iqm9)s zJjvKSUS;X@F-3;^kGgz~ zc(d~MFQNi)obdL#0CGFXr9Jj{dE5mhr9p4D(A0nj z(FJd|_);Tik2pL~V&M}7p37vZip9v9*N^ou*N7`7C*`Niymek#oUuJVN--4GUCZnR zHs-GJj1Em)njceRD!J|9XS)Im{iYFJ#qK&c>rMNByLilZaPb!7D}z$jsGsTjR~e_a z<6`iAH9pM7WK0fJ}CA)7*ReGf_FD}|1tH1SbQA{bh z{^Y#!43jK{e@&!4{Sja66mjrL_%f)FKF}9s=-^5braVPEGC{IOkXf4M{z!Iew`H+tts7fXd-p!cBEG27o;%LF_=tht z;?KF|!b19ZXtd%@w20$Z0s4{c6(jdjjQAcS2|<&nf}020rQU&d`GYF$SU;5GDFe12 zN`5APZu;-PywdRH)m6PMTjWB#fYI)4M_|+_$rkOw`{h)-_^eCS=T2BE@p@FmA98yA z56GKU&Q58|4=BzjAC^Di4;_0{HulVVEm{jcDQ9XQ-6vUly}RVE*pJIJl+Ew$e^UN= zswO*NBQASgj@C*pmkruIuok^nzb@Y+Yp?xWO4qinz!;j|l6|>wBhjlLidg{(DghC7 zs6kqT*%0{#RS!*^@YOTnx+UOimhUH`i@NvaU$EYTN8}Ss zI~B(*YQ31r`rh|Hkn810Nwp}c*4~Sg?Y$E}mfw@L?ccLh?bW^9puKVdi|w6tT)rsD zkaXrR{Cw`#UgnC^mBd^X2ieQv@`DxM+E_y1g~k`kg%S0f`IL9_R85tXlB8KdhVM-r z6)#RX7+?wEF9Q3*lh|WgtEAM=)5&l{%hUz$$7|O}aFZhyuf<`0@F5KrqolMJ!5*I_ zXlEWz@Wm-FR)mkaPL;pJ9uH4^sXY9JuwrMv##f8Cu}=^hjo0~U7FkKj+x_B;71y(S zuCEYj`26eB4_gnHuUl7+B(Q$H5O@BWm+{?LwKiQ=riCijuB~`K^+<5ty5N!2o61+R z)ho)Md8T|tc;(9S-5_2_Zl1?pgV68}^>_Xp2U$=GDz8Ens89@31>8(iAoPyLMKoj^**; z5K9cdg7#q0MFGUy{&Eh3D8V40AgCnytcL4YPWYK}R;x~tEEP=(tMrEtFl!i}$uYW^ z{Je;L^p9<=K8e{`woSQM3Qbm7Z7{qWOfG3)tHPf%zdyWBX_6wH*POW%pS@LM@A-$8 zjG;mp8u1p|B3w!nmQ0*)9I1GTE>5~g4iUV7&l|K`4Bns*(R8e)!Dp*^Z{Im{p1JS+ z3!5JL@w7kG6zP0e_sUfzKVRtH61FhhR$I$=Bz zo?SSkhh0-EDw5d5u81;rg&$}4S5}7SN1CKVy)S`!rx=v{P&(UDTN`d;T}vvMJA9;K z$@NXuBO5Oaguk$bKQF)FoGviv)}i?@4tdlZsS(4+S%aC85Z8_#DuMSDj}0o`q{O3Y z_?sIAd)805~LW2LcC9{qYN+>kHvwIV)%jemo;!j-Q;=lEi@Gfx%nn?;xzbban6 z*E@UoAe=1vE)j-KtfE>_$#6VWe?zzL!Ukv_DI=;2nPM;%1J$|Bm4M)deH zqFuYO7_mfstBIaEMfCJ83ehtgh@Lq~^xID0FwwIciJn_d^!#R`7tq;0G<*rquN)mqs|!W=n7^yd>qAD$%o2z`Cr zPV@=deTwqqgCU~7`iTaQ5dE!>=f$hYU3S#PNVo}?O#Tbd1+KI&-t1v79Z$yqk#SZX}imK-;nbV!jQ;{73^Qhy}r`VllDG-NdR66AM*uCpKy=vD&S~>bDUa z1Ie2bi8Vv0@rA@%z-)q_*u>?;CUp~=oJMR4Xs4bcHccirql?%~JkN;VvEpz5%^uair6I(a&Z>0m)K=8u_fSlc^ZY-Qa7om%mKjTR?x1+Al89) zJ%rk@f!NQ%;}@~S?$}HW3e7eh3K6@rli1y0a`!G`n+u8E+eT~)1iF6}u?G$k+q#X| zgC~e>L;a(P#I_$J_L~l3k2#3#Tuy9PGqEQ@+k1dmKZJlnvu6j0Jx9d$f_^W`Uqq+- zkiLvgU%|kB?fmVjs2< z`v^jO41qs!6Z>>IvCkF}`y9-_SV`=!Cy9M|5P#VMoB-X|)x^HpOzhib#J-zD?0YkC zgxIM;VyD3)yq$Q6+_1;1aT*;WF0CQ1z_6=pi5ohJ8}VI2R0HwoZNy{ih{u7(w1;>+ z>dhA+UpZ3ewaA#GT;d+)O+jY5H;EE~KtQ6yolq z#50Z%&jdU`HX7tCC+;QUxkz&l;;$SAh!@Dj3!8}-?IvCVhGiYV2I9U>05pE&1Fgi% z3xUPJcHj{4U@TAvKuBmm51t}kQ39;SA6=w{h*yDlWFoK?I7Ym>koYKcT+;@CsHP7% zNxXIsFi5-(jq6qsuXg~Oh>yne7(9>JOT0k_AY`K(=qBC-nkER`gigm+11M`w0}c@% zhq`g04*co^(D8UMX+eI15!gh0;vC|Wz;F^en6whuO}q`GX+wG2apKsDeDZeUQx*Ye zH`NRb5^t{t28d5X{j^qK8*qg9bO<$lG4UA<0A^z*>O(V8Ir9+lSslO$;Q3(de*;un+Q*+z?CP7|0I|APxljFagg{miNJBV4G{kgnEVFij{%RbCcZO~_^vI)pIinU zCf)}|{TRv9V7|M9_%pf0e|wPlvnPq~*+%?1?i&K@*kr7BR}zvONbxa030Iz$x5vMXNAO%qvG=&#J>Q; zL3A>>iTIb@#Q)X>94CGPOuj~=lNj+y)O`!O?=g~7pgDz^IK7&9I1AWL{LDtY!`na- zTTBw~B1sw`Nj^l9+C~!oC`^h*ee@tnF-J)bE)MpNfVEf)V7VJDX{=(+aU`ILM%7Y|b1)86rujQLa`Y8r?^kCXHW@{fY&_I9lQuh7A-LHuh7@tZ9q z?SOQTcapU05J^w0BB{3!I7(6<>YhSf|1y%Eb^v=w+6@7JyOyLqVD?-$NzbEhFWSC{ z4qiM((tdRO67sL)l60V&q*oV{^oPSFy^iNU;`xnEtpA&dB)xT#q(j?DdS?Sk?}G7r zu_V2Z4nIKMhYcisgpU8Rk)&grNcsf$44od|Mba1BNcw9BNrPzrCGvjBz=$i)2m1dw*iABojFA^>n2$`PO=(mAlZm(792-8u9@U`R9Lo%ck^FNo=;~{si(TkpBw#uiCKwUu^|WlKeF~_<9X+nB;H3 z;2SXb281UQfknU$0CeACnZDfu93vTbL-{+D!?l*b2mSZx{1nPg^^ttK1lUG$*bQtV z`HT#70|TT`39tzWog#&G0y{{2D5vl`U>ksP3FXoOQe-!<8aPghf_h~uDXJOh0``(( z05ih|;3O$V&>4@C64eGACMCK8*h)$a{u(r94RC^#*p;NjfoI$RDIwDqQcP$Vj|TDR z*h~QOW|Ujn0W`Ag1_nt<$O1ZmwZI+#%&fUU7qAI9NJ=8wC!#D7Z4*}k+kr!**kqst zm;->P4Logw6jGAFGzm@gO2EIQ8#qQvsULtqrAJ69YXdfhNbwPX3Ll7kVBiNs|1w|) zDS<)&hN42OUh_JfOccHke?bK)7wslx@iJ0iZIvZvtbaF= za`|dfu5ggDtdEqdK)Ae}locRYd4QB_*OId8Bq=v6C*{U8Qf`(>S%c?W!Dub&)MK=T(O<>OpZj_o4l6ZHEj&VS`Nq(6=+`YRfLiDy_W1r|#=ae|bu z+DZBP2r1vnKp!bEG|K5_U_0LRb&|?Ak}7W_Rl)yr#LxVu2~r&>PX@D;#Q+$mB8uv4 z1JJQ+1F3ExqXZZrHFFWEo-9(c4v?Ci2n>?yMVa>)sd>4i=AXoiCkQiQ4XMTL0M=hE z0i#k#Q?`~=AEfi6q5lY}q~2tTfb)bSYDc=R(KWi8;Zcol1Eh8o0%$jL1F5rikvb<47$kM>e&7_T^Gbj&U<;}9 zmjj1LU9g+f^Fe#TK~gUSp9@13__Y|=3>+qP(P{t$i;j_c5$Y~l1EAr>-K1UuMvGB) zX)LLibpl&~qogiL1Ms|L2dUl7z!6d}UrFjxGtfus6{x$CNWBVmKN%$TryEGU8a!7n z!}?#_M(Qe5Ue`eC^{8B32Y_HTgt-wTym2|HH~E1Rq~3gx)HRz)y#@5QbOL)xy)}!} z+wgoF=+{D+b)Z?_1)zhUIe-pQHzWcFNWHzA)Sn~YgLEU(jeS`EJ3usydvZzruQXtQ z)Caed`VczVR!!=|eqc3llGI1*0OYrW!7d}IPo5&RcaT)r19dk9ezuj=Jq-~&cY@U4 eq2uS#;ob@&d>rx~UKvFGYed3&_m~VNHUAHuW`KbJ From 8126e0da7e2405e5aeb5788277f74bae975beddd Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Mon, 10 Jan 2022 20:53:38 +0100 Subject: [PATCH 142/355] Fix cursor storage (#120) --- apps/code/python_text_area.h | 2 +- apps/code/script.cpp | 9 ++++----- apps/code/script.h | 12 ++++++------ apps/code/script_store.cpp | 2 +- apps/code/script_store.h | 4 ++-- apps/code/script_template.cpp | 2 +- apps/code/test/variable_box_controller.cpp | 2 +- ion/include/ion/storage.h | 4 ++-- poincare/include/poincare/evaluation.h | 2 +- 9 files changed, 19 insertions(+), 20 deletions(-) diff --git a/apps/code/python_text_area.h b/apps/code/python_text_area.h index c1a64db5ffe..fb4bba2dafe 100644 --- a/apps/code/python_text_area.h +++ b/apps/code/python_text_area.h @@ -30,7 +30,7 @@ class PythonTextArea : public TextArea { * - MiddleOfIdentifier is the cursor is in the middle of an identifier, * - No identifier otherwise. * The autocompletionLocation can be provided with autocompletionLocation, or - * retreived with autocompletionLocationBeginning and autocompletionLocationEnd. */ + * retrieved with autocompletionLocationBeginning and autocompletionLocationEnd. */ AutocompletionType autocompletionType(const char * autocompletionLocation = nullptr, const char ** autocompletionLocationBeginning = nullptr, const char ** autocompletionLocationEnd = nullptr) const; bool isAutocompleting() const { return m_contentView.isAutocompleting(); } protected: diff --git a/apps/code/script.cpp b/apps/code/script.cpp index 06b377e95ed..ed666d36c48 100644 --- a/apps/code/script.cpp +++ b/apps/code/script.cpp @@ -2,7 +2,7 @@ #include "script_store.h" #if APP_SCRIPT_LOG -#include +#include #endif namespace Code { @@ -72,13 +72,12 @@ uint8_t * StatusFromData(Script::Data d) { return const_cast(static_cast(d.buffer)); } -uint8_t * Script::CursorPosition() { +uint16_t * Script::CursorPosition() { assert(!isNull()); Data d = value(); - return StatusFromData(d) + StatusSize(); + return (uint16_t *)(StatusFromData(d) + StatusSize()); } - -void Script::setCursorPosition(uint8_t position) { +void Script::setCursorPosition(uint16_t position) { assert(!isNull()); Data d = value(); *CursorPosition() = position; diff --git a/apps/code/script.h b/apps/code/script.h index 1d816e1adfb..1c6ff1d15aa 100644 --- a/apps/code/script.h +++ b/apps/code/script.h @@ -23,9 +23,9 @@ namespace Code { * FetchedForVariableBoxBit is used to prevent circular importation problems, * such as scriptA importing scriptB, which imports scriptA. Once we get the * variables from a script to put them in the variable box, we switch the bit to - * 1 and won't reload it afterwards. - * - * Cursor Position is one byte long and has the cursor position value*/ + * 1 and won't reload it afterwards. + * + * Cursor Position is two bytes long and has the cursor position value */ class Script : public Ion::Storage::Record { private: @@ -35,7 +35,7 @@ class Script : public Ion::Storage::Record { // See the comment at the beginning of the file static constexpr size_t k_statusSize = 1; - static constexpr size_t k_cursorPositionSize = 1; + static constexpr size_t k_cursorPositionSize = 2; public: static constexpr int k_defaultScriptNameMaxSize = 6 + k_defaultScriptNameNumberMaxSize + 1; @@ -54,8 +54,8 @@ class Script : public Ion::Storage::Record { void toggleAutoimportationStatus(); const char * content() const; size_t contentSize() { return value().size - k_statusSize - k_cursorPositionSize; } - void setCursorPosition(uint8_t position); - uint8_t * CursorPosition(); + void setCursorPosition(uint16_t position); + uint16_t * CursorPosition(); /* Fetched status */ bool fetchedFromConsole() const; diff --git a/apps/code/script_store.cpp b/apps/code/script_store.cpp index 724e842cfc4..03632b04b3f 100644 --- a/apps/code/script_store.cpp +++ b/apps/code/script_store.cpp @@ -51,7 +51,7 @@ void ScriptStore::clearConsoleFetchInformation() { } Script::ErrorStatus ScriptStore::addScriptFromTemplate(const ScriptTemplate * scriptTemplate) { - size_t valueSize = Script::StatusSize() + Script::CursorPositionSize() + strlen(scriptTemplate->content()) + 1; // (auto importation status + content fetched status) + scriptcontent size + null-terminating char + size_t valueSize = Script::StatusSize() + Script::CursorPositionSize() + strlen(scriptTemplate->content()) + 1; // (auto importation status + cursor position + content fetched status) + scriptcontent size + null-terminating char assert(Script::nameCompliant(scriptTemplate->name())); Script::ErrorStatus err = Ion::Storage::sharedStorage()->createRecordWithFullName(scriptTemplate->name(), scriptTemplate->value(), valueSize); assert(err != Script::ErrorStatus::NonCompliantName); diff --git a/apps/code/script_store.h b/apps/code/script_store.h index 17f1d5f4597..9d232d00210 100644 --- a/apps/code/script_store.h +++ b/apps/code/script_store.h @@ -49,9 +49,9 @@ class ScriptStore : public MicroPython::ScriptProvider { * k_fullFreeSpaceSizeLimit, we consider the script store as full. * To be able to add a new empty record, the available space should at least * be able to store a Script with default name and its extension, the - * importation status (1 char), the default content "from math import *\n" + * importation status (1 char), the cursor (2 char), the default content "from math import *\n" * (20 char) and 10 char of free space. */ - static constexpr int k_fullFreeSpaceSizeLimit = sizeof(Ion::Storage::record_size_t)+Script::k_defaultScriptNameMaxSize+k_scriptExtensionLength+1+20+10+1; + static constexpr int k_fullFreeSpaceSizeLimit = sizeof(Ion::Storage::record_size_t)+Script::k_defaultScriptNameMaxSize+k_scriptExtensionLength+1+20+10+2; }; } diff --git a/apps/code/script_template.cpp b/apps/code/script_template.cpp index 15810c00c8b..eabdc427da8 100644 --- a/apps/code/script_template.cpp +++ b/apps/code/script_template.cpp @@ -2,7 +2,7 @@ namespace Code { -constexpr ScriptTemplate emptyScriptTemplate(".py", "\x01" "\x00" R"(from math import * +constexpr ScriptTemplate emptyScriptTemplate(".py", "\x01" "\x00\x00" R"(from math import * )"); diff --git a/apps/code/test/variable_box_controller.cpp b/apps/code/test/variable_box_controller.cpp index 855e3961af2..482ab84f629 100644 --- a/apps/code/test/variable_box_controller.cpp +++ b/apps/code/test/variable_box_controller.cpp @@ -63,7 +63,7 @@ QUIZ_CASE(variable_box_controller) { }; // FIXME This test does not load imported variables for now assert_variables_are( - "\x01\x01 from math import *\nfroo=3", + "\x01\x01\x01 from math import *\nfroo=3", "fr", expectedVariables, sizeof(expectedVariables) / sizeof(const char *)); diff --git a/ion/include/ion/storage.h b/ion/include/ion/storage.h index 14d70900463..56ee0fc26e9 100644 --- a/ion/include/ion/storage.h +++ b/ion/include/ion/storage.h @@ -122,8 +122,8 @@ class Storage { // Useful static bool FullNameCompliant(const char * name); - - // User by Python OS module + + // Used by Python OS module int numberOfRecords(); Record recordAtIndex(int index); diff --git a/poincare/include/poincare/evaluation.h b/poincare/include/poincare/evaluation.h index d0f50812361..ab25568ba4b 100644 --- a/poincare/include/poincare/evaluation.h +++ b/poincare/include/poincare/evaluation.h @@ -46,7 +46,7 @@ class Evaluation : public TreeHandle { /* This function allows to convert Evaluation to derived Evaluation. * * We could have overriden the operator T(). However, even with the - * 'explicit' keyword (which prevents implicit casts), direct initilization + * 'explicit' keyword (which prevents implicit casts), direct initialization * are enable which can lead to weird code: * ie, you can write: 'Complex a(2); MatrixComplex b(a);' * */ From 98c919db888a94bff7ed7fde8deaaa6f38f0f7fb Mon Sep 17 00:00:00 2001 From: Overengined <71342876+Overengined@users.noreply.github.com> Date: Mon, 10 Jan 2022 22:20:30 +0100 Subject: [PATCH 143/355] added mention of the installer in the readme (#119) --- README.fr.md | 7 +++++-- README.md | 8 +++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/README.fr.md b/README.fr.md index 937df4cb5f6..58555c9c808 100644 --- a/README.fr.md +++ b/README.fr.md @@ -25,9 +25,12 @@ Upsilon est un fork d'Omega, un fork d'Epsilon, l'OS de Numworks tournant sur le ## Installation -### Manuelle +### Site web +Rendez-vous sur le [site d'Upsilon](https://lolocomotive.github.io/Upsilon-website) à la section "Installer". +Si votre calculatrice est reconnue, qu'elle contient une version d'Epsilon inférieure à 16 et que votre navigateur accepte WebUSB, la page vous proposera d'installer Upsilon. +Ne débranchez votre calculatrice qu'une fois l'installation terminée. -*A l'heure actuelle, seule l'installation manuelle est possible.* +### Manuelle Tout d'abord, suivez **la première étape** [ici](https://www.numworks.com/resources/engineering/software/build/), puis : diff --git a/README.md b/README.md index cc98dc84fd6..3d391aa0c86 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,15 @@ Upsilon is a fork of Omega, an user-made OS that runs on the Numworks calculator ## Installation +### Installer + +Go to the [Upsilon website](https://lolocomotive.github.io/Upsilon-website) to the "Install" section. +If your calculator is recognized, contains a version of Epsilon lower than 16 and your browser accepts WebUSB, the page will suggest you to install Upsilon. +Do not disconnect your calculator until the installation is complete. + ### Manual -*As of today, only the manual installation is available. You can refer to this [website](https://www.numworks.com/resources/engineering/software/build/) for the first step if you get errors.* + *You can refer to this [website](https://www.numworks.com/resources/engineering/software/build/) for the first step if you get errors.* From a3e2c9732feef3d5ea3128b35051dd2654a11e33 Mon Sep 17 00:00:00 2001 From: apoleon33 <71031475+apoleon33@users.noreply.github.com> Date: Wed, 12 Jan 2022 22:25:26 +0100 Subject: [PATCH 144/355] fix link to the discord (#122) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3d391aa0c86..bf1dae20e2c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ cc by-nc-sa 4.0 Issues
    - Discord + Discord

    > Vous ne comprenez pas l'anglais ? Vous êtes francophone ? Regardez le [*LISEZ-MOI* français](./README.fr.md) ! From 77707cd930a88e57e8f2542f544c8bf5b3827ced Mon Sep 17 00:00:00 2001 From: Laury Date: Thu, 13 Jan 2022 17:20:53 +0100 Subject: [PATCH 145/355] [kandinsky] Fix bugs in fill_polygon and removed the limit of 32 points --- kandinsky/include/kandinsky/context.h | 1 - kandinsky/src/context_polygon.cpp | 10 ++++++---- python/port/mod/kandinsky/modkandinsky.cpp | 12 ++++-------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/kandinsky/include/kandinsky/context.h b/kandinsky/include/kandinsky/context.h index 09a1d73ec60..472f0b47b7d 100644 --- a/kandinsky/include/kandinsky/context.h +++ b/kandinsky/include/kandinsky/context.h @@ -42,7 +42,6 @@ class KDContext { virtual void pullRect(KDRect rect, KDColor * pixels) = 0; //Polygon - static const int k_polygonMaxNumberOfPoints = 32; void fillPolygon(KDCoordinate pointsX[], KDCoordinate pointsY[], int numberOfPoints, KDColor color); protected: KDContext(KDPoint origin, KDRect clippingRect); diff --git a/kandinsky/src/context_polygon.cpp b/kandinsky/src/context_polygon.cpp index 7dafd3cbef5..dc97ada5aa0 100644 --- a/kandinsky/src/context_polygon.cpp +++ b/kandinsky/src/context_polygon.cpp @@ -11,6 +11,7 @@ void KDContext::fillPolygon(KDCoordinate pointsX[], KDCoordinate pointsY[], int KDCoordinate right = m_clippingRect.width(); KDCoordinate left = 0; + // We find top and bottom of the polygon for (int i = 0; i < numberOfPoints; i++) { if (pointsY[i] < top) { top = pointsY[i]; @@ -20,6 +21,7 @@ void KDContext::fillPolygon(KDCoordinate pointsX[], KDCoordinate pointsY[], int } } + // We update them if needed if (top < 0) { top = 0; } @@ -29,13 +31,13 @@ void KDContext::fillPolygon(KDCoordinate pointsX[], KDCoordinate pointsY[], int for (int y=top; y<=bottom; y++) { - int switches=0; - KDCoordinate switchesX[KDContext::k_polygonMaxNumberOfPoints]; + KDCoordinate switchesX[numberOfPoints]; int lastPointIndex = numberOfPoints-1; for (int i=0; i=y) || (pointsY[lastPointIndex]<= y && pointsY[i]>=y)) && pointsY[i] != pointsY[lastPointIndex]) { + if (((pointsY[i]=y) || (pointsY[lastPointIndex]=y)) && + pointsY[i] != pointsY[lastPointIndex]) { switchesX[switches++] = (int) round(pointsX[i]+1.0*(y-pointsY[i])/(pointsY[lastPointIndex]-pointsY[i])*(pointsX[lastPointIndex]-pointsX[i])); } lastPointIndex=i; @@ -60,7 +62,7 @@ void KDContext::fillPolygon(KDCoordinate pointsX[], KDCoordinate pointsY[], int break; } if (switchesX[i+1]>left) { - fillRect( KDRect( switchesX[ i ] , y, switchesX[ i+1 ] - switchesX[ i ], 1 ), color ) ; + fillRect( KDRect(switchesX[ i ], y, switchesX[ i+1 ] - switchesX[ i ], 1 ), color ) ; } } } diff --git a/python/port/mod/kandinsky/modkandinsky.cpp b/python/port/mod/kandinsky/modkandinsky.cpp index 8053979f836..90568a4e156 100644 --- a/python/port/mod/kandinsky/modkandinsky.cpp +++ b/python/port/mod/kandinsky/modkandinsky.cpp @@ -132,23 +132,19 @@ mp_obj_t modkandinsky_fill_circle(size_t n_args, const mp_obj_t * args) { } mp_obj_t modkandinsky_fill_polygon(size_t n_args, const mp_obj_t * args) { - KDCoordinate pointsX[KDContext::k_polygonMaxNumberOfPoints]; - KDCoordinate pointsY[KDContext::k_polygonMaxNumberOfPoints]; - size_t itemLength; mp_obj_t * items; mp_obj_get_array(args[0], &itemLength, &items); + KDCoordinate pointsX[itemLength]; + KDCoordinate pointsY[itemLength]; + if (itemLength < 3) { mp_raise_ValueError("polygon must have at least 3 points"); } - else if (itemLength > KDContext::k_polygonMaxNumberOfPoints) { - mp_raise_ValueError("polygon is defined by too many points"); - } - for(unsigned int i=0; i Date: Thu, 13 Jan 2022 18:57:34 +0100 Subject: [PATCH 146/355] [apps/calculation] Make the second_degree_controller works --- .../second_degree_list_controller.cpp | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/apps/calculation/additional_outputs/second_degree_list_controller.cpp b/apps/calculation/additional_outputs/second_degree_list_controller.cpp index 2533c12f9d6..657792239ee 100644 --- a/apps/calculation/additional_outputs/second_degree_list_controller.cpp +++ b/apps/calculation/additional_outputs/second_degree_list_controller.cpp @@ -170,28 +170,27 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { if (x0Opposite.isUninitialized()) { PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::User); if (x0.type() == ExpressionNode::Type::Addition || x0.type() == ExpressionNode::Type::Subtraction) { - firstFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), Parenthesis::Builder(x0)); + firstFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), Parenthesis::Builder(x0.clone())); } else { - firstFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0); + firstFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0.clone()); } } else { - PoincareHelpers::Simplify(&x0Opposite, context, ExpressionNode::ReductionTarget::User); - firstFactor = Addition::Builder(Symbol::Builder("x", strlen("x")), x0Opposite); - } - if (x0.type() == ExpressionNode::Type::Opposite) { - factorized = Parenthesis::Builder(Addition::Builder(Symbol::Builder("x", strlen("x")), x0.childAtIndex(0).clone())); + if (x0Opposite.type() == ExpressionNode::Type::Addition || x0Opposite.type() == ExpressionNode::Type::Subtraction) { + x0Opposite = Parenthesis::Builder(x0Opposite.clone()); + } + secondFactor = Addition::Builder(Symbol::Builder("x", strlen("x")), x0Opposite.clone()); } Expression x1Opposite = getOppositeIfExists(x1, &reductionContext); if (x1Opposite.isUninitialized()) { PoincareHelpers::Simplify(&x1, context, ExpressionNode::ReductionTarget::User); - if (x0.type() == ExpressionNode::Type::Addition || x1.type() == ExpressionNode::Type::Subtraction) { - secondFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), Parenthesis::Builder(x1)); + if (x1.type() == ExpressionNode::Type::Addition || x1.type() == ExpressionNode::Type::Subtraction) { + secondFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), Parenthesis::Builder(x1.clone())); } else { - secondFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x1); + secondFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x1.clone()); } } else { @@ -199,7 +198,7 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { if (x1Opposite.type() == ExpressionNode::Type::Addition || x1Opposite.type() == ExpressionNode::Type::Subtraction) { x1Opposite = Parenthesis::Builder(x1Opposite.clone()); } - secondFactor = Addition::Builder(Symbol::Builder("x", strlen("x")), x1Opposite); + secondFactor = Addition::Builder(Symbol::Builder("x", strlen("x")), x1Opposite.clone()); } Expression solutionProduct = Multiplication::Builder(Parenthesis::Builder(firstFactor), Parenthesis::Builder(secondFactor)); @@ -228,16 +227,16 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { if (x0Opposite.isUninitialized()) { PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::User); - if (x0.type() == ExpressionNode::Type::Addition || x1.type() == ExpressionNode::Type::Subtraction) { - factor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), Parenthesis::Builder(x0)); + if (x0.type() == ExpressionNode::Type::Addition || x0.type() == ExpressionNode::Type::Subtraction) { + factor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), Parenthesis::Builder(x0.clone())); } else { - factor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0); + factor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0.clone()); } } else { PoincareHelpers::Simplify(&x0Opposite, context, ExpressionNode::ReductionTarget::User); - factor = Addition::Builder(Symbol::Builder("x", strlen("x")), x0Opposite); + factor = Addition::Builder(Symbol::Builder("x", strlen("x")), x0Opposite.clone()); } Expression solutionProduct = Power::Builder(Parenthesis::Builder(factor), Rational::Builder(2)); From f29677801461913db2ae50789f84a8d9984eed42 Mon Sep 17 00:00:00 2001 From: Laury Date: Fri, 14 Jan 2022 13:36:11 +0100 Subject: [PATCH 147/355] [python] Moved get_keys to ion module --- apps/code/python_toolbox.cpp | 2 +- python/port/genhdr/qstrdefs.in.h | 2 +- python/port/mod/ion/modion.cpp | 85 ++++++++++++++++++- python/port/mod/ion/modion.h | 3 +- python/port/mod/ion/modion_table.cpp | 6 ++ python/port/mod/kandinsky/modkandinsky.cpp | 79 ----------------- .../port/mod/kandinsky/modkandinsky_table.c | 2 - 7 files changed, 92 insertions(+), 87 deletions(-) diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index ad8f7b5481e..6b5686b189b 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -365,7 +365,6 @@ const ToolboxMessageTree KandinskyModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillRect, I18n::Message::PythonFillRect), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillCircle, I18n::Message::PythonFillCircle), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillPolygon, I18n::Message::PythonFillPolygon), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetKeys, I18n::Message::PythonGetKeys), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetPalette, I18n::Message::PythonGetPalette)}; const ToolboxMessageTree IonModuleChildren[] = { @@ -373,6 +372,7 @@ const ToolboxMessageTree IonModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromIon, I18n::Message::PythonImportIon, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIonFunction, I18n::Message::PythonIonFunction, false, I18n::Message::PythonCommandIonFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIsKeyDown, I18n::Message::PythonIsKeyDown), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetKeys, I18n::Message::PythonGetKeys), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBattery, I18n::Message::PythonBattery), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBatteryLevel, I18n::Message::PythonBatteryLevel), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBatteryIscharging, I18n::Message::PythonBatteryIscharging), diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index ec26ab46f6c..4e3b493cee5 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -340,6 +340,7 @@ Q(version_info) Q(zip) // Ion QSTR +Q(get_keys) Q(ion) Q(keydown) Q(battery) @@ -406,7 +407,6 @@ Q(set_pixel) Q(large_font) Q(small_font) Q(wait_vblank) -Q(get_keys) Q(get_palette) Q(PrimaryText) diff --git a/python/port/mod/ion/modion.cpp b/python/port/mod/ion/modion.cpp index 6a892a9f597..a631ebef8ac 100644 --- a/python/port/mod/ion/modion.cpp +++ b/python/port/mod/ion/modion.cpp @@ -18,9 +18,88 @@ mp_obj_t modion_battery() { } mp_obj_t modion_battery_level(){ - return mp_obj_new_int(static_cast(Ion::Battery::level())); + return mp_obj_new_int(static_cast(Ion::Battery::level())); } mp_obj_t modion_battery_ischarging(){ - return mp_obj_new_bool(Ion::Battery::isCharging()); -} \ No newline at end of file + return mp_obj_new_bool(Ion::Battery::isCharging()); +} + +struct key2mp +{ + Ion::Keyboard::Key key; + mp_obj_t string; +}; + +const static key2mp keyMapping[] = +{ + { Ion::Keyboard::Key::Left, MP_ROM_QSTR(MP_QSTR_left) }, + { Ion::Keyboard::Key::Right, MP_ROM_QSTR(MP_QSTR_right) }, + { Ion::Keyboard::Key::Down, MP_ROM_QSTR(MP_QSTR_down) }, + { Ion::Keyboard::Key::Up, MP_ROM_QSTR(MP_QSTR_up) }, + { Ion::Keyboard::Key::OK, MP_ROM_QSTR(MP_QSTR_OK) }, + { Ion::Keyboard::Key::Back, MP_ROM_QSTR(MP_QSTR_back) }, + + { Ion::Keyboard::Key::Home, MP_ROM_QSTR(MP_QSTR_home) }, + { Ion::Keyboard::Key::OnOff, MP_ROM_QSTR(MP_QSTR_onOff) }, + + { Ion::Keyboard::Key::Shift, MP_ROM_QSTR(MP_QSTR_shift) }, + { Ion::Keyboard::Key::Alpha, MP_ROM_QSTR(MP_QSTR_alpha) }, + { Ion::Keyboard::Key::XNT, MP_ROM_QSTR(MP_QSTR_xnt) }, + { Ion::Keyboard::Key::Var, MP_ROM_QSTR(MP_QSTR_var) }, + { Ion::Keyboard::Key::Toolbox, MP_ROM_QSTR(MP_QSTR_toolbox) }, + { Ion::Keyboard::Key::Backspace, MP_ROM_QSTR(MP_QSTR_backspace) }, + + { Ion::Keyboard::Key::Exp, MP_ROM_QSTR(MP_QSTR_exp) }, + { Ion::Keyboard::Key::Ln, MP_ROM_QSTR(MP_QSTR_ln) }, + { Ion::Keyboard::Key::Log, MP_ROM_QSTR(MP_QSTR_log) }, + { Ion::Keyboard::Key::Imaginary, MP_ROM_QSTR(MP_QSTR_imaginary) }, + { Ion::Keyboard::Key::Comma, MP_ROM_QSTR(MP_QSTR_comma) }, + { Ion::Keyboard::Key::Power, MP_ROM_QSTR(MP_QSTR_power) }, + + { Ion::Keyboard::Key::Sine, MP_ROM_QSTR(MP_QSTR_sin) }, + { Ion::Keyboard::Key::Cosine, MP_ROM_QSTR(MP_QSTR_cos) }, + { Ion::Keyboard::Key::Tangent, MP_ROM_QSTR(MP_QSTR_tan) }, + { Ion::Keyboard::Key::Pi, MP_ROM_QSTR(MP_QSTR_pi) }, + { Ion::Keyboard::Key::Sqrt, MP_ROM_QSTR(MP_QSTR_sqrt) }, + { Ion::Keyboard::Key::Square, MP_ROM_QSTR(MP_QSTR_square) }, + + { Ion::Keyboard::Key::Seven, MP_ROM_QSTR(MP_QSTR_7) }, + { Ion::Keyboard::Key::Eight, MP_ROM_QSTR(MP_QSTR_8) }, + { Ion::Keyboard::Key::Nine, MP_ROM_QSTR(MP_QSTR_9) }, + { Ion::Keyboard::Key::RightParenthesis, MP_ROM_QSTR(MP_QSTR__paren_open_) }, + { Ion::Keyboard::Key::LeftParenthesis, MP_ROM_QSTR(MP_QSTR__paren_close_) }, + + { Ion::Keyboard::Key::Four, MP_ROM_QSTR(MP_QSTR_4) }, + { Ion::Keyboard::Key::Five, MP_ROM_QSTR(MP_QSTR_5) }, + { Ion::Keyboard::Key::Six, MP_ROM_QSTR(MP_QSTR_6) }, + { Ion::Keyboard::Key::Multiplication, MP_ROM_QSTR(MP_QSTR__star_) }, + { Ion::Keyboard::Key::Division, MP_ROM_QSTR(MP_QSTR__slash_) }, + + { Ion::Keyboard::Key::One, MP_ROM_QSTR(MP_QSTR_1) }, + { Ion::Keyboard::Key::Two, MP_ROM_QSTR(MP_QSTR_2) }, + { Ion::Keyboard::Key::Three, MP_ROM_QSTR(MP_QSTR_3) }, + { Ion::Keyboard::Key::Plus, MP_ROM_QSTR(MP_QSTR__plus_) }, + { Ion::Keyboard::Key::Minus, MP_ROM_QSTR(MP_QSTR__hyphen_) }, + + { Ion::Keyboard::Key::Zero, MP_ROM_QSTR(MP_QSTR_0) }, + { Ion::Keyboard::Key::Dot, MP_ROM_QSTR(MP_QSTR__dot_) }, + { Ion::Keyboard::Key::EE, MP_ROM_QSTR(MP_QSTR_EE) }, + { Ion::Keyboard::Key::Ans, MP_ROM_QSTR(MP_QSTR_Ans) }, + { Ion::Keyboard::Key::EXE, MP_ROM_QSTR(MP_QSTR_EXE) }, +}; + +mp_obj_t modion_get_keys() { + micropython_port_interrupt_if_needed(); + + Ion::Keyboard::State keys = Ion::Keyboard::scan(); + mp_obj_t result = mp_obj_new_set(0, nullptr); + + for (unsigned i = 0; i < sizeof(keyMapping)/sizeof(key2mp); i++) { + if (keys.keyDown(keyMapping[i].key)) { + mp_obj_set_store(result, keyMapping[i].string); + } + } + + return result; +} diff --git a/python/port/mod/ion/modion.h b/python/port/mod/ion/modion.h index b4c99df027c..88367300cfd 100644 --- a/python/port/mod/ion/modion.h +++ b/python/port/mod/ion/modion.h @@ -4,4 +4,5 @@ mp_obj_t modion_keyboard_keydown(mp_obj_t key_o); mp_obj_t modion_battery(); mp_obj_t modion_battery_level(); mp_obj_t modion_battery_ischarging(); -extern const mp_obj_type_t file_type; \ No newline at end of file +mp_obj_t modion_get_keys(); +extern const mp_obj_type_t file_type; diff --git a/python/port/mod/ion/modion_table.cpp b/python/port/mod/ion/modion_table.cpp index 4b120d494d4..0e07b006b46 100644 --- a/python/port/mod/ion/modion_table.cpp +++ b/python/port/mod/ion/modion_table.cpp @@ -16,6 +16,11 @@ const mp_obj_fun_builtin_fixed_t modion_keyboard_keydown_obj = { {(mp_fun_0_t)modion_keyboard_keydown} }; +const mp_obj_fun_builtin_fixed_t modion_get_keys_obj = { + {&mp_type_fun_builtin_0}, + {(mp_fun_0_t)modion_get_keys} +}; + const mp_obj_fun_builtin_fixed_t modion_battery_obj = { {&mp_type_fun_builtin_0}, {(mp_fun_0_t)modion_battery} @@ -37,6 +42,7 @@ extern "C" const mp_rom_map_elem_t modion_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_battery_level), MP_ROM_PTR(&modion_battery_level_obj) }, { MP_ROM_QSTR(MP_QSTR_battery_ischarging), MP_ROM_PTR(&modion_battery_ischarging_obj) }, { MP_ROM_QSTR(MP_QSTR_keydown), MP_ROM_PTR(&modion_keyboard_keydown_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_keys), MP_ROM_PTR(&modion_get_keys_obj) }, { MP_ROM_QSTR(MP_QSTR_KEY_LEFT), MP_OBJ_NEW_SMALL_INT(Ion::Keyboard::Key::Left) }, { MP_ROM_QSTR(MP_QSTR_KEY_UP), MP_OBJ_NEW_SMALL_INT(Ion::Keyboard::Key::Up) }, { MP_ROM_QSTR(MP_QSTR_KEY_DOWN), MP_OBJ_NEW_SMALL_INT(Ion::Keyboard::Key::Down) }, diff --git a/python/port/mod/kandinsky/modkandinsky.cpp b/python/port/mod/kandinsky/modkandinsky.cpp index 90568a4e156..63e48320933 100644 --- a/python/port/mod/kandinsky/modkandinsky.cpp +++ b/python/port/mod/kandinsky/modkandinsky.cpp @@ -164,85 +164,6 @@ mp_obj_t modkandinsky_wait_vblank() { return mp_const_none; } -struct key2mp -{ - Ion::Keyboard::Key key; - mp_obj_t string; -}; - -const static key2mp keyMapping[] = -{ - { Ion::Keyboard::Key::Left, MP_ROM_QSTR(MP_QSTR_left) }, - { Ion::Keyboard::Key::Right, MP_ROM_QSTR(MP_QSTR_right) }, - { Ion::Keyboard::Key::Down, MP_ROM_QSTR(MP_QSTR_down) }, - { Ion::Keyboard::Key::Up, MP_ROM_QSTR(MP_QSTR_up) }, - { Ion::Keyboard::Key::OK, MP_ROM_QSTR(MP_QSTR_OK) }, - { Ion::Keyboard::Key::Back, MP_ROM_QSTR(MP_QSTR_back) }, - - { Ion::Keyboard::Key::Home, MP_ROM_QSTR(MP_QSTR_home) }, - { Ion::Keyboard::Key::OnOff, MP_ROM_QSTR(MP_QSTR_onOff) }, - - { Ion::Keyboard::Key::Shift, MP_ROM_QSTR(MP_QSTR_shift) }, - { Ion::Keyboard::Key::Alpha, MP_ROM_QSTR(MP_QSTR_alpha) }, - { Ion::Keyboard::Key::XNT, MP_ROM_QSTR(MP_QSTR_xnt) }, - { Ion::Keyboard::Key::Var, MP_ROM_QSTR(MP_QSTR_var) }, - { Ion::Keyboard::Key::Toolbox, MP_ROM_QSTR(MP_QSTR_toolbox) }, - { Ion::Keyboard::Key::Backspace, MP_ROM_QSTR(MP_QSTR_backspace) }, - - { Ion::Keyboard::Key::Exp, MP_ROM_QSTR(MP_QSTR_exp) }, - { Ion::Keyboard::Key::Ln, MP_ROM_QSTR(MP_QSTR_ln) }, - { Ion::Keyboard::Key::Log, MP_ROM_QSTR(MP_QSTR_log) }, - { Ion::Keyboard::Key::Imaginary, MP_ROM_QSTR(MP_QSTR_imaginary) }, - { Ion::Keyboard::Key::Comma, MP_ROM_QSTR(MP_QSTR_comma) }, - { Ion::Keyboard::Key::Power, MP_ROM_QSTR(MP_QSTR_power) }, - - { Ion::Keyboard::Key::Sine, MP_ROM_QSTR(MP_QSTR_sin) }, - { Ion::Keyboard::Key::Cosine, MP_ROM_QSTR(MP_QSTR_cos) }, - { Ion::Keyboard::Key::Tangent, MP_ROM_QSTR(MP_QSTR_tan) }, - { Ion::Keyboard::Key::Pi, MP_ROM_QSTR(MP_QSTR_pi) }, - { Ion::Keyboard::Key::Sqrt, MP_ROM_QSTR(MP_QSTR_sqrt) }, - { Ion::Keyboard::Key::Square, MP_ROM_QSTR(MP_QSTR_square) }, - - { Ion::Keyboard::Key::Seven, MP_ROM_QSTR(MP_QSTR_7) }, - { Ion::Keyboard::Key::Eight, MP_ROM_QSTR(MP_QSTR_8) }, - { Ion::Keyboard::Key::Nine, MP_ROM_QSTR(MP_QSTR_9) }, - { Ion::Keyboard::Key::RightParenthesis, MP_ROM_QSTR(MP_QSTR__paren_open_) }, - { Ion::Keyboard::Key::LeftParenthesis, MP_ROM_QSTR(MP_QSTR__paren_close_) }, - - { Ion::Keyboard::Key::Four, MP_ROM_QSTR(MP_QSTR_4) }, - { Ion::Keyboard::Key::Five, MP_ROM_QSTR(MP_QSTR_5) }, - { Ion::Keyboard::Key::Six, MP_ROM_QSTR(MP_QSTR_6) }, - { Ion::Keyboard::Key::Multiplication, MP_ROM_QSTR(MP_QSTR__star_) }, - { Ion::Keyboard::Key::Division, MP_ROM_QSTR(MP_QSTR__slash_) }, - - { Ion::Keyboard::Key::One, MP_ROM_QSTR(MP_QSTR_1) }, - { Ion::Keyboard::Key::Two, MP_ROM_QSTR(MP_QSTR_2) }, - { Ion::Keyboard::Key::Three, MP_ROM_QSTR(MP_QSTR_3) }, - { Ion::Keyboard::Key::Plus, MP_ROM_QSTR(MP_QSTR__plus_) }, - { Ion::Keyboard::Key::Minus, MP_ROM_QSTR(MP_QSTR__hyphen_) }, - - { Ion::Keyboard::Key::Zero, MP_ROM_QSTR(MP_QSTR_0) }, - { Ion::Keyboard::Key::Dot, MP_ROM_QSTR(MP_QSTR__dot_) }, - { Ion::Keyboard::Key::EE, MP_ROM_QSTR(MP_QSTR_EE) }, - { Ion::Keyboard::Key::Ans, MP_ROM_QSTR(MP_QSTR_Ans) }, - { Ion::Keyboard::Key::EXE, MP_ROM_QSTR(MP_QSTR_EXE) }, -}; - -mp_obj_t modkandinsky_get_keys() { - micropython_port_interrupt_if_needed(); - - Ion::Keyboard::State keys = Ion::Keyboard::scan(); - mp_obj_t result = mp_obj_new_set(0, nullptr); - - for (unsigned i = 0; i < sizeof(keyMapping)/sizeof(key2mp); i++) { - if (keys.keyDown(keyMapping[i].key)) { - mp_obj_set_store(result, keyMapping[i].string); - } - } - - return result; -} - mp_obj_t modkandinsky_get_palette() { mp_obj_t modkandinsky_palette_table = mp_obj_new_dict(0); mp_obj_dict_store(modkandinsky_palette_table, MP_ROM_QSTR(MP_QSTR_PrimaryText), TupleForKDColor(Palette::PrimaryText)); diff --git a/python/port/mod/kandinsky/modkandinsky_table.c b/python/port/mod/kandinsky/modkandinsky_table.c index 902338c5f3b..033dbf5b673 100644 --- a/python/port/mod/kandinsky/modkandinsky_table.c +++ b/python/port/mod/kandinsky/modkandinsky_table.c @@ -10,7 +10,6 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_fill_rect_obj, 5, 5, mod STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_fill_circle_obj, 4, 4, modkandinsky_fill_circle); STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_fill_polygon_obj, 2, 2, modkandinsky_fill_polygon); STATIC MP_DEFINE_CONST_FUN_OBJ_0(modkandinsky_wait_vblank_obj, modkandinsky_wait_vblank); -STATIC MP_DEFINE_CONST_FUN_OBJ_0(modkandinsky_get_keys_obj, modkandinsky_get_keys); STATIC MP_DEFINE_CONST_FUN_OBJ_0(modkandinsky_get_palette_obj, modkandinsky_get_palette); STATIC const mp_rom_map_elem_t modkandinsky_module_globals_table[] = { @@ -27,7 +26,6 @@ STATIC const mp_rom_map_elem_t modkandinsky_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_large_font), mp_const_true }, { MP_ROM_QSTR(MP_QSTR_small_font), mp_const_false }, { MP_ROM_QSTR(MP_QSTR_wait_vblank), (mp_obj_t)&modkandinsky_wait_vblank_obj }, - { MP_ROM_QSTR(MP_QSTR_get_keys), (mp_obj_t)&modkandinsky_get_keys_obj }, { MP_ROM_QSTR(MP_QSTR_get_palette), (mp_obj_t)&modkandinsky_get_palette_obj }, }; From f862cb023a97c6217e2581bc9515ab45537808b3 Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Fri, 14 Jan 2022 13:42:30 +0100 Subject: [PATCH 148/355] [apps/code] Add a setting for cursor saving (#125) --- apps/code/editor_controller.cpp | 7 ++++++- apps/global_preferences.h | 4 ++++ apps/settings/base.de.i18n | 1 + apps/settings/base.en.i18n | 1 + apps/settings/base.es.i18n | 1 + apps/settings/base.fr.i18n | 1 + apps/settings/base.hu.i18n | 1 + apps/settings/base.it.i18n | 1 + apps/settings/base.nl.i18n | 1 + apps/settings/base.pt.i18n | 1 + apps/settings/main_controller.cpp | 2 +- apps/settings/main_controller.h | 2 +- .../sub_menu/code_options_controller.cpp | 20 +++++++++++++++---- .../sub_menu/code_options_controller.h | 3 ++- 14 files changed, 38 insertions(+), 8 deletions(-) diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index 4cb3e5a9857..bb02d1ff66b 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -4,6 +4,7 @@ #include "app.h" #include #include +#include "../global_preferences.h" using namespace Shared; @@ -63,7 +64,11 @@ void EditorController::didBecomeFirstResponder() { void EditorController::viewWillAppear() { ViewController::viewWillAppear(); m_editorView.loadSyntaxHighlighter(); - m_editorView.setCursorLocation(m_script.content() + *m_script.CursorPosition()); + if(GlobalPreferences::sharedGlobalPreferences()->cursorSaving()) { + m_editorView.setCursorLocation(m_script.content() + *m_script.CursorPosition()); + } else { + m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); + } } void EditorController::viewDidDisappear() { diff --git a/apps/global_preferences.h b/apps/global_preferences.h index a54a5d8a650..bcfa324ea21 100644 --- a/apps/global_preferences.h +++ b/apps/global_preferences.h @@ -38,6 +38,8 @@ class GlobalPreferences { void setAutocomplete(bool autocomple) { m_autoComplete = autocomple; } bool syntaxhighlighting() const { return m_syntaxhighlighting; } void setSyntaxhighlighting(bool syntaxhighlight) { m_syntaxhighlighting = syntaxhighlight; } + bool cursorSaving() const { return m_cursorSaving; } + void setCursorSaving(bool cursorsave) { m_cursorSaving = cursorsave; } int brightnessLevel() const { return m_brightnessLevel; } void setBrightnessLevel(int brightnessLevel); const KDFont * font() const { return m_font; } @@ -56,6 +58,7 @@ class GlobalPreferences { m_dfuLevel(0), m_autoComplete(true), m_syntaxhighlighting(true), + m_cursorSaving(true), m_brightnessLevel(Ion::Backlight::MaxBrightness), m_font(KDFont::LargeFont) {} I18n::Language m_language; @@ -69,6 +72,7 @@ class GlobalPreferences { uint8_t m_dfuLevel; bool m_autoComplete; bool m_syntaxhighlighting; + bool m_cursorSaving; int m_brightnessLevel; const KDFont * m_font; }; diff --git a/apps/settings/base.de.i18n b/apps/settings/base.de.i18n index 5675747fa10..2f38be2bb73 100644 --- a/apps/settings/base.de.i18n +++ b/apps/settings/base.de.i18n @@ -71,6 +71,7 @@ Time = "Uhrzeit" RTCWarning1 = "Das Aktivieren der Uhr verkürzt die" RTCWarning2 = "Akkulaufzeit im Bereitschaftsmodus." SyntaxHighlighting = "Syntaxhervorhebung" +CursorSaving = "Cursor speichern" USBExplanation1 = "USB-Schutz schützt Ihren" USBExplanation2 = "Taschenrechner vor" USBExplanation3 = "unbeabsichtigter Verriegelung" diff --git a/apps/settings/base.en.i18n b/apps/settings/base.en.i18n index ddc854c0ed9..ebdc55abe8b 100644 --- a/apps/settings/base.en.i18n +++ b/apps/settings/base.en.i18n @@ -71,6 +71,7 @@ Time = "Time" RTCWarning1 = "Enabling the clock drains the battery faster" RTCWarning2 = "when the calculator is powered off." SyntaxHighlighting = "Syntax Highlighting" +CursorSaving = "Cursor saving" USBExplanation1 = "The USB protection protects" USBExplanation2 = "the calculator from" USBExplanation3 = "unintentional locking" diff --git a/apps/settings/base.es.i18n b/apps/settings/base.es.i18n index 1e59a1d160a..b3b8ec6e6ed 100644 --- a/apps/settings/base.es.i18n +++ b/apps/settings/base.es.i18n @@ -71,6 +71,7 @@ Time = "Hora" RTCWarning1 = "Activar el reloj gasta la batería más rápido" RTCWarning2 = "cuando la calculadora está apagada." SyntaxHighlighting = "Resaltado de sintaxis" +CursorSaving = "Ahorro de cursor" USBExplanation1 = "La protección USB protege" USBExplanation2 = "su calculadora del" USBExplanation3 = "bloqueo involuntario" diff --git a/apps/settings/base.fr.i18n b/apps/settings/base.fr.i18n index 758cb2087ef..3204ba62161 100644 --- a/apps/settings/base.fr.i18n +++ b/apps/settings/base.fr.i18n @@ -71,6 +71,7 @@ Time = "Heure" RTCWarning1 = "Activer l'horloge décharge la batterie plus" RTCWarning2 = "vite quand la calculatrice est éteinte." SyntaxHighlighting = "Coloration syntaxique" +CursorSaving = "Sauvegarde du curseur" USBExplanation1 = "La protection USB protège votre" USBExplanation2 = "calculatrice contre un verrouillage" USBExplanation3 = "non-intentionnel" diff --git a/apps/settings/base.hu.i18n b/apps/settings/base.hu.i18n index 1518f3982e9..080b7b5da40 100644 --- a/apps/settings/base.hu.i18n +++ b/apps/settings/base.hu.i18n @@ -71,6 +71,7 @@ Time = "Óra" RTCWarning1 = "Amikor a számológép alvómódban van, az óra" RTCWarning2 = "használása az elemet gyorsabban meríti ki." SyntaxHighlighting = "Szintaxis kiemelés" +CursorSaving = "Kurzor mentése" USBExplanation1 = "Az USB-védelem megvédi" USBExplanation2 = "a számológépet a nem" USBExplanation3 = "szándékos reteszeléstől" diff --git a/apps/settings/base.it.i18n b/apps/settings/base.it.i18n index fb253c16114..ae64c22ddc7 100644 --- a/apps/settings/base.it.i18n +++ b/apps/settings/base.it.i18n @@ -71,6 +71,7 @@ Time = "Time" RTCWarning1 = "Enabling the clock drains the battery faster" RTCWarning2 = "when the calculator is powered off." SyntaxHighlighting = "Evidenziazione della sintassi" +CursorSaving = "Salvataggio cursore" USBExplanation1 = "La protezione USB protegge" USBExplanation2 = "la calcolatrice dal" USBExplanation3 = "blocco involontario" diff --git a/apps/settings/base.nl.i18n b/apps/settings/base.nl.i18n index 88e42f70c3e..6d72e340249 100644 --- a/apps/settings/base.nl.i18n +++ b/apps/settings/base.nl.i18n @@ -71,6 +71,7 @@ Time = "Time" RTCWarning1 = "Enabling the clock drains the battery faster" RTCWarning2 = "when the calculator is powered off." SyntaxHighlighting = "Syntax Highlighting" +CursorSaving = "Cursor opslaan" USBExplanation1 = "USB-beveiliging beschermt uw" USBExplanation2 = "rekenmachine tegen" USBExplanation3 = "onbedoelde vergrendeling" diff --git a/apps/settings/base.pt.i18n b/apps/settings/base.pt.i18n index 7cc14a15fda..d1f42789c1a 100644 --- a/apps/settings/base.pt.i18n +++ b/apps/settings/base.pt.i18n @@ -71,6 +71,7 @@ Time = "Time" RTCWarning1 = "Enabling the clock drains the battery faster" RTCWarning2 = "when the calculator is powered off." SyntaxHighlighting = "Destaque da sintaxe" +CursorSaving = "Economia de cursor" USBExplanation1 = "A proteção USB protege" USBExplanation2 = "sua calculadora contra" USBExplanation3 = "bloqueios não intencionais" diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index 6ee5ff5b103..df488099020 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -24,7 +24,7 @@ constexpr SettingsMessageTree s_contributorsChildren[23] = {SettingsMessageTree( // Code Settings #ifdef HAS_CODE -constexpr SettingsMessageTree s_codeChildren[3] = {SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Autocomplete), SettingsMessageTree(I18n::Message::SyntaxHighlighting)}; +constexpr SettingsMessageTree s_codeChildren[4] = {SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Autocomplete), SettingsMessageTree(I18n::Message::SyntaxHighlighting), SettingsMessageTree(I18n::Message::CursorSaving)}; #endif constexpr SettingsMessageTree s_modelFontChildren[2] = {SettingsMessageTree(I18n::Message::LargeFont), SettingsMessageTree(I18n::Message::SmallFont)}; diff --git a/apps/settings/main_controller.h b/apps/settings/main_controller.h index ffcc2cfe63f..cb3aae2f966 100644 --- a/apps/settings/main_controller.h +++ b/apps/settings/main_controller.h @@ -24,7 +24,7 @@ extern const Shared::SettingsMessageTree s_symbolChildren[4]; extern const Shared::SettingsMessageTree s_symbolFunctionChildren[3]; extern const Shared::SettingsMessageTree s_modelMathOptionsChildren[6]; extern const Shared::SettingsMessageTree s_modelFontChildren[2]; -extern const Shared::SettingsMessageTree s_codeChildren[3]; +extern const Shared::SettingsMessageTree s_codeChildren[4]; extern const Shared::SettingsMessageTree s_modelDateTimeChildren[3]; extern const Shared::SettingsMessageTree s_accessibilityChildren[6]; extern const Shared::SettingsMessageTree s_contributorsChildren[23]; diff --git a/apps/settings/sub_menu/code_options_controller.cpp b/apps/settings/sub_menu/code_options_controller.cpp index 8f8ecdbf8ce..f3c17b2fc3f 100644 --- a/apps/settings/sub_menu/code_options_controller.cpp +++ b/apps/settings/sub_menu/code_options_controller.cpp @@ -13,6 +13,7 @@ CodeOptionsController::CodeOptionsController(Responder * parentResponder) : m_chevronCellFontSize.setMessageFont(KDFont::LargeFont); m_switchCellAutoCompletion.setMessageFont(KDFont::LargeFont); m_switchCellSyntaxHighlighting.setMessageFont(KDFont::LargeFont); + m_switchCellCursorSaving.setMessageFont(KDFont::LargeFont); } bool CodeOptionsController::handleEvent(Ion::Events::Event event) { @@ -21,11 +22,15 @@ bool CodeOptionsController::handleEvent(Ion::Events::Event event) { case 1: GlobalPreferences::sharedGlobalPreferences()->setAutocomplete(!GlobalPreferences::sharedGlobalPreferences()->autocomplete()); m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); - break; + break; case 2: GlobalPreferences::sharedGlobalPreferences()->setSyntaxhighlighting(!GlobalPreferences::sharedGlobalPreferences()->syntaxhighlighting()); m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); break; + case 3: + GlobalPreferences::sharedGlobalPreferences()->setCursorSaving(!GlobalPreferences::sharedGlobalPreferences()->cursorSaving()); + m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); + break; default: GenericSubController * subController = nullptr; subController = &m_preferencesController; @@ -34,7 +39,6 @@ bool CodeOptionsController::handleEvent(Ion::Events::Event event) { m_lastSelect = selectedRow(); stack->push(subController); break; - } return true; } @@ -50,6 +54,9 @@ HighlightCell * CodeOptionsController::reusableCell(int index, int type) { else if (index == 1) { return &m_switchCellAutoCompletion; } + else if (index == 2) { + return &m_switchCellCursorSaving; + } return &m_switchCellSyntaxHighlighting; } @@ -66,9 +73,9 @@ void CodeOptionsController::willDisplayCellForIndex(HighlightCell * cell, int in MessageTableCellWithChevronAndMessage * myTextCell = (MessageTableCellWithChevronAndMessage *)cell; myTextCell->setMessage(thisLabel); GlobalPreferences::sharedGlobalPreferences()->font() == KDFont::LargeFont - ? myTextCell->setSubtitle(I18n::Message::LargeFont) + ? myTextCell->setSubtitle(I18n::Message::LargeFont) : myTextCell->setSubtitle(I18n::Message::SmallFont); - } + } #ifdef HAS_CODE else if (thisLabel == I18n::Message::Autocomplete) { MessageTableCellWithSwitch * mySwitchCell = (MessageTableCellWithSwitch *)cell; @@ -80,6 +87,11 @@ void CodeOptionsController::willDisplayCellForIndex(HighlightCell * cell, int in SwitchView * mySwitch = (SwitchView *)mySwitchCell->accessoryView(); mySwitch->setState(GlobalPreferences::sharedGlobalPreferences()->syntaxhighlighting()); } + else if (thisLabel == I18n::Message::CursorSaving) { + MessageTableCellWithSwitch * mySwitchCell = (MessageTableCellWithSwitch *)cell; + SwitchView * mySwitch = (SwitchView *)mySwitchCell->accessoryView(); + mySwitch->setState(GlobalPreferences::sharedGlobalPreferences()->cursorSaving()); + } #endif } diff --git a/apps/settings/sub_menu/code_options_controller.h b/apps/settings/sub_menu/code_options_controller.h index 650ea6da52e..854de5e8e8a 100644 --- a/apps/settings/sub_menu/code_options_controller.h +++ b/apps/settings/sub_menu/code_options_controller.h @@ -14,11 +14,12 @@ class CodeOptionsController : public GenericSubController { int reusableCellCount(int type) override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; private: - constexpr static int k_totalNumberOfCell = 3; + constexpr static int k_totalNumberOfCell = 4; PreferencesController m_preferencesController; MessageTableCellWithChevronAndMessage m_chevronCellFontSize; MessageTableCellWithSwitch m_switchCellAutoCompletion; MessageTableCellWithSwitch m_switchCellSyntaxHighlighting; + MessageTableCellWithSwitch m_switchCellCursorSaving; }; } From cbbe7ef6accd35db4ac493a9862a49f53549e110 Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 15 Jan 2022 10:55:52 +0100 Subject: [PATCH 149/355] [poincare] Fix bug in equal simplifcation --- poincare/src/equal.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/poincare/src/equal.cpp b/poincare/src/equal.cpp index 8f3b2932ef5..ea0336a9625 100644 --- a/poincare/src/equal.cpp +++ b/poincare/src/equal.cpp @@ -52,19 +52,15 @@ Expression Equal::standardEquation(Context * context, Preferences::ComplexFormat Expression Equal::shallowReduce(ExpressionNode::ReductionContext reductionContext) { Expression e = Equal::Builder(Subtraction::Builder(childAtIndex(0).clone(), childAtIndex(1).clone()).shallowReduce(reductionContext), Rational::Builder(0)); + Expression leftSide = e.childAtIndex(0); - if (leftSide.isIdenticalTo(e.childAtIndex(1))) { - Expression result = Rational::Builder(1); - replaceWithInPlace(result); - return result; - } if (leftSide.isUndefined()) { - return leftSide; + return leftSide; // <=> undefined } - if (leftSide.type() == ExpressionNode::Type::Multiplication) { + if (leftSide.type() == ExpressionNode::Type::Multiplication) { // Simplify multiplication Multiplication m = static_cast(leftSide); int i = 0; - while (i < numberOfChildren()-1) { + while (i < m.numberOfChildren()) { if (m.childAtIndex(i).nullStatus(reductionContext.context()) == ExpressionNode::NullStatus::NonNull) { m.removeChildAtIndexInPlace(i); } @@ -72,6 +68,23 @@ Expression Equal::shallowReduce(ExpressionNode::ReductionContext reductionContex i++; } } + + // Replace if 0 child + if (m.numberOfChildren() == 0) { + e.replaceChildAtIndexInPlace(0, Rational::Builder(0)); + } else { + // Squash if one child + Expression result = m.squashUnaryHierarchyInPlace(); + if (result != *this) { + e.replaceChildAtIndexInPlace(0, result); + } + } + } + + if (leftSide.isIdenticalTo(e.childAtIndex(1))) { + Expression result = Rational::Builder(1); + replaceWithInPlace(result); + return result; } return e; From f591816122e19dae3abc5675c30e8e9f3b6a9eea Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 15 Jan 2022 12:40:06 +0100 Subject: [PATCH 150/355] [apps/functions] Save if user want display derivative even when quitting the app --- apps/graph/app.cpp | 6 ++++-- apps/graph/app.h | 2 ++ apps/graph/graph/graph_controller.cpp | 6 +++--- apps/graph/graph/graph_controller.h | 10 +++++----- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/apps/graph/app.cpp b/apps/graph/app.cpp index 68a4b4a9e7f..b4a74d8e4fc 100644 --- a/apps/graph/app.cpp +++ b/apps/graph/app.cpp @@ -27,7 +27,8 @@ const Image * App::Descriptor::icon() { App::Snapshot::Snapshot() : Shared::FunctionApp::Snapshot::Snapshot(), m_functionStore(), - m_graphRange() + m_graphRange(), + m_shouldDisplayDerivative(false) { } @@ -40,6 +41,7 @@ void App::Snapshot::reset() { for (int i = 0; i < Shared::ContinuousFunction::k_numberOfPlotTypes; i++) { m_interval[i].reset(); } + m_shouldDisplayDerivative = false; } App::Descriptor * App::Snapshot::descriptor() { @@ -58,7 +60,7 @@ App::App(Snapshot * snapshot) : m_listFooter(&m_listHeader, &m_listController, &m_listController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGray), m_listHeader(&m_listStackViewController, &m_listFooter, &m_listController), m_listStackViewController(&m_tabViewController, &m_listHeader), - m_graphController(&m_graphAlternateEmptyViewController, this, snapshot->graphRange(), snapshot->cursor(), snapshot->indexFunctionSelectedByCursor(), snapshot->rangeVersion(), &m_graphHeader), + m_graphController(&m_graphAlternateEmptyViewController, this, snapshot->graphRange(), snapshot->cursor(), snapshot->indexFunctionSelectedByCursor(), snapshot->rangeVersion(), &m_graphHeader, snapshot->shouldDisplayDerivative()), m_graphAlternateEmptyViewController(&m_graphHeader, &m_graphController, &m_graphController), m_graphHeader(&m_graphStackViewController, &m_graphAlternateEmptyViewController, &m_graphController), m_graphStackViewController(&m_tabViewController, &m_graphHeader), diff --git a/apps/graph/app.h b/apps/graph/app.h index f9255732908..715c8bd2d7f 100644 --- a/apps/graph/app.h +++ b/apps/graph/app.h @@ -31,11 +31,13 @@ class App : public Shared::FunctionApp { Shared::Interval * intervalForType(Shared::ContinuousFunction::PlotType plotType) { return m_interval + static_cast(plotType); } + bool * shouldDisplayDerivative() { return &m_shouldDisplayDerivative; } private: void tidy() override; ContinuousFunctionStore m_functionStore; Shared::InteractiveCurveViewRange m_graphRange; Shared::Interval m_interval[Shared::ContinuousFunction::k_numberOfPlotTypes]; + bool m_shouldDisplayDerivative; }; static App * app() { return static_cast(Container::activeApp()); diff --git a/apps/graph/graph/graph_controller.cpp b/apps/graph/graph/graph_controller.cpp index a6de8426862..a07d49b39c9 100644 --- a/apps/graph/graph/graph_controller.cpp +++ b/apps/graph/graph/graph_controller.cpp @@ -7,13 +7,13 @@ using namespace Shared; namespace Graph { -GraphController::GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, ButtonRowController * header) : +GraphController::GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, ButtonRowController * header, bool * shouldDisplayDerivative) : FunctionGraphController(parentResponder, inputEventHandlerDelegate, header, curveViewRange, &m_view, cursor, indexFunctionSelectedByCursor, rangeVersion), m_bannerView(this, inputEventHandlerDelegate, this), m_view(curveViewRange, m_cursor, &m_bannerView, &m_cursorView), m_graphRange(curveViewRange), m_curveParameterController(inputEventHandlerDelegate, curveViewRange, &m_bannerView, m_cursor, &m_view, this), - m_displayDerivativeInBanner(false) + m_displayDerivativeInBanner(shouldDisplayDerivative) { m_graphRange->setDelegate(this); } @@ -47,7 +47,7 @@ void GraphController::selectFunctionWithCursor(int functionIndex) { void GraphController::reloadBannerView() { Ion::Storage::Record record = functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor()); - bool displayDerivative = m_displayDerivativeInBanner && + bool displayDerivative = *m_displayDerivativeInBanner && functionStore()->modelForRecord(record)->plotType() == ContinuousFunction::PlotType::Cartesian; m_bannerView.setNumberOfSubviews(Shared::XYBannerView::k_numberOfSubviews + displayDerivative); FunctionGraphController::reloadBannerView(); diff --git a/apps/graph/graph/graph_controller.h b/apps/graph/graph/graph_controller.h index 433ab9ebe23..a740f4adcb5 100644 --- a/apps/graph/graph/graph_controller.h +++ b/apps/graph/graph/graph_controller.h @@ -15,13 +15,13 @@ namespace Graph { class GraphController : public Shared::FunctionGraphController, public GraphControllerHelper { public: - GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, ButtonRowController * header); + GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, ButtonRowController * header, bool * shouldDisplayDerivative); I18n::Message emptyMessage() override; void viewWillAppear() override; - bool displayDerivativeInBanner() const { return m_displayDerivativeInBanner; } - void setDisplayDerivativeInBanner(bool displayDerivative) { m_displayDerivativeInBanner = displayDerivative; } + bool displayDerivativeInBanner() const { return *m_displayDerivativeInBanner; } + void setDisplayDerivativeInBanner(bool displayDerivative) { *m_displayDerivativeInBanner = displayDerivative; } private: - int estimatedBannerNumberOfLines() const override { return 1 + m_displayDerivativeInBanner; } + int estimatedBannerNumberOfLines() const override { return 1 + *m_displayDerivativeInBanner; } void selectFunctionWithCursor(int functionIndex) override; BannerView * bannerView() override { return &m_bannerView; } void reloadBannerView() override; @@ -41,7 +41,7 @@ class GraphController : public Shared::FunctionGraphController, public GraphCont GraphView m_view; Shared::InteractiveCurveViewRange * m_graphRange; CurveParameterController m_curveParameterController; - bool m_displayDerivativeInBanner; + bool * m_displayDerivativeInBanner; }; } From e575ffc7ce3ce0b5c3091546cf0eab2d73ecec07 Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 15 Jan 2022 21:33:32 +0100 Subject: [PATCH 151/355] [apps/code] Shift + OK don't quit toolbox --- apps/code/python_toolbox.cpp | 8 +++++--- apps/code/python_toolbox.h | 3 ++- apps/code/variable_box_controller.cpp | 2 +- apps/code/variable_box_controller.h | 2 +- apps/math_toolbox.cpp | 2 +- apps/math_toolbox.h | 2 +- apps/math_variable_box_controller.cpp | 2 +- apps/math_variable_box_controller.h | 2 +- escher/include/escher/nested_menu_controller.h | 3 ++- escher/src/nested_menu_controller.cpp | 5 ++++- ion/include/ion/events.h | 3 +++ ion/src/shared/keyboard/layout_B2/layout_events.cpp | 8 ++++---- ion/src/shared/keyboard/layout_B3/layout_events.cpp | 8 ++++---- 13 files changed, 30 insertions(+), 20 deletions(-) diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 6b5686b189b..f08bdcacd77 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -696,14 +696,13 @@ KDCoordinate PythonToolbox::rowHeight(int j) { return Toolbox::rowHeight(j); } -bool PythonToolbox::selectLeaf(int selectedRow) { +bool PythonToolbox::selectLeaf(int selectedRow, bool quitToolbox) { ToolboxMessageTree * node = (ToolboxMessageTree *)m_messageTreeModel->childAtIndex(selectedRow); #if defined(INCLUDE_ULAB) if(node->text() == I18n::Message::UlabDocumentationLink){ return true; } #endif - m_selectableTableView.deselectTable(); if(node->insertedText() == I18n::Message::IonSelector){ m_ionKeys.setSender(sender()); Container::activeApp()->displayModalViewController(static_cast(&m_ionKeys), 0.f, 0.f, Metric::PopUpTopMargin, Metric::PopUpLeftMargin, 0, Metric::PopUpRightMargin); @@ -719,7 +718,10 @@ bool PythonToolbox::selectLeaf(int selectedRow) { editedText = strippedEditedText; } sender()->handleEventWithText(editedText, true); - Container::activeApp()->dismissModalViewController(); + if (quitToolbox) { + m_selectableTableView.deselectTable(); + Container::activeApp()->dismissModalViewController(); + } return true; } diff --git a/apps/code/python_toolbox.h b/apps/code/python_toolbox.h index 7c66af03f2c..9be9f4aa9c5 100644 --- a/apps/code/python_toolbox.h +++ b/apps/code/python_toolbox.h @@ -20,10 +20,11 @@ class PythonToolbox : public Toolbox { const ToolboxMessageTree * rootModel() const override; protected: KDCoordinate rowHeight(int j) override; - bool selectLeaf(int selectedRow) override; + bool selectLeaf(int selectedRow, bool quitToolbox) override; MessageTableCellWithMessage * leafCellAtIndex(int index) override; MessageTableCellWithChevron* nodeCellAtIndex(int index) override; int maxNumberOfDisplayedRows() override; + bool canStayInMenu() override { return true; } constexpr static int k_maxNumberOfDisplayedRows = 13; // = 240/(13+2*3) // 13 = minimal string height size // 3 = vertical margins diff --git a/apps/code/variable_box_controller.cpp b/apps/code/variable_box_controller.cpp index 279c9c57c44..5f3e73bcc70 100644 --- a/apps/code/variable_box_controller.cpp +++ b/apps/code/variable_box_controller.cpp @@ -365,7 +365,7 @@ int VariableBoxController::typeAndOriginAtLocation(int i, NodeOrigin * resultOri } -bool VariableBoxController::selectLeaf(int rowIndex) { +bool VariableBoxController::selectLeaf(int rowIndex, bool quitToolbox) { assert(rowIndex >= 0 && rowIndex < numberOfRows()); m_selectableTableView.deselectTable(); diff --git a/apps/code/variable_box_controller.h b/apps/code/variable_box_controller.h index 81504ac0a43..f20b6409182 100644 --- a/apps/code/variable_box_controller.h +++ b/apps/code/variable_box_controller.h @@ -80,7 +80,7 @@ class VariableBoxController : public AlternateEmptyNestedMenuController { // NestedMenuController HighlightCell * leafCellAtIndex(int index) override { assert(false); return nullptr; } HighlightCell * nodeCellAtIndex(int index) override { assert(false); return nullptr; } - bool selectLeaf(int rowIndex) override; + bool selectLeaf(int rowIndex, bool quitToolbox) override; void insertTextInCaller(const char * text, int textLength = -1); // Loading diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index e2146ecc65f..da06d0bb6dc 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -877,7 +877,7 @@ MathToolbox::MathToolbox() : { } -bool MathToolbox::selectLeaf(int selectedRow) { +bool MathToolbox::selectLeaf(int selectedRow, bool quitToolbox) { ToolboxMessageTree * messageTree = (ToolboxMessageTree *)m_messageTreeModel->childAtIndex(selectedRow); m_selectableTableView.deselectTable(); diff --git a/apps/math_toolbox.h b/apps/math_toolbox.h index 2ade3318f62..b883bf1823e 100644 --- a/apps/math_toolbox.h +++ b/apps/math_toolbox.h @@ -9,7 +9,7 @@ class MathToolbox : public Toolbox { MathToolbox(); const ToolboxMessageTree * rootModel() const override; protected: - bool selectLeaf(int selectedRow) override; + bool selectLeaf(int selectedRow, bool quitToolbox) override; MessageTableCellWithMessage * leafCellAtIndex(int index) override; MessageTableCellWithChevron* nodeCellAtIndex(int index) override; int maxNumberOfDisplayedRows() override; diff --git a/apps/math_variable_box_controller.cpp b/apps/math_variable_box_controller.cpp index f8e01ea9f33..87b671788ec 100644 --- a/apps/math_variable_box_controller.cpp +++ b/apps/math_variable_box_controller.cpp @@ -193,7 +193,7 @@ bool MathVariableBoxController::returnToPreviousMenu() { return AlternateEmptyNestedMenuController::returnToPreviousMenu(); } -bool MathVariableBoxController::selectLeaf(int selectedRow) { +bool MathVariableBoxController::selectLeaf(int selectedRow, bool quitToolbox) { if (isDisplayingEmptyController()) { /* We do not want to handle OK/EXE events in that case. */ return false; diff --git a/apps/math_variable_box_controller.h b/apps/math_variable_box_controller.h index 86f968303a5..6fe80f576f3 100644 --- a/apps/math_variable_box_controller.h +++ b/apps/math_variable_box_controller.h @@ -42,7 +42,7 @@ class MathVariableBoxController : public AlternateEmptyNestedMenuController { void setPage(Page page); bool selectSubMenu(int selectedRow) override; bool returnToPreviousMenu() override; - bool selectLeaf(int selectedRow) override; + bool selectLeaf(int selectedRow, bool quitToolbox) override; I18n::Message nodeLabelAtIndex(int index); Poincare::Layout expressionLayoutForRecord(Ion::Storage::Record record, int index); const char * extension() const; diff --git a/escher/include/escher/nested_menu_controller.h b/escher/include/escher/nested_menu_controller.h index ca671a1b750..41333398f4c 100644 --- a/escher/include/escher/nested_menu_controller.h +++ b/escher/include/escher/nested_menu_controller.h @@ -65,7 +65,8 @@ class NestedMenuController : public StackViewController, public ListViewDataSour bool handleEventForRow(Ion::Events::Event event, int selectedRow); virtual bool selectSubMenu(int selectedRow); virtual bool returnToPreviousMenu(); - virtual bool selectLeaf(int selectedRow) = 0; + virtual bool selectLeaf(int selectedRow, bool quitToolbox) = 0; + virtual bool canStayInMenu() { return false; }; virtual int stackRowOffset() const { return 0; } InputEventHandler * sender() { return m_sender; } virtual HighlightCell * leafCellAtIndex(int index) = 0; diff --git a/escher/src/nested_menu_controller.cpp b/escher/src/nested_menu_controller.cpp index 735f5a32c6f..3911ffcc3b4 100644 --- a/escher/src/nested_menu_controller.cpp +++ b/escher/src/nested_menu_controller.cpp @@ -156,8 +156,11 @@ bool NestedMenuController::handleEventForRow(Ion::Events::Event event, int rowIn if ((event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) && typeAtLocation(0, selectedRow()) == NodeCellType) { return selectSubMenu(rowIndex); } + if (canStayInMenu() && ((event == Ion::Events::ShiftOK || event == Ion::Events::ShiftEXE) && typeAtLocation(0, selectedRow()) == LeafCellType)) { + return selectLeaf(rowIndex, false); + } if ((event == Ion::Events::OK || event == Ion::Events::EXE) && typeAtLocation(0, selectedRow()) == LeafCellType) { - return selectLeaf(rowIndex); + return selectLeaf(rowIndex, true); } return false; } diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index 1764eb5952d..4dccae5be95 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -141,6 +141,7 @@ constexpr Event ShiftLeft = Event::ShiftKey(Keyboard::Key::Left); constexpr Event ShiftRight = Event::ShiftKey(Keyboard::Key::Right); constexpr Event ShiftUp = Event::ShiftKey(Keyboard::Key::Up); constexpr Event ShiftDown = Event::ShiftKey(Keyboard::Key::Down); +constexpr Event ShiftOK = Event::ShiftKey(Keyboard::Key::OK); constexpr Event AlphaLock = Event::ShiftKey(Keyboard::Key::Alpha); constexpr Event Cut = Event::ShiftKey(Keyboard::Key::XNT); @@ -183,6 +184,8 @@ constexpr Event ShiftSeven = Event::ShiftKey(Keyboard::Key::Seven); constexpr Event ShiftEight = Event::ShiftKey(Keyboard::Key::Eight); constexpr Event ShiftNine = Event::ShiftKey(Keyboard::Key::Nine); +constexpr Event ShiftEXE = Event::ShiftKey(Keyboard::Key::EXE); + // Alpha constexpr Event AlphaLeft = Event::AlphaKey(Keyboard::Key::Left); diff --git a/ion/src/shared/keyboard/layout_B2/layout_events.cpp b/ion/src/shared/keyboard/layout_B2/layout_events.cpp index 33909259764..cc659c32154 100644 --- a/ion/src/shared/keyboard/layout_B2/layout_events.cpp +++ b/ion/src/shared/keyboard/layout_B2/layout_events.cpp @@ -16,7 +16,7 @@ const EventData s_dataForEvent[4 * Event::PageSize] = { T("1"), T("2"), T("3"), T("+"), T("-"), U(), T("0"), T("."), T("ᴇ"), TL(), TL(), U(), // Shift - TL(), TL(), TL(), TL(), U(), U(), + TL(), TL(), TL(), TL(), TL(), U(), TL(), U(), U(), U(), U(), U(), U(), U(), TL(), TL(), TL(), TL(), T("["), T("]"), T("{"), T("}"), T("_"), T("→"), @@ -24,7 +24,7 @@ const EventData s_dataForEvent[4 * Event::PageSize] = { T("μ"), TL(), T("Ω"), T("(\x11)"), U(), U(), TL(), TL(), TL(), TL(), TL(), U(), TL(), TL(), TL(), TL(), TL(), U(), - TL(), TL(), TL(), TL(), U(), U(), + TL(), TL(), TL(), TL(), TL(), U(), // Alpha TL(), TL(), TL(), TL(), U(), U(), U(), U(), U(), U(), U(), U(), @@ -61,7 +61,7 @@ const char * const s_nameForEvent[255] = { "One", "Two", "Three", "Plus", "Minus", nullptr, "Zero", "Dot", "EE", "Ans", "EXE", nullptr, //Shift, - "ShiftLeft", "ShiftUp", "ShiftDown", "ShiftRight", nullptr, nullptr, + "ShiftLeft", "ShiftUp", "ShiftDown", "ShiftRight", "ShiftOK", nullptr, "ShiftHome", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "AlphaLock", "Cut", "Copy", "Paste", "Clear", "LeftBracket", "RightBracket", "LeftBrace", "RightBrace", "Underscore", "Sto", @@ -69,7 +69,7 @@ const char * const s_nameForEvent[255] = { "Micro", nullptr, "Omega", nullptr, "DoubleParenthesis", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "BrightnessPlus", "BrightnessMinus", nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, "ShiftEXE", nullptr, //Alpha, "AlphaLeft", "AlphaUp", "AlphaDown", "AlphaRight", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, diff --git a/ion/src/shared/keyboard/layout_B3/layout_events.cpp b/ion/src/shared/keyboard/layout_B3/layout_events.cpp index 2cd94ce5353..6ccf51c3b44 100644 --- a/ion/src/shared/keyboard/layout_B3/layout_events.cpp +++ b/ion/src/shared/keyboard/layout_B3/layout_events.cpp @@ -16,7 +16,7 @@ const EventData s_dataForEvent[4 * Event::PageSize] = { T("1"), T("2"), T("3"), T("+"), T("-"), U(), T("0"), T("."), T("ᴇ"), TL(), TL(), U(), // Shift - TL(), TL(), TL(), TL(), U(), U(), + TL(), TL(), TL(), TL(), TL(), U(), TL(), U(), U(), U(), U(), U(), U(), U(), TL(), TL(), TL(), TL(), T("["), T("]"), T("{"), T("}"), T("_"), T("→"), @@ -24,7 +24,7 @@ const EventData s_dataForEvent[4 * Event::PageSize] = { T("μ"), TL(), T("Ω"), T("(\x11)"), U(), U(), TL(), TL(), TL(), TL(), TL(), U(), TL(), TL(), TL(), TL(), TL(), U(), - TL(), TL(), TL(), TL(), U(), U(), + TL(), TL(), TL(), TL(), TL(), U(), // Alpha TL(), TL(), TL(), TL(), U(), U(), U(), U(), U(), U(), U(), U(), @@ -61,7 +61,7 @@ const char * const s_nameForEvent[255] = { "One", "Two", "Three", "Plus", "Minus", nullptr, "Zero", "Dot", "EE", "Ans", "EXE", nullptr, //Shift, - "ShiftLeft", "ShiftUp", "ShiftDown", "ShiftRight", nullptr, nullptr, + "ShiftLeft", "ShiftUp", "ShiftDown", "ShiftRight", "ShiftOK", nullptr, "ShiftHome", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "AlphaLock", "Cut", "Copy", "Paste", "Clear", "LeftBracket", "RightBracket", "LeftBrace", "RightBrace", "Underscore", "Sto", @@ -69,7 +69,7 @@ const char * const s_nameForEvent[255] = { "Micro", nullptr, "Omega", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "BrightnessPlus", "BrightnessMinus", nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, "ShiftEXE", nullptr, //Alpha, "AlphaLeft", "AlphaUp", "AlphaDown", "AlphaRight", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, From 169fb7404e8ef2de13ac3a48f8d73436d8e6ac6a Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Thu, 20 Jan 2022 17:21:35 +0100 Subject: [PATCH 152/355] Fix spelling (#128) * Fix spelling in .cpp files * Fix spelling in all files --- Makefile | 2 +- README.md | 20 +++--- apps/Makefile | 4 +- apps/apps_container.cpp | 4 +- apps/backlight_dimming_timer.cpp | 2 +- apps/backlight_dimming_timer.h | 2 +- apps/calculation/calculation_store.cpp | 10 +-- apps/code/catalog.en.i18n | 2 +- apps/code/catalog.es.i18n | 2 +- apps/code/catalog.pt.i18n | 2 +- apps/code/test/variable_box_controller.cpp | 2 +- apps/code/variable_box_controller.cpp | 12 ++-- apps/code/variable_box_controller.h | 2 +- apps/graph/values/values_controller.cpp | 8 +-- apps/home/Makefile | 2 +- apps/home/base.de.i18n | 4 +- apps/home/base.en.i18n | 4 +- apps/home/base.es.i18n | 4 +- apps/home/base.fr.i18n | 4 +- apps/home/base.hu.i18n | 4 +- apps/home/base.it.i18n | 4 +- apps/home/base.nl.i18n | 4 +- apps/home/base.pt.i18n | 4 +- apps/home/controller.cpp | 4 +- apps/math_toolbox.cpp | 4 +- apps/probability/calculation_controller.cpp | 2 +- .../probability/distribution/distribution.cpp | 2 +- .../distribution/student_distribution.cpp | 2 +- apps/reader/tex_parser.cpp | 2 +- .../regression/go_to_parameter_controller.cpp | 2 +- apps/regression/model/model.h | 2 +- apps/regression/test/model.cpp | 6 +- .../list/type_parameter_controller.cpp | 4 +- .../sequence/list/type_parameter_controller.h | 2 +- apps/sequence/test/sequence.cpp | 2 +- .../about_controller_non_official.cpp | 2 +- .../sub_menu/usb_protection_controller.cpp | 8 +-- apps/shared/double_pair_store.cpp | 10 +-- apps/shared/expression_model.cpp | 2 +- apps/shared/global_context.cpp | 2 +- apps/shared/sequence_context.h | 2 +- apps/shared/store_controller.cpp | 2 +- apps/shared/sum_graph_controller.cpp | 4 +- apps/solver/base.de.i18n | 2 +- apps/solver/base.en.i18n | 2 +- apps/solver/base.es.i18n | 2 +- apps/solver/base.fr.i18n | 2 +- apps/solver/base.hu.i18n | 2 +- apps/solver/base.it.i18n | 2 +- apps/solver/base.nl.i18n | 2 +- apps/solver/base.pt.i18n | 2 +- apps/solver/equation_store.cpp | 12 ++-- apps/solver/equation_store.h | 4 +- apps/solver/interval_controller.cpp | 2 +- apps/solver/solutions_controller.cpp | 2 +- apps/title_bar_view.cpp | 2 +- apps/toolbox.de.i18n | 4 +- apps/toolbox.en.i18n | 4 +- apps/toolbox.es.i18n | 4 +- apps/toolbox.fr.i18n | 4 +- apps/toolbox.hu.i18n | 4 +- apps/toolbox.it.i18n | 4 +- apps/toolbox.nl.i18n | 4 +- apps/toolbox.pt.i18n | 4 +- build/device/dfu.py | 2 +- build/device/ram_map.py | 2 +- build/device/usb/_debug.py | 4 +- build/device/usb/backend/__init__.py | 22 +++---- build/device/usb/backend/libusb1.py | 2 +- build/device/usb/core.py | 6 +- build/doc/Doxyfile | 2 +- build/pimp.mak | 2 +- build/targets.device.n0110.mak | 2 +- docs/architecture.svg | 2 +- docs/build/index.md | 12 ++-- docs/index.md | 4 +- escher/image/inliner.c | 2 +- escher/include/escher/text_area.h | 2 +- escher/include/escher/view_controller.h | 4 +- escher/src/button_row_controller.cpp | 4 +- escher/src/icon_view.cpp | 2 +- escher/src/selectable_table_view.cpp | 2 +- escher/src/text_field.cpp | 4 +- ion/Makefile | 2 +- ion/include/ion/timing.h | 2 +- ion/include/ion/unicode/utf8_helper.h | 10 +-- ion/src/device/n0100/flash.ld | 2 +- ion/src/device/n0110/drivers/board.cpp | 4 +- ion/src/device/n0110/drivers/config/clocks.h | 2 +- ion/src/device/n0110/flash.ld | 3 +- ion/src/device/n0110/internal_flash.ld | 2 +- ion/src/device/shared/drivers/battery.cpp | 4 +- ion/src/device/shared/drivers/display.cpp | 2 +- ion/src/device/shared/drivers/exam_mode.cpp | 24 +++---- ion/src/device/shared/drivers/reset.cpp | 2 +- ion/src/device/shared/drivers/wakeup.h | 2 +- ion/src/device/shared/regs/tim.h | 2 +- ion/src/device/shared/usb/calculator.h | 2 +- ion/src/device/shared/usb/dfu.ld | 2 +- ion/src/device/shared/usb/dfu_interface.h | 2 +- ion/src/device/shared/usb/dfu_relocated.cpp | 2 +- .../descriptor/device_capability_descriptor.h | 4 +- .../platform_device_capability_descriptor.h | 4 +- ion/src/device/shared/usb/stack/device.cpp | 2 +- ion/src/external/lz4/lz4.c | 2 +- ion/src/external/lz4/lz4.h | 2 +- ion/src/shared/events_benchmark.cpp | 8 +-- ion/src/shared/events_keyboard.cpp | 16 ++--- ion/src/simulator/3ds/Makefile | 2 +- ion/src/simulator/3ds/driver/led.cpp | 6 +- ion/src/simulator/android/Makefile | 4 +- .../github/omega/simulator/OmegaActivity.java | 2 +- .../src/java/org/libsdl/app/SDLActivity.java | 2 +- .../src/core/winrt/SDL_winrtapp_direct3d.cpp | 2 +- .../external/sdl/src/hidapi/ios/hid.m | 4 +- .../external/sdl/src/hidapi/linux/hid.c | 2 +- .../external/sdl/src/hidapi/mac/hid.c | 8 +-- .../simulator/external/sdl/src/libm/e_log.c | 2 +- .../simulator/external/sdl/src/libm/e_pow.c | 2 +- .../simulator/external/sdl/src/libm/e_sqrt.c | 8 +-- .../external/sdl/src/libm/k_rem_pio2.c | 4 +- .../sdl/src/video/khronos/vulkan/vk_icd.h | 2 +- .../sdl/src/video/khronos/vulkan/vk_layer.h | 2 +- .../simulator/external/sdl/src/video/qnx/gl.c | 2 +- .../external/sdl/src/video/qnx/keyboard.c | 2 +- .../external/sdl/src/video/qnx/video.c | 2 +- .../sdl/src/video/winrt/SDL_winrtvideo.cpp | 2 +- ion/src/simulator/linux/Makefile | 2 +- ion/src/simulator/shared/apple/helpers.mak | 2 +- ion/src/simulator/shared/apple/targets.mak | 2 +- ion/src/simulator/shared/events_platform.cpp | 2 +- ion/src/simulator/web/clipboard_helper.cpp | 4 +- ion/src/simulator/windows/platform_files.cpp | 2 +- ion/test/device/n0110/external_flash_tests.ld | 2 +- ion/test/utf8_helper.cpp | 10 +-- kandinsky/fonts/code_points.h | 2 +- kandinsky/fonts/rasterizer.c | 2 +- kandinsky/src/context_rect.cpp | 6 +- liba/src/armv7m/longjmp.s | 2 +- liba/src/external/openbsd/e_log.c | 2 +- liba/src/external/openbsd/e_sqrt.c | 8 +-- liba/src/external/openbsd/e_sqrtf.c | 2 +- liba/src/external/openbsd/k_rem_pio2.c | 4 +- liba/src/external/openbsd/k_rem_pio2f.c | 2 +- liba/src/external/sqlite/sqliteInt.h | 2 +- libaxx/README.txt | 2 +- poincare/include/poincare/based_integer.h | 2 +- poincare/include/poincare/equal.h | 2 +- poincare/include/poincare/expression.h | 4 +- poincare/include/poincare/expression_node.h | 2 +- poincare/include/poincare/float.h | 2 +- poincare/include/poincare/infinity.h | 2 +- poincare/include/poincare/n_ary_expression.h | 2 +- poincare/include/poincare/print_float.h | 14 ++-- poincare/include/poincare/rational.h | 2 +- poincare/include/poincare/store.h | 2 +- poincare/include/poincare/undefined.h | 2 +- poincare/include/poincare/unit.h | 64 +++++++++---------- poincare/include/poincare/unit_convert.h | 2 +- poincare/include/poincare/unreal.h | 2 +- poincare/include/poincare/zoom.h | 2 +- poincare/src/approximation_helper.cpp | 6 +- poincare/src/complex_cartesian.cpp | 8 +-- poincare/src/decimal.cpp | 4 +- poincare/src/derivative.cpp | 2 +- poincare/src/erf_inv.cpp | 2 +- poincare/src/expression.cpp | 4 +- poincare/src/hyperbolic_arc_tangent.cpp | 2 +- poincare/src/logarithm.cpp | 4 +- poincare/src/matrix.cpp | 8 +-- poincare/src/multiplication.cpp | 2 +- poincare/src/parsing/parser.cpp | 4 +- poincare/src/power.cpp | 4 +- poincare/src/print_float.cpp | 2 +- poincare/src/tree_handle.cpp | 2 +- poincare/src/trigonometry.cpp | 8 +-- poincare/src/trigonometry_cheat_table.cpp | 2 +- poincare/src/unit.cpp | 2 +- poincare/test/approximation.cpp | 2 +- poincare/test/expression_properties.cpp | 2 +- python/Makefile | 2 +- python/port/mod/ion/file.cpp | 2 +- python/port/mod/ion/modion_table.cpp | 2 +- .../port/mod/matplotlib/pyplot/modpyplot.cpp | 2 +- python/port/mod/time/modtime.cpp | 2 +- python/port/mod/time/modtime.h | 4 +- python/port/mod/ulab/ndarray.c | 2 +- python/port/mod/ulab/numpy/fft/fft.c | 2 +- python/port/mod/ulab/numpy/poly.c | 2 +- .../port/mod/ulab/scipy/optimize/optimize.c | 4 +- python/port/mod/ulab/user/user.c | 2 +- python/port/port.cpp | 2 +- python/src/py/compile.c | 2 +- python/src/py/emitnative.c | 6 +- python/src/py/formatfloat.c | 2 +- python/src/py/gc.c | 2 +- python/src/py/lexer.c | 2 +- python/src/py/lexer.h | 2 +- python/src/py/makecompresseddata.py | 2 +- python/src/py/mkrules.mk | 6 +- python/src/py/modbuiltins.c | 2 +- python/src/py/mpconfig.h | 2 +- python/src/py/obj.c | 2 +- python/src/py/parsenum.c | 2 +- python/test/basics.cpp | 2 +- python/test/execution_environment.cpp | 4 +- python/test/execution_environment.h | 2 +- python/test/ion.cpp | 6 +- python/test/kandinsky.cpp | 6 +- python/test/math.cpp | 4 +- python/test/matplotlib.cpp | 24 +++---- python/test/random.cpp | 2 +- python/test/time.cpp | 2 +- python/test/turtle.cpp | 4 +- themes/themes_manager.py | 2 +- 215 files changed, 424 insertions(+), 425 deletions(-) diff --git a/Makefile b/Makefile index 1d9969a6282..fa4c53c50c7 100644 --- a/Makefile +++ b/Makefile @@ -105,7 +105,7 @@ print-%: @echo $*\'s origin is $(origin $*) # Since we're building out-of-tree, we need to make sure the output directories -# are created, otherwise the receipes will fail (e.g. gcc will fail to create +# are created, otherwise the recipes will fail (e.g. gcc will fail to create # "output/foo/bar.o" because the directory "output/foo" doesn't exist). # We need to mark those directories as precious, otherwise Make will try to get # rid of them upon completion (and fail, since those folders won't be empty). diff --git a/README.md b/README.md index bf1dae20e2c..fb5f3144c39 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Upsilon is a fork of Omega, an user-made OS that runs on the Numworks calculator ### Some new features - Enhancements for the Kandinsky python module - Support for wallpapers -- Exernal apps +- External apps - A custom theme - Operator overload for python - Improvements for the Periodic table application @@ -154,18 +154,18 @@ You need a windows version >= 1903. ```powershell dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux all /norestart ``` -This command activate WSL functionnalities. +This command activate WSL functionalities. ```powershell dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart ``` -This one allows virtual machines developped by Microsoft. +This one allows virtual machines developed by Microsoft. 2. Restart your computer. 3. Download [this file](https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi) and follow instructions. -4. Now open powershell admain like before and type: +4. Now open powershell admin like before and type: ```powershell wsl --set-default-version 2 ``` @@ -174,7 +174,7 @@ wsl --set-default-version 2 WSL is now installed. ### Usbipd installation to connect your calculator -If you want to connect to the calculator, you have to connect to install this [tool](https://github.com/dorssel/usbipd-win/releases/download/v1.3.0/usbipd-win_1.3.0.msi). This will allow you to connect WSL to the calculator through internet. Follow the on screen informations to install. +If you want to connect to the calculator, you have to connect to install this [tool](https://github.com/dorssel/usbipd-win/releases/download/v1.3.0/usbipd-win_1.3.0.msi). This will allow you to connect WSL to the calculator through internet. Follow the on screen information to install. #### Ubuntu 1. In a WSL Ubuntu command prompt, type: ```bash @@ -188,7 +188,7 @@ sudo visudo `Defaults secure_path="/usr/lib/linux-tools/5.4.0-77-generic:/usr/local/sbin:..."` #### Debian -1. If you use debian for your WSL distro, use this comand instead: +1. If you use debian for your WSL distro, use this command instead: ```bash sudo apt install usbip hwdata usbutils ``` @@ -244,7 +244,7 @@ Now, run either: ```bash make MODEL=n0100 epsilon_flash ``` -to directly flash the calculator after pressing simultaneously `reset` and `6` buttons and pluging in. +to directly flash the calculator after pressing simultaneously `reset` and `6` buttons and plugging in.
    @@ -253,7 +253,7 @@ or: ```bash make MODEL=n0100 OMEGA_USERNAME="" binpack -j4 ``` -to make binpack wich you can flash to the caculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). Binpacks are a great way to share a custom build of Upsilon to friends. +to make binpack witch you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). Binpacks are a great way to share a custom build of Upsilon to friends.

    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 C@nWa| literal 226352 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

  • @@ -271,7 +271,7 @@ Now, run either: ```bash make epsilon_flash ``` -to directly flash the calculator after pressing simultaneously `reset` and `6` buttons and pluging in. +to directly flash the calculator after pressing simultaneously `reset` and `6` buttons and plugging in.
    @@ -280,7 +280,7 @@ or: ```bash make OMEGA_USERNAME="" binpack -j4 ``` -to make binpack wich you can flash to the caculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0110/). Binpacks are a great way to share a custom build of Upsilon to friends. +to make binpack witch you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0110/). Binpacks are a great way to share a custom build of Upsilon to friends.
    diff --git a/apps/Makefile b/apps/Makefile index 820ff690ce3..e1ff3c84981 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -23,7 +23,7 @@ ifneq ($(strip $(apps_missing)),) $(foreach i, $(SUBMODULES_APPS), $(if $(call app_equals, $(filter $(i), $(apps_missing)), $(i)), $(eval miss_modules=1))) ifeq ($(miss_modules), 1) - PLS_IGNORE := $(shell >&2 printf "\nSome submodules apps seem to be missing. To download them, assumming you git clone'd the repo, do\n") + PLS_IGNORE := $(shell >&2 printf "\nSome submodules apps seem to be missing. To download them, assuming you git clone'd the repo, do\n") PLS_IGNORE := $(shell >&2 printf " git submodule init\n") PLS_IGNORE := $(shell >&2 printf " git submodule update\n\n") endif @@ -81,7 +81,7 @@ $(call object_for,apps/apps_container_storage.cpp apps/apps_container.cpp apps/m country_preferences = apps/country_preferences.csv language_preferences = apps/language_preferences.csv -# The header is refered to as so make sure it's findable this way +# The header is referred to as so make sure it's findable this way SFLAGS += -I$(BUILD_DIR) i18n_files += $(addprefix apps/language_,$(addsuffix .universal.i18n, $(EPSILON_I18N))) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index 1eee9ce075e..9423684e46e 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -47,7 +47,7 @@ AppsContainer::AppsContainer() : * poincareCircuitBreaker is run. This means either whitelisting all Epsilon * (which makes bigger files to download and slower execution), or * whitelisting all the symbols (that's a big amount of symbols to find and - * quite painy to maintain). + * quite paint to maintain). * We just remove the circuit breaker for now. * TODO: Put the Poincare circuit breaker back on epsilon's web emulator */ @@ -319,7 +319,7 @@ void AppsContainer::run() { * destroyed from the pool. To avoid using them before packing the app * (in App::willBecomeInactive for instance), we tidy them early on. */ s_activeApp->snapshot()->tidy(); - /* When an app encoutered an exception due to a full pool, the next time + /* When an app encountered an exception due to a full pool, the next time * the user enters the app, the same exception could happen again which * would prevent from reopening the app. To avoid being stuck outside the * app causing the issue, we reset its snapshot when leaving it due to diff --git a/apps/backlight_dimming_timer.cpp b/apps/backlight_dimming_timer.cpp index efce3e87187..0d98d2749e8 100644 --- a/apps/backlight_dimming_timer.cpp +++ b/apps/backlight_dimming_timer.cpp @@ -9,7 +9,7 @@ BacklightDimmingTimer::BacklightDimmingTimer() : bool BacklightDimmingTimer::fire() { if (m_dimerExecutions == 0) { m_brightnessLevel = GlobalPreferences::sharedGlobalPreferences()->brightnessLevel(); - m_dimerSteps = m_brightnessLevel / decreaseby; + m_dimerSteps = m_brightnessLevel / decreaseBy; m_timeToSleep = decreasetime / m_dimerSteps; m_period = m_timeToSleep / Timer::TickDuration; if (m_period == 0) { diff --git a/apps/backlight_dimming_timer.h b/apps/backlight_dimming_timer.h index 029891eaef1..1502ea2dcc4 100644 --- a/apps/backlight_dimming_timer.h +++ b/apps/backlight_dimming_timer.h @@ -10,7 +10,7 @@ class BacklightDimmingTimer : public Timer { private: constexpr static int k_idleBeforeDimmingDuration = 30*1000; // In miliseconds constexpr static int k_dimBacklightBrightness = 0; - constexpr static int decreaseby = 15; + constexpr static int decreaseBy = 15; constexpr static int decreasetime = 1*1000; // In miliseconds int m_dimerExecutions = 0; int m_brightnessLevel; diff --git a/apps/calculation/calculation_store.cpp b/apps/calculation/calculation_store.cpp index e0dcc12f161..a2ae0b3e833 100644 --- a/apps/calculation/calculation_store.cpp +++ b/apps/calculation/calculation_store.cpp @@ -25,7 +25,7 @@ CalculationStore::CalculationStore(char * buffer, int size) : // Returns an expiring pointer to the calculation of index i ExpiringPointer CalculationStore::calculationAtIndex(int i) { assert(i >= 0 && i < m_numberOfCalculations); - // m_buffer is the adress of the oldest calculation in calculation store + // m_buffer is the address of the oldest calculation in calculation store Calculation * c = (Calculation *) m_buffer; if (i != m_numberOfCalculations-1) { // The calculation we want is not the oldest one so we get its pointer @@ -100,9 +100,9 @@ ExpiringPointer CalculationStore::push(const char * text, Context * numberOfSignificantDigits = Poincare::Preferences::sharedPreferences()->numberOfSignificantDigits(); } if (!pushSerializeExpression(outputs[i], beginingOfFreeSpace, &endOfFreeSpace, numberOfSignificantDigits)) { - /* If the exat/approximate output does not fit in the store (event if the + /* If the exact/approximate output does not fit in the store (event if the * current calculation is the only calculation), replace the output with - * undef if it fits, else replace the whole calcualtion with undef. */ + * undef if it fits, else replace the whole calculation with undef. */ Expression undef = Undefined::Builder(); if (!pushSerializeExpression(undef, beginingOfFreeSpace, &endOfFreeSpace)) { return emptyStoreAndPushUndef(context, heightComputer); @@ -177,8 +177,8 @@ Expression CalculationStore::ansExpression(Context * context) { * parsed), ans is replaced by the approximation output when any Store or * Equal expression appears. */ Expression e = mostRecentCalculation->exactOutput(); - bool exactOuptutInvolvesStoreEqual = e.type() == ExpressionNode::Type::Store || e.type() == ExpressionNode::Type::Equal; - if (mostRecentCalculation->input().recursivelyMatches(Expression::IsApproximate, context) || exactOuptutInvolvesStoreEqual) { + bool exactOutputInvolvesStoreEqual = e.type() == ExpressionNode::Type::Store || e.type() == ExpressionNode::Type::Equal; + if (mostRecentCalculation->input().recursivelyMatches(Expression::IsApproximate, context) || exactOutputInvolvesStoreEqual) { return mostRecentCalculation->approximateOutput(context, Calculation::NumberOfSignificantDigits::Maximal); } return mostRecentCalculation->exactOutput(); diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index b0ff6c78420..472ba88cc92 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -169,7 +169,7 @@ PythonTurtlePosition = "Return the current (x,y) location" PythonTurtleReset = "Reset the drawing" PythonTurtleRight = "Turn right by a degrees" PythonTurtleSetheading = "Set the orientation to a degrees" -PythonTurtleSetposition = "Positionne la tortue" +PythonTurtleSetposition = "Positioning the turtle" PythonTurtleShowturtle = "Show the turtle" PythonTurtleSpeed = "Drawing speed between 0 and 10" PythonTurtleWrite = "Display a text" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index b6e2c870467..4b7401d3e47 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -169,7 +169,7 @@ PythonTurtlePosition = "Return the current (x,y) location" PythonTurtleReset = "Reset the drawing" PythonTurtleRight = "Turn right by a degrees" PythonTurtleSetheading = "Set the orientation to a degrees" -PythonTurtleSetposition = "Positionne la tortue" +PythonTurtleSetposition = "Colocar la tortuga" PythonTurtleShowturtle = "Show the turtle" PythonTurtleSpeed = "Drawing speed between 0 and 10" PythonTurtleWrite = "Display a text" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index 4245cc850d0..ba1a2731103 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -169,7 +169,7 @@ PythonTurtlePosition = "Devolve a posição atual (x,y)" PythonTurtleReset = "Reiniciar o desenho" PythonTurtleRight = "Virar à esquerda por a graus" PythonTurtleSetheading = "Definir a orientação por a graus" -PythonTurtleSetposition = "Positionne la tortue" +PythonTurtleSetposition = "Posicionamento da tartaruga" PythonTurtleShowturtle = "Mostrar o turtle" PythonTurtleSpeed = "Velocidade do desenho entre 0 e 10" PythonTurtleWrite = "Mostrar um texto" diff --git a/apps/code/test/variable_box_controller.cpp b/apps/code/test/variable_box_controller.cpp index 482ab84f629..4dec7e43a1f 100644 --- a/apps/code/test/variable_box_controller.cpp +++ b/apps/code/test/variable_box_controller.cpp @@ -40,7 +40,7 @@ void assert_variables_are(const char * script, const char * nameToComplete, cons &addParentheses, i, &index); - quiz_assert(i == index); // If false, the autompletion has cycled: there are not as many results as expected + quiz_assert(i == index); // If false, the autocompletion has cycled: there are not as many results as expected quiz_assert(strncmp(*(expectedVariables + i), autocompletionI - nameToCompleteLength, textToInsertLength + nameToCompleteLength) == 0); index++; } diff --git a/apps/code/variable_box_controller.cpp b/apps/code/variable_box_controller.cpp index 5f3e73bcc70..31604b6716a 100644 --- a/apps/code/variable_box_controller.cpp +++ b/apps/code/variable_box_controller.cpp @@ -516,7 +516,7 @@ void VariableBoxController::loadBuiltinNodes(const char * textToAutocomplete, in assert(sizeof(builtinNames) / sizeof(builtinNames[0]) == k_totalBuiltinNodesCount); for (int i = 0; i < k_totalBuiltinNodesCount; i++) { if (addNodeIfMatches(textToAutocomplete, textToAutocompleteLength, builtinNames[i].type, NodeOrigin::Builtins, builtinNames[i].name)) { - /* We can leverage on the fact that buitin nodes are stored in + /* We can leverage on the fact that builtin nodes are stored in * alphabetical order. */ return; } @@ -575,7 +575,7 @@ void VariableBoxController::loadImportedVariablesInScript(const char * scriptCon } void VariableBoxController::loadCurrentVariablesInScript(const char * scriptContent, const char * textToAutocomplete, int textToAutocompleteLength) { - /* To find variable and funtion names: we lex the script and keep all + /* To find variable and function names: we lex the script and keep all * MP_TOKEN_NAME that complete the text to autocomplete and are not already in * the builtins or imported scripts. */ @@ -653,7 +653,7 @@ void VariableBoxController::loadGlobalAndImportedVariablesInScriptAsImported(Scr /* At this point, if the script node is not of type "file_input_2", it * will not have main structures of the wanted type. * We look for structures at first level (not inside nested scopes) that - * are either dunction definitions, variables statements or imports. */ + * are either function definitions, variables statements or imports. */ size_t n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); for (size_t i = 0; i < n; i++) { mp_parse_node_t child = pns->nodes[i]; @@ -826,15 +826,15 @@ bool VariableBoxController::importationSourceIsModule(const char * sourceName, c return mp_module_get(qstr_from_str(sourceName)) != MP_OBJ_NULL; } -bool VariableBoxController::importationSourceIsScript(const char * sourceName, const char * * scriptFullName, Script * retreivedScript) { +bool VariableBoxController::importationSourceIsScript(const char * sourceName, const char * * scriptFullName, Script * retrievedScript) { // Try fetching the nodes from a script Script importedScript = ScriptStore::ScriptBaseNamed(sourceName); if (importedScript.isNull()) { return false; } *scriptFullName = importedScript.fullName(); - if (retreivedScript != nullptr) { - *retreivedScript = importedScript; + if (retrievedScript != nullptr) { + *retrievedScript = importedScript; } return true; } diff --git a/apps/code/variable_box_controller.h b/apps/code/variable_box_controller.h index f20b6409182..eb25b054425 100644 --- a/apps/code/variable_box_controller.h +++ b/apps/code/variable_box_controller.h @@ -92,7 +92,7 @@ class VariableBoxController : public AlternateEmptyNestedMenuController { bool addNodesFromImportMaybe(mp_parse_node_struct_t * parseNode, const char * textToAutocomplete, int textToAutocompleteLength, bool importFromModules = true); const char * importationSourceNameFromNode(mp_parse_node_t & node); bool importationSourceIsModule(const char * sourceName, const ToolboxMessageTree * * moduleChildren = nullptr, int * numberOfModuleChildren = nullptr); - bool importationSourceIsScript(const char * sourceName, const char * * scriptFullName, Script * retreivedScript = nullptr); + bool importationSourceIsScript(const char * sourceName, const char * * scriptFullName, Script * retrievedScript = nullptr); bool addImportStructFromScript(mp_parse_node_struct_t * pns, uint structKind, const char * scriptName, const char * textToAutocomplete, int textToAutocompleteLength); /* Add a node if it completes the text to autocomplete and if it is not * already contained in the variable box. The returned boolean means we diff --git a/apps/graph/values/values_controller.cpp b/apps/graph/values/values_controller.cpp index d5783efce66..56bebd1c14a 100644 --- a/apps/graph/values/values_controller.cpp +++ b/apps/graph/values/values_controller.cpp @@ -343,7 +343,7 @@ EvenOddBufferTextCell * ValuesController::floatCells(int j) { /* ValuesController::ValuesSelectableTableView */ -int writeMatrixBrakets(char * buffer, const int bufferSize, int type) { +int writeMatrixBrackets(char * buffer, const int bufferSize, int type) { /* Write the double brackets required in matrix notation. * - type == 1: "[[" * - type == 0: "][" @@ -365,14 +365,14 @@ bool ValuesController::ValuesSelectableTableView::handleEvent(Ion::Events::Event constexpr int bufferSize = 2*PrintFloat::k_maxFloatCharSize + 6; // "[[a][b]]" gives 6 characters in addition to the 2 floats char buffer[bufferSize]; int currentChar = 0; - currentChar += writeMatrixBrakets(buffer + currentChar, bufferSize - currentChar, -1); + currentChar += writeMatrixBrackets(buffer + currentChar, bufferSize - currentChar, -1); assert(currentChar < bufferSize-1); size_t semiColonPosition = UTF8Helper::CopyUntilCodePoint(buffer+currentChar, TextField::maxBufferSize() - currentChar, text+1, ';'); currentChar += semiColonPosition; - currentChar += writeMatrixBrakets(buffer + currentChar, bufferSize - currentChar, 0); + currentChar += writeMatrixBrackets(buffer + currentChar, bufferSize - currentChar, 0); assert(currentChar < bufferSize-1); currentChar += UTF8Helper::CopyUntilCodePoint(buffer+currentChar, TextField::maxBufferSize() - currentChar, text+1+semiColonPosition+1, ')'); - currentChar += writeMatrixBrakets(buffer + currentChar, bufferSize - currentChar, 1); + currentChar += writeMatrixBrackets(buffer + currentChar, bufferSize - currentChar, 1); assert(currentChar < bufferSize-1); buffer[currentChar] = 0; Clipboard::sharedClipboard()->store(buffer); diff --git a/apps/home/Makefile b/apps/home/Makefile index 880302a5262..d23bcdc30f2 100644 --- a/apps/home/Makefile +++ b/apps/home/Makefile @@ -12,7 +12,7 @@ i18n_files += $(call i18n_without_universal_for,home/base) # Apps layout file generation -# The header is refered to as so make sure it's +# The header is referred to as so make sure it's # findable this way SFLAGS += -I$(BUILD_DIR) diff --git a/apps/home/base.de.i18n b/apps/home/base.de.i18n index a0fc91420a0..e360771484b 100644 --- a/apps/home/base.de.i18n +++ b/apps/home/base.de.i18n @@ -1,4 +1,4 @@ Apps = "Anwendungen" AppsCapital = "UPSILON" -ForbidenAppInExamMode1 = "Diese Anwendung ist im" -ForbidenAppInExamMode2 = "Prüfungsmodus nicht erlaubt." +ForbiddenAppInExamMode1 = "Diese Anwendung ist im" +ForbiddenAppInExamMode2 = "Prüfungsmodus nicht erlaubt." diff --git a/apps/home/base.en.i18n b/apps/home/base.en.i18n index 3468cca663c..2a65458e814 100644 --- a/apps/home/base.en.i18n +++ b/apps/home/base.en.i18n @@ -1,4 +1,4 @@ Apps = "Applications" AppsCapital = "UPSILON" -ForbidenAppInExamMode1 = "This application is" -ForbidenAppInExamMode2 = "forbidden in exam mode" +ForbiddenAppInExamMode1 = "This application is" +ForbiddenAppInExamMode2 = "forbidden in exam mode" diff --git a/apps/home/base.es.i18n b/apps/home/base.es.i18n index 3c262497cb6..8d71a322920 100644 --- a/apps/home/base.es.i18n +++ b/apps/home/base.es.i18n @@ -1,4 +1,4 @@ Apps = "Aplicaciones" AppsCapital = "UPSILON" -ForbidenAppInExamMode1 = "Esta aplicación está prohibida" -ForbidenAppInExamMode2 = "en el modo de examen" +ForbiddenAppInExamMode1 = "Esta aplicación está prohibida" +ForbiddenAppInExamMode2 = "en el modo de examen" diff --git a/apps/home/base.fr.i18n b/apps/home/base.fr.i18n index 4b4ab02dd5f..8c7270e051c 100644 --- a/apps/home/base.fr.i18n +++ b/apps/home/base.fr.i18n @@ -1,4 +1,4 @@ Apps = "Applications" AppsCapital = "UPSILON" -ForbidenAppInExamMode1 = "Cette application n'est" -ForbidenAppInExamMode2 = "pas autorisée en mode examen." +ForbiddenAppInExamMode1 = "Cette application n'est" +ForbiddenAppInExamMode2 = "pas autorisée en mode examen." diff --git a/apps/home/base.hu.i18n b/apps/home/base.hu.i18n index d526193eb1a..aa3c0f1e3b7 100644 --- a/apps/home/base.hu.i18n +++ b/apps/home/base.hu.i18n @@ -1,4 +1,4 @@ Apps = "Alkalmazások" AppsCapital = "UPSILON" -ForbidenAppInExamMode1 = "Ez az alkalmazás" -ForbidenAppInExamMode2 = "tilos vizsga módban" +ForbiddenAppInExamMode1 = "Ez az alkalmazás" +ForbiddenAppInExamMode2 = "tilos vizsga módban" diff --git a/apps/home/base.it.i18n b/apps/home/base.it.i18n index 4bd26ced310..a4bf010540b 100644 --- a/apps/home/base.it.i18n +++ b/apps/home/base.it.i18n @@ -1,4 +1,4 @@ Apps = "Applicazioni" AppsCapital = "UPSILON" -ForbidenAppInExamMode1 = "Questa applicazione è" -ForbidenAppInExamMode2 = "proibita nella modalità d'esame" +ForbiddenAppInExamMode1 = "Questa applicazione è" +ForbiddenAppInExamMode2 = "proibita nella modalità d'esame" diff --git a/apps/home/base.nl.i18n b/apps/home/base.nl.i18n index 496e8d6a3d7..4e9b4470eff 100644 --- a/apps/home/base.nl.i18n +++ b/apps/home/base.nl.i18n @@ -1,4 +1,4 @@ Apps = "Applicaties" AppsCapital = "UPSILON" -ForbidenAppInExamMode1 = "Deze applicatie is" -ForbidenAppInExamMode2 = "uitgesloten in examenstand" +ForbiddenAppInExamMode1 = "Deze applicatie is" +ForbiddenAppInExamMode2 = "uitgesloten in examenstand" diff --git a/apps/home/base.pt.i18n b/apps/home/base.pt.i18n index 6c074aa86bb..7f5c4d004e7 100644 --- a/apps/home/base.pt.i18n +++ b/apps/home/base.pt.i18n @@ -1,4 +1,4 @@ Apps = "Aplicações" AppsCapital = "UPSILON" -ForbidenAppInExamMode1 = "Esta aplicação é" -ForbidenAppInExamMode2 = "proibida no Modo de Exame" +ForbiddenAppInExamMode1 = "Esta aplicação é" +ForbiddenAppInExamMode2 = "proibida no Modo de Exame" diff --git a/apps/home/controller.cpp b/apps/home/controller.cpp index df725f10fdc..a79700b3f0d 100644 --- a/apps/home/controller.cpp +++ b/apps/home/controller.cpp @@ -94,7 +94,7 @@ bool Controller::handleEvent(Ion::Events::Event event) { #ifdef HOME_DISPLAY_EXTERNALS if (index >= container->numberOfApps()) { if (GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::Dutch || GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::NoSymNoText || GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::NoSym) { - App::app()->displayWarning(I18n::Message::ForbidenAppInExamMode1, I18n::Message::ForbidenAppInExamMode2); + App::app()->displayWarning(I18n::Message::ForbiddenAppInExamMode1, I18n::Message::ForbiddenAppInExamMode2); } else { External::Archive::File executable; if (External::Archive::executableAtIndex(index - container->numberOfApps(), executable)) { @@ -120,7 +120,7 @@ bool Controller::handleEvent(Ion::Events::Event event) { #endif ::App::Snapshot * selectedSnapshot = container->appSnapshotAtIndex(index); if (ExamModeConfiguration::appIsForbiddenInExamMode(selectedSnapshot->descriptor()->examinationLevel(), GlobalPreferences::sharedGlobalPreferences()->examMode())) { - App::app()->displayWarning(I18n::Message::ForbidenAppInExamMode1, I18n::Message::ForbidenAppInExamMode2); + App::app()->displayWarning(I18n::Message::ForbiddenAppInExamMode1, I18n::Message::ForbiddenAppInExamMode2); } else { bool switched = container->switchTo(selectedSnapshot); assert(switched); diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index da06d0bb6dc..1ff78b68127 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -30,7 +30,7 @@ const ToolboxMessageTree calculChildren[] = { const ToolboxMessageTree complexChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::AbsCommandWithArg,I18n::Message::ComplexAbsoluteValue), - ToolboxMessageTree::Leaf(I18n::Message::ArgCommandWithArg, I18n::Message::Agument), + ToolboxMessageTree::Leaf(I18n::Message::ArgCommandWithArg, I18n::Message::Argument), ToolboxMessageTree::Leaf(I18n::Message::ReCommandWithArg, I18n::Message::RealPart), ToolboxMessageTree::Leaf(I18n::Message::ImCommandWithArg, I18n::Message::ImaginaryPart), ToolboxMessageTree::Leaf(I18n::Message::ConjCommandWithArg, I18n::Message::Conjugate) @@ -252,7 +252,7 @@ const ToolboxMessageTree unitEnergyElectronVoltChildren[] = { }; const ToolboxMessageTree unitEnergyChildren[] = { ToolboxMessageTree::Node(I18n::Message::UnitEnergyJouleMenu, unitEnergyJouleChildren), - ToolboxMessageTree::Node(I18n::Message::UnitEnergyEletronVoltMenu, unitEnergyElectronVoltChildren)}; + ToolboxMessageTree::Node(I18n::Message::UnitEnergyElectronVoltMenu, unitEnergyElectronVoltChildren)}; const ToolboxMessageTree unitPowerWattChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattMicroSymbol, I18n::Message::UnitPowerWattMicro), diff --git a/apps/probability/calculation_controller.cpp b/apps/probability/calculation_controller.cpp index 2d4ddd1b639..3b69a7b3f7e 100644 --- a/apps/probability/calculation_controller.cpp +++ b/apps/probability/calculation_controller.cpp @@ -183,7 +183,7 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int constexpr int precision = Preferences::LargeNumberOfSignificantDigits; constexpr int bufferSize = PrintFloat::charSizeForFloatsWithPrecision(precision); char buffer[bufferSize]; - // FIXME: Leo has not decided yet if we should use the prefered mode instead of always using scientific mode + // FIXME: Leo has not decided yet if we should use the preferred mode instead of always using scientific mode PoincareHelpers::ConvertFloatToTextWithDisplayMode(m_calculation->parameterAtIndex(i-1), buffer, bufferSize, precision, Preferences::PrintFloatMode::Decimal); field->setText(buffer); } diff --git a/apps/probability/distribution/distribution.cpp b/apps/probability/distribution/distribution.cpp index aae3631d01a..d9872d177de 100644 --- a/apps/probability/distribution/distribution.cpp +++ b/apps/probability/distribution/distribution.cpp @@ -134,7 +134,7 @@ double Distribution::cumulativeDistributiveInverseForProbabilityUsingIncreasingF * the given ax bx bounds */ if (!(std::isnan(result.x2()) || std::fabs(result.x2()) <= FLT_EPSILON || std::fabs(result.x1()- ax) < FLT_EPSILON || std::fabs(result.x1() - bx) < FLT_EPSILON)) { /* TODO We would like to put this as an assertion, but sometimes we do get - * false result: we replace them with inf to make the problem obvisous to + * false result: we replace them with inf to make the problem obvious to * the student. */ return *probability > 0.5 ? INFINITY : -INFINITY; } diff --git a/apps/probability/distribution/student_distribution.cpp b/apps/probability/distribution/student_distribution.cpp index bdeb1acf84a..a4e6617d86d 100644 --- a/apps/probability/distribution/student_distribution.cpp +++ b/apps/probability/distribution/student_distribution.cpp @@ -33,7 +33,7 @@ double StudentDistribution::cumulativeDistributiveFunctionAtAbscissa(double x) c if (std::isinf(x)) { return x > 0 ? 1.0 : 0.0; } - /* TODO There are some computation errors, where the probability falsly jumps to 1. + /* TODO There are some computation errors, where the probability falsy jumps to 1. * k = 0.001 and P(x < 42000000) (for 41000000 it is around 0.5) * k = 0.01 and P(x < 8400000) (for 41000000 it is around 0.6) */ const double k = m_parameter1; diff --git a/apps/reader/tex_parser.cpp b/apps/reader/tex_parser.cpp index f2b69afeea5..fe105176d92 100644 --- a/apps/reader/tex_parser.cpp +++ b/apps/reader/tex_parser.cpp @@ -27,7 +27,7 @@ namespace Reader { // List of available Function Commands that don't require a specific handling static constexpr char const * k_FunctionCommands[] = { - "arcos", "arcsin", "arctan", "arg", "cos", "cosh", "cot", "coth", + "arccos", "arcsin", "arctan", "arg", "cos", "cosh", "cot", "coth", "csc", "deg", "det", "dim", "exp", "gcd", "hom", "inf", "ker", "lg", "lim", "liminf", "limsup", "ln", "log", "max", "min", "Pr", "sec", "sin", "sinh", "sup", "tan", "tanh" diff --git a/apps/regression/go_to_parameter_controller.cpp b/apps/regression/go_to_parameter_controller.cpp index 461f98bfadb..984ebbfcd1e 100644 --- a/apps/regression/go_to_parameter_controller.cpp +++ b/apps/regression/go_to_parameter_controller.cpp @@ -67,7 +67,7 @@ bool GoToParameterController::confirmParameterAtIndex(int parameterIndex, double } else { double yFromX = m_store->modelForSeries(series)->evaluate(m_store->coefficientsForSeries(series, globContext), unknown); /* We here compute y2 = a*((y1-b)/a)+b, which does not always give y1, - * because of computation precision. y2 migth thus be invalid. */ + * because of computation precision. y2 might thus be invalid. */ if (std::isnan(yFromX) || std::isinf(yFromX)) { Container::activeApp()->displayWarning(I18n::Message::ForbiddenValue); return false; diff --git a/apps/regression/model/model.h b/apps/regression/model/model.h index 1b36fa6a8a0..149cad00b34 100644 --- a/apps/regression/model/model.h +++ b/apps/regression/model/model.h @@ -45,7 +45,7 @@ class Model { Poincare::Layout m_layout; private: // Model attributes - virtual Poincare::Expression expression(double * modelCoefficients) { return Poincare::Expression(); } // expression is overrided only by Models that do not override levelSet + virtual Poincare::Expression expression(double * modelCoefficients) { return Poincare::Expression(); } // expression is overridden only by Models that do not override levelSet virtual double partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const = 0; // Levenberg-Marquardt diff --git a/apps/regression/test/model.cpp b/apps/regression/test/model.cpp index eebec477603..4fcb7cdb1ae 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -190,7 +190,7 @@ QUIZ_CASE(power_regression) { // assert_regression_is(x2, y2, 4, Model::Type::Power, coefficients2, r22); } -void assert_trigonomatric_regression_is(double * xi, double * yi, int numberOfPoints, double * trueCoefficients, double trueR2, Poincare::Preferences::AngleUnit trueCoeffcientsUnit) { +void assert_trigonometric_regression_is(double * xi, double * yi, int numberOfPoints, double * trueCoefficients, double trueR2, Poincare::Preferences::AngleUnit trueCoeffcientsUnit) { // Test the trigonometric regression at all angle units const Preferences::AngleUnit previousAngleUnit = Preferences::sharedPreferences()->angleUnit(); const Poincare::Preferences::AngleUnit units[3] = {Poincare::Preferences::AngleUnit::Radian, Poincare::Preferences::AngleUnit::Degree, Poincare::Preferences::AngleUnit::Gradian}; @@ -214,7 +214,7 @@ QUIZ_CASE(trigonometric_regression1) { int numberOfPoints = sizeof(x) / sizeof(double); assert(sizeof(y) == sizeof(double) * numberOfPoints); - assert_trigonomatric_regression_is(x, y, numberOfPoints, coefficients, r2, Poincare::Preferences::AngleUnit::Radian); + assert_trigonometric_regression_is(x, y, numberOfPoints, coefficients, r2, Poincare::Preferences::AngleUnit::Radian); } QUIZ_CASE(trigonometric_regression2) { @@ -225,7 +225,7 @@ QUIZ_CASE(trigonometric_regression2) { int numberOfPoints = sizeof(x) / sizeof(double); assert(sizeof(y) == sizeof(double) * numberOfPoints); - assert_trigonomatric_regression_is(x, y, numberOfPoints, coefficients, r2, Poincare::Preferences::AngleUnit::Radian); + assert_trigonometric_regression_is(x, y, numberOfPoints, coefficients, r2, Poincare::Preferences::AngleUnit::Radian); } diff --git a/apps/sequence/list/type_parameter_controller.cpp b/apps/sequence/list/type_parameter_controller.cpp index e7ad0c56e9d..90cee0d7ccc 100644 --- a/apps/sequence/list/type_parameter_controller.cpp +++ b/apps/sequence/list/type_parameter_controller.cpp @@ -17,7 +17,7 @@ TypeParameterController::TypeParameterController(Responder * parentResponder, Li ViewController(parentResponder), m_explicitCell(&m_selectableTableView, I18n::Message::Explicit, cellLayout), m_singleRecurrenceCell(&m_selectableTableView, I18n::Message::SingleRecurrence, cellLayout), - m_doubleRecurenceCell(&m_selectableTableView, I18n::Message::DoubleRecurrence, cellLayout), + m_doubleRecurrenceCell(&m_selectableTableView, I18n::Message::DoubleRecurrence, cellLayout), m_layouts{}, m_selectableTableView(this), m_record(), @@ -100,7 +100,7 @@ int TypeParameterController::numberOfRows() const { HighlightCell * TypeParameterController::reusableCell(int index) { assert(index >= 0); assert(index < k_totalNumberOfCell); - HighlightCell * cells[] = {&m_explicitCell, &m_singleRecurrenceCell, &m_doubleRecurenceCell}; + HighlightCell * cells[] = {&m_explicitCell, &m_singleRecurrenceCell, &m_doubleRecurrenceCell}; return cells[index]; } diff --git a/apps/sequence/list/type_parameter_controller.h b/apps/sequence/list/type_parameter_controller.h index f6bf68862fc..f4d18d3ac26 100644 --- a/apps/sequence/list/type_parameter_controller.h +++ b/apps/sequence/list/type_parameter_controller.h @@ -36,7 +36,7 @@ class TypeParameterController : public ViewController, public SimpleListViewData constexpr static int k_totalNumberOfCell = 3; ExpressionTableCellWithPointer m_explicitCell; ExpressionTableCellWithPointer m_singleRecurrenceCell; - ExpressionTableCellWithPointer m_doubleRecurenceCell; + ExpressionTableCellWithPointer m_doubleRecurrenceCell; Poincare::Layout m_layouts[k_totalNumberOfCell]; SelectableTableView m_selectableTableView; Ion::Storage::Record m_record; diff --git a/apps/sequence/test/sequence.cpp b/apps/sequence/test/sequence.cpp index fb729ab14c1..3a390d6a651 100644 --- a/apps/sequence/test/sequence.cpp +++ b/apps/sequence/test/sequence.cpp @@ -47,7 +47,7 @@ void check_sequences_defined_by(double result[MaxNumberOfSequences][10], Sequenc } } store->removeAll(); - /* The store is a global variable that has been contructed through + /* The store is a global variable that has been constructed through * GlobalContext::sequenceStore singleton. It won't be destructed. However, * we need to make sure that the pool is empty between quiz_cases. */ store->tidy(); diff --git a/apps/settings/sub_menu/about_controller_non_official.cpp b/apps/settings/sub_menu/about_controller_non_official.cpp index 52e111eb513..cedd20636a5 100644 --- a/apps/settings/sub_menu/about_controller_non_official.cpp +++ b/apps/settings/sub_menu/about_controller_non_official.cpp @@ -5,7 +5,7 @@ namespace Settings { void AboutController::viewWillAppear() { GenericSubController::viewWillAppear(); - // IN OMEGA, THE FOLLOWING LINES ARE ADDED IN A SUBMENU "LEGAL INFORMATION", BECAUSE MESSAGES DELETE THE SCROLLBAR. + // IN UPSILON, THE FOLLOWING LINES ARE ADDED IN A SUBMENU "LEGAL INFORMATION", BECAUSE MESSAGES DELETE THE SCROLLBAR. // --------------------- Please don't edit these lines ---------------------- I18n::Message cautionMessages[] = {I18n::Message::AboutWarning1, I18n::Message::AboutWarning2, I18n::Message::AboutWarning3, I18n::Message::AboutWarning4}; // m_view.setMessages(cautionMessages, sizeof(cautionMessages)/sizeof(I18n::Message)); diff --git a/apps/settings/sub_menu/usb_protection_controller.cpp b/apps/settings/sub_menu/usb_protection_controller.cpp index 734cf95faeb..08dc94f235e 100644 --- a/apps/settings/sub_menu/usb_protection_controller.cpp +++ b/apps/settings/sub_menu/usb_protection_controller.cpp @@ -63,15 +63,15 @@ void UsbInfoController::willDisplayCellForIndex(HighlightCell *cell, int index) SwitchView *mySwitch = (SwitchView *)myCell->accessoryView(); mySwitch->setState(!GlobalPreferences::sharedGlobalPreferences()->dfuUnlocked()); } else if (index == 1) { - MessageTableCellWithChevronAndMessage *mcell = (MessageTableCellWithChevronAndMessage *)cell; + MessageTableCellWithChevronAndMessage *m_cell = (MessageTableCellWithChevronAndMessage *)cell; int currentLevel = GlobalPreferences::sharedGlobalPreferences()->dfuLevel(); if (currentLevel == 0) { - mcell->setSubtitle(I18n::Message::USBDefaultLevelDesc); + m_cell->setSubtitle(I18n::Message::USBDefaultLevelDesc); } else if (currentLevel == 1) {; - mcell->setSubtitle(I18n::Message::USBLowLevelDesc); + m_cell->setSubtitle(I18n::Message::USBLowLevelDesc); } else { assert(currentLevel == 2); - mcell->setSubtitle(I18n::Message::USBParanoidLevelDesc); + m_cell->setSubtitle(I18n::Message::USBParanoidLevelDesc); } } } diff --git a/apps/shared/double_pair_store.cpp b/apps/shared/double_pair_store.cpp index b35c56de70f..f78d4f08133 100644 --- a/apps/shared/double_pair_store.cpp +++ b/apps/shared/double_pair_store.cpp @@ -115,15 +115,15 @@ bool DoublePairStore::seriesNumberOfAbscissaeGreaterOrEqualTo(int series, int i) if (count >= i) { return true; } - double currentAbsissa = m_data[series][0][j]; - bool firstOccurence = true; + double currentAbscissa = m_data[series][0][j]; + bool firstOccurrence = true; for (int k = 0; k < j; k++) { - if (m_data[series][0][k] == currentAbsissa) { - firstOccurence = false; + if (m_data[series][0][k] == currentAbscissa) { + firstOccurrence = false; break; } } - if (firstOccurence) { + if (firstOccurrence) { count++; } } diff --git a/apps/shared/expression_model.cpp b/apps/shared/expression_model.cpp index 54170b6447a..ab7dd0d0933 100644 --- a/apps/shared/expression_model.cpp +++ b/apps/shared/expression_model.cpp @@ -143,7 +143,7 @@ Ion::Storage::Record::ErrorStatus ExpressionModel::setExpressionContent(Ion::Sto // Set the data with the right size newData.size = newDataSize; error = record->setValue(newData); - // Any error would have occured at the first call to setValue + // Any error would have occurred at the first call to setValue assert(error == Ion::Storage::Record::ErrorStatus::None); /* Here we delete only the elements relative to the expression model kept in diff --git a/apps/shared/global_context.cpp b/apps/shared/global_context.cpp index 5df9889538f..bf8c3cdb4c4 100644 --- a/apps/shared/global_context.cpp +++ b/apps/shared/global_context.cpp @@ -134,7 +134,7 @@ const Expression GlobalContext::ExpressionForSequence(const SymbolAbstract & sym } else if (!std::isnan(unknownSymbolValue)) { /* If unknownSymbolValue is not nan, then we are in the graph app. In order * to allow functions like f(x) = u(x+0.5) to be ploted, we need to - * approximate the rank and check if it is an integer. Unfortunatly this + * approximate the rank and check if it is an integer. Unfortunately this * leads to some edge cases were, because of quantification, we have * floor(x) = x while x is not integer.*/ rankIsInteger = std::floor(rankValue) == rankValue; diff --git a/apps/shared/sequence_context.h b/apps/shared/sequence_context.h index 83c8d8e9f04..f2995a58e82 100644 --- a/apps/shared/sequence_context.h +++ b/apps/shared/sequence_context.h @@ -62,7 +62,7 @@ class SequenceContext : public Poincare::ContextWithParent { m_sequenceStore(sequenceStore) {} /* expressionForSymbolAbstract & setExpressionForSymbolAbstractName directly call the parent * context respective methods. Indeed, special chars like n, u(n), u(n+1), - * v(n), v(n+1) are taken into accound only when evaluating sequences which + * v(n), v(n+1) are taken into account only when evaluating sequences which * is done in another context. */ template T valueOfCommonRankSequenceAtPreviousRank(int sequenceIndex, int rank) { return static_cast*>(helper())->valueOfCommonRankSequenceAtPreviousRank(sequenceIndex, rank); diff --git a/apps/shared/store_controller.cpp b/apps/shared/store_controller.cpp index ee4244f3bbd..ebabb133983 100644 --- a/apps/shared/store_controller.cpp +++ b/apps/shared/store_controller.cpp @@ -232,7 +232,7 @@ bool StoreController::privateFillColumnWithFormula(Expression formula, Expressio variables[0][0] = 0; AppsContainer * appsContainer = AppsContainer::sharedAppsContainer(); int nbOfVariables = formula.getVariables(appsContainer->globalContext(), isVariable, (char *)variables, k_maxSizeOfStoreSymbols); - (void) nbOfVariables; // Remove compilation warning of nused variable + (void) nbOfVariables; // Remove compilation warning of unused variable assert(nbOfVariables >= 0); int numberOfValuesToCompute = -1; int index = 0; diff --git a/apps/shared/sum_graph_controller.cpp b/apps/shared/sum_graph_controller.cpp index 423582e900c..08ddd6926b4 100644 --- a/apps/shared/sum_graph_controller.cpp +++ b/apps/shared/sum_graph_controller.cpp @@ -232,12 +232,12 @@ void SumGraphController::LegendView::layoutSubviews(bool force) { void SumGraphController::LegendView::layoutSubviews(Step step, bool force) { KDCoordinate width = bounds().width(); - KDCoordinate heigth = bounds().height(); + KDCoordinate height = bounds().height(); KDSize legendSize = m_legend.minimalSizeForOptimalDisplay(); if (legendSize.width() > 0) { m_sum.setFrame(KDRect(0, k_symbolHeightMargin, width-legendSize.width(), m_sum.minimalSizeForOptimalDisplay().height()), force); - m_legend.setFrame(KDRect(width-legendSize.width(), 0, legendSize.width(), heigth), force); + m_legend.setFrame(KDRect(width-legendSize.width(), 0, legendSize.width(), height), force); } else { m_sum.setFrame(bounds(), force); m_legend.setFrame(KDRectZero, force); diff --git a/apps/solver/base.de.i18n b/apps/solver/base.de.i18n index f3a41673887..89b770872f1 100644 --- a/apps/solver/base.de.i18n +++ b/apps/solver/base.de.i18n @@ -11,7 +11,7 @@ TooManyVariables = "Es gibt zu viele Unbekannte" NonLinearSystem = "Das System ist nicht linear" Solution = "Lösung" ApproximateSolution = "Ungefähre Lösung" -SearchInverval = "Lösungssuche Intervall" +SearchInterval = "Lösungssuche Intervall" NoSolutionSystem = "Das System hat keine Lösung" NoSolutionEquation = "Die Gleichung hat keine Lösung" NoSolutionInterval = "Keine Lösung im Intervall gefunden" diff --git a/apps/solver/base.en.i18n b/apps/solver/base.en.i18n index 017cad04d3d..b8239dae01c 100644 --- a/apps/solver/base.en.i18n +++ b/apps/solver/base.en.i18n @@ -11,7 +11,7 @@ TooManyVariables = "There are too many unknowns" NonLinearSystem = "The system is not linear" Solution = "Solution" ApproximateSolution = "Approximate solution" -SearchInverval = "Search interval" +SearchInterval = "Search interval" NoSolutionSystem = "The system has no solution" NoSolutionEquation = "The equation has no solution" NoSolutionInterval = "No solution found in the interval" diff --git a/apps/solver/base.es.i18n b/apps/solver/base.es.i18n index ea595cf2dcd..40c567c9d1a 100644 --- a/apps/solver/base.es.i18n +++ b/apps/solver/base.es.i18n @@ -11,7 +11,7 @@ TooManyVariables = "Hay demasiadas incógnitas" NonLinearSystem = "El sistema no es lineal" Solution = "Solución" ApproximateSolution = "Solución aproximada" -SearchInverval = "Intervalo de búsqueda" +SearchInterval = "Intervalo de búsqueda" NoSolutionSystem = "El sistema no tiene solución" NoSolutionEquation = "La ecuación no tiene solución" NoSolutionInterval = "Ninguna solución encontrada en el intervalo" diff --git a/apps/solver/base.fr.i18n b/apps/solver/base.fr.i18n index c337a0525d3..424c15cfebe 100644 --- a/apps/solver/base.fr.i18n +++ b/apps/solver/base.fr.i18n @@ -11,7 +11,7 @@ TooManyVariables = "Le nombre d'inconnues est trop grand" NonLinearSystem = "Le système n'est pas linéaire" Solution = "Solution" ApproximateSolution = "Solution approchée" -SearchInverval = "Intervalle de recherche" +SearchInterval = "Intervalle de recherche" NoSolutionSystem = "Le système n'admet aucune solution" NoSolutionEquation = "L'équation n'admet aucune solution" NoSolutionInterval = "Aucune solution trouvée dans cet intervalle" diff --git a/apps/solver/base.hu.i18n b/apps/solver/base.hu.i18n index baba451b794..a36ed7ad4cf 100644 --- a/apps/solver/base.hu.i18n +++ b/apps/solver/base.hu.i18n @@ -11,7 +11,7 @@ TooManyVariables = "Túl sok ismeretlen van" NonLinearSystem = "A rendszer nem lineáris" Solution = "Megoldás" ApproximateSolution = "Hozzávetöleges megoldás" -SearchInverval = "Keresési intervallum" +SearchInterval = "Keresési intervallum" NoSolutionSystem = "A rendszernek nincs megoldása" NoSolutionEquation = "Az egyenletnek nincs megoldása" NoSolutionInterval = "Nincs megoldás ebben az intervallumban" diff --git a/apps/solver/base.it.i18n b/apps/solver/base.it.i18n index 5b6f79dbfa3..bf0848d14e2 100644 --- a/apps/solver/base.it.i18n +++ b/apps/solver/base.it.i18n @@ -11,7 +11,7 @@ TooManyVariables = "Il numero di incognite è troppo elevato" NonLinearSystem = "Il sistema non è lineare" Solution = "Soluzione" ApproximateSolution = "Soluzione approssimata" -SearchInverval = "Intervallo di ricerca" +SearchInterval = "Intervallo di ricerca" NoSolutionSystem = "Il sistema non ammette nessuna soluzione" NoSolutionEquation = "L'equazione non ammette nessuna soluzione" NoSolutionInterval = "Nessuna soluzione trovata dentro questo intervallo" diff --git a/apps/solver/base.nl.i18n b/apps/solver/base.nl.i18n index 89e9d3d4da3..b6c838f8566 100644 --- a/apps/solver/base.nl.i18n +++ b/apps/solver/base.nl.i18n @@ -11,7 +11,7 @@ TooManyVariables = "Er zijn te veel onbekenden" NonLinearSystem = "Het stelsel is niet lineair" Solution = "Oplossing" ApproximateSolution = "Benaderde oplossing" -SearchInverval = "Intervalbepaling" +SearchInterval = "Intervalbepaling" NoSolutionSystem = "Het stelsel heeft geen oplossing" NoSolutionEquation = "De vergelijking heeft geen oplossing" NoSolutionInterval = "Geen oplossing gevonden binnen het interval" diff --git a/apps/solver/base.pt.i18n b/apps/solver/base.pt.i18n index 356eaa24f01..e3e0c19de15 100644 --- a/apps/solver/base.pt.i18n +++ b/apps/solver/base.pt.i18n @@ -11,7 +11,7 @@ TooManyVariables = "Existem muitas incógnitas" NonLinearSystem = "O sistema não é linear" Solution = "Solução" ApproximateSolution = "Solução aproximada" -SearchInverval = "Intervalo de pesquisa" +SearchInterval = "Intervalo de pesquisa" NoSolutionSystem = "O sistema não tem solução" NoSolutionEquation = "A equação não tem solução" NoSolutionInterval = "Nenhuma solução encontrada no intervalo" diff --git a/apps/solver/equation_store.cpp b/apps/solver/equation_store.cpp index e5865a19a74..30812405b56 100644 --- a/apps/solver/equation_store.cpp +++ b/apps/solver/equation_store.cpp @@ -136,8 +136,8 @@ EquationStore::Error EquationStore::exactSolve(Poincare::Context * context, bool * the exact answer is given to the user. * 3) If no classic form has been found in the developped form, we need to use * numerical approximation. Therefore, to prevent precision losses, we work - * with the undevelopped form of the equation. Therefore we set reductionTarget - * to SystemForApproximation. Solutions are then numericaly approximated + * with the undeveloped form of the equation. Therefore we set reductionTarget + * to SystemForApproximation. Solutions are then numerically approximated * between the bounds provided by the user. */ EquationStore::Error EquationStore::privateExactSolve(Poincare::Context * context, bool replaceFunctionsButNotSymbols) { @@ -244,7 +244,7 @@ EquationStore::Error EquationStore::privateExactSolve(Poincare::Context * contex if (degree == 2) { // Polynomial degree <= 2 m_type = Type::PolynomialMonovariable; - error = oneDimensialPolynomialSolve(exactSolutions, exactSolutionsApproximations, polynomialCoefficients, degree, context); + error = oneDimensionalPolynomialSolve(exactSolutions, exactSolutionsApproximations, polynomialCoefficients, degree, context); } else { // Step 4. Monovariable non-polynomial or polynomial with degree > 2 m_type = Type::Monovariable; @@ -340,7 +340,7 @@ EquationStore::Error EquationStore::resolveLinearSystem(Expression exactSolution return Error::NoError; } -EquationStore::Error EquationStore::oneDimensialPolynomialSolve(Expression exactSolutions[k_maxNumberOfExactSolutions], Expression exactSolutionsApproximations[k_maxNumberOfExactSolutions], Expression coefficients[Expression::k_maxNumberOfPolynomialCoefficients], int degree, Context * context) { +EquationStore::Error EquationStore::oneDimensionalPolynomialSolve(Expression exactSolutions[k_maxNumberOfExactSolutions], Expression exactSolutionsApproximations[k_maxNumberOfExactSolutions], Expression coefficients[Expression::k_maxNumberOfPolynomialCoefficients], int degree, Context * context) { /* Equation ax^2+bx+c = 0 */ assert(degree == 2); // Compute delta = b*b-4ac @@ -441,13 +441,13 @@ void EquationStore::tidySolution() { Preferences::ComplexFormat EquationStore::updatedComplexFormat(Context * context) { Preferences::ComplexFormat complexFormat = Preferences::sharedPreferences()->complexFormat(); - if (complexFormat == Preferences::ComplexFormat::Real && isExplictlyComplex(context)) { + if (complexFormat == Preferences::ComplexFormat::Real && isExplicitlyComplex(context)) { return Preferences::ComplexFormat::Cartesian; } return complexFormat; } -bool EquationStore::isExplictlyComplex(Context * context) { +bool EquationStore::isExplicitlyComplex(Context * context) { for (int i = 0; i < numberOfDefinedModels(); i++) { if (modelForRecord(definedRecordAtIndex(i))->containsIComplex(context)) { return true; diff --git a/apps/solver/equation_store.h b/apps/solver/equation_store.h index 362392091dc..a473587a97a 100644 --- a/apps/solver/equation_store.h +++ b/apps/solver/equation_store.h @@ -100,9 +100,9 @@ class EquationStore : public Shared::ExpressionModelStore { Error privateExactSolve(Poincare::Context * context, bool replaceFunctionsButNotSymbols); Error resolveLinearSystem(Poincare::Expression solutions[k_maxNumberOfExactSolutions], Poincare::Expression solutionApproximations[k_maxNumberOfExactSolutions], Poincare::Expression coefficients[k_maxNumberOfEquations][Poincare::Expression::k_maxNumberOfVariables], Poincare::Expression constants[k_maxNumberOfEquations], Poincare::Context * context); - Error oneDimensialPolynomialSolve(Poincare::Expression solutions[k_maxNumberOfExactSolutions], Poincare::Expression solutionApproximations[k_maxNumberOfExactSolutions], Poincare::Expression polynomialCoefficients[Poincare::Expression::k_maxNumberOfPolynomialCoefficients], int degree, Poincare::Context * context); + Error oneDimensionalPolynomialSolve(Poincare::Expression solutions[k_maxNumberOfExactSolutions], Poincare::Expression solutionApproximations[k_maxNumberOfExactSolutions], Poincare::Expression polynomialCoefficients[Poincare::Expression::k_maxNumberOfPolynomialCoefficients], int degree, Poincare::Context * context); void tidySolution(); - bool isExplictlyComplex(Poincare::Context * context); + bool isExplicitlyComplex(Poincare::Context * context); Poincare::Preferences::ComplexFormat updatedComplexFormat(Poincare::Context * context); mutable Equation m_equations[k_maxNumberOfEquations]; diff --git a/apps/solver/interval_controller.cpp b/apps/solver/interval_controller.cpp index 986f5b4cf4b..9f83a9685d1 100644 --- a/apps/solver/interval_controller.cpp +++ b/apps/solver/interval_controller.cpp @@ -57,7 +57,7 @@ IntervalController::IntervalController(Responder * parentResponder, InputEventHa } const char * IntervalController::title() { - return I18n::translate(I18n::Message::SearchInverval); + return I18n::translate(I18n::Message::SearchInterval); } int IntervalController::numberOfRows() const { diff --git a/apps/solver/solutions_controller.cpp b/apps/solver/solutions_controller.cpp index 8e5e985dcc1..59bd8a9512f 100644 --- a/apps/solver/solutions_controller.cpp +++ b/apps/solver/solutions_controller.cpp @@ -168,7 +168,7 @@ int SolutionsController::numberOfRows() const { void SolutionsController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { const int rowOfUserVariablesMessage = userVariablesMessageRow(); if (j == rowOfUserVariablesMessage) { - // Predefined varaible used/ignored message + // Predefined variable used/ignored message assert(i >= 0); MessageCell * messageCell = static_cast(cell); messageCell->setHorizontalAlignment(i == 0 ? 1.0f : 0.0f); diff --git a/apps/title_bar_view.cpp b/apps/title_bar_view.cpp index a842b394e50..cd0bb5337b4 100644 --- a/apps/title_bar_view.cpp +++ b/apps/title_bar_view.cpp @@ -102,7 +102,7 @@ View * TitleBarView::subviewAtIndex(int index) { void TitleBarView::layoutSubviews(bool force) { /* We here cheat to layout the main title. The application title is written * with upper cases. But, as upper letters are on the same baseline as lower - * letters, they seem to be slightly above when they are perferctly centered + * letters, they seem to be slightly above when they are perfectly centered * (because their glyph never cross the baseline). To avoid this effect, we * translate the frame of the title downwards.*/ m_titleView.setFrame(KDRect(0, 2, bounds().width(), bounds().height()-2), force); diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index 4c2ef8e9d0e..e4a7c49e41f 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -58,7 +58,7 @@ UnitPressurePascalHecto = "Hektopascal" UnitPressureAtm = "Atmosphere" UnitEnergyMenu = "Energie" UnitEnergyJouleMilli = "Millijoule" -UnitEnergyEletronVoltMenu = "Electronvolt" +UnitEnergyElectronVoltMenu = "Electronvolt" UnitEnergyElectronVoltMega = "Megaelectronvolt" UnitEnergyElectronVoltKilo = "Kiloelectronvolt" UnitEnergyElectronVolt = "Electronvolt" @@ -116,7 +116,7 @@ Integral = "Integral" Sum = "Summe" Product = "Produkt" ComplexAbsoluteValue = "Betrag" -Agument = "Argument" +Argument = "Argument" RealPart = "Realteil" ImaginaryPart = "Imaginärteil" Conjugate = "Konjugiert" diff --git a/apps/toolbox.en.i18n b/apps/toolbox.en.i18n index d6c1c235937..e2d7dd94ad3 100644 --- a/apps/toolbox.en.i18n +++ b/apps/toolbox.en.i18n @@ -58,7 +58,7 @@ UnitPressurePascalHecto = "Hectopascal" UnitPressureAtm = "Atmosphere" UnitEnergyMenu = "Energy" UnitEnergyJouleMilli = "Millijoule" -UnitEnergyEletronVoltMenu = "Electronvolt" +UnitEnergyElectronVoltMenu = "Electronvolt" UnitEnergyElectronVoltMega = "Megaelectronvolt" UnitEnergyElectronVoltKilo = "Kiloelectronvolt" UnitEnergyElectronVolt = "Electronvolt" @@ -116,7 +116,7 @@ Integral = "Integral" Sum = "Sum" Product = "Product" ComplexAbsoluteValue = "Absolute value" -Agument = "Argument" +Argument = "Argument" RealPart = "Real part" ImaginaryPart = "Imaginary part" Conjugate = "Conjugate" diff --git a/apps/toolbox.es.i18n b/apps/toolbox.es.i18n index 3ec2d7db5b3..21247a2711a 100644 --- a/apps/toolbox.es.i18n +++ b/apps/toolbox.es.i18n @@ -58,7 +58,7 @@ UnitPressurePascalHecto = "Hectopascal" UnitPressureAtm = "Atmosphere" UnitEnergyMenu = "Energy" UnitEnergyJouleMilli = "Millijoule" -UnitEnergyEletronVoltMenu = "Electronvolt" +UnitEnergyElectronVoltMenu = "Electronvolt" UnitEnergyElectronVoltMega = "Megaelectronvolt" UnitEnergyElectronVoltKilo = "Kiloelectronvolt" UnitEnergyElectronVolt = "Electronvolt" @@ -116,7 +116,7 @@ Integral = "Integral" Sum = "Suma" Product = "Productorio" ComplexAbsoluteValue = "Modulo" -Agument = "Argumento" +Argument = "Argumento" RealPart = "Parte real" ImaginaryPart = "Parte imaginaria" Conjugate = "Conjugado" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index 2eb1c183ec2..4cd0db6fa0f 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -62,7 +62,7 @@ UnitPressurePascalHecto = "Hectopascal" UnitPressureAtm = "Atmosphère" UnitEnergyMenu = "Énergie" UnitEnergyJouleMilli = "Millijoule" -UnitEnergyEletronVoltMenu = "Électronvolt" +UnitEnergyElectronVoltMenu = "Électronvolt" UnitEnergyElectronVoltMega = "Mégaélectronvolt" UnitEnergyElectronVoltKilo = "Kiloélectronvolt" UnitEnergyElectronVolt = "Électronvolt" @@ -120,7 +120,7 @@ Integral = "Intégrale de f sur [a;b]" Sum = "Somme" Product = "Produit" ComplexAbsoluteValue = "Module" -Agument = "Argument" +Argument = "Argument" RealPart = "Partie réelle" ImaginaryPart = "Partie imaginaire" Conjugate = "Conjugué" diff --git a/apps/toolbox.hu.i18n b/apps/toolbox.hu.i18n index bfcd3a4ccaa..137c3791733 100644 --- a/apps/toolbox.hu.i18n +++ b/apps/toolbox.hu.i18n @@ -58,7 +58,7 @@ UnitPressurePascalHecto = "Hectopascal" UnitPressureAtm = "Légkör" UnitEnergyMenu = "Energia" UnitEnergyJouleMilli = "Millijoule" -UnitEnergyEletronVoltMenu = "Electronvolt" +UnitEnergyElectronVoltMenu = "Electronvolt" UnitEnergyElectronVoltMega = "Megaelectronvolt" UnitEnergyElectronVoltKilo = "Kiloelectronvolt" UnitEnergyElectronVolt = "Electronvolt" @@ -116,7 +116,7 @@ Integral = "Integral" Sum = "Összeg" Product = "Termék" ComplexAbsoluteValue = "Abszolút érték" -Agument = "érv" +Argument = "érv" RealPart = "Igazi rész" ImaginaryPart = "Képzeletbeli rész" Conjugate = "Konjugátum" diff --git a/apps/toolbox.it.i18n b/apps/toolbox.it.i18n index af1d99047f7..69ac55c6a95 100644 --- a/apps/toolbox.it.i18n +++ b/apps/toolbox.it.i18n @@ -58,7 +58,7 @@ UnitPressurePascalHecto = "Hectopascal" UnitPressureAtm = "Atmosfera" UnitEnergyMenu = "Energia" UnitEnergyJouleMilli = "Millijoule" -UnitEnergyEletronVoltMenu = "Electronvolt" +UnitEnergyElectronVoltMenu = "Electronvolt" UnitEnergyElectronVoltMega = "Megaelectronvolt" UnitEnergyElectronVoltKilo = "Kiloelectronvolt" UnitEnergyElectronVolt = "Electronvolt" @@ -116,7 +116,7 @@ Integral = "Integrale" Sum = "Somma" Product = "Prodotto" ComplexAbsoluteValue = "Modulo" -Agument = "Argomento" +Argument = "Argomento" RealPart = "Parte reale" ImaginaryPart = "Parte immaginaria" Conjugate = "Coniugato" diff --git a/apps/toolbox.nl.i18n b/apps/toolbox.nl.i18n index 84a800366b4..ecd1c364bae 100644 --- a/apps/toolbox.nl.i18n +++ b/apps/toolbox.nl.i18n @@ -58,7 +58,7 @@ UnitPressurePascalHecto = "Hectopascal" UnitPressureAtm = "Atmosfeer" UnitEnergyMenu = "Energie" UnitEnergyJouleMilli = "Millijoule" -UnitEnergyEletronVoltMenu = "Elektronvolt" +UnitEnergyElectronVoltMenu = "Elektronvolt" UnitEnergyElectronVoltMega = "Megaelektronvolt" UnitEnergyElectronVoltKilo = "Kiloelektronvolt" UnitEnergyElectronVolt = "Elektronvolt" @@ -116,7 +116,7 @@ Integral = "Integraal" Sum = "Som" Product = "Product" ComplexAbsoluteValue = "Absolute waarde" -Agument = "Argument" +Argument = "Argument" RealPart = "Reëel deel" ImaginaryPart = "Imaginair deel" Conjugate = "Geconjugeerde" diff --git a/apps/toolbox.pt.i18n b/apps/toolbox.pt.i18n index 41a6709dc92..584bc753d27 100644 --- a/apps/toolbox.pt.i18n +++ b/apps/toolbox.pt.i18n @@ -58,7 +58,7 @@ UnitPressurePascalHecto = "Hectopascal" UnitPressureAtm = "Atmosfera" UnitEnergyMenu = "Energia" UnitEnergyJouleMilli = "Milijoule" -UnitEnergyEletronVoltMenu = "Eletrão-volt" +UnitEnergyElectronVoltMenu = "Eletrão-volt" UnitEnergyElectronVoltMega = "Megaeletrão-volt" UnitEnergyElectronVoltKilo = "Kiloeletrão-volt" UnitEnergyElectronVolt = "Eletrão-volt" @@ -116,7 +116,7 @@ Integral = "Integral" Sum = "Somatório" Product = "Produto" ComplexAbsoluteValue = "Módulo" -Agument = "Argumento" +Argument = "Argumento" RealPart = "Parte real" ImaginaryPart = "Parte imaginária" Conjugate = "Conjugado" diff --git a/build/device/dfu.py b/build/device/dfu.py index 14f8ff03060..7245632adfa 100644 --- a/build/device/dfu.py +++ b/build/device/dfu.py @@ -9,7 +9,7 @@ """This module implements enough functionality to program the STM32F4xx over DFU, without requiring dfu-util. See app note AN3156 for a description of the DFU protocol. -See document UM0391 for a dscription of the DFuse file. +See document UM0391 for a description of the DFuse file. """ from __future__ import print_function diff --git a/build/device/ram_map.py b/build/device/ram_map.py index d97ab9e04d4..3676a58d6fd 100644 --- a/build/device/ram_map.py +++ b/build/device/ram_map.py @@ -13,7 +13,7 @@ def parse_line(line): readelf_output = subprocess.check_output([ "arm-none-eabi-readelf", - "-W", # Don't limit line lenght + "-W", # Don't limit line length "-s", # Sizes sys.argv[1] ]).decode('utf-8') diff --git a/build/device/usb/_debug.py b/build/device/usb/_debug.py index a4ca7e82e4a..bd2abcc9fed 100644 --- a/build/device/usb/_debug.py +++ b/build/device/usb/_debug.py @@ -55,7 +55,7 @@ def decorator_logging(f): if not _enable_tracing: return f def do_trace(*args, **named_args): - # this if is just a optimization to avoid unecessary string formatting + # this if is just a optimization to avoid unnecessary string formatting if logging.DEBUG >= logger.getEffectiveLevel(): fn = type(args[0]).__name__ + '.' + f.__name__ _trace_function_call(logger, fn, *args[1:], **named_args) @@ -70,7 +70,7 @@ def decorator_logging(f): if not _enable_tracing: return f def do_trace(*args, **named_args): - # this if is just a optimization to avoid unecessary string formatting + # this if is just a optimization to avoid unnecessary string formatting if logging.DEBUG >= logger.getEffectiveLevel(): _trace_function_call(logger, f.__name__, *args, **named_args) return f(*args, **named_args) diff --git a/build/device/usb/backend/__init__.py b/build/device/usb/backend/__init__.py index f28e4940f6a..ca1ead987c7 100644 --- a/build/device/usb/backend/__init__.py +++ b/build/device/usb/backend/__init__.py @@ -35,7 +35,7 @@ IBackend - backend interface. Backends are Python objects which implement the IBackend interface. -The easiest way to do so is inherinting from IBackend. +The easiest way to do so is inheriting from IBackend. PyUSB already provides backends for libusb versions 0.1 and 1.0, and OpenUSB library. Backends modules included with PyUSB are required to @@ -87,7 +87,7 @@ class IBackend(_objfinalizer.AutoFinalizedObject): IBackend is the basic interface for backend implementations. By default, the methods of the interface raise a NotImplementedError exception. A - backend implementation should replace the methods to provide the funcionality + backend implementation should replace the methods to provide the functionality necessary. As Python is a dynamic typed language, you are not obligated to inherit from @@ -126,7 +126,7 @@ def get_configuration_descriptor(self, dev, config): r"""Return a configuration descriptor of the given device. The object returned is required to have all the Configuration Descriptor - fields acessible as member variables. They must be convertible (but + fields accessible as member variables. They must be convertible (but not required to be equal) to the int type. The dev parameter is the device identification object. @@ -156,7 +156,7 @@ def get_endpoint_descriptor(self, dev, ep, intf, alt, config): r"""Return an endpoint descriptor of the given device. The object returned is required to have all the Endpoint Descriptor - fields acessible as member variables. They must be convertible (but + fields accessible as member variables. They must be convertible (but not required to be equal) to the int type. The ep parameter is the endpoint logical index (not the bEndpointAddress @@ -246,7 +246,7 @@ def bulk_write(self, dev_handle, ep, intf, data, timeout): of the interface containing the endpoint. The data parameter is the data to be sent. It must be an instance of the array.array class. The timeout parameter specifies a time limit to the operation - in miliseconds. + in milliseconds. The method returns the number of bytes written. """ @@ -261,7 +261,7 @@ def bulk_read(self, dev_handle, ep, intf, buff, timeout): of the interface containing the endpoint. The buff parameter is the buffer to receive the data read, the length of the buffer tells how many bytes should be read. The timeout parameter - specifies a time limit to the operation in miliseconds. + specifies a time limit to the operation in milliseconds. The method returns the number of bytes actually read. """ @@ -276,7 +276,7 @@ def intr_write(self, dev_handle, ep, intf, data, timeout): of the interface containing the endpoint. The data parameter is the data to be sent. It must be an instance of the array.array class. The timeout parameter specifies a time limit to the operation - in miliseconds. + in milliseconds. The method returns the number of bytes written. """ @@ -291,7 +291,7 @@ def intr_read(self, dev_handle, ep, intf, size, timeout): of the interface containing the endpoint. The buff parameter is the buffer to receive the data read, the length of the buffer tells how many bytes should be read. The timeout parameter - specifies a time limit to the operation in miliseconds. + specifies a time limit to the operation in milliseconds. The method returns the number of bytes actually read. """ @@ -306,7 +306,7 @@ def iso_write(self, dev_handle, ep, intf, data, timeout): of the interface containing the endpoint. The data parameter is the data to be sent. It must be an instance of the array.array class. The timeout parameter specifies a time limit to the operation - in miliseconds. + in milliseconds. The method returns the number of bytes written. """ @@ -321,7 +321,7 @@ def iso_read(self, dev_handle, ep, intf, size, timeout): of the interface containing the endpoint. The buff parameter is buffer to receive the data read, the length of the buffer tells how many bytes should be read. The timeout parameter specifies - a time limit to the operation in miliseconds. + a time limit to the operation in milliseconds. The method returns the number of bytes actually read. """ @@ -347,7 +347,7 @@ def ctrl_transfer(self, IN requests it is the buffer to hold the data read. The number of bytes requested to transmit or receive is equal to the length of the array times the data.itemsize field. The timeout parameter - specifies a time limit to the operation in miliseconds. + specifies a time limit to the operation in milliseconds. Return the number of bytes written (for OUT transfers) or the data read (for IN transfers), as an array.array object. diff --git a/build/device/usb/backend/libusb1.py b/build/device/usb/backend/libusb1.py index 6615d68b780..65b17621391 100644 --- a/build/device/usb/backend/libusb1.py +++ b/build/device/usb/backend/libusb1.py @@ -300,7 +300,7 @@ def _setup_prototypes(lib): # void libusb_exit (struct libusb_context *ctx) lib.libusb_exit.argtypes = [c_void_p] - # ssize_t libusb_get_device_list (libusb_context *ctx, + # size_t libusb_get_device_list (libusb_context *ctx, # libusb_device ***list) lib.libusb_get_device_list.argtypes = [ c_void_p, diff --git a/build/device/usb/core.py b/build/device/usb/core.py index 7546de2bc2e..282e55afe07 100644 --- a/build/device/usb/core.py +++ b/build/device/usb/core.py @@ -735,7 +735,7 @@ class Device(_objfinalizer.AutoFinalizedObject): value for most devices) and then writes some data to the endpoint 0x01. Timeout values for the write, read and ctrl_transfer methods are specified - in miliseconds. If the parameter is omitted, Device.default_timeout value + in milliseconds. If the parameter is omitted, Device.default_timeout value will be used instead. This property can be set by the user at anytime. """ @@ -961,7 +961,7 @@ def write(self, endpoint, data, timeout = None): The data parameter should be a sequence like type convertible to the array type (see array module). - The timeout is specified in miliseconds. + The timeout is specified in milliseconds. The method returns the number of bytes written. """ @@ -993,7 +993,7 @@ def read(self, endpoint, size_or_buffer, timeout = None): tells how many bytes you want to read or supplies the buffer to receive the data (it *must* be an object of the type array). - The timeout is specified in miliseconds. + The timeout is specified in milliseconds. If the size_or_buffer parameter is the number of bytes to read, the method returns an array object with the data read. If the diff --git a/build/doc/Doxyfile b/build/doc/Doxyfile index 8f9c49eebbf..f903ba0f1cc 100644 --- a/build/doc/Doxyfile +++ b/build/doc/Doxyfile @@ -32,7 +32,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = Omega +PROJECT_NAME = Upsilon # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version diff --git a/build/pimp.mak b/build/pimp.mak index 68ee83b22c4..afc96c862bf 100644 --- a/build/pimp.mak +++ b/build/pimp.mak @@ -22,7 +22,7 @@ endif DISPLAY_TARGET ?= "Undefined" -PLS_IGNORE := $(shell >&2 printf "\e[32m Targetting $(DISPLAY_TARGET)\n") +PLS_IGNORE := $(shell >&2 printf "\e[32m Targeting $(DISPLAY_TARGET)\n") ifeq ($(OS),Windows_NT) DISPLAY_OS = Windows diff --git a/build/targets.device.n0110.mak b/build/targets.device.n0110.mak index 5b1f9acda15..c71f80aaeb2 100644 --- a/build/targets.device.n0110.mak +++ b/build/targets.device.n0110.mak @@ -1,7 +1,7 @@ HANDY_TARGETS += test.external_flash.write test.external_flash.read $(BUILD_DIR)/test.external_flash.%.$(EXE): LDSCRIPT = ion/test/device/n0110/external_flash_tests.ld -test_external_flash_src = $(ion_src) $(liba_src) $(libaxx_src) $(default_kandinsky_src) $(poincare_src) $(ion_device_dfu_relogated_src) $(runner_src) +test_external_flash_src = $(ion_src) $(liba_src) $(libaxx_src) $(default_kandinsky_src) $(poincare_src) $(ion_device_dfu_relegated_src) $(runner_src) $(BUILD_DIR)/test.external_flash.read.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_external_flash_read_symbols.o $(call object_for,$(test_external_flash_src) $(test_ion_external_flash_read_src)) $(BUILD_DIR)/test.external_flash.write.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_external_flash_write_symbols.o $(call object_for,$(test_external_flash_src) $(test_ion_external_flash_write_src)) diff --git a/docs/architecture.svg b/docs/architecture.svg index 8a848854e92..59c37af9f3a 100644 --- a/docs/architecture.svg +++ b/docs/architecture.svg @@ -22,7 +22,7 @@ - Esher + Escher diff --git a/docs/build/index.md b/docs/build/index.md index 9827314db9b..1fd79621d34 100644 --- a/docs/build/index.md +++ b/docs/build/index.md @@ -2,7 +2,7 @@ title: Installing the SDK breadcrumb: SDK --- -# Build and run your own version of Epsilon +# Build and run your own version of Upsilon ## Install the SDK @@ -46,13 +46,13 @@ apt-get install gcc-arm-none-eabi binutils-arm-none-eabi ## Retrieve the source code -The code is hosted on GitHub. You can retrieve it using the follwing command. +The code is hosted on GitHub. You can retrieve it using the following command. ``` -git clone https://github.com/numworks/epsilon.git +git clone https://github.com/Lauryy06/Upsilon.git ``` -## Run Epsilon on your computer +## Run Upsilon on your computer Once the SDK has been installed, just open your terminal (Msys2, Terminal.app, xterm…) and type the following commands: @@ -61,7 +61,7 @@ make PLATFORM=simulator clean make PLATFORM=simulator epsilon_run ``` -## Run Epsilon on your calculator +## Run Upsilon on your calculator You can also update your NumWorks calculator easily. Note that you'll need to press the Reset button and that all data on your calculator will be lost. @@ -71,4 +71,4 @@ make make epsilon_flash ``` -Congratulations, you're running your very own version of Epsilon! +Congratulations, you're running your very own version of Upsilon! diff --git a/docs/index.md b/docs/index.md index edc12868821..e5418a9e388 100644 --- a/docs/index.md +++ b/docs/index.md @@ -45,7 +45,7 @@ We're listing here all the topics you should be familiar with before being able The choice of a programming language is a controversial topic. Not all of them can be used to write an operating system, but quite a few can. We settled on C++ for several reasons: - It is a [system](https://en.wikipedia.org/wiki/System_programming_language) programming language, which is something we need since we have to write some low-level code. -- It has excellent tooling: several extremly high-quality compilers +- It has excellent tooling: several extremely high-quality compilers - It is used for several high-profile projects LLVM, WebKit, MySQL, Photoshop… This ensures a strong ecosystem of tools, code and documentation. - It easily allows Object-Oriented Programming, which is a convenient abstraction. @@ -57,7 +57,7 @@ If you want to contribute to Epsilon, you'll need to learn some C++. ### Working with limited memory -Our device has 256 KB of RAM. That's very little memory by today's standards. That being said, by writing code carefuly, a huge lot can be achieved in that space. After all, that's 64 times more memory than the computer of the Apollo mission! +Our device has 256 KB of RAM. That's very little memory by today's standards. That being said, by writing code carefully, a huge lot can be achieved in that space. After all, that's 64 times more memory than the computer of the Apollo mission! #### Stack memory diff --git a/escher/image/inliner.c b/escher/image/inliner.c index c2e9d489448..df31430dfd2 100644 --- a/escher/image/inliner.c +++ b/escher/image/inliner.c @@ -4,7 +4,7 @@ * * The inliner creates a .h and a .cpp file in the same directory as the input * file. The implementation file declares an Image in the ImageStore namespace, - * and the header exposes a pointer to this variable. The Image embedds the + * and the header exposes a pointer to this variable. The Image embedded the * bitmap data in the RGB565 format. */ #include diff --git a/escher/include/escher/text_area.h b/escher/include/escher/text_area.h index e8aa34aa514..13bd84e03ed 100644 --- a/escher/include/escher/text_area.h +++ b/escher/include/escher/text_area.h @@ -60,7 +60,7 @@ class TextArea : public TextInput, public InputEventHandler { class Position { /* column and line correspond to the visual column and line. The glyph at * the kth column is not the the glyph of kth code point, because of - * combining code points that do not fave a personnal glyph. */ + * combining code points that do not fave a personal glyph. */ public: Position(int column, int line) : m_column(column), m_line(line) {} int column() const { return m_column; } diff --git a/escher/include/escher/view_controller.h b/escher/include/escher/view_controller.h index 60243a8e038..ffee1f1a01a 100644 --- a/escher/include/escher/view_controller.h +++ b/escher/include/escher/view_controller.h @@ -8,7 +8,7 @@ extern "C" { #include } -/* ViewControllers are reponsible for +/* ViewControllers are responsible for * - Building the view hierarchy * - Handling user input * @@ -23,7 +23,7 @@ extern "C" { * - willExitResponderChain * - willResignFirstResponder * - * Both methods are always called after setting a view and laying its subwiews + * Both methods are always called after setting a view and laying its subviews * out. * * The method initView is called before setting a View (or often sets itself) diff --git a/escher/src/button_row_controller.cpp b/escher/src/button_row_controller.cpp index 021c0003813..94a2e67f930 100644 --- a/escher/src/button_row_controller.cpp +++ b/escher/src/button_row_controller.cpp @@ -37,7 +37,7 @@ int ButtonRowController::ContentView::numberOfSubviews() const { } View * ButtonRowController::ContentView::subviewAtIndex(int index) { - /* Warning: the order of the subviews is important for drity tracking. + /* Warning: the order of the subviews is important for dirty tracking. * Indeed, when a child is redrawn, the redrawn area is the smallest * rectangle unioniong the dirty rectangle and the previous redrawn area. * As the main view is more likely to be bigger, we prefer to set it as the @@ -217,7 +217,7 @@ void ButtonRowController::initView() { void ButtonRowController::viewWillAppear() { /* We need to layout subviews at first appearance because the number of - * buttons might have changed between 2 appearences. */ + * buttons might have changed between 2 appearances. */ m_contentView.layoutSubviews(); m_contentView.mainViewController()->viewWillAppear(); } diff --git a/escher/src/icon_view.cpp b/escher/src/icon_view.cpp index 0637d62c67d..cb9a31749c4 100644 --- a/escher/src/icon_view.cpp +++ b/escher/src/icon_view.cpp @@ -54,7 +54,7 @@ void IconView::drawRect(KDContext * ctx, KDRect rect) const { //Then we push the rectangular part of the image ctx->fillRectWithPixels(KDRect(0, 6, m_frame.width(), 44),pixelBuffer+(6*55), nullptr); - //Finaly we push the last 5 lines of the image so that they are truncated on the sides + //Finally we push the last 5 lines of the image so that they are truncated on the sides ctx->fillRectWithPixels(KDRect(1, 50, m_frame.width()-2, 1),pixelBuffer+1+(50*55), nullptr); ctx->fillRectWithPixels(KDRect(1, 51, m_frame.width()-2, 1),pixelBuffer+1+(51*55), nullptr); ctx->fillRectWithPixels(KDRect(2, 52, m_frame.width()-4, 1),pixelBuffer+2+(52*55), nullptr); diff --git a/escher/src/selectable_table_view.cpp b/escher/src/selectable_table_view.cpp index 44804fcb51b..226ed1b0958 100644 --- a/escher/src/selectable_table_view.cpp +++ b/escher/src/selectable_table_view.cpp @@ -93,7 +93,7 @@ bool SelectableTableView::selectCellAtLocation(int i, int j, bool setFirstRespon * ExpressionModelListController needs to update its memoized cell before * being able to scroll; * - after scrolling: for instance, the calculation history table might - * change its cell content when selected (outup toggling, ellipsis toggling) + * change its cell content when selected (output toggling, ellipsis toggling) * and thus need to access the right used cell - which is defined only * after scrolling. */ diff --git a/escher/src/text_field.cpp b/escher/src/text_field.cpp index 4411f12a46c..2587bf024b3 100644 --- a/escher/src/text_field.cpp +++ b/escher/src/text_field.cpp @@ -155,7 +155,7 @@ bool TextField::ContentView::removePreviousGlyph() { assert(m_isEditing); if (m_horizontalAlignment > 0.0f) { - /* Reload the view. If we do it later, the text beins supposedly shorter, we + /* Reload the view. If we do it later, the text beings supposedly shorter, we * will not clean the first char. */ reloadRectFromPosition(s_draftTextBuffer); } @@ -290,7 +290,7 @@ bool TextField::privateHandleEvent(Ion::Events::Event event) { return true; } if (isEditing() && shouldFinishEditing(event)) { - /* If textFieldDidFinishEditing displays a pop-up (because of an unvalid + /* If textFieldDidFinishEditing displays a pop-up (because of an invalid * text for instance), the text field will call willResignFirstResponder. * This will call textFieldDidAbortEditing if the textfield is still editing, * which we do not want, as we are not really aborting edition, just diff --git a/ion/Makefile b/ion/Makefile index 3558e713315..1b04baf698f 100644 --- a/ion/Makefile +++ b/ion/Makefile @@ -18,7 +18,7 @@ include ion/src/$(PLATFORM)/Makefile include ion/src/shared/tools/Makefile # We need to work around a GCC bug (concerning versions < 5.1). It is valid in -# C++11 to initialize a character array by providing a string litteral (e.g. +# C++11 to initialize a character array by providing a string literal (e.g. # char test[4]= "ab"; is valid and should initialize test to 'a','b',0,0). # Older versions of GCC are not conformant so we resort to an initializer list. initializer_list = $(shell echo $(1) | sed "s/\(.\)/'\1',/g")0 diff --git a/ion/include/ion/timing.h b/ion/include/ion/timing.h index 523232f8ec8..45d7b6496a3 100644 --- a/ion/include/ion/timing.h +++ b/ion/include/ion/timing.h @@ -9,7 +9,7 @@ namespace Timing { void usleep(uint32_t us); void msleep(uint32_t ms); -/* millis is the number of milliseconds ellapsed since a random epoch. +/* millis is the number of milliseconds elapsed since a random epoch. * On the device, epoch is the boot time. */ uint64_t millis(); diff --git a/ion/include/ion/unicode/utf8_helper.h b/ion/include/ion/unicode/utf8_helper.h index edc3793259c..ba00c195039 100644 --- a/ion/include/ion/unicode/utf8_helper.h +++ b/ion/include/ion/unicode/utf8_helper.h @@ -19,17 +19,17 @@ class TextPair { bool m_removeParenthesesExtention; }; -// Returns the number of occurences of a code point in a string +// Returns the number of occurrences of a code point in a string int CountOccurrences(const char * s, CodePoint c); -/* Returns the first occurence of a code point in a string, the position of the +/* Returns the first occurrence of a code point in a string, the position of the * null terminating char otherwise. */ const char * CodePointSearch(const char * s, CodePoint c, const char * stoppingPosition = nullptr); // Returns true if the text had the code point bool HasCodePoint(const char * s, CodePoint c, const char * stoppingPosition = nullptr); -/* Returns the first occurence of a code point that is not c in a string, +/* Returns the first occurrence of a code point that is not c in a string, * stopping at the null-terminating char or the start of string. */ const char * NotCodePointSearch(const char * s, CodePoint c, bool goingLeft = false, const char * initialPosition = nullptr); @@ -41,9 +41,9 @@ bool CopyAndRemoveCodePoints(char * dst, size_t dstSize, const char * src, CodeP * points where removed before it. Ensure null-termination of dst. */ void RemoveCodePoint(char * buffer, CodePoint c, const char * * indexToUpdate = nullptr, const char * stoppingPosition = nullptr); -/* Slides a string by a number of chars. If slidingSize < 0, the string is slided +/* Slides a string by a number of chars. If slidingSize < 0, the string is slid * to the left losing the first chars. Returns true if successful. - * Exemples : + * Examples : * SlideStringByNumberOfChar("12345", 2, 7) gives "1212345" * SlideStringByNumberOfChar("12345", 2, 5) gives "12123" * SlideStringByNumberOfChar("12345", -2, 5) gives "34545"*/ diff --git a/ion/src/device/n0100/flash.ld b/ion/src/device/n0100/flash.ld index e9b073bb21e..d22fe7b9fd8 100644 --- a/ion/src/device/n0100/flash.ld +++ b/ion/src/device/n0100/flash.ld @@ -71,7 +71,7 @@ SECTIONS { /* The data section is written to Flash but linked as if it were in RAM. * * This is required because its initial value matters (so it has to be in - * persistant memory in the first place), but it is a R/W area of memory + * persistent memory in the first place), but it is a R/W area of memory * so it will have to live in RAM upon execution (in linker lingo, that * translates to the data section having a LMA in Flash and a VMA in RAM). * diff --git a/ion/src/device/n0110/drivers/board.cpp b/ion/src/device/n0110/drivers/board.cpp index 98403abb683..24b8cfc3648 100644 --- a/ion/src/device/n0110/drivers/board.cpp +++ b/ion/src/device/n0110/drivers/board.cpp @@ -42,7 +42,7 @@ void initMPU() { /* This is needed for interfacing with the LCD * We define the whole FMC memory bank 1 as strongly ordered, non-executable * and not accessible. We define the FMC command and data addresses as - * writeable non-cachable, non-buffereable and non shareable. */ + * writeable non-cacheable, non-buffereable and non shareable. */ int sector = 0; MPU.RNR()->setREGION(sector++); MPU.RBAR()->setADDR(0x60000000); @@ -84,7 +84,7 @@ void initMPU() { * then an AHB error is given (AN4760). To prevent this to happen, we * configure the MPU to define the whole Quad-SPI addressable space as * strongly ordered, non-executable and not accessible. Plus, we define the - * Quad-SPI region corresponding to the Expternal Chip as executable and + * Quad-SPI region corresponding to the External Chip as executable and * fully accessible (AN4861). */ MPU.RNR()->setREGION(sector++); MPU.RBAR()->setADDR(0x90000000); diff --git a/ion/src/device/n0110/drivers/config/clocks.h b/ion/src/device/n0110/drivers/config/clocks.h index 0cc551cb03b..9043f71cac6 100644 --- a/ion/src/device/n0110/drivers/config/clocks.h +++ b/ion/src/device/n0110/drivers/config/clocks.h @@ -59,7 +59,7 @@ constexpr static double modulationDepth = 0.25; // Must be (0.25% <= md <= 2%) constexpr static uint32_t SSCG_INCSTEP = (32767*modulationDepth*PLL_N)/(1.0*100*5*SSCG_MODPER); static_assert(SSCG_MODPER == 250, "SSCG_MODPER changed"); static_assert(SSCG_INCSTEP == 25, "SSCG_INCSTEP changed"); -static_assert(SSCG_INCSTEP * SSCG_MODPER < 32767, "Wrong values for the Spread spectrun clock generator"); +static_assert(SSCG_INCSTEP * SSCG_MODPER < 32767, "Wrong values for the Spread spectrum clock generator"); } } } diff --git a/ion/src/device/n0110/flash.ld b/ion/src/device/n0110/flash.ld index 0ae0e5910dc..e66cbbac1d8 100644 --- a/ion/src/device/n0110/flash.ld +++ b/ion/src/device/n0110/flash.ld @@ -190,7 +190,6 @@ SECTIONS { *(.text._ZN3Ion6Device9Backlight*) *(.text._ZN3Ion9Backlight*) - /* Rodata Truc Relou */ *(.text._ZNK10Statistics5Store6medianEi) *(.text._ZNK10Regression5Store12meanOfColumnEiib) *(.text._ZNK6Shared15DoublePairStore11sumOfColumnEiib) @@ -322,7 +321,7 @@ SECTIONS { /* The data section is written to Flash but linked as if it were in RAM. * * This is required because its initial value matters (so it has to be in - * persistant memory in the first place), but it is a R/W area of memory + * persistent memory in the first place), but it is a R/W area of memory * so it will have to live in RAM upon execution (in linker lingo, that * translates to the data section having a LMA in Flash and a VMA in RAM). * diff --git a/ion/src/device/n0110/internal_flash.ld b/ion/src/device/n0110/internal_flash.ld index 76089e337a2..2e1ce78bbb1 100644 --- a/ion/src/device/n0110/internal_flash.ld +++ b/ion/src/device/n0110/internal_flash.ld @@ -52,7 +52,7 @@ SECTIONS { /* The data section is written to Flash but linked as if it were in RAM. * * This is required because its initial value matters (so it has to be in - * persistant memory in the first place), but it is a R/W area of memory + * persistent memory in the first place), but it is a R/W area of memory * so it will have to live in RAM upon execution (in linker lingo, that * translates to the data section having a LMA in Flash and a VMA in RAM). * diff --git a/ion/src/device/shared/drivers/battery.cpp b/ion/src/device/shared/drivers/battery.cpp index c0f4877bd64..8686c2a97bc 100644 --- a/ion/src/device/shared/drivers/battery.cpp +++ b/ion/src/device/shared/drivers/battery.cpp @@ -4,10 +4,10 @@ /* To measure the battery voltage, we're using the internal ADC. The ADC works * by comparing the input voltage to a reference voltage. The only fixed voltage - * we have around is 2.8V, so that's the one we're using as a refrence. However, + * we have around is 2.8V, so that's the one we're using as a reference. However, * and ADC can only measure voltage that is lower than the reference voltage. So * we need to use a voltage divider before sampling Vbat. - * To avoid draining the battery, we're using an high-impedence voltage divider, + * To avoid draining the battery, we're using a high-impedance voltage divider, * so we need to be careful when sampling the ADC. See AN2834 for more info. */ diff --git a/ion/src/device/shared/drivers/display.cpp b/ion/src/device/shared/drivers/display.cpp index 508242ddeb3..6cc1f762c83 100644 --- a/ion/src/device/shared/drivers/display.cpp +++ b/ion/src/device/shared/drivers/display.cpp @@ -51,7 +51,7 @@ bool waitForVBlank() { uint64_t startTime = Timing::millis(); uint64_t timeout = startTime + timeoutDelta; - /* If current time is big enough, currentTime + timeout wraps aroud the + /* If current time is big enough, currentTime + timeout wraps around the * uint64_t. We need to take this into account when computing the terminating * event. * diff --git a/ion/src/device/shared/drivers/exam_mode.cpp b/ion/src/device/shared/drivers/exam_mode.cpp index 51fd7be06f2..0f319bc0df6 100644 --- a/ion/src/device/shared/drivers/exam_mode.cpp +++ b/ion/src/device/shared/drivers/exam_mode.cpp @@ -51,28 +51,28 @@ size_t numberOfBitsAfterLeadingZeroes(int i) { } uint8_t * SignificantExamModeAddress() { - uint32_t * persitence_start_32 = (uint32_t *)&_exam_mode_buffer_start; - uint32_t * persitence_end_32 = (uint32_t *)&_exam_mode_buffer_end; - assert((persitence_end_32 - persitence_start_32) % 4 == 0); - while (persitence_start_32 < persitence_end_32 && *persitence_start_32 == 0x0) { + uint32_t * persistence_start_32 = (uint32_t *)&_exam_mode_buffer_start; + uint32_t * persistence_end_32 = (uint32_t *)&_exam_mode_buffer_end; + assert((persistence_end_32 - persistence_start_32) % 4 == 0); + while (persistence_start_32 < persistence_end_32 && *persistence_start_32 == 0x0) { // Scan by groups of 32 bits to reach first non-zero bit - persitence_start_32++; + persistence_start_32++; } - uint8_t * persitence_start_8 = (uint8_t *)persitence_start_32; - uint8_t * persitence_end_8 = (uint8_t *)persitence_end_32; - while (persitence_start_8 < persitence_end_8 && *persitence_start_8 == 0x0) { + uint8_t * persistence_start_8 = (uint8_t *)persistence_start_32; + uint8_t * persistence_end_8 = (uint8_t *)persistence_end_32; + while (persistence_start_8 < persistence_end_8 && *persistence_start_8 == 0x0) { // Scan by groups of 8 bits to reach first non-zero bit - persitence_start_8++; + persistence_start_8++; } - if (persitence_start_8 == persitence_end_8 + if (persistence_start_8 == persistence_end_8 // we can't toggle from 0[3] to 2[3] when there is only one 1 bit in the whole sector - || (persitence_start_8 + 1 == persitence_end_8 && *persitence_start_8 == 1)) { + || (persistence_start_8 + 1 == persistence_end_8 && *persistence_start_8 == 1)) { assert(Ion::Device::Flash::SectorAtAddress((uint32_t)&_exam_mode_buffer_start) >= 0); Ion::Device::Flash::EraseSector(Ion::Device::Flash::SectorAtAddress((uint32_t)&_exam_mode_buffer_start)); return (uint8_t *)&_exam_mode_buffer_start; } - return persitence_start_8; + return persistence_start_8; } uint8_t FetchExamMode() { diff --git a/ion/src/device/shared/drivers/reset.cpp b/ion/src/device/shared/drivers/reset.cpp index ef605357004..67e52f3d6b4 100644 --- a/ion/src/device/shared/drivers/reset.cpp +++ b/ion/src/device/shared/drivers/reset.cpp @@ -49,7 +49,7 @@ void jump(uint32_t jumpIsrVectorAddress) { // Disable cache before reset Ion::Device::Cache::disable(); - /* Shutdown all clocks and periherals to mimic a hardware reset. */ + /* Shutdown all clocks and peripherals to mimic a hardware reset. */ Board::shutdownPeripherals(); internalFlashJump(jumpIsrVectorAddress); diff --git a/ion/src/device/shared/drivers/wakeup.h b/ion/src/device/shared/drivers/wakeup.h index d8cb22c7b81..7bf12d585f1 100644 --- a/ion/src/device/shared/drivers/wakeup.h +++ b/ion/src/device/shared/drivers/wakeup.h @@ -8,7 +8,7 @@ namespace Device { namespace WakeUp { /* All wakeup functions can be called together without overwriting the same - * register. All togethed, they will set SYSCFG and EXTi registers as follow: + * register. All together, they will set SYSCFG and EXTi registers as follow: * * GPIO Pin Number|EXTI_EMR|EXTI_FTSR|EXTI_RTSR|EXTICR1|EXTICR2|EXTICR3| Wake up * ---------------+--------+---------+---------+-------+-------+-------+------------------------- diff --git a/ion/src/device/shared/regs/tim.h b/ion/src/device/shared/regs/tim.h index f92d119e6da..704bd4fa901 100644 --- a/ion/src/device/shared/regs/tim.h +++ b/ion/src/device/shared/regs/tim.h @@ -17,7 +17,7 @@ class TIM { }; class CCMR : Register64 { - /* We're declaring CCMR as a 64 bits register. CCMR doesn't exsist per se, + /* We're declaring CCMR as a 64 bits register. CCMR doesn't exist per se, * it is in fact the consolidation of CCMR1 and CCMR2. Both are 16 bits * registers, so one could expect the consolidation to be 32 bits. However, * both CCMR1 and CCMR2 live on 32-bits boundaries, so the consolidation has diff --git a/ion/src/device/shared/usb/calculator.h b/ion/src/device/shared/usb/calculator.h index bf0a04db42c..5a3d898ef67 100644 --- a/ion/src/device/shared/usb/calculator.h +++ b/ion/src/device/shared/usb/calculator.h @@ -154,7 +154,7 @@ class Calculator : public Device { ExtendedCompatIDDescriptor m_extendedCompatIdDescriptor; Descriptor * m_descriptors[8]; - /* m_descriptors contains only descriptors that sould be returned via the + /* m_descriptors contains only descriptors that should be returned via the * method descriptor(uint8_t type, uint8_t index), so do not count descriptors * included in other descriptors or returned by other functions. */ diff --git a/ion/src/device/shared/usb/dfu.ld b/ion/src/device/shared/usb/dfu.ld index 957bcfdd536..70aff4accbb 100644 --- a/ion/src/device/shared/usb/dfu.ld +++ b/ion/src/device/shared/usb/dfu.ld @@ -1,5 +1,5 @@ /* DFU transfers can serve two purposes: - * - Transfering RAM data between the machine and the host, e.g. Python scripts + * - Transferring RAM data between the machine and the host, e.g. Python scripts * - Upgrading the flash memory to perform a software update * * The second case raises a huge issue: code cannot be executed from memory that diff --git a/ion/src/device/shared/usb/dfu_interface.h b/ion/src/device/shared/usb/dfu_interface.h index f05604e77b4..44ccdb5582d 100644 --- a/ion/src/device/shared/usb/dfu_interface.h +++ b/ion/src/device/shared/usb/dfu_interface.h @@ -74,7 +74,7 @@ class DFUInterface : public Interface { Unlock = 11 }; - // DFU Download Commmand Codes + // DFU Download Command Codes enum class DFUDownloadCommand { GetCommand = 0x00, SetAddressPointer = 0x21, diff --git a/ion/src/device/shared/usb/dfu_relocated.cpp b/ion/src/device/shared/usb/dfu_relocated.cpp index f68cd2e96b1..9b68244c488 100644 --- a/ion/src/device/shared/usb/dfu_relocated.cpp +++ b/ion/src/device/shared/usb/dfu_relocated.cpp @@ -57,7 +57,7 @@ void DFU(bool exitWithKeyboard, bool unlocked, int level) { /* 4- Disable all interrupts * The interrupt service routines live in the Flash and could be overwritten - * by garbage during a firmware upgrade opration, so we disable them. */ + * by garbage during a firmware upgrade operation, so we disable them. */ Device::Timing::shutdown(); /* 5- Jump to DFU bootloader code. We made sure in the linker script that the diff --git a/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h index f434352f24b..8b67cabc6ca 100644 --- a/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h @@ -1,5 +1,5 @@ -#ifndef ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H -#define ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABILITY_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABILITY_DESCRIPTOR_H #include "descriptor.h" diff --git a/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h index d2c425d4a40..3c117348c50 100644 --- a/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h @@ -1,5 +1,5 @@ -#ifndef ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H -#define ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABILITY_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABILITY_DESCRIPTOR_H #include "device_capability_descriptor.h" diff --git a/ion/src/device/shared/usb/stack/device.cpp b/ion/src/device/shared/usb/stack/device.cpp index 294b647e12c..6bfcbef7d6a 100644 --- a/ion/src/device/shared/usb/stack/device.cpp +++ b/ion/src/device/shared/usb/stack/device.cpp @@ -108,7 +108,7 @@ bool Device::processSetupInRequest(SetupPacket * request, uint8_t * transferBuff case (int) Request::GetStatus: return getStatus(transferBuffer, transferBufferLength, transferBufferMaxLength); case (int) Request::SetAddress: - // Make sure the request is adress is valid. + // Make sure the request is address is valid. assert(request->wValue() < 128); /* According to the reference manual, the address should be set after the * Status stage of the current transaction, but this is not true. diff --git a/ion/src/external/lz4/lz4.c b/ion/src/external/lz4/lz4.c index 4046102e6de..3021a96f284 100644 --- a/ion/src/external/lz4/lz4.c +++ b/ion/src/external/lz4/lz4.c @@ -837,7 +837,7 @@ LZ4_FORCE_INLINE int LZ4_compress_generic( _next_match: /* at this stage, the following variables must be correctly set : * - ip : at start of LZ operation - * - match : at start of previous pattern occurence; can be within current prefix, or within extDict + * - match : at start of previous pattern occurrence; can be within current prefix, or within extDict * - offset : if maybe_ext_memSegment==1 (constant) * - lowLimit : must be == dictionary to mean "match is within extDict"; must be == source otherwise * - token and *token : position to write 4-bits for match length; higher 4-bits for literal length supposed already written diff --git a/ion/src/external/lz4/lz4.h b/ion/src/external/lz4/lz4.h index 059ef7c1b7d..517667c3fb0 100644 --- a/ion/src/external/lz4/lz4.h +++ b/ion/src/external/lz4/lz4.h @@ -103,7 +103,7 @@ extern "C" { #define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version */ -LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; unseful to check dll version */ +LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; useful to check dll version */ /*-************************************ diff --git a/ion/src/shared/events_benchmark.cpp b/ion/src/shared/events_benchmark.cpp index adad15e5cfc..9080ae27bea 100644 --- a/ion/src/shared/events_benchmark.cpp +++ b/ion/src/shared/events_benchmark.cpp @@ -41,10 +41,10 @@ static constexpr Event scenariPythonMandelbrot[] = { Right, Right, OK, Down, Dow static constexpr Event scenariStatistics[] = { Down, OK, One, OK, Two, OK, Right, Five, OK, One, Zero, OK, Back, Right, OK, Right, Right, Right, OK, One, OK, Down, OK, Back, Right, OK, Back, Right, OK, Down, Down, Down, Down, Down, Down, Down, Down, Down, Down, Up, Up, Up, Up, Up, Up, Up, Up, Up, Home, Home }; -static constexpr Event scenaryProbability[] = { Down, Right, OK, Down, Down, Down, OK, Two, OK, Zero, Dot, Three, OK, OK, Left, Down, Down, OK, Right, Right, Right, Zero, Dot, Eight, OK, Home, Home +static constexpr Event scenariProbability[] = { Down, Right, OK, Down, Down, Down, OK, Two, OK, Zero, Dot, Three, OK, OK, Left, Down, Down, OK, Right, Right, Right, Zero, Dot, Eight, OK, Home, Home }; -static constexpr Event scenaryEquation[] = { Down, Right, Right, OK, OK, Down, Down, OK, Six, OK, Down, Down, OK, Left, Left, Left, Down, Down, Home, Home +static constexpr Event scenariEquation[] = { Down, Right, Right, OK, OK, Down, Down, OK, Six, OK, Down, Down, OK, Left, Left, Left, Down, Down, Home, Home }; static constexpr Scenario scenari[] = { @@ -52,8 +52,8 @@ static constexpr Scenario scenari[] = { Scenario::build("Sin/Cos graph", scenariFunctionCosSin), Scenario::build("Mandelbrot(15)", scenariPythonMandelbrot), Scenario::build("Statistics", scenariStatistics), - Scenario::build("Probability", scenaryProbability), - Scenario::build("Equation", scenaryEquation) + Scenario::build("Probability", scenariProbability), + Scenario::build("Equation", scenariEquation) }; constexpr static int numberOfScenari = sizeof(scenari)/sizeof(Scenario); diff --git a/ion/src/shared/events_keyboard.cpp b/ion/src/shared/events_keyboard.cpp index 72d6533ede5..73038b619cc 100644 --- a/ion/src/shared/events_keyboard.cpp +++ b/ion/src/shared/events_keyboard.cpp @@ -40,14 +40,14 @@ static bool canRepeatEvent(Event e) { Event getPlatformEvent(); -void ComputeAndSetRepetionFactor(int eventRepetitionCount) { +void ComputeAndSetRepetitionFactor(int eventRepetitionCount) { // The Repetition factor is increased by 4 every 20 loops in getEvent(2 sec) setLongRepetition((eventRepetitionCount / 20) * 4 + 1); } void resetLongRepetition() { sEventRepetitionCount = 0; - ComputeAndSetRepetionFactor(sEventRepetitionCount); + ComputeAndSetRepetitionFactor(sEventRepetitionCount); } static Keyboard::Key keyFromState(Keyboard::State state) { @@ -59,7 +59,7 @@ static inline Event innerGetEvent(int * timeout) { assert(*timeout > delayBetweenRepeat); int time = 0; uint64_t keysSeenUp = 0; - uint64_t keysSeenTransitionningFromUpToDown = 0; + uint64_t keysSeenTransitioningFromUpToDown = 0; while (true) { Event platformEvent = getPlatformEvent(); if (platformEvent != None) { @@ -68,11 +68,11 @@ static inline Event innerGetEvent(int * timeout) { Keyboard::State state = Keyboard::scan(); keysSeenUp |= ~state; - keysSeenTransitionningFromUpToDown = keysSeenUp & state; + keysSeenTransitioningFromUpToDown = keysSeenUp & state; bool lock = isLockActive(); - if (keysSeenTransitionningFromUpToDown != 0) { + if (keysSeenTransitioningFromUpToDown != 0) { sEventIsRepeating = false; resetLongRepetition(); didPressNewKey(); @@ -81,7 +81,7 @@ static inline Event innerGetEvent(int * timeout) { * processors have an instruction (ARM thumb uses CLZ). * Unfortunately there's no way to express this in standard C, so we have * to resort to using a builtin function. */ - Keyboard::Key key = (Keyboard::Key)(63-__builtin_clzll(keysSeenTransitionningFromUpToDown)); + Keyboard::Key key = (Keyboard::Key)(63-__builtin_clzll(keysSeenTransitioningFromUpToDown)); bool shift = isShiftActive() || state.keyDown(Keyboard::Key::Shift); bool alpha = isAlphaActive() || state.keyDown(Keyboard::Key::Alpha); bool lock = isLockActive(); @@ -114,7 +114,7 @@ static inline Event innerGetEvent(int * timeout) { } time += 10; - // At this point, we know that keysSeenTransitionningFromUpToDown has *always* been zero + // At this point, we know that keysSeenTransitioningFromUpToDown has *always* been zero // In other words, no new key has been pressed if (canRepeatEvent(sLastEvent) && state == sLastKeyboardState @@ -125,7 +125,7 @@ static inline Event innerGetEvent(int * timeout) { if (time >= delay) { sEventIsRepeating = true; sEventRepetitionCount++; - ComputeAndSetRepetionFactor(sEventRepetitionCount); + ComputeAndSetRepetitionFactor(sEventRepetitionCount); return sLastEvent; } } diff --git a/ion/src/simulator/3ds/Makefile b/ion/src/simulator/3ds/Makefile index 4c424f65c2e..a7de79eddc8 100644 --- a/ion/src/simulator/3ds/Makefile +++ b/ion/src/simulator/3ds/Makefile @@ -35,6 +35,6 @@ sdl_simu_needs_to_be_removed += $(addprefix ion/src/simulator/shared/, \ dummy/store_script.cpp \ ) -# Remove the dummy diplay (re-implemented) and the SDL simulator stuff. +# Remove the dummy display (re-implemented) and the SDL simulator stuff. ion_src := $(filter-out $(sdl_simu_needs_to_be_removed),$(ion_src)) diff --git a/ion/src/simulator/3ds/driver/led.cpp b/ion/src/simulator/3ds/driver/led.cpp index 819390f57ce..8c403b5134d 100644 --- a/ion/src/simulator/3ds/driver/led.cpp +++ b/ion/src/simulator/3ds/driver/led.cpp @@ -21,7 +21,7 @@ void setColor(KDColor c) { /* * According to https://www.3dbrew.org/wiki/MCURTC:SetInfoLEDPattern - * annimation pattern is as follow + * animation pattern is as follow * u8 ??? | u8 loop_delay | u8 smoothing | u8 delay */ RGBLedPattern pat; @@ -44,10 +44,10 @@ void setBlinking(uint16_t period, float dutyCycle) { /* * According to https://www.3dbrew.org/wiki/MCURTC:SetInfoLEDPattern - * annimation pattern is as follow + * animation pattern is as follow * u8 ??? | u8 loop_delay | u8 smoothing | u8 delay * - * Se, we seet ??? to 0, loop_delay to 0 (to have it loop) + * Se, we set ??? to 0, loop_delay to 0 (to have it loop) */ RGBLedPattern pat; memset(&pat, 0, sizeof(pat)); diff --git a/ion/src/simulator/android/Makefile b/ion/src/simulator/android/Makefile index b4afe8767b7..0af9abb9798 100644 --- a/ion/src/simulator/android/Makefile +++ b/ion/src/simulator/android/Makefile @@ -19,8 +19,8 @@ $(call object_for,ion/src/simulator/shared/main.cpp) : SFLAGS += -DEPSILON_SDL_F LDFLAGS += -ljnigraphics -llog -# If ARCH is not defined, we will re-trigger a build for each avaialble ARCH. -# This is used to build APKs, which needs to embbed a binary for each ARCH. +# If ARCH is not defined, we will re-trigger a build for each available ARCH. +# This is used to build APKs, which needs to embed a binary for each ARCH. ifndef ARCH diff --git a/ion/src/simulator/android/src/java/io/github/omega/simulator/OmegaActivity.java b/ion/src/simulator/android/src/java/io/github/omega/simulator/OmegaActivity.java index 25992236565..317847f3aa8 100644 --- a/ion/src/simulator/android/src/java/io/github/omega/simulator/OmegaActivity.java +++ b/ion/src/simulator/android/src/java/io/github/omega/simulator/OmegaActivity.java @@ -32,7 +32,7 @@ public Bitmap retrieveBitmapAsset(String identifier) { this.getResources().getAssets().open(identifier) ); } catch (Exception e) { - Log.w("LoadTexture", "Coundn't load a file:" + identifier); + Log.w("LoadTexture", "Couldn't load a file:" + identifier); } return bitmap; } diff --git a/ion/src/simulator/android/src/java/org/libsdl/app/SDLActivity.java b/ion/src/simulator/android/src/java/org/libsdl/app/SDLActivity.java index 082ae4be174..8d6ad984586 100644 --- a/ion/src/simulator/android/src/java/org/libsdl/app/SDLActivity.java +++ b/ion/src/simulator/android/src/java/org/libsdl/app/SDLActivity.java @@ -164,7 +164,7 @@ protected String[] getArguments() { } public static void initialize() { - // The static nature of the singleton and Android quirkyness force us to initialize everything here + // The static nature of the singleton and Android quirkiness force us to initialize everything here // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values mSingleton = null; mSurface = null; diff --git a/ion/src/simulator/external/sdl/src/core/winrt/SDL_winrtapp_direct3d.cpp b/ion/src/simulator/external/sdl/src/core/winrt/SDL_winrtapp_direct3d.cpp index a6f76db52b1..d01225f38f7 100644 --- a/ion/src/simulator/external/sdl/src/core/winrt/SDL_winrtapp_direct3d.cpp +++ b/ion/src/simulator/external/sdl/src/core/winrt/SDL_winrtapp_direct3d.cpp @@ -586,7 +586,7 @@ void SDL_WinRTApp::OnVisibilityChanged(CoreWindow^ sender, VisibilityChangedEven } // HACK: Prevent SDL's window-hide handling code, which currently - // triggers a fake window resize (possibly erronously), from + // triggers a fake window resize (possibly erroneously), from // marking the SDL window's surface as invalid. // // A better solution to this probably involves figuring out if the diff --git a/ion/src/simulator/external/sdl/src/hidapi/ios/hid.m b/ion/src/simulator/external/sdl/src/hidapi/ios/hid.m index a0ca7cd0222..9470d44d4c8 100644 --- a/ion/src/simulator/external/sdl/src/hidapi/ios/hid.m +++ b/ion/src/simulator/external/sdl/src/hidapi/ios/hid.m @@ -337,7 +337,7 @@ - (void)centralManagerDidUpdateState:(CBCentralManager *)central NSLog( @"CoreBluetooth BLE hardware is powered on and ready" ); // at startup, if we have no already attached peripherals, do a 20s scan for new unpaired devices, - // otherwise callers should occaisionally do additional scans. we don't want to continuously be + // otherwise callers should occasionally do additional scans. we don't want to continuously be // scanning because it drains battery, causes other nearby people to have a hard time pairing their // Steam Controllers, and may also trigger firmware weirdness when a device attempts to start // the pairing sequence multiple times concurrently @@ -684,7 +684,7 @@ - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CB - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { - NSLog( @"didUpdateNotifcationStateForCharacteristic %@ (%@)", characteristic, error ); + NSLog( @"didUpdateNotificationStateForCharacteristic %@ (%@)", characteristic, error ); } @end diff --git a/ion/src/simulator/external/sdl/src/hidapi/linux/hid.c b/ion/src/simulator/external/sdl/src/hidapi/linux/hid.c index 2cc2d8b9892..f4e05a2649b 100644 --- a/ion/src/simulator/external/sdl/src/hidapi/linux/hid.c +++ b/ion/src/simulator/external/sdl/src/hidapi/linux/hid.c @@ -143,7 +143,7 @@ static wchar_t *utf8_to_wchar_t(const char *utf8) return ret; } -/* Get an attribute value from a udev_device and return it as a whar_t +/* Get an attribute value from a udev_device and return it as a wchar_t string. The returned string must be freed with free() when done.*/ static wchar_t *copy_udev_string(struct udev_device *dev, const char *udev_name) { diff --git a/ion/src/simulator/external/sdl/src/hidapi/mac/hid.c b/ion/src/simulator/external/sdl/src/hidapi/mac/hid.c index 55daf57820e..98522a60f6c 100644 --- a/ion/src/simulator/external/sdl/src/hidapi/mac/hid.c +++ b/ion/src/simulator/external/sdl/src/hidapi/mac/hid.c @@ -644,7 +644,7 @@ static void hid_report_callback(void *context, IOReturn result, void *sender, } -/* This gets called when the read_thred's run loop gets signaled by +/* This gets called when the read_thread's run loop gets signaled by hid_close(), and serves to stop the read_thread's run loop. */ static void perform_signal_callback(void *context) { @@ -702,7 +702,7 @@ static void *read_thread(void *param) /* Now that the read thread is stopping, Wake any threads which are waiting on data (in hid_read_timeout()). Do this under a mutex to make sure that a thread which is about to go to sleep waiting on - the condition acutally will go to sleep before the condition is + the condition actually will go to sleep before the condition is signaled. */ pthread_mutex_lock(&dev->mutex); pthread_cond_broadcast(&dev->condition); @@ -874,7 +874,7 @@ static int cond_wait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_ return res; /* A res of 0 means we may have been signaled or it may - be a spurious wakeup. Check to see that there's acutally + be a spurious wakeup. Check to see that there's actually data in the queue before returning, and if not, go back to sleep. See the pthread_cond_timedwait() man page for details. */ @@ -894,7 +894,7 @@ static int cond_timedwait(const hid_device *dev, pthread_cond_t *cond, pthread_m return res; /* A res of 0 means we may have been signaled or it may - be a spurious wakeup. Check to see that there's acutally + be a spurious wakeup. Check to see that there's actually data in the queue before returning, and if not, go back to sleep. See the pthread_cond_timedwait() man page for details. */ diff --git a/ion/src/simulator/external/sdl/src/libm/e_log.c b/ion/src/simulator/external/sdl/src/libm/e_log.c index 208df815c38..e4f4c393e6e 100644 --- a/ion/src/simulator/external/sdl/src/libm/e_log.c +++ b/ion/src/simulator/external/sdl/src/libm/e_log.c @@ -15,7 +15,7 @@ #endif /* __ieee754_log(x) - * Return the logrithm of x + * Return the logarithm of x * * Method : * 1. Argument Reduction: find k and f such that diff --git a/ion/src/simulator/external/sdl/src/libm/e_pow.c b/ion/src/simulator/external/sdl/src/libm/e_pow.c index a3d24ced9c6..eed93d076b8 100644 --- a/ion/src/simulator/external/sdl/src/libm/e_pow.c +++ b/ion/src/simulator/external/sdl/src/libm/e_pow.c @@ -16,7 +16,7 @@ * 1. Compute and return log2(x) in two pieces: * log2(x) = w1 + w2, * where w1 has 53-24 = 29 bit trailing zeros. - * 2. Perform y*log2(x) = n+y' by simulating muti-precision + * 2. Perform y*log2(x) = n+y' by simulating multi-precision * arithmetic, where |y'|<=0.5. * 3. Return x**y = 2**n*exp(y'*log2) * diff --git a/ion/src/simulator/external/sdl/src/libm/e_sqrt.c b/ion/src/simulator/external/sdl/src/libm/e_sqrt.c index 39c83e1f334..652fc01c3e9 100644 --- a/ion/src/simulator/external/sdl/src/libm/e_sqrt.c +++ b/ion/src/simulator/external/sdl/src/libm/e_sqrt.c @@ -37,7 +37,7 @@ * If (2) is false, then q = q ; otherwise q = q + 2 . * i+1 i i+1 i * - * With some algebric manipulation, it is not difficult to see + * With some algebraic manipulation, it is not difficult to see * that (2) is equivalent to * -(i+1) * s + 2 <= y (3) @@ -116,7 +116,7 @@ double attribute_hidden __ieee754_sqrt(double x) ix0 |= (ix1>>(32-i)); ix1 <<= i; } - m -= 1023; /* unbias exponent */ + m -= 1023; /* unbiased exponent */ ix0 = (ix0&0x000fffff)|0x00100000; if(m&1){ /* odd m, double x to make it even */ ix0 += ix0 + ((ix1&sign)>>31); @@ -280,7 +280,7 @@ A. sqrt(x) by Newton Iteration This formula has one division fewer than the one above; however, it requires more multiplications and additions. Also x must be scaled in advance to avoid spurious overflow in evaluating the - expression 3y*y+x. Hence it is not recommended uless division + expression 3y*y+x. Hence it is not recommended unless division is slow. If division is very slow, then one should use the reciproot algorithm given in section B. @@ -330,7 +330,7 @@ B. sqrt(x) by Reciproot Iteration Let x0 and x1 be the leading and the trailing 32-bit words of a floating point number x (in IEEE double format) respectively - (see section A). By performing shifs and subtracts on x0 and y0, + (see section A). By performing shifts and subtracts on x0 and y0, we obtain a 7.8-bit approximation of 1/sqrt(x) as follows. k := 0x5fe80000 - (x0>>1); diff --git a/ion/src/simulator/external/sdl/src/libm/k_rem_pio2.c b/ion/src/simulator/external/sdl/src/libm/k_rem_pio2.c index 393db541d18..d4b8155b2de 100644 --- a/ion/src/simulator/external/sdl/src/libm/k_rem_pio2.c +++ b/ion/src/simulator/external/sdl/src/libm/k_rem_pio2.c @@ -47,7 +47,7 @@ * 64-bit precision 2 * 113-bit precision 3 * The actual value is the sum of them. Thus for 113-bit - * precison, one may have to do something like: + * precision, one may have to do something like: * * long double t,w,r_head, r_tail; * t = (long double)y[2] + (long double)y[1]; @@ -184,7 +184,7 @@ int32_t attribute_hidden __kernel_rem_pio2(double *x, double *y, int e0, int nx, jz = jk; recompute: - /* distill q[] into iq[] reversingly */ + /* distill q[] into iq[] reversely */ for(i=0,j=jz,z=q[jz];j>0;i++,j--) { fw = (double)((int32_t)(twon24* z)); iq[i] = (int32_t)(z-two24*fw); diff --git a/ion/src/simulator/external/sdl/src/video/khronos/vulkan/vk_icd.h b/ion/src/simulator/external/sdl/src/video/khronos/vulkan/vk_icd.h index b935fa1786e..e0835b7abf4 100644 --- a/ion/src/simulator/external/sdl/src/video/khronos/vulkan/vk_icd.h +++ b/ion/src/simulator/external/sdl/src/video/khronos/vulkan/vk_icd.h @@ -33,7 +33,7 @@ // Version 2 - Add Loader/ICD Interface version negotiation // via vk_icdNegotiateLoaderICDInterfaceVersion. // Version 3 - Add ICD creation/destruction of KHR_surface objects. -// Version 4 - Add unknown physical device extension qyering via +// Version 4 - Add unknown physical device extension querying via // vk_icdGetPhysicalDeviceProcAddr. // Version 5 - Tells ICDs that the loader is now paying attention to the // application version of Vulkan passed into the ApplicationInfo diff --git a/ion/src/simulator/external/sdl/src/video/khronos/vulkan/vk_layer.h b/ion/src/simulator/external/sdl/src/video/khronos/vulkan/vk_layer.h index 823c88ab759..631353d5827 100644 --- a/ion/src/simulator/external/sdl/src/video/khronos/vulkan/vk_layer.h +++ b/ion/src/simulator/external/sdl/src/video/khronos/vulkan/vk_layer.h @@ -52,7 +52,7 @@ typedef PFN_vkVoidFunction (VKAPI_PTR *PFN_GetPhysicalDeviceProcAddr)(VkInstance // Version negotiation values typedef enum VkNegotiateLayerStructType { - LAYER_NEGOTIATE_UNINTIALIZED = 0, + LAYER_NEGOTIATE_UNINITIALIZED = 0, LAYER_NEGOTIATE_INTERFACE_STRUCT = 1, } VkNegotiateLayerStructType; diff --git a/ion/src/simulator/external/sdl/src/video/qnx/gl.c b/ion/src/simulator/external/sdl/src/video/qnx/gl.c index 19e1bd4f710..2aebec675b3 100644 --- a/ion/src/simulator/external/sdl/src/video/qnx/gl.c +++ b/ion/src/simulator/external/sdl/src/video/qnx/gl.c @@ -25,7 +25,7 @@ static EGLDisplay egl_disp; /** - * Detertmines the pixel format to use based on the current display and EGL + * Determines the pixel format to use based on the current display and EGL * configuration. * @param egl_conf EGL configuration to use * @return A SCREEN_FORMAT* constant for the pixel format to use diff --git a/ion/src/simulator/external/sdl/src/video/qnx/keyboard.c b/ion/src/simulator/external/sdl/src/video/qnx/keyboard.c index 86c6395bafe..c7288a215e9 100644 --- a/ion/src/simulator/external/sdl/src/video/qnx/keyboard.c +++ b/ion/src/simulator/external/sdl/src/video/qnx/keyboard.c @@ -27,7 +27,7 @@ #include /** - * A map thta translates Screen key names to SDL scan codes. + * A map that translates Screen key names to SDL scan codes. * This map is incomplete, but should include most major keys. */ static int key_to_sdl[] = { diff --git a/ion/src/simulator/external/sdl/src/video/qnx/video.c b/ion/src/simulator/external/sdl/src/video/qnx/video.c index ff8223c77a1..84ed13cf5a7 100644 --- a/ion/src/simulator/external/sdl/src/video/qnx/video.c +++ b/ion/src/simulator/external/sdl/src/video/qnx/video.c @@ -182,7 +182,7 @@ createWindowFramebuffer(_THIS, SDL_Window * window, Uint32 * format, * Informs the window manager that the window needs to be updated. * @param _THIS * @param window The window to update - * @param rects An array of reectangular areas to update + * @param rects An array of rectangular areas to update * @param numrects Rect array length * @return 0 if successful, -1 on error */ diff --git a/ion/src/simulator/external/sdl/src/video/winrt/SDL_winrtvideo.cpp b/ion/src/simulator/external/sdl/src/video/winrt/SDL_winrtvideo.cpp index 87c61c928a3..fa10a49c01c 100644 --- a/ion/src/simulator/external/sdl/src/video/winrt/SDL_winrtvideo.cpp +++ b/ion/src/simulator/external/sdl/src/video/winrt/SDL_winrtvideo.cpp @@ -597,7 +597,7 @@ WINRT_CreateWindow(_THIS, SDL_Window * window) /* OpenGL ES 2 wasn't requested. Don't set up an EGL surface. */ data->egl_surface = EGL_NO_SURFACE; } else { - /* OpenGL ES 2 was reuqested. Set up an EGL surface. */ + /* OpenGL ES 2 was requested. Set up an EGL surface. */ SDL_VideoData * video_data = (SDL_VideoData *)_this->driverdata; /* Call SDL_EGL_ChooseConfig and eglCreateWindowSurface directly, diff --git a/ion/src/simulator/linux/Makefile b/ion/src/simulator/linux/Makefile index 0a58ad996f8..5042f976c6d 100644 --- a/ion/src/simulator/linux/Makefile +++ b/ion/src/simulator/linux/Makefile @@ -47,5 +47,5 @@ $(eval $(call rule_for, \ $(call object_for,ion/src/simulator/linux/platform_images.cpp): $(BUILD_DIR)/ion/src/simulator/linux/platform_images.h -# The header is refered to as so make sure it's findable this way +# The header is referred to as so make sure it's findable this way $(call object_for,ion/src/simulator/linux/platform_images.cpp): SFLAGS += -I$(BUILD_DIR) diff --git a/ion/src/simulator/shared/apple/helpers.mak b/ion/src/simulator/shared/apple/helpers.mak index bf765b0d530..6298239ee32 100644 --- a/ion/src/simulator/shared/apple/helpers.mak +++ b/ion/src/simulator/shared/apple/helpers.mak @@ -1,4 +1,4 @@ -# This file contains all the recipies shared between iOS and macOS. +# This file contains all the recipes shared between iOS and macOS. # The only things that have to be customized per platform are the icons and the # Info.plist. diff --git a/ion/src/simulator/shared/apple/targets.mak b/ion/src/simulator/shared/apple/targets.mak index 027602f2ef7..8c790886b48 100644 --- a/ion/src/simulator/shared/apple/targets.mak +++ b/ion/src/simulator/shared/apple/targets.mak @@ -1,7 +1,7 @@ .PRECIOUS: $(simulator_app_deps) # CAUTION: The empty line in this rule is important! -# Indeed, rule without receipe serve a special purpose +# Indeed, rule without recipe serve a special purpose # https://www.gnu.org/software/make/manual/html_node/Canceling-Rules.html .PRECIOUS: $(BUILD_DIR)/%.app $(BUILD_DIR)/%.app: $(simulator_app_deps) diff --git a/ion/src/simulator/shared/events_platform.cpp b/ion/src/simulator/shared/events_platform.cpp index 8eb5b894a01..ad60ce0ce71 100644 --- a/ion/src/simulator/shared/events_platform.cpp +++ b/ion/src/simulator/shared/events_platform.cpp @@ -163,7 +163,7 @@ Event getPlatformEvent() { SDL_GetMouseState(&p.x, &p.y); Simulator::Layout::highlightKeyAt(&p); } - /* On smarphones, don't forget to unhighlight the key when the finger is up. + /* On smartphones, don't forget to unhighlight the key when the finger is up. * (finger up doesn't imply a mouse motion!) */ if (event.type == SDL_FINGERUP) { Simulator::Layout::unhighlightKey(); diff --git a/ion/src/simulator/web/clipboard_helper.cpp b/ion/src/simulator/web/clipboard_helper.cpp index c7423cb31f5..1ad83287e73 100644 --- a/ion/src/simulator/web/clipboard_helper.cpp +++ b/ion/src/simulator/web/clipboard_helper.cpp @@ -49,8 +49,8 @@ EM_JS(void, get_clipboard_text, (char * buffer, uint32_t bufferSize, AsyncStatus try { navigator.clipboard.readText().then( function(text) { - var lenghtBytes = Math.min(lengthBytesUTF8(text) + 1, bufferSize); - stringToUTF8(text, buffer, lenghtBytes); + var lengthBytes = Math.min(lengthBytesUTF8(text) + 1, bufferSize); + stringToUTF8(text, buffer, lengthBytes); HEAP32[status>>2] = success; }, function(text) { HEAP32[status>>2] = failure; } diff --git a/ion/src/simulator/windows/platform_files.cpp b/ion/src/simulator/windows/platform_files.cpp index 865f47179cd..528e8a354b5 100644 --- a/ion/src/simulator/windows/platform_files.cpp +++ b/ion/src/simulator/windows/platform_files.cpp @@ -15,7 +15,7 @@ static OPENFILENAME * getOFN(const char * extension) { static char filter[32]; if (snprintf(filter, sizeof(filter), "%s%c*.%s%c%c", extension, 0, extension, 0, 0) < 0) { - /* Note: We cannot use litteral \0 in the format string, otherwise snprintf + /* Note: We cannot use literal \0 in the format string, otherwise snprintf * will think the format string is finished... */ return nullptr; } diff --git a/ion/test/device/n0110/external_flash_tests.ld b/ion/test/device/n0110/external_flash_tests.ld index 61a8b97d36e..bf6b07c1d8f 100644 --- a/ion/test/device/n0110/external_flash_tests.ld +++ b/ion/test/device/n0110/external_flash_tests.ld @@ -61,7 +61,7 @@ SECTIONS { /* The data section is written to Flash but linked as if it were in RAM. * * This is required because its initial value matters (so it has to be in - * persistant memory in the first place), but it is a R/W area of memory + * persistent memory in the first place), but it is a R/W area of memory * so it will have to live in RAM upon execution (in linker lingo, that * translates to the data section having a LMA in Flash and a VMA in RAM). * diff --git a/ion/test/utf8_helper.cpp b/ion/test/utf8_helper.cpp index 4e182142be3..8a53da37306 100644 --- a/ion/test/utf8_helper.cpp +++ b/ion/test/utf8_helper.cpp @@ -3,15 +3,15 @@ #include #include -void assert_occurences_count(const char * string, CodePoint c, int result) { +void assert_occurrences_count(const char * string, CodePoint c, int result) { quiz_assert(UTF8Helper::CountOccurrences(string, c) == result); } QUIZ_CASE(ion_utf8_helper_count_occurrences) { - assert_occurences_count("1234", '1', 1); - assert_occurences_count("2232", '2', 3); - assert_occurences_count("2π2∑32∑", UCodePointGreekSmallLetterPi, 1); - assert_occurences_count("2π2∑32∑", UCodePointNArySummation, 2); + assert_occurrences_count("1234", '1', 1); + assert_occurrences_count("2232", '2', 3); + assert_occurrences_count("2π2∑32∑", UCodePointGreekSmallLetterPi, 1); + assert_occurrences_count("2π2∑32∑", UCodePointNArySummation, 2); } void assert_code_point_searched_is(const char * string, CodePoint c, const char * result) { diff --git a/kandinsky/fonts/code_points.h b/kandinsky/fonts/code_points.h index 0c1509b8b70..ec011e91ac7 100644 --- a/kandinsky/fonts/code_points.h +++ b/kandinsky/fonts/code_points.h @@ -135,7 +135,7 @@ uint32_t SimpleCodePoints[] = { 0x394, // Δ // GREEK CAPITAL LETTER DELTA 0x3a9, // Ω // GREEK CAPITAL LETTER OMEGA 0x3b8, // θ // GREEK SMALL LETTER THETA - 0x3bb, // λ // GREEK SMALL LETTER LAMDA + 0x3bb, // λ // GREEK SMALL LETTER LAMBDA 0x3bc, // μ // GREEK SMALL LETTER MU 0x3c0, // π // GREEK SMALL LETTER PI 0x3c3, // σ // GREEK SMALL LETTER SIGMA diff --git a/kandinsky/fonts/rasterizer.c b/kandinsky/fonts/rasterizer.c index 7e4e8435940..fcc9a2ed138 100644 --- a/kandinsky/fonts/rasterizer.c +++ b/kandinsky/fonts/rasterizer.c @@ -185,7 +185,7 @@ int main(int argc, char * argv[]) { fprintf(sourceFile, "/* This file is auto-generated by the rasterizer */\n\n"); fprintf(sourceFile, "#include \n\n"); - // Step 1 - Build the GlyphIndex <-> UnicodeCodePoint correspondance table + // Step 1 - Build the GlyphIndex <-> UnicodeCodePoint correspondence table int previousIndex = -1; uint32_t previousCodePoint = 0; diff --git a/kandinsky/src/context_rect.cpp b/kandinsky/src/context_rect.cpp index 69f5f333800..4e46c2e4eaf 100644 --- a/kandinsky/src/context_rect.cpp +++ b/kandinsky/src/context_rect.cpp @@ -77,10 +77,10 @@ void KDContext::blendRectWithMask(KDRect rect, KDColor color, const uint8_t * ma startingJ = startingJ > 0 ? startingJ : 0; for (KDCoordinate j=0; j>(32-i)); ix1 <<= i; } - m -= 1023; /* unbias exponent */ + m -= 1023; /* unbiased exponent */ ix0 = (ix0&0x000fffff)|0x00100000; if(m&1){ /* odd m, double x to make it even */ ix0 += ix0 + ((ix1&sign)>>31); @@ -266,7 +266,7 @@ A. sqrt(x) by Newton Iteration This formula has one division fewer than the one above; however, it requires more multiplications and additions. Also x must be scaled in advance to avoid spurious overflow in evaluating the - expression 3y*y+x. Hence it is not recommended uless division + expression 3y*y+x. Hence it is not recommended unless division is slow. If division is very slow, then one should use the reciproot algorithm given in section B. @@ -316,7 +316,7 @@ B. sqrt(x) by Reciproot Iteration Let x0 and x1 be the leading and the trailing 32-bit words of a floating point number x (in IEEE double format) respectively - (see section A). By performing shifs and subtracts on x0 and y0, + (see section A). By performing shifts and subtracts on x0 and y0, we obtain a 7.8-bit approximation of 1/sqrt(x) as follows. k := 0x5fe80000 - (x0>>1); diff --git a/liba/src/external/openbsd/e_sqrtf.c b/liba/src/external/openbsd/e_sqrtf.c index 80d35278cad..64eb82e0e90 100644 --- a/liba/src/external/openbsd/e_sqrtf.c +++ b/liba/src/external/openbsd/e_sqrtf.c @@ -45,7 +45,7 @@ sqrtf(float x) for(i=0;(ix&0x00800000)==0;i++) ix<<=1; m -= i-1; } - m -= 127; /* unbias exponent */ + m -= 127; /* unbiased exponent */ ix = (ix&0x007fffff)|0x00800000; if(m&1) /* odd m, double x to make it even */ ix += ix; diff --git a/liba/src/external/openbsd/k_rem_pio2.c b/liba/src/external/openbsd/k_rem_pio2.c index 80279a74377..6fec6717026 100644 --- a/liba/src/external/openbsd/k_rem_pio2.c +++ b/liba/src/external/openbsd/k_rem_pio2.c @@ -48,7 +48,7 @@ * 64-bit precision 2 * 113-bit precision 3 * The actual value is the sum of them. Thus for 113-bit - * precison, one may have to do something like: + * precision, one may have to do something like: * * long double t,w,r_head, r_tail; * t = (long double)y[2] + (long double)y[1]; @@ -307,7 +307,7 @@ __kernel_rem_pio2(double *x, double *y, int e0, int nx, int prec) jz = jk; recompute: - /* distill q[] into iq[] reversingly */ + /* distill q[] into iq[] reversely */ for(i=0,j=jz,z=q[jz];j>0;i++,j--) { fw = (double)((int32_t)(twon24* z)); iq[i] = (int32_t)(z-two24*fw); diff --git a/liba/src/external/openbsd/k_rem_pio2f.c b/liba/src/external/openbsd/k_rem_pio2f.c index 5931cbab9be..50208c64537 100644 --- a/liba/src/external/openbsd/k_rem_pio2f.c +++ b/liba/src/external/openbsd/k_rem_pio2f.c @@ -68,7 +68,7 @@ __kernel_rem_pio2f(float *x, float *y, int e0, int nx, int prec, jz = jk; recompute: - /* distill q[] into iq[] reversingly */ + /* distill q[] into iq[] reversely */ for(i=0,j=jz,z=q[jz];j>0;i++,j--) { fw = (float)((int32_t)(twon8* z)); iq[i] = (int32_t)(z-two8*fw); diff --git a/liba/src/external/sqlite/sqliteInt.h b/liba/src/external/sqlite/sqliteInt.h index 785939ca7e3..96f9ff0b623 100644 --- a/liba/src/external/sqlite/sqliteInt.h +++ b/liba/src/external/sqlite/sqliteInt.h @@ -29,7 +29,7 @@ typedef int64_t sqlite3_int64; #define UNUSED_PARAMETER(x) ((void)0) #define SQLITE_OK 0 -/* Completly ignore asserts: one of them contains a modulo, which our platform +/* Completely ignore asserts: one of them contains a modulo, which our platform * doesn't support in hardware. This therefore translates to a __aeabi_idivmod * call, which we do not provide. */ #define assert(x) ((void)0) diff --git a/libaxx/README.txt b/libaxx/README.txt index ee62c7629e3..2835e304e23 100644 --- a/libaxx/README.txt +++ b/libaxx/README.txt @@ -1,3 +1,3 @@ libaxx is an adhoc libc++, just like liba is an adhoc libc. -See liba for more informations. +See liba for more information. diff --git a/poincare/include/poincare/based_integer.h b/poincare/include/poincare/based_integer.h index 4dd2627d978..58fe4aa2dc1 100644 --- a/poincare/include/poincare/based_integer.h +++ b/poincare/include/poincare/based_integer.h @@ -50,7 +50,7 @@ class BasedIntegerNode final : public NumberNode { class BasedInteger final : public Number { friend class BasedIntegerNode; public: - /* The constructor build a irreductible fraction */ + /* The constructor build a irreducible fraction */ BasedInteger(const BasedIntegerNode * node) : Number(node) {} static BasedInteger Builder(const char * digits, size_t size, Integer::Base base); static BasedInteger Builder(const Integer & m, Integer::Base base = Integer::Base::Decimal); diff --git a/poincare/include/poincare/equal.h b/poincare/include/poincare/equal.h index 1f61b2c579b..14be15df131 100644 --- a/poincare/include/poincare/equal.h +++ b/poincare/include/poincare/equal.h @@ -27,7 +27,7 @@ class EqualNode final : public ExpressionNode { // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; - // Evalutation + // Evaluation Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } template Evaluation templatedApproximate(ApproximationContext approximationContext) const; diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index d75a684cfbe..452c078d17a 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -338,7 +338,7 @@ class Expression : public TreeHandle { * Float to Symbol for instance). * * We could have overriden the operator T(). However, even with the - * 'explicit' keyword (which prevents implicit casts), direct initilization + * 'explicit' keyword (which prevents implicit casts), direct initialization * are enable which can lead to weird code: * ie, you can write: 'Rational a(2); AbsoluteValue b(a);' * */ @@ -407,7 +407,7 @@ class Expression : public TreeHandle { static constexpr int k_maxSymbolReplacementsCount = 10; static bool sSymbolReplacementsCountLock; - /* Add missing parenthesis will add parentheses that easen the reading of the + /* Add missing parenthesis will add parentheses that ease the reading of the * expression or that are required by math rules. For example: * 2+-1 --> 2+(-1) * *(+(2,1),3) --> (2+1)*3 diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 5a5dbccb241..ce0a702cf1b 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -298,7 +298,7 @@ class ExpressionNode : public TreeNode { virtual Expression unaryFunctionDifferential(ReductionContext reductionContext); /* Return a clone of the denominator part of the expression */ /*!*/ virtual Expression denominator(ExpressionNode::ReductionContext reductionContext) const; - /* LayoutShape is used to check if the multiplication sign can be omitted between two expressions. It depends on the "layout syle" of the on the right of the left expression */ + /* LayoutShape is used to check if the multiplication sign can be omitted between two expressions. It depends on the "layout style" of the on the right of the left expression */ enum class LayoutShape { Decimal, Integer, diff --git a/poincare/include/poincare/float.h b/poincare/include/poincare/float.h index 1c08efe3bba..ae0a7dd7d18 100644 --- a/poincare/include/poincare/float.h +++ b/poincare/include/poincare/float.h @@ -6,7 +6,7 @@ namespace Poincare { -/* Float reprensents an approximated number. This class is use to avoid turning +/* Float represents an approximated number. This class is use to avoid turning * float/double into Decimal back and forth because performances are * dramatically affected when doing so. For instance, when plotting a graph, we * need to set a float/double value for a symbol and approximate an expression diff --git a/poincare/include/poincare/infinity.h b/poincare/include/poincare/infinity.h index bc17f010155..1a3023c042c 100644 --- a/poincare/include/poincare/infinity.h +++ b/poincare/include/poincare/infinity.h @@ -39,7 +39,7 @@ class InfinityNode final : public NumberNode { int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode = Preferences::PrintFloatMode::Decimal, int numberOfSignificantDigits = 0) const override; /* Derivation - * Unlike Numbers that derivate to 0, Infinity derivates to Undefined. */ + * Unlike Numbers that derivate to 0, Infinity derivatives to Undefined. */ bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; private: diff --git a/poincare/include/poincare/n_ary_expression.h b/poincare/include/poincare/n_ary_expression.h index 64dedd156df..c27c8f0e862 100644 --- a/poincare/include/poincare/n_ary_expression.h +++ b/poincare/include/poincare/n_ary_expression.h @@ -48,7 +48,7 @@ class NAryExpression : public Expression { /* allChildrenAreReal returns: * - 1 if all children are real * - 0 if all non real children are ComplexCartesian - * - -1 if some chidren are non-real and non ComplexCartesian */ + * - -1 if some children are non-real and non ComplexCartesian */ int allChildrenAreReal(Context * context) const; protected: void sortChildrenInPlace(NAryExpressionNode::ExpressionOrder order, Context * context, bool canSwapMatrices, bool canBeInterrupted) { diff --git a/poincare/include/poincare/print_float.h b/poincare/include/poincare/print_float.h index a3d2474bec4..c12da059cd6 100644 --- a/poincare/include/poincare/print_float.h +++ b/poincare/include/poincare/print_float.h @@ -21,13 +21,13 @@ class PrintFloat { constexpr static int k_maxFloatGlyphLength = 2 // '-' and '.' + k_numberOfStoredSignificantDigits // mantissa + 1 // ᴇ - + 1 // exponant '-' - + 3; // exponant + + 1 // exponent '-' + + 3; // exponent constexpr static int k_maxFloatCharSize = 2 // '-' and '.' + k_numberOfStoredSignificantDigits // mantissa + k_specialECodePointByteLength // ᴇ - + 1 // exponant '-' - + 3 // exponant + + 1 // exponent '-' + + 3 // exponent + 1; // null-terminated constexpr static int glyphLengthForFloatWithPrecision(int numberOfSignificantDigits) { @@ -36,15 +36,15 @@ class PrintFloat { + numberOfSignificantDigits // mantissa + 1 // glyph ᴇ + 1 // '-' - + 3; // exponant + + 3; // exponent } constexpr static int charSizeForFloatsWithPrecision(int numberOfSignificantDigits) { // The worst case is -1.234ᴇ-328 return 2 // '-' and '.' + numberOfSignificantDigits // mantissa + k_specialECodePointByteLength // ᴇ - + 1 // exponant '-' - + 3 // exponant + + 1 // exponent '-' + + 3 // exponent + 1; // null-terminated } diff --git a/poincare/include/poincare/rational.h b/poincare/include/poincare/rational.h index 2b7bf627149..0e1b3837470 100644 --- a/poincare/include/poincare/rational.h +++ b/poincare/include/poincare/rational.h @@ -73,7 +73,7 @@ class Rational final : public Number { friend class PowerNode; friend class Power; public: - /* The constructor build a irreductible fraction */ + /* The constructor build a irreducible fraction */ Rational(const RationalNode * node) : Number(node) {} static Rational Builder(Integer & num, Integer & den); static Rational Builder(const Integer & numerator); diff --git a/poincare/include/poincare/store.h b/poincare/include/poincare/store.h index cc89d40ec71..e32f50cc94e 100644 --- a/poincare/include/poincare/store.h +++ b/poincare/include/poincare/store.h @@ -22,7 +22,7 @@ class StoreNode /*final*/ : public RightwardsArrowExpressionNode { private: // Simplification Expression shallowReduce(ReductionContext reductionContext) override; - // Evalutation + // Evaluation Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } template Evaluation templatedApproximate(ApproximationContext approximationContext) const; diff --git a/poincare/include/poincare/undefined.h b/poincare/include/poincare/undefined.h index 7dfb094f35c..e10b6b16f96 100644 --- a/poincare/include/poincare/undefined.h +++ b/poincare/include/poincare/undefined.h @@ -30,7 +30,7 @@ class UndefinedNode : public NumberNode { } /* Derivation - * Unlike Numbers that derivate to 0, Undefined derivates to Undefined. */ + * Unlike Numbers that derivate to 0, Undefined derivatives to Undefined. */ bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override { return true; } // Layout diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 4d4eee249d9..f99d2d59d9b 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -60,15 +60,15 @@ class UnitNode final : public ExpressionNode { static Vector FromBaseUnits(const Expression baseUnits); const T coefficientAtIndex(size_t i) const { assert(i < k_numberOfBaseUnits); - const T coefficients[k_numberOfBaseUnits] = {time, distance, mass, current, temperature, amountOfSubstance, luminuousIntensity}; + const T coefficients[k_numberOfBaseUnits] = {time, distance, mass, current, temperature, amountOfSubstance, luminousIntensity}; return coefficients[i]; } void setCoefficientAtIndex(size_t i, T c) { assert(i < k_numberOfBaseUnits); - T * coefficientsAddresses[k_numberOfBaseUnits] = {&time, &distance, &mass, ¤t, &temperature, &amountOfSubstance, &luminuousIntensity}; + T * coefficientsAddresses[k_numberOfBaseUnits] = {&time, &distance, &mass, ¤t, &temperature, &amountOfSubstance, &luminousIntensity}; *(coefficientsAddresses[i]) = c; } - bool operator==(const Vector &rhs) const { return time == rhs.time && distance == rhs.distance && mass == rhs.mass && current == rhs.current && temperature == rhs.temperature && amountOfSubstance == rhs.amountOfSubstance && luminuousIntensity == rhs.luminuousIntensity; } + bool operator==(const Vector &rhs) const { return time == rhs.time && distance == rhs.distance && mass == rhs.mass && current == rhs.current && temperature == rhs.temperature && amountOfSubstance == rhs.amountOfSubstance && luminousIntensity == rhs.luminousIntensity; } bool operator!=(const Vector &rhs) const { return !(*this == rhs); } void addAllCoefficients(const Vector other, int factor); Expression toBaseUnits() const; @@ -78,7 +78,7 @@ class UnitNode final : public ExpressionNode { T current; T temperature; T amountOfSubstance; - T luminuousIntensity; + T luminousIntensity; }; class Representative { @@ -104,7 +104,7 @@ class UnitNode final : public ExpressionNode { m_outputPrefixable(outputPrefixable) {} - virtual const Vector dimensionVector() const { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; }; + virtual const Vector dimensionVector() const { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; }; virtual int numberOfRepresentatives() const { return 0; }; /* representativesOfSameDimension returns a pointer to the array containing * all representatives for this's dimension. */ @@ -113,7 +113,7 @@ class UnitNode final : public ExpressionNode { virtual bool isBaseUnit() const { return false; } virtual const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const { return DefaultFindBestRepresentative(value, exponent, representativesOfSameDimension(), numberOfRepresentatives(), prefix); } /* hasSpecialAdditionalExpressions return true if the unit has special - * forms suchas as splits (for time and imperial units) or common + * forms such as as splits (for time and imperial units) or common * conversions (such as speed and energy). */ virtual bool hasSpecialAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const { return false; } virtual int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { return 0; } @@ -145,7 +145,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static TimeRepresentative Default() { return TimeRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = 1, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = 1, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 7; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } @@ -159,7 +159,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static DistanceRepresentative Default() { return DistanceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 1, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 1, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 8; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } @@ -174,7 +174,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static MassRepresentative Default() { return MassRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 7; } const Representative * representativesOfSameDimension() const override; const Prefix * basePrefix() const override; @@ -190,7 +190,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static CurrentRepresentative Default() { return CurrentRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 1, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 1, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } @@ -203,7 +203,7 @@ class UnitNode final : public ExpressionNode { public: static double ConvertTemperatures(double value, const Representative * source, const Representative * target); constexpr static TemperatureRepresentative Default() { return TemperatureRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 1, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 1, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 3; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } @@ -220,7 +220,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static AmountOfSubstanceRepresentative Default() { return AmountOfSubstanceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 1, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 1, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } @@ -232,7 +232,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static LuminousIntensityRepresentative Default() { return LuminousIntensityRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 1}; } + const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 1}; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } @@ -244,7 +244,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static FrequencyRepresentative Default() { return FrequencyRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = -1, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = -1, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -255,7 +255,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static ForceRepresentative Default() { return ForceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 1, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 1, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -266,7 +266,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static PressureRepresentative Default() { return PressureRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = -2, .distance = -1, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = -2, .distance = -1, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 3; } const Representative * representativesOfSameDimension() const override; private: @@ -277,7 +277,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static EnergyRepresentative Default() { return EnergyRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 2, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 2, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 2; } const Representative * representativesOfSameDimension() const override; bool hasSpecialAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } @@ -290,7 +290,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static PowerRepresentative Default() { return PowerRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = -3, .distance = 2, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = -3, .distance = 2, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -302,7 +302,7 @@ class UnitNode final : public ExpressionNode { public: using Representative::Representative; constexpr static ElectricChargeRepresentative Default() { return ElectricChargeRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = 1, .distance = 0, .mass = 0, .current = 1, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = 1, .distance = 0, .mass = 0, .current = 1, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; }; @@ -311,7 +311,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static ElectricPotentialRepresentative Default() { return ElectricPotentialRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = -3, .distance = 2, .mass = 1, .current = -1, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = -3, .distance = 2, .mass = 1, .current = -1, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -322,7 +322,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static ElectricCapacitanceRepresentative Default() { return ElectricCapacitanceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = 4, .distance = -2, .mass = -1, .current = 2, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = 4, .distance = -2, .mass = -1, .current = 2, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -333,7 +333,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static ElectricResistanceRepresentative Default() { return ElectricResistanceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = -3, .distance = 2, .mass = 1, .current = -2, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = -3, .distance = 2, .mass = 1, .current = -2, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -344,7 +344,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static ElectricConductanceRepresentative Default() { return ElectricConductanceRepresentative(nullptr, 1., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = 3, .distance = -2, .mass = -1, .current = 2, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = 3, .distance = -2, .mass = -1, .current = 2, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -355,7 +355,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static MagneticFluxRepresentative Default() { return MagneticFluxRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 2, .mass = 1, .current = -1, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 2, .mass = 1, .current = -1, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -366,7 +366,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static MagneticFieldRepresentative Default() { return MagneticFieldRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 0, .mass = 1, .current = -1, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 0, .mass = 1, .current = -1, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -377,7 +377,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static InductanceRepresentative Default() { return InductanceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 2, .mass = 1, .current = -2, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 2, .mass = 1, .current = -2, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -388,7 +388,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static CatalyticActivityRepresentative Default() { return CatalyticActivityRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = -1, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 1, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = -1, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 1, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -399,7 +399,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static SurfaceRepresentative Default() { return SurfaceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 2, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 2, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 2; } const Representative * representativesOfSameDimension() const override; const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const override; @@ -413,7 +413,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static VolumeRepresentative Default() { return VolumeRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 3, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 3, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } int numberOfRepresentatives() const override { return 8; } const Representative * representativesOfSameDimension() const override; const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const override; @@ -427,7 +427,7 @@ class UnitNode final : public ExpressionNode { friend class Unit; public: constexpr static SpeedRepresentative Default() { return SpeedRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } - const VectordimensionVector() const override { return Vector{.time = -1, .distance = 1, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const VectordimensionVector() const override { return Vector{.time = -1, .distance = 1, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const override { return nullptr; } bool hasSpecialAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const override; @@ -494,7 +494,7 @@ constexpr bool strings_equal(const char * s1, const char * s2) { return *s1 == * class Unit : public Expression { friend class UnitNode; public: - /* Prefixes and Representativees defined below must be defined only once and + /* Prefixes and Representatives defined below must be defined only once and * all units must be constructed from their pointers. This way we can easily * check if two Unit objects are equal by comparing pointers. This saves us * from overloading the == operator on Prefix and Representative and saves @@ -613,7 +613,7 @@ class Unit : public Expression { VolumeRepresentative("gal", 128*0.0000295735295625, Prefixable::None, Prefixable::None), }; /* FIXME : Some ratio are too precise too be well approximated by double. - * Maybe switch to a rationnal representation with two int. */ + * Maybe switch to a rational representation with two int. */ /* Define access points to some prefixes and representatives. */ static constexpr int k_emptyPrefixIndex = 6; diff --git a/poincare/include/poincare/unit_convert.h b/poincare/include/poincare/unit_convert.h index c97eb3cdf5d..b3e55776d05 100644 --- a/poincare/include/poincare/unit_convert.h +++ b/poincare/include/poincare/unit_convert.h @@ -25,7 +25,7 @@ class UnitConvertNode /*final*/ : public RightwardsArrowExpressionNode { void deepReduceChildren(ExpressionNode::ReductionContext reductionContext) override; void deepBeautifyChildren(ExpressionNode::ReductionContext reductionContext) override; Expression shallowBeautify(ReductionContext * reductionContext) override; - // Evalutation + // Evaluation Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } template Evaluation templatedApproximate(ApproximationContext approximationContext) const; diff --git a/poincare/include/poincare/unreal.h b/poincare/include/poincare/unreal.h index d53facec441..7c9c55d38b6 100644 --- a/poincare/include/poincare/unreal.h +++ b/poincare/include/poincare/unreal.h @@ -28,7 +28,7 @@ class UnrealNode final : public UndefinedNode { } /* Derivation - * Unlike Numbers that derivate to 0, Unreal derivates to Unreal. */ + * Unlike Numbers that derivate to 0, Unreal derivatives to Unreal. */ bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override { return true; } // Layout diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index 56bd4587180..293b3506228 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -6,7 +6,7 @@ /* FIXME : This class is concerned with manipulating the ranges of graphing * window, often represented by four float xMin, xMax, yMin, yMax. Handling - * those same four values has proven repetititve, tredious, and prone to error. + * those same four values has proven repetitive, tedious, and prone to error. * This code could benefit from a data structure to regroup those values in a * single object. */ diff --git a/poincare/src/approximation_helper.cpp b/poincare/src/approximation_helper.cpp index 77b924eb4f6..20eae31ceb8 100644 --- a/poincare/src/approximation_helper.cpp +++ b/poincare/src/approximation_helper.cpp @@ -10,7 +10,7 @@ extern "C" { namespace Poincare { -template bool isNegligeable(T x, T precision, T norm1, T norm2) { +template bool isNegligible(T x, T precision, T norm1, T norm2) { T absX = std::fabs(x); return absX <= 10.0*precision && absX/norm1 <= precision && absX/norm2 <= precision; } @@ -70,10 +70,10 @@ template std::complex ApproximationHelper::NeglectRealOrImaginar T magnitude1 = minimalNonNullMagnitudeOfParts(input1); T magnitude2 = minimalNonNullMagnitudeOfParts(input2); T precision = Epsilon(); - if (isNegligeable(result.imag(), precision, magnitude1, magnitude2)) { + if (isNegligible(result.imag(), precision, magnitude1, magnitude2)) { result.imag(0); } - if (isNegligeable(result.real(), precision, magnitude1, magnitude2)) { + if (isNegligible(result.real(), precision, magnitude1, magnitude2)) { result.real(0); } return result; diff --git a/poincare/src/complex_cartesian.cpp b/poincare/src/complex_cartesian.cpp index 758829eabff..4972f13d496 100644 --- a/poincare/src/complex_cartesian.cpp +++ b/poincare/src/complex_cartesian.cpp @@ -39,18 +39,18 @@ Expression ComplexCartesianNode::shallowBeautify(ReductionContext * reductionCon template Complex ComplexCartesianNode::templatedApproximate(ApproximationContext approximationContext) const { Evaluation realEvaluation = childAtIndex(0)->approximate(T(), approximationContext); - Evaluation imagEvalution = childAtIndex(1)->approximate(T(), approximationContext); + Evaluation imagEvaluation = childAtIndex(1)->approximate(T(), approximationContext); assert(realEvaluation.type() == EvaluationNode::Type::Complex); - assert(imagEvalution.type() == EvaluationNode::Type::Complex); + assert(imagEvaluation.type() == EvaluationNode::Type::Complex); std::complex a = static_cast &>(realEvaluation).stdComplex(); - std::complex b = static_cast &>(imagEvalution).stdComplex(); + std::complex b = static_cast &>(imagEvaluation).stdComplex(); if ((a.imag() != (T)0.0 && !std::isnan(a.imag())) || (b.imag() != (T)0.0 && !std::isnan(b.imag()))) { /* a and b are supposed to be real (if they are not undefined). However, * due to double precision limit, the approximation of the real part or the * imaginary part can lead to complex values. * For instance, let the real part be * sqrt(2*sqrt(5E23+1)-1E12*sqrt(2)) ~ 1.1892E-6. Due to std::sqrt(2.0) - * unprecision, 2*sqrt(5E23+1)-1E12*sqrt(2) < 0 which leads to + * imprecision, 2*sqrt(5E23+1)-1E12*sqrt(2) < 0 which leads to * sqrt(2*sqrt(5E23+1)-1E12*sqrt(2)) being a complex number. * In this case, we return an undefined complex because the approximation * is very likely to be false. */ diff --git a/poincare/src/decimal.cpp b/poincare/src/decimal.cpp index 30ee799ebea..9612fd8246b 100644 --- a/poincare/src/decimal.cpp +++ b/poincare/src/decimal.cpp @@ -234,7 +234,7 @@ int DecimalNode::convertToText(char * buffer, int bufferSize, Preferences::Print * * We should use the UTF8Helper to manipulate chars, but it is clearer to * manipulate chars directly, so we just put assumptions on the char size - * of the code points we manipuate. */ + * of the code points we manipulate. */ assert(UTF8Decoder::CharSizeOfCodePoint('.') == 1); currentChar++; if (currentChar >= bufferSize-1) { return bufferSize-1; } @@ -275,7 +275,7 @@ int DecimalNode::convertToText(char * buffer, int bufferSize, Preferences::Print } } currentChar += mantissaLength; - if (currentChar >= bufferSize - 1) { return bufferSize-1; } // Check if strlcpy returned prematuraly + if (currentChar >= bufferSize - 1) { return bufferSize-1; } // Check if strlcpy returned prematurely if (exponent >= 0 && exponent < mantissaLength-1) { if (currentChar+1 >= bufferSize-1) { return bufferSize-1; } int decimalMarkerPosition = m_negative ? exponent + 1 : exponent; diff --git a/poincare/src/derivative.cpp b/poincare/src/derivative.cpp index 0d139fd730c..3096db10243 100644 --- a/poincare/src/derivative.cpp +++ b/poincare/src/derivative.cpp @@ -102,7 +102,7 @@ T DerivativeNode::growthRateAroundAbscissa(T x, T h, ApproximationContext approx template T DerivativeNode::riddersApproximation(ApproximationContext approximationContext, T x, T h, T * error) const { /* Ridders' Algorithm - * Blibliography: + * Bibliography: * - Ridders, C.J.F. 1982, Advances in Helperering Software, vol. 4, no. 2, * pp. 75–76. */ diff --git a/poincare/src/erf_inv.cpp b/poincare/src/erf_inv.cpp index 63b0323aec9..2be51c68b10 100644 --- a/poincare/src/erf_inv.cpp +++ b/poincare/src/erf_inv.cpp @@ -31,7 +31,7 @@ namespace Poincare { * library. */ double erfInv(double x) { // beware that the logarithm argument must be - // commputed as (1.0 - x) * (1.0 + x), + // computed as (1.0 - x) * (1.0 + x), // it must NOT be simplified as 1.0 - x * x as this // would induce rounding errors near the boundaries +/-1 double w = - std::log((1.0 - x) * (1.0 + x)); diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index a5c5f2b8fc3..4c066e6c1eb 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -570,7 +570,7 @@ bool Expression::isReal(Context * context) const { bool Expression::isIdenticalTo(const Expression e) const { /* We use the simplification order only because it is a already-coded total - * order on expresssions. */ + * order on expressions. */ return ExpressionNode::SimplificationOrder(node(), e.node(), true, true) == 0; } @@ -656,7 +656,7 @@ void Expression::beautifyAndApproximateScalar(Expression * simplifiedExpression, if (approximateExpression) { /* Step 1: Approximation * We compute the approximate expression from the Cartesian form to avoid - * unprecision. For example, if the result is the ComplexCartesian(a,b), + * imprecision. For example, if the result is the ComplexCartesian(a,b), * the final expression is going to be sqrt(a^2+b^2)*exp(i*atan(b/a)... * in Polar ComplexFormat. If we approximate this expression instead of * ComplexCartesian(a,b), we are going to loose precision on the resulting diff --git a/poincare/src/hyperbolic_arc_tangent.cpp b/poincare/src/hyperbolic_arc_tangent.cpp index 09453d22a07..8f23f085fcf 100644 --- a/poincare/src/hyperbolic_arc_tangent.cpp +++ b/poincare/src/hyperbolic_arc_tangent.cpp @@ -23,7 +23,7 @@ Complex HyperbolicArcTangentNode::computeOnComplex(const std::complex c, P * this cut. We followed the convention chosen by the lib c++ of llvm on * ]-inf+0i, -1+0i[ (warning: atanh takes the other side of the cut values on * ]-inf-0i, -1-0i[) and choose the values on ]1+0i, +inf+0i[ to comply with - * atanh(-x) = -atanh(x) and sin(artanh(x)) = x/sqrt(1-x^2). */ + * atanh(-x) = -atanh(x) and sin(atanh(x)) = x/sqrt(1-x^2). */ if (c.imag() == 0 && c.real() > 1) { result.imag(-result.imag()); // other side of the cut } diff --git a/poincare/src/logarithm.cpp b/poincare/src/logarithm.cpp index c2cd788c388..d8187d8619c 100644 --- a/poincare/src/logarithm.cpp +++ b/poincare/src/logarithm.cpp @@ -247,8 +247,8 @@ Expression Logarithm::shallowReduce(ExpressionNode::ReductionContext reductionCo if (childAtIndex(1).type() == ExpressionNode::Type::Rational && childAtIndex(1).convert().isInteger()) { Integer b = childAtIndex(1).convert().signedIntegerNumerator(); Integer newNumerator = simplifyLogarithmIntegerBaseInteger(r.signedIntegerNumerator(), b, a, false); - Integer newDenomitor = simplifyLogarithmIntegerBaseInteger(r.integerDenominator(), b, a, true); - r = Rational::Builder(newNumerator, newDenomitor); + Integer newDenominator = simplifyLogarithmIntegerBaseInteger(r.integerDenominator(), b, a, true); + r = Rational::Builder(newNumerator, newDenominator); } // log(r) = a0log(p0)+a1log(p1)+... with r = p0^a0*p1^a1*... (Prime decomposition) a.addChildAtIndexInPlace(splitLogarithmInteger(r.signedIntegerNumerator(), false, reductionContext), a.numberOfChildren(), a.numberOfChildren()); diff --git a/poincare/src/matrix.cpp b/poincare/src/matrix.cpp index 48422d6b6bd..43a169962cb 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -185,7 +185,7 @@ int Matrix::ArrayInverse(T * array, int numberOfRows, int numberOfColumns) { } } ArrayRowCanonize(operands, dim, 2*dim); - // Check inversibility + // Check invertibility for (int i = 0; i < dim; i++) { T cell = operands[i*2*dim+i]; if (!std::isfinite(std::abs(cell)) || std::abs(cell - (T)1.0) > Expression::Epsilon()) { @@ -407,7 +407,7 @@ Expression Matrix::createRef(ExpressionNode::ReductionContext reductionContext, if (ExceptionRun(ecp)) { /* We clone the current matrix to extract its children later. We can't clone * its children directly. Indeed, the current matrix node (this->node()) is - * located before the exception checkpoint. In order to clone its chidlren, + * located before the exception checkpoint. In order to clone its children, * we would temporary increase the reference counter of each child (also * located before the checkpoint). If an exception is raised before * destroying the child handle, its reference counter would be off by one @@ -568,7 +568,7 @@ Expression Matrix::computeInverseOrDeterminant(bool computeDeterminant, Expressi if (ExceptionRun(ecp)) { /* We clone the current matrix to extract its children later. We can't clone * its children directly. Indeed, the current matrix node (this->node()) is - * located before the exception checkpoint. In order to clone its chidlren, + * located before the exception checkpoint. In order to clone its children, * we would temporary increase the reference counter of each child (also * located before the checkpoint). If an exception is raised before * destroying the child handle, its reference counter would be off by one @@ -596,7 +596,7 @@ Expression Matrix::computeInverseOrDeterminant(bool computeDeterminant, Expressi } // Compute the inverse matrixAI = matrixAI.rowCanonize(reductionContext, nullptr); - // Check inversibility + // Check invertibility for (int i = 0; i < dim; i++) { if (!matrixAI.matrixChild(i, i).isRationalOne()) { return Undefined::Builder(); diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index 1b95a31c07d..a5ba391fc15 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -444,7 +444,7 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext * re if (hasUnit()) { Expression units; /* removeUnit has to be called on reduced expression but we want to modify - * the least the expression so we use the uninvasive reduction context. */ + * the least the expression so we use the noninvasive reduction context. */ self = self.reduceAndRemoveUnit(ExpressionNode::ReductionContext::NonInvasiveReductionContext(*reductionContext), &units); if (self.isUndefined() || units.isUninitialized()) { diff --git a/poincare/src/parsing/parser.cpp b/poincare/src/parsing/parser.cpp index 55090711e25..953c64d4ca2 100644 --- a/poincare/src/parsing/parser.cpp +++ b/poincare/src/parsing/parser.cpp @@ -159,7 +159,7 @@ void Parser::parseNumber(Expression & leftHandSide, Token::Type stoppingType) { leftHandSide = m_currentToken.expression(); // No implicit multiplication between two numbers if (m_nextToken.isNumber() - // No implicit multiplication between a hexadecimal number and an identifer (avoid parsing 0x2abch as 0x2ABC*h) + // No implicit multiplication between a hexadecimal number and an identifier (avoid parsing 0x2abch as 0x2ABC*h) || (m_currentToken.is(Token::Type::HexadecimalNumber) && m_nextToken.is(Token::Type::Identifier))) { m_status = Status::Error; return; @@ -239,7 +239,7 @@ void Parser::parseCaret(Expression & leftHandSide, Token::Type stoppingType) { void Parser::parseCaretWithParenthesis(Expression & leftHandSide, Token::Type stoppingType) { /* When parsing 2^(4) ! (with system parentheses), the factorial should stay * out of the power. To do this, we tokenized ^( as one token that should be - * matched by a closing parenthesis. Otherwise, the ! would take precendence + * matched by a closing parenthesis. Otherwise, the ! would take precedence * over the power. */ if (leftHandSide.isUninitialized()) { m_status = Status::Error; // Power must have a left operand diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index 68d80943de3..c69b0111e7f 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -195,10 +195,10 @@ Complex PowerNode::compute(const std::complex c, const std::complex d, * avoid weird results as e(i*pi) = -1+6E-17*i, we compute the argument of * the result of c^d and if arg ~ 0 [Pi], we discard the residual imaginary * part and if arg ~ Pi/2 [Pi], we discard the residual real part. - * Let's determine when the arg [Pi] (or arg [Pi/2]) is negligeable: + * Let's determine when the arg [Pi] (or arg [Pi/2]) is negligible: * With c = r*e^(iθ) and d = x+iy, c^d = r^x*e^(yθ)*e^i(yln(r)+xθ) * so arg(c^d) = y*ln(r)+xθ. - * We consider that arg[π] is negligeable if it is negligeable compared to + * We consider that arg[π] is negligible if it is negligible compared to * norm(d) = sqrt(x^2+y^2) and ln(r) = ln(norm(c)).*/ return Complex::Builder(ApproximationHelper::NeglectRealOrImaginaryPartIfNeglectable(result, c, d, false)); } diff --git a/poincare/src/print_float.cpp b/poincare/src/print_float.cpp index d9147a275c9..09d06ed7a9a 100644 --- a/poincare/src/print_float.cpp +++ b/poincare/src/print_float.cpp @@ -243,7 +243,7 @@ PrintFloat::TextLengths PrintFloat::ConvertFloatToTextPrivate(T f, char * buffer if (mode == Preferences::PrintFloatMode::Decimal && exponentInBase10 >= numberOfSignificantDigits) { /* Exception 1: avoid inventing digits to fill the printed float: when - * displaying 12345 with 2 significant digis in Decimal mode for instance. + * displaying 12345 with 2 significant digits in Decimal mode for instance. * This exception is caught by ConvertFloatToText and forces the mode to * Scientific */ return exceptionResult; diff --git a/poincare/src/tree_handle.cpp b/poincare/src/tree_handle.cpp index 3c1a6cdfd27..057a237dbec 100644 --- a/poincare/src/tree_handle.cpp +++ b/poincare/src/tree_handle.cpp @@ -233,7 +233,7 @@ TreeHandle TreeHandle::BuildWithGhostChildren(TreeNode * node) { assert(node != nullptr); TreePool * pool = TreePool::sharedPool(); int expectedNumberOfChildren = node->numberOfChildren(); - /* Ensure the pool is syntaxically correct by creating ghost children for + /* Ensure the pool is syntactically correct by creating ghost children for * nodes that have a fixed, non-zero number of children. */ for (int i = 0; i < expectedNumberOfChildren; i++) { GhostNode * ghost = new (pool->alloc(sizeof(GhostNode))) GhostNode(); diff --git a/poincare/src/trigonometry.cpp b/poincare/src/trigonometry.cpp index bdaa0a1652c..b2279f27f69 100644 --- a/poincare/src/trigonometry.cpp +++ b/poincare/src/trigonometry.cpp @@ -230,9 +230,9 @@ Expression Trigonometry::shallowReduceDirectFunction(Expression & e, ExpressionN /* Step 4.1. In radians: * We first check if p/q * π is already in the right quadrant: * p/q * π < π/2 => p/q < 2 => 2p < q */ - Integer dividand = Integer::Addition(r.unsignedIntegerNumerator(), r.unsignedIntegerNumerator()); + Integer dividend = Integer::Addition(r.unsignedIntegerNumerator(), r.unsignedIntegerNumerator()); Integer divisor = Integer::Multiplication(r.integerDenominator(), Integer(s_piDivisor[(int)angleUnit])); - if (divisor.isLowerThan(dividand)) { + if (divisor.isLowerThan(dividend)) { /* Step 4.2. p/q * π is not in the wanted trigonometrical quadrant. * We could subtract n*π to p/q with n an integer. * Given p/q = (q'*q+r')/q, we have @@ -241,8 +241,8 @@ Expression Trigonometry::shallowReduceDirectFunction(Expression & e, ExpressionN int unaryCoefficient = 1; // store 1 or -1 for the final result. Integer piDivisor = Integer::Multiplication(r.integerDenominator(), Integer(s_piDivisor[(int)angleUnit])); IntegerDivision div = Integer::Division(r.unsignedIntegerNumerator(), piDivisor); - dividand = Integer::Addition(div.remainder, div.remainder); - if (divisor.isLowerThan(dividand)) { + dividend = Integer::Addition(div.remainder, div.remainder); + if (divisor.isLowerThan(dividend)) { /* Step 4.3. r'/q * π is not in the wanted trigonometrical quadrant, * and because r' UnitNode::Vector::FromBaseUnits(const Expression base .current = 0, .temperature = 0, .amountOfSubstance = 0, - .luminuousIntensity = 0, + .luminousIntensity = 0, }; int numberOfFactors; int factorIndex = 0; diff --git a/poincare/test/approximation.cpp b/poincare/test/approximation.cpp index f66a92b24bb..7fa58206222 100644 --- a/poincare/test/approximation.cpp +++ b/poincare/test/approximation.cpp @@ -987,7 +987,7 @@ QUIZ_CASE(poincare_approximation_complex_format) { QUIZ_CASE(poincare_approximation_mix) { assert_expression_approximates_to("-2-3", "-5"); assert_expression_approximates_to("1.2×ℯ^(1)", "3.261938"); - assert_expression_approximates_to("2ℯ^(3)", "40.1711", Radian, Metric, Cartesian, 6); // WARNING: the 7th significant digit is wrong on blackbos simulator + assert_expression_approximates_to("2ℯ^(3)", "40.1711", Radian, Metric, Cartesian, 6); // WARNING: the 7th significant digit is wrong on blackbox simulator assert_expression_approximates_to("ℯ^2×ℯ^(1)", "20.0855", Radian, Metric, Cartesian, 6); // WARNING: the 7th significant digit is wrong on simulator assert_expression_approximates_to("ℯ^2×ℯ^(1)", "20.085536923188"); assert_expression_approximates_to("2×3^4+2", "164"); diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index 337bcca1753..3fe80a1b1cb 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -393,7 +393,7 @@ QUIZ_CASE(poincare_properties_get_polynomial_coefficients) { assert_reduced_expression_has_polynomial_coefficient("x^2+x+2", "x", coefficient0); const char * coefficient1[] = {"12+(-6)×π", "12", "3", 0}; //3×x^2+12×x-6×π+12 assert_reduced_expression_has_polynomial_coefficient("3×(x+2)^2-6×π", "x", coefficient1); - // TODO: decomment when enable 3-degree polynomes + // TODO: uncomment when enable 3-degree polynomials //const char * coefficient2[] = {"2+32×x", "2", "6", "2", 0}; //2×n^3+6×n^2+2×n+2+32×x //assert_reduced_expression_has_polynomial_coefficient("2×(n+1)^3-4n+32×x", "n", coefficient2); const char * coefficient3[] = {"1", "-π", "1", 0}; //x^2-π×x+1 diff --git a/python/Makefile b/python/Makefile index a957082c1d6..95b95bbab0e 100644 --- a/python/Makefile +++ b/python/Makefile @@ -201,7 +201,7 @@ $(call object_for,python/src/py/objmodule.c): SFLAGS += -DMP_QSTR_urandom="MP_QS $(call object_for,python/src/extmod/modurandom.c): SFLAGS += -DMP_QSTR_urandom="MP_QSTR_random" # Rename usys to sys -# In order to change the name of the micropython module 'usys' to 'usys' +# In order to change the name of the micropython module 'usys' to 'sys' # (without altering micropython files), we redefined the macro MP_QSTR_usys # by DMP_QSTR_sys. $(call object_for,python/src/py/objmodule.c): SFLAGS += -DMP_QSTR_usys="MP_QSTR_sys" diff --git a/python/port/mod/ion/file.cpp b/python/port/mod/ion/file.cpp index 2efe8e091b2..721d79eee04 100644 --- a/python/port/mod/ion/file.cpp +++ b/python/port/mod/ion/file.cpp @@ -762,7 +762,7 @@ STATIC mp_obj_t file_writelines(mp_obj_t o_in, mp_obj_t o_lines) { } /* - * Simpler read function usef by read and readline. + * Simpler read function used by read and readline. */ STATIC mp_obj_t __file_read_backend(file_obj_t* file, mp_int_t size, bool with_line_sep) { size_t file_size = file->record.value().size; diff --git a/python/port/mod/ion/modion_table.cpp b/python/port/mod/ion/modion_table.cpp index 0e07b006b46..aa496a95576 100644 --- a/python/port/mod/ion/modion_table.cpp +++ b/python/port/mod/ion/modion_table.cpp @@ -4,7 +4,7 @@ extern "C" { #include -/* We cannot use C99-style struct initizalition in C++. As a result, we cannot +/* We cannot use C99-style struct initialization in C++. As a result, we cannot * use the macros that micropython recommends, and we have to hand build those * structs. To avoid errors, we drop in a few static_asserts. */ diff --git a/python/port/mod/matplotlib/pyplot/modpyplot.cpp b/python/port/mod/matplotlib/pyplot/modpyplot.cpp index c81eeb7bf59..76c4bfc2cb5 100644 --- a/python/port/mod/matplotlib/pyplot/modpyplot.cpp +++ b/python/port/mod/matplotlib/pyplot/modpyplot.cpp @@ -26,7 +26,7 @@ static size_t extractArgument(mp_obj_t arg, mp_obj_t ** items) { return itemLength; } -// Extract two scalar or array arguments and check for their strickly equal dimension +// Extract two scalar or array arguments and check for their strictly equal dimension static size_t extractArgumentsAndCheckEqualSize(mp_obj_t x, mp_obj_t y, mp_obj_t ** xItems, mp_obj_t ** yItems) { size_t xLength = extractArgument(x, xItems); diff --git a/python/port/mod/time/modtime.cpp b/python/port/mod/time/modtime.cpp index 58ae09b49bc..a87bd1f8bb3 100644 --- a/python/port/mod/time/modtime.cpp +++ b/python/port/mod/time/modtime.cpp @@ -21,7 +21,7 @@ mp_obj_t modtime_monotonic() { } // -// Omega extensions, based off MicroPython's modutime.c +// Upsilon extensions, based off MicroPython's modutime.c // // LEAPOCH corresponds to 2000-03-01, which is a mod-400 year, immediately diff --git a/python/port/mod/time/modtime.h b/python/port/mod/time/modtime.h index f52ebaffce3..785bf806b6b 100644 --- a/python/port/mod/time/modtime.h +++ b/python/port/mod/time/modtime.h @@ -4,14 +4,14 @@ mp_obj_t modtime_sleep(mp_obj_t seconds_o); mp_obj_t modtime_monotonic(); // -// Omega extensions. +// Upsilon extensions. // mp_obj_t modtime_localtime(size_t n_args, const mp_obj_t *args); mp_obj_t modtime_mktime(mp_obj_t tuple); mp_obj_t modtime_time(void); -// Omega private extensions. +// Upsilon private extensions. mp_obj_t modtime_rtcmode(void); mp_obj_t modtime_setrtcmode(mp_obj_t mode); diff --git a/python/port/mod/ulab/ndarray.c b/python/port/mod/ulab/ndarray.c index 45e19c26255..88fbcfbdafc 100644 --- a/python/port/mod/ulab/ndarray.c +++ b/python/port/mod/ulab/ndarray.c @@ -849,7 +849,7 @@ STATIC uint8_t ndarray_init_helper(size_t n_args, const mp_obj_t *pos_args, mp_m if(mp_obj_is_type(args[1].u_obj, &ulab_dtype_type)) { dtype_obj_t *dtype = MP_OBJ_TO_PTR(args[1].u_obj); _dtype = dtype->dtype; - } else { // this must be an integer defined as a class constant (ulba.uint8 etc.) + } else { // this must be an integer defined as a class constant (ulab.uint8 etc.) _dtype = mp_obj_get_int(args[1].u_obj); } #else diff --git a/python/port/mod/ulab/numpy/fft/fft.c b/python/port/mod/ulab/numpy/fft/fft.c index 6f6534f497b..5c6af832d2d 100644 --- a/python/port/mod/ulab/numpy/fft/fft.c +++ b/python/port/mod/ulab/numpy/fft/fft.c @@ -55,7 +55,7 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_fft_obj, 1, 2, fft_fft); //| :param ulab.numpy.ndarray c: An optional 1-dimension array of values whose size is a power of 2, giving the complex part of the value //| :return tuple (r, c): The real and complex parts of the inverse FFT //| -//| Perform an Inverse Fast Fourier Transform from the frequeny domain into the time domain""" +//| Perform an Inverse Fast Fourier Transform from the frequency domain into the time domain""" //| ... //| diff --git a/python/port/mod/ulab/numpy/poly.c b/python/port/mod/ulab/numpy/poly.c index 124d3bc8697..7ea7feb1a02 100644 --- a/python/port/mod/ulab/numpy/poly.c +++ b/python/port/mod/ulab/numpy/poly.c @@ -164,7 +164,7 @@ mp_obj_t poly_polyval(mp_obj_t o_p, mp_obj_t o_x) { mp_float_t (*func)(void *) = ndarray_get_float_function(source->dtype); - // TODO: these loops are really nothing, but the re-impplementation of + // TODO: these loops are really nothing, but the re-implementation of // ITERATE_VECTOR from vectorise.c. We could pass a function pointer here #if ULAB_MAX_DIMS > 3 size_t i = 0; diff --git a/python/port/mod/ulab/scipy/optimize/optimize.c b/python/port/mod/ulab/scipy/optimize/optimize.c index c2ed6fff86b..30b61283de6 100644 --- a/python/port/mod/ulab/scipy/optimize/optimize.c +++ b/python/port/mod/ulab/scipy/optimize/optimize.c @@ -121,7 +121,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(optimize_bisect_obj, 3, optimize_bisect); //| Find a minimum of the function ``f(x)`` using the downhill simplex method. //| The located ``x`` is within ``fxtol`` of the actual minimum, and ``f(x)`` //| is within ``fatol`` of the actual minimum unless more than ``maxiter`` -//| steps are requried.""" +//| steps are required.""" //| ... //| @@ -344,7 +344,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(optimize_curve_fit_obj, 2, optimize_curve_fit); //| //| Find a solution (zero) of the function ``f(x)`` using Newton's Method. //| The result is accurate to within ``xtol * rtol * |f(x)|`` unless more than -//| ``maxiter`` steps are requried.""" +//| ``maxiter`` steps are required.""" //| ... //| diff --git a/python/port/mod/ulab/user/user.c b/python/port/mod/ulab/user/user.c index 835c091c7c2..f69089da5f6 100644 --- a/python/port/mod/ulab/user/user.c +++ b/python/port/mod/ulab/user/user.c @@ -74,7 +74,7 @@ static mp_obj_t user_square(mp_obj_t arg) { *rarray++ = (*array) * (*array); } } - // at the end, return a micrppython object + // at the end, return a micropython object return MP_OBJ_FROM_PTR(results); } diff --git a/python/port/port.cpp b/python/port/port.cpp index 21c9645dfbb..e86a6ef2245 100644 --- a/python/port/port.cpp +++ b/python/port/port.cpp @@ -212,7 +212,7 @@ void MicroPython::collectRootsAtAddress(char * address, int byteLength) { uintptr_t alignedAddress = reinterpret_cast(address) & bitMaskZeros; /* Increase the length consequently with the new alignment * (We don't need to increase the byteLength to a sizeof(uintptr_t)-aligned - * lenght because no pointer can be stored on less than sizeof(uintptr_t) + * length because no pointer can be stored on less than sizeof(uintptr_t) * bytes.) */ int alignedByteLength = byteLength; alignedByteLength += reinterpret_cast(address) & bitMaskOnes; diff --git a/python/src/py/compile.c b/python/src/py/compile.c index 0b02746a564..3d5c5f21e61 100644 --- a/python/src/py/compile.c +++ b/python/src/py/compile.c @@ -705,7 +705,7 @@ STATIC void compile_funcdef_lambdef_param(compiler_t *comp, mp_parse_node_t pn) } else { // this parameter has a default value - // in CPython, None (and True, False?) as default parameters are loaded with LOAD_NAME; don't understandy why + // in CPython, None (and True, False?) as default parameters are loaded with LOAD_NAME; don't understand why if (comp->have_star) { comp->num_dict_params += 1; diff --git a/python/src/py/emitnative.c b/python/src/py/emitnative.c index 7c7c3428397..f63b6d289d3 100644 --- a/python/src/py/emitnative.c +++ b/python/src/py/emitnative.c @@ -574,7 +574,7 @@ STATIC void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scop if (emit->pass == MP_PASS_CODE_SIZE) { // Commit to the encoding size based on the value of prelude_offset in this pass. // By using 32768 as the cut-off it is highly unlikely that prelude_offset will - // grow beyond 65535 by the end of thiss pass, and so require the larger encoding. + // grow beyond 65535 by the end of this pass, and so require the larger encoding. emit->prelude_offset_uses_u16_encoding = emit->prelude_offset < 32768; } if (emit->prelude_offset_uses_u16_encoding) { @@ -874,7 +874,7 @@ STATIC vtype_kind_t load_reg_stack_imm(emit_t *emit, int reg_dest, const stack_i } } -// Copies all unsettled registers and immediates that are Python values into the +// Copies all unsettled registers and immediate that are Python values into the // concrete Python stack. This ensures the concrete Python stack holds valid // values for the current stack_size. // This function may clobber REG_TEMP1. @@ -1070,7 +1070,7 @@ STATIC void emit_get_stack_pointer_to_reg_for_pop(emit_t *emit, mp_uint_t reg_de } } - // Adujust the stack for a pop of n_pop items, and load the stack pointer into reg_dest. + // Adjust the stack for a pop of n_pop items, and load the stack pointer into reg_dest. adjust_stack(emit, -n_pop); emit_native_mov_reg_state_addr(emit, reg_dest, emit->stack_start + emit->stack_size); } diff --git a/python/src/py/formatfloat.c b/python/src/py/formatfloat.c index 9d28b2317dc..6f4eee8220e 100644 --- a/python/src/py/formatfloat.c +++ b/python/src/py/formatfloat.c @@ -318,7 +318,7 @@ int mp_format_float(FPTYPE f, char *buf, size_t buf_size, char fmt, int prec, ch // We now have num.f as a floating point number between >= 1 and < 10 // (or equal to zero), and e contains the absolute value of the power of - // 10 exponent. and (dec + 1) == the number of dgits before the decimal. + // 10 exponent. and (dec + 1) == the number of digits before the decimal. // For e, prec is # digits after the decimal // For f, prec is # digits after the decimal diff --git a/python/src/py/gc.c b/python/src/py/gc.c index 8284c435ba9..1256d35249c 100644 --- a/python/src/py/gc.c +++ b/python/src/py/gc.c @@ -512,7 +512,7 @@ void *gc_alloc(size_t n_bytes, unsigned int alloc_flags) { // Set last free ATB index to block after last block we found, for start of // next scan. To reduce fragmentation, we only do this if we were looking // for a single free block, which guarantees that there are no free blocks - // before this one. Also, whenever we free or shink a block we must check + // before this one. Also, whenever we free or shrink a block we must check // if this index needs adjusting (see gc_realloc and gc_free). if (n_free == 1) { MP_STATE_MEM(gc_last_free_atb_index) = (i + 1) / BLOCKS_PER_ATB; diff --git a/python/src/py/lexer.c b/python/src/py/lexer.c index 69c7d14a776..e1858d8eed0 100644 --- a/python/src/py/lexer.c +++ b/python/src/py/lexer.c @@ -594,7 +594,7 @@ void mp_lexer_to_next(mp_lexer_t *lex) { // a string or bytes literal // Python requires adjacent string/bytes literals to be automatically - // concatenated. We do it here in the tokeniser to make efficient use of RAM, + // concatenated. We do it here in the tokenizer to make efficient use of RAM, // because then the lexer's vstr can be used to accumulate the string literal, // in contrast to creating a parse tree of strings and then joining them later // in the compiler. It's also more compact in code size to do it here. diff --git a/python/src/py/lexer.h b/python/src/py/lexer.h index e16b9a8ce86..4b0c097af5d 100644 --- a/python/src/py/lexer.h +++ b/python/src/py/lexer.h @@ -32,7 +32,7 @@ #include "py/qstr.h" #include "py/reader.h" -/* lexer.h -- simple tokeniser for MicroPython +/* lexer.h -- simple tokenizer for MicroPython * * Uses (byte) length instead of null termination. * Tokens are the same - UTF-8 with (byte) length. diff --git a/python/src/py/makecompresseddata.py b/python/src/py/makecompresseddata.py index 9603de87131..1bce3e8e837 100644 --- a/python/src/py/makecompresseddata.py +++ b/python/src/py/makecompresseddata.py @@ -24,7 +24,7 @@ def check_non_ascii(msg): # Replace with . -# Trival scheme to demo/test. +# Trivial scheme to demo/test. def space_compression(error_strings): for line in error_strings: check_non_ascii(line) diff --git a/python/src/py/mkrules.mk b/python/src/py/mkrules.mk index d0c0a53c262..bde96c7b4b7 100644 --- a/python/src/py/mkrules.mk +++ b/python/src/py/mkrules.mk @@ -16,7 +16,7 @@ endif # QSTR generation uses the same CFLAGS, with these modifications. QSTR_GEN_FLAGS = -DNO_QSTR -# Note: := to force evalulation immediately. +# Note: := to force evaluation immediately. QSTR_GEN_CFLAGS := $(CFLAGS) QSTR_GEN_CFLAGS += $(QSTR_GEN_FLAGS) QSTR_GEN_CXXFLAGS := $(CXXFLAGS) @@ -28,7 +28,7 @@ QSTR_GEN_CXXFLAGS += $(QSTR_GEN_FLAGS) # tree. # # So for example, py/map.c would have an object file name py/map.o -# The object files will go into the build directory and mantain the same +# The object files will go into the build directory and maintain the same # directory structure as the source tree. So the final dependency will look # like this: # @@ -184,7 +184,7 @@ endif ifneq ($(PROG),) # Build a standalone executable (unix does this) -# The executable should have an .exe extension for builds targetting 'pure' +# The executable should have an .exe extension for builds targeting 'pure' # Windows, i.e. msvc or mingw builds, but not when using msys or cygwin's gcc. COMPILER_TARGET := $(shell $(CC) -dumpmachine) ifneq (,$(findstring mingw,$(COMPILER_TARGET))) diff --git a/python/src/py/modbuiltins.c b/python/src/py/modbuiltins.c index a7e49a1ed9a..2a142a6bb87 100644 --- a/python/src/py/modbuiltins.c +++ b/python/src/py/modbuiltins.c @@ -79,7 +79,7 @@ STATIC mp_obj_t mp_builtin___build_class__(size_t n_args, const mp_obj_t *args) meta_args[2] = class_locals; // dict of members mp_obj_t new_class = mp_call_function_n_kw(meta, 3, 0, meta_args); - // store into cell if neede + // store into cell if needed if (cell != mp_const_none) { mp_obj_cell_set(cell, new_class); } diff --git a/python/src/py/mpconfig.h b/python/src/py/mpconfig.h index 26268403d43..680b43d62cd 100644 --- a/python/src/py/mpconfig.h +++ b/python/src/py/mpconfig.h @@ -1221,7 +1221,7 @@ typedef double mp_float_t; // the semantics of CPython's pkg_resources.resource_stream() // (allows to access binary resources in frozen source packages). // Note that the same functionality can be achieved in "pure -// Python" by prepocessing binary resources into Python source +// Python" by preprocessing binary resources into Python source // and bytecode-freezing it (with a simple helper module available // e.g. in micropython-lib). #ifndef MICROPY_PY_IO_RESOURCE_STREAM diff --git a/python/src/py/obj.c b/python/src/py/obj.c index f66a9d183c7..e7a23be5321 100644 --- a/python/src/py/obj.c +++ b/python/src/py/obj.c @@ -279,7 +279,7 @@ mp_obj_t mp_obj_equal_not_equal(mp_binary_op_t op, mp_obj_t o1, mp_obj_t o2) { o2 = temp; } - // equality not implemented, so fall back to pointer conparison + // equality not implemented, so fall back to pointer comparison return (o1 == o2) ? local_true : local_false; } diff --git a/python/src/py/parsenum.c b/python/src/py/parsenum.c index 1cfe8425770..54cd2bf862b 100644 --- a/python/src/py/parsenum.c +++ b/python/src/py/parsenum.c @@ -98,7 +98,7 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m break; } - // add next digi and check for overflow + // add next digit and check for overflow if (mp_small_int_mul_overflow(int_val, base)) { goto overflow; } diff --git a/python/test/basics.cpp b/python/test/basics.cpp index 84a7a2275fc..8d1358d17fa 100644 --- a/python/test/basics.cpp +++ b/python/test/basics.cpp @@ -2,7 +2,7 @@ #include "execution_environment.h" QUIZ_CASE(python_basics) { - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_succeeds(env, "2+3","5\n"); deinit_environment(); } diff --git a/python/test/execution_environment.cpp b/python/test/execution_environment.cpp index 7fb6ae91c51..da5ea3f68c6 100644 --- a/python/test/execution_environment.cpp +++ b/python/test/execution_environment.cpp @@ -51,7 +51,7 @@ bool execute_input(TestExecutionEnvironment env, bool singleCommandLine, const c return executionResult; } -TestExecutionEnvironment init_environement() { +TestExecutionEnvironment init_environnement() { MicroPython::init(TestExecutionEnvironment::s_pythonHeap, TestExecutionEnvironment::s_pythonHeap + TestExecutionEnvironment::s_pythonHeapSize); return TestExecutionEnvironment(); } @@ -61,7 +61,7 @@ void deinit_environment() { } void assert_script_execution_result(bool expectedResult, const char * script, const char * outputText = nullptr) { - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); quiz_assert(expectedResult == execute_input(env, false, script, outputText)); deinit_environment(); } diff --git a/python/test/execution_environment.h b/python/test/execution_environment.h index cc711ad603f..aaa81e39e6c 100644 --- a/python/test/execution_environment.h +++ b/python/test/execution_environment.h @@ -15,7 +15,7 @@ class TestExecutionEnvironment : public MicroPython::ExecutionEnvironment { size_t m_printTextIndex; }; -TestExecutionEnvironment init_environement(); +TestExecutionEnvironment init_environnement(); void deinit_environment(); void assert_script_execution_succeeds(const char * script, const char * outputText = nullptr); diff --git a/python/test/ion.cpp b/python/test/ion.cpp index 991f47a6b23..e1fb7c9fbb2 100644 --- a/python/test/ion.cpp +++ b/python/test/ion.cpp @@ -3,14 +3,14 @@ QUIZ_CASE(python_ion_import) { // Test "from ion import *" - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_fails(env, "keydown(KEY_LEFT)"); assert_command_execution_succeeds(env, "from ion import *"); assert_command_execution_succeeds(env, "keydown(KEY_LEFT)"); deinit_environment(); // "import ion" - env = init_environement(); + env = init_environnement(); assert_command_execution_fails(env, "ion.keydown(ion.KEY_LEFT)"); assert_command_execution_succeeds(env, "import ion"); assert_command_execution_succeeds(env, "ion.keydown(ion.KEY_LEFT)"); @@ -18,7 +18,7 @@ QUIZ_CASE(python_ion_import) { } QUIZ_CASE(python_ion_keydown) { - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_succeeds(env, "from ion import *"); assert_command_execution_succeeds(env, "keydown(KEY_LEFT)", "False\n"); deinit_environment(); diff --git a/python/test/kandinsky.cpp b/python/test/kandinsky.cpp index 516e0d00881..0226f83f61d 100644 --- a/python/test/kandinsky.cpp +++ b/python/test/kandinsky.cpp @@ -3,14 +3,14 @@ QUIZ_CASE(python_kandinsky_import) { // Test "from kandinsky import *" - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_fails(env, "get_pixel(0,0)"); assert_command_execution_succeeds(env, "from kandinsky import *"); assert_command_execution_succeeds(env, "get_pixel(0,0)"); deinit_environment(); // "import kandinsky" - env = init_environement(); + env = init_environnement(); assert_command_execution_fails(env, "kandinsky.get_pixel(0,0)"); assert_command_execution_succeeds(env, "import kandinsky"); assert_command_execution_succeeds(env, "kandinsky.get_pixel(0,0)"); @@ -18,7 +18,7 @@ QUIZ_CASE(python_kandinsky_import) { } QUIZ_CASE(python_kandinsky_basics) { - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_succeeds(env, "from kandinsky import *"); assert_command_execution_succeeds(env, "set_pixel(0,0,color(12,12,12))"); assert_command_execution_succeeds(env, "get_pixel(0,0)"); diff --git a/python/test/math.cpp b/python/test/math.cpp index e7e4c06f7fd..0c63d86764c 100644 --- a/python/test/math.cpp +++ b/python/test/math.cpp @@ -2,7 +2,7 @@ #include "execution_environment.h" QUIZ_CASE(python_math) { - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_succeeds(env, "from math import *"); assert_command_execution_succeeds(env, "e", "2.718281828459045\n"); assert_command_execution_succeeds(env, "gamma(3)", "2.0\n"); @@ -10,7 +10,7 @@ QUIZ_CASE(python_math) { } QUIZ_CASE(python_cmath) { - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_succeeds(env, "from cmath import *"); assert_command_execution_succeeds(env, "cos(0)", "(1+-0j)\n"); deinit_environment(); diff --git a/python/test/matplotlib.cpp b/python/test/matplotlib.cpp index 3ba0bd6df83..d7fb456de70 100644 --- a/python/test/matplotlib.cpp +++ b/python/test/matplotlib.cpp @@ -3,28 +3,28 @@ QUIZ_CASE(python_matplotlib_pyplot_import) { // Test "from matplotlib.pyplot import *" - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_fails(env, "arrow(2,3,4,5)"); assert_command_execution_succeeds(env, "from matplotlib.pyplot import *"); assert_command_execution_succeeds(env, "arrow(2,3,4,5)"); deinit_environment(); // "from matplotlib import *" - env = init_environement(); + env = init_environnement(); assert_command_execution_fails(env, "pyplot.arrow(2,3,4,5)"); assert_command_execution_succeeds(env, "from matplotlib import *"); assert_command_execution_succeeds(env, "pyplot.arrow(2,3,4,5)"); deinit_environment(); // "import matplotlib" - env = init_environement(); + env = init_environnement(); assert_command_execution_fails(env, "matplotlib.pyplot.arrow(2,3,4,5)"); assert_command_execution_succeeds(env, "import matplotlib"); assert_command_execution_succeeds(env, "matplotlib.pyplot.arrow(2,3,4,5)"); deinit_environment(); // "import matplotlib.pyplot" - env = init_environement(); + env = init_environnement(); assert_command_execution_fails(env, "matplotlib.pyplot.arrow(2,3,4,5)"); assert_command_execution_succeeds(env, "import matplotlib.pyplot"); assert_command_execution_succeeds(env, "matplotlib.pyplot.arrow(2,3,4,5)"); @@ -32,7 +32,7 @@ QUIZ_CASE(python_matplotlib_pyplot_import) { } QUIZ_CASE(python_matplotlib_pyplot_arrow) { - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_succeeds(env, "from matplotlib.pyplot import *"); assert_command_execution_succeeds(env, "arrow(2,3,4,5)"); assert_command_execution_fails(env, "arrow(2,3,4,5, 0.1)"); @@ -45,7 +45,7 @@ QUIZ_CASE(python_matplotlib_pyplot_arrow) { } QUIZ_CASE(python_matplotlib_pyplot_axis) { - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_succeeds(env, "from matplotlib.pyplot import *"); assert_command_execution_succeeds(env, "axis((2,3,4,5))"); assert_command_execution_succeeds(env, "axis([2,3,4,5])"); @@ -57,7 +57,7 @@ QUIZ_CASE(python_matplotlib_pyplot_axis) { } QUIZ_CASE(python_matplotlib_pyplot_bar) { - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_succeeds(env, "from matplotlib.pyplot import *"); assert_command_execution_succeeds(env, "bar([0,2,3],[10,12,23])"); assert_command_execution_succeeds(env, "bar([0,2,3],10)"); @@ -71,7 +71,7 @@ QUIZ_CASE(python_matplotlib_pyplot_bar) { } QUIZ_CASE(python_matplotlib_pyplot_grid) { - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_succeeds(env, "from matplotlib.pyplot import *"); assert_command_execution_succeeds(env, "grid(True)"); assert_command_execution_succeeds(env, "grid()"); @@ -79,7 +79,7 @@ QUIZ_CASE(python_matplotlib_pyplot_grid) { } QUIZ_CASE(python_matplotlib_pyplot_hist) { - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_succeeds(env, "from matplotlib.pyplot import *"); assert_command_execution_succeeds(env, "hist([2,3,4,5,6])"); assert_command_execution_succeeds(env, "hist([2,3,4,5,6],23)"); @@ -91,7 +91,7 @@ QUIZ_CASE(python_matplotlib_pyplot_hist) { } QUIZ_CASE(python_matplotlib_pyplot_plot) { - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_succeeds(env, "from matplotlib.pyplot import *"); assert_command_execution_succeeds(env, "plot([2,3,4,5,6])"); assert_command_execution_succeeds(env, "plot(2,3)"); @@ -103,7 +103,7 @@ QUIZ_CASE(python_matplotlib_pyplot_plot) { } QUIZ_CASE(python_matplotlib_pyplot_scatter) { - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_succeeds(env, "from matplotlib.pyplot import *"); assert_command_execution_succeeds(env, "scatter(2,3)"); assert_command_execution_succeeds(env, "scatter([2,3,4,5,6],[3,4,5,6,7])"); @@ -114,7 +114,7 @@ QUIZ_CASE(python_matplotlib_pyplot_scatter) { } QUIZ_CASE(python_matplotlib_pyplot_text) { - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_succeeds(env, "from matplotlib.pyplot import *"); assert_command_execution_succeeds(env, "text(2,3,'hello')"); assert_command_execution_succeeds(env, "show()"); diff --git a/python/test/random.cpp b/python/test/random.cpp index 9ff3acb0930..c3a2db3b0fa 100644 --- a/python/test/random.cpp +++ b/python/test/random.cpp @@ -2,7 +2,7 @@ #include "execution_environment.h" QUIZ_CASE(python_random) { - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_fails(env, "random()"); assert_command_execution_succeeds(env, "from random import *"); assert_command_execution_succeeds(env, "random()"); diff --git a/python/test/time.cpp b/python/test/time.cpp index a2baa3fa0c2..f1c2b7896cf 100644 --- a/python/test/time.cpp +++ b/python/test/time.cpp @@ -2,7 +2,7 @@ #include "execution_environment.h" QUIZ_CASE(python_time) { - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_fails(env, "monotonic()"); assert_command_execution_succeeds(env, "from time import *"); assert_command_execution_succeeds(env, "monotonic()"); diff --git a/python/test/turtle.cpp b/python/test/turtle.cpp index e5f5572af13..f6984970324 100644 --- a/python/test/turtle.cpp +++ b/python/test/turtle.cpp @@ -4,7 +4,7 @@ // TODO: to be completed QUIZ_CASE(python_turtle) { - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_fails(env, "forward(3)"); assert_command_execution_succeeds(env, "from turtle import *"); assert_command_execution_succeeds(env, "reset()"); @@ -28,7 +28,7 @@ QUIZ_CASE(python_turtle) { QUIZ_CASE(python_turtle_circle) { // Turtle position should be unchanged after a complete circle - TestExecutionEnvironment env = init_environement(); + TestExecutionEnvironment env = init_environnement(); assert_command_execution_succeeds(env, "from turtle import *"); assert_command_execution_succeeds(env, "goto(0,0)"); assert_command_execution_succeeds(env, "circle(28)"); diff --git a/themes/themes_manager.py b/themes/themes_manager.py index 18551e8fb38..b13d2bcb223 100644 --- a/themes/themes_manager.py +++ b/themes/themes_manager.py @@ -215,7 +215,7 @@ def main(args): if __name__ == "__main__": parser = argparse.ArgumentParser(description="Process the themes.") - parser.add_argument("repo", nargs="?", help="git remote from wtich to get the themes from. Set to \"local\" for included themes") + parser.add_argument("repo", nargs="?", help="git remote from witch to get the themes from. Set to \"local\" for included themes") parser.add_argument("theme", nargs="?", help="the name of the theme") parser.add_argument("output", nargs="?", help="path to the output header file") parser.add_argument("build_dir", nargs="?", help="path to the output folder") From f4ce415e32b4cd0fcfd3bcf1e4c3dc8705b60461 Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Fri, 21 Jan 2022 16:51:58 +0100 Subject: [PATCH 153/355] [apps/code] Fix multiplication in Python app on simulator (#126) --- ion/src/shared/keyboard/layout_B2/layout_events.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/shared/keyboard/layout_B2/layout_events.cpp b/ion/src/shared/keyboard/layout_B2/layout_events.cpp index cc659c32154..6ff1b74b7ee 100644 --- a/ion/src/shared/keyboard/layout_B2/layout_events.cpp +++ b/ion/src/shared/keyboard/layout_B2/layout_events.cpp @@ -12,7 +12,7 @@ const EventData s_dataForEvent[4 * Event::PageSize] = { T("ℯ^(\x11)"), T("ln(\x11)"), T("log(\x11)"), T("𝐢"), T(","), T("^"), T("sin(\x11)"), T("cos(\x11)"), T("tan(\x11)"), T("π"), T("√(\x11)"), T("^2"), T("7"), T("8"), T("9"), T("("), T(")"), U(), - T("4"), T("5"), T("6"), T("×"), T("/"), U(), + T("4"), T("5"), T("6"), T("*"), T("/"), U(), T("1"), T("2"), T("3"), T("+"), T("-"), U(), T("0"), T("."), T("ᴇ"), TL(), TL(), U(), // Shift From 24205a7e03528d413fe201b58096c245bd42091b Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Mon, 24 Jan 2022 18:22:07 +0100 Subject: [PATCH 154/355] Fix CI and replacement character (#133) --- ion/test/events.cpp | 2 +- kandinsky/include/kandinsky/font.h | 2 +- kandinsky/src/font.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ion/test/events.cpp b/ion/test/events.cpp index 0c3fd3298c7..8dbe9bde90a 100644 --- a/ion/test/events.cpp +++ b/ion/test/events.cpp @@ -18,7 +18,7 @@ QUIZ_CASE(ion_events_from_keyboard) { // Test some fallbacks quiz_assert(Event(Key::EXE, false, false, false) == EXE); - quiz_assert(Event(Key::EXE, true, false, false) == EXE); + quiz_assert(Event(Key::EXE, true, false, false) == ShiftEXE); quiz_assert(Event(Key::EXE, false, true, false) == EXE); quiz_assert(Event(Key::EXE, true, true, false) == EXE); quiz_assert(Event(Key::EXE, false, true, true) == EXE); diff --git a/kandinsky/include/kandinsky/font.h b/kandinsky/include/kandinsky/font.h index 3195d01808c..2b7921cced1 100644 --- a/kandinsky/include/kandinsky/font.h +++ b/kandinsky/include/kandinsky/font.h @@ -61,7 +61,7 @@ class KDFont { CodePoint m_codePoint; GlyphIndex m_glyphIndex; }; - static constexpr GlyphIndex IndexForReplacementCharacterCodePoint = 134; + static constexpr GlyphIndex IndexForReplacementCharacterCodePoint = 190; GlyphIndex indexForCodePoint(CodePoint c) const; void setGlyphGrayscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const; diff --git a/kandinsky/src/font.cpp b/kandinsky/src/font.cpp index a1d217521c1..67d39383bf5 100644 --- a/kandinsky/src/font.cpp +++ b/kandinsky/src/font.cpp @@ -155,7 +155,7 @@ KDFont::GlyphIndex KDFont::indexForCodePoint(CodePoint c) const { return endPair->glyphIndex(); } NoMatchingGlyph: - assert(SimpleCodePoints[IndexForReplacementCharacterCodePoint] == 0xFFFD); + assert(ExtendedCodePoints[IndexForReplacementCharacterCodePoint] == 0xFFFD); return IndexForReplacementCharacterCodePoint; #endif } From 38614a849f2c4a5e494e1075cf3cae758c95002f Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Tue, 25 Jan 2022 18:58:25 +0100 Subject: [PATCH 155/355] [apps/python] Add set_brightness and get_brightness in Ion module (#132) --- apps/code/catalog.de.i18n | 2 ++ apps/code/catalog.en.i18n | 2 ++ apps/code/catalog.es.i18n | 2 ++ apps/code/catalog.fr.i18n | 4 +++- apps/code/catalog.hu.i18n | 2 ++ apps/code/catalog.it.i18n | 4 +++- apps/code/catalog.nl.i18n | 4 +++- apps/code/catalog.pt.i18n | 2 ++ apps/code/catalog.universal.i18n | 2 ++ apps/code/python_toolbox.cpp | 2 ++ python/port/genhdr/qstrdefs.in.h | 4 +++- python/port/mod/ion/modion.cpp | 16 ++++++++++++++++ python/port/mod/ion/modion.h | 2 ++ python/port/mod/ion/modion_table.cpp | 12 ++++++++++++ 14 files changed, 56 insertions(+), 4 deletions(-) diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index 403c914a1fc..e66c309a70f 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -105,6 +105,8 @@ PythonIsKeyDown = "Wahr, wenn die Taste k gedrückt ist" PythonBattery = "Rückgabe der Batteriespannung" PythonBatteryLevel = "Gibt den Batteriestand zurück" PythonBatteryIscharging = "Gibt zurück, ob die Batterie geladen wird" +PythonSetBrightness = "Helligkeitsstufe festlegen" +PythonGetBrightness = "Helligkeitsstufe abrufen" PythonKandinskyFunction = "Kandinsky-Modul Funktionspräfix" PythonLdexp = "Liefert x*(2**i), Inverse von frexp" PythonLength = "Länge eines Objekts" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index 472ba88cc92..651954ffb90 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -90,6 +90,8 @@ PythonIsKeyDown = "Return True if the k key is down" PythonBattery = "Return battery voltage" PythonBatteryLevel = "Return battery level" PythonBatteryIscharging = "Return if battery is charging" +PythonSetBrightness = "Set brightness level" +PythonGetBrightness = "Get brightness level" PythonIsNaN = "Check if x is a NaN" PythonKandinskyFunction = "kandinsky module function prefix" PythonLdexp = "Return x*(2**i), inverse of frexp" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index 4b7401d3e47..c58b97e8087 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -90,6 +90,8 @@ PythonIsKeyDown = "Return True if the k key is down" PythonBattery = "Rückgabe der Batteriespannung" PythonBatteryLevel = "Gibt den Batteriestand zurück" PythonBatteryIscharging = "Gibt zurück, ob die Batterie geladen wird" +PythonSetBrightness = "Establecer nivel de brillo" +PythonGetBrightness = "Obtener nivel de brillo" PythonIsNaN = "Check if x is a NaN" PythonKandinskyFunction = "kandinsky module function prefix" PythonLdexp = "Return x*(2**i), inverse of frexp" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index 0fc97fd7400..1e348cda30f 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -90,6 +90,8 @@ PythonIsKeyDown = "Renvoie True si touche k enfoncée" PythonBattery = "Renvoie le voltage de la batterie" PythonBatteryLevel = "Renvoie le niveau de la batterie" PythonBatteryIscharging = "Chargement en cours" +PythonSetBrightness = "Définir le niveau de luminosité" +PythonGetBrightness = "Obtenir le niveau de luminosité" PythonIsNaN = "Teste si x est NaN" PythonKandinskyFunction = "Préfixe fonction module kandinsky" PythonLdexp = "Inverse de frexp : x*(2**i)" @@ -127,7 +129,7 @@ PythonRandrange = "Nombre dans range(start,stop)" PythonRangeStartStop = "Liste de start à stop-1" PythonRangeStop = "Liste de 0 à stop-1" PythonRect = "Conversion en algébrique" -PythonRemove = "Supprime le premier x de la liste" +PythonRemove = "Supprime le premier x de la liste" PythonReverse = "Inverse les éléments de la liste" PythonRound = "Arrondi à n décimales" PythonScatter = "Nuage des points (x,y)" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index 13654180758..c3c8cb00ed4 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -90,6 +90,8 @@ PythonIsKeyDown = "True-t válaszol ha a k gomb le van nyomva" PythonBattery = "Az akkumulátor feszültségének visszaadása" PythonBatteryLevel = "Az akkumulátor töltöttségi szintjének visszaadása" PythonBatteryIscharging = "Visszaadja, ha az akkumulátor töltődik" +PythonSetBrightness = "Fényerőszint beállítása" +PythonGetBrightness = "Get brightness level" PythonIsNaN = "Ellenörizze hogy x nem NaN" PythonKandinskyFunction = "kandinsky modul funkció elötag" PythonLdexp = "frexp ellentéte : x*(2**i)" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index f525e7297f1..c029afabc4b 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -104,6 +104,8 @@ PythonIsKeyDown = "Restituisce True premendo tasto k" PythonBattery = "Restituisce la tensione della batteria" PythonBatteryLevel = "Restituisce il livello della batteria" PythonBatteryIscharging = "Restituisce se la batteria è in carica" +PythonSetBrightness = "Imposta livello di luminosità" +PythonGetBrightness = "Ottieni livello di luminosità" PythonIsNaN = "Testa se x è NaN" PythonKandinskyFunction = "Prefisso funzione modulo kandinsky" PythonLdexp = "Inversa di frexp : x*(2**i)" @@ -141,7 +143,7 @@ PythonRandrange = "Numero dentro il range(start, stop)" PythonRangeStartStop = "Lista da start a stop-1" PythonRangeStop = "Lista da 0 a stop-1" PythonRect = "Converte in coordinate algebriche" -PythonRemove = "Cancella la prima x dalla lista" +PythonRemove = "Cancella la prima x dalla lista" PythonReverse = "Inverte gli elementi della lista" PythonRound = "Arrotondato a n cifre decimali" PythonScatter = "Diagramma dispersione y in f. di x" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index 2a1373594a9..66a9b934453 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -105,6 +105,8 @@ PythonIsKeyDown = "Geef True als k toets omlaag is" PythonBattery = "Return batterijspanning" PythonBatteryLevel = "Batterijniveau teruggeven" PythonBatteryIscharging = "Keer terug als de batterij wordt opgeladen" +PythonSetBrightness = "Set brightness level" +PythonGetBrightness = "Get brightness level" PythonIsNaN = "Controleer of x geen getal is" PythonKandinskyFunction = "kandinsky module voorvoegsel" PythonLdexp = "Geeft x*(2**i), inversie van frexp" @@ -149,7 +151,7 @@ PythonScatter = "Teken scatterplot van y versus x" PythonSeed = "Start willek. getallengenerator" PythonSetPixel = "Kleur pixel (x,y)" PythonShow = "Figuur weergeven" -PythonSin= "Sinus" +PythonSin = "Sinus" PythonSinh = "Sinus hyperbolicus" PythonSleep = "Stel executie voor t seconden uit" PythonLocalTime = "Zet tijd om in tuple" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index ba1a2731103..c7f8e2ab49c 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -90,6 +90,8 @@ PythonIsKeyDown = "Devolve True se tecla k pressionada" PythonBattery = "Retornar a voltagem da bateria" PythonBatteryLevel = "Retornar nível de bateria" PythonBatteryIscharging = "Retorne se a bateria estiver carregando" +PythonSetBrightness = "Definir nível de brilho" +PythonGetBrightness = "Obter nível de brilho" PythonIsNaN = "Verificar se x é um NaN" PythonKandinskyFunction = "Prefixo da função do módulo kandinsky" PythonLdexp = "Devolve x*(2**i), inverso de frexp" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index be9ca2f03bf..967125d72fa 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -163,6 +163,8 @@ PythonCommandIsKeyDown = "keydown(k)" PythonCommandBattery = "battery()" PythonCommandBatteryLevel = "battery_level()" PythonCommandBatteryIscharging = "battery_ischarging()" +PythonCommandSetBrightness = "set_brightness()" +PythonCommandGetBrightness = "get_brightness()" PythonCommandLdexp = "ldexp(x,i)" PythonCommandLength = "len(object)" PythonCommandLgamma = "lgamma(x)" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index f08bdcacd77..3f073401e03 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -376,6 +376,8 @@ const ToolboxMessageTree IonModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBattery, I18n::Message::PythonBattery), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBatteryLevel, I18n::Message::PythonBatteryLevel), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBatteryIscharging, I18n::Message::PythonBatteryIscharging), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSetBrightness, I18n::Message::PythonSetBrightness), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetBrightness, I18n::Message::PythonGetBrightness), ToolboxMessageTree::Leaf(I18n::Message::IonSelector, I18n::Message::IonSelector) }; diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index 4e3b493cee5..322b23c0c6b 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -9,7 +9,7 @@ * - "cat build/genhdr/qstrdefs.preprocessed.h|grep '^Q'|uniq". CAUTION: the * order is important, don't sort. * - Insert the result below in the MicroPython QSTRs section - * - remove "QSTR(urandom)" as we renamed it to random + * - remove "QSTR(urandom)" as we renamed it to random * - remove "QSTR(usys)" as we renamed it to sys */ // Global configuration @@ -346,6 +346,8 @@ Q(keydown) Q(battery) Q(battery_level) Q(battery_ischarging) +Q(set_brightness) +Q(get_brightness) Q(KEY_LEFT) Q(KEY_UP) Q(KEY_DOWN) diff --git a/python/port/mod/ion/modion.cpp b/python/port/mod/ion/modion.cpp index a631ebef8ac..c9694a69ef6 100644 --- a/python/port/mod/ion/modion.cpp +++ b/python/port/mod/ion/modion.cpp @@ -4,6 +4,8 @@ extern "C" { #include } #include +#include "apps/apps_container.h" +#include "apps/global_preferences.h" #include "port.h" mp_obj_t modion_keyboard_keydown(mp_obj_t key_o) { @@ -103,3 +105,17 @@ mp_obj_t modion_get_keys() { return result; } + +mp_obj_t modion_set_brightness(mp_obj_t brightness_mp){ + uint8_t brightness = static_cast(mp_obj_get_int(brightness_mp)); + GlobalPreferences::sharedGlobalPreferences()->setBrightnessLevel(brightness); + Ion::Backlight::setBrightness(brightness); + micropython_port_interrupt_if_needed(); + return mp_const_none; +} + +mp_obj_t modion_get_brightness(){ + uint8_t brightness = GlobalPreferences::sharedGlobalPreferences()->brightnessLevel(); + micropython_port_interrupt_if_needed(); + return mp_obj_new_int((int)brightness); +} diff --git a/python/port/mod/ion/modion.h b/python/port/mod/ion/modion.h index 88367300cfd..a1431ecf37a 100644 --- a/python/port/mod/ion/modion.h +++ b/python/port/mod/ion/modion.h @@ -5,4 +5,6 @@ mp_obj_t modion_battery(); mp_obj_t modion_battery_level(); mp_obj_t modion_battery_ischarging(); mp_obj_t modion_get_keys(); +mp_obj_t modion_set_brightness(mp_obj_t brightness_mp); +mp_obj_t modion_get_brightness(); extern const mp_obj_type_t file_type; diff --git a/python/port/mod/ion/modion_table.cpp b/python/port/mod/ion/modion_table.cpp index aa496a95576..dfe266abf1a 100644 --- a/python/port/mod/ion/modion_table.cpp +++ b/python/port/mod/ion/modion_table.cpp @@ -36,6 +36,16 @@ const mp_obj_fun_builtin_fixed_t modion_battery_ischarging_obj = { {(mp_fun_0_t)modion_battery_ischarging} }; +const mp_obj_fun_builtin_fixed_t modion_set_brightness_obj = { + {&mp_type_fun_builtin_1}, + {(mp_fun_0_t)modion_set_brightness} +}; + +const mp_obj_fun_builtin_fixed_t modion_get_brightness_obj = { + {&mp_type_fun_builtin_0}, + {(mp_fun_0_t)modion_get_brightness} +}; + extern "C" const mp_rom_map_elem_t modion_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ion) }, { MP_ROM_QSTR(MP_QSTR_battery), MP_ROM_PTR(&modion_battery_obj) }, @@ -43,6 +53,8 @@ extern "C" const mp_rom_map_elem_t modion_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_battery_ischarging), MP_ROM_PTR(&modion_battery_ischarging_obj) }, { MP_ROM_QSTR(MP_QSTR_keydown), MP_ROM_PTR(&modion_keyboard_keydown_obj) }, { MP_ROM_QSTR(MP_QSTR_get_keys), MP_ROM_PTR(&modion_get_keys_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_brightness), MP_ROM_PTR(&modion_set_brightness_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_brightness), MP_ROM_PTR(&modion_get_brightness_obj) }, { MP_ROM_QSTR(MP_QSTR_KEY_LEFT), MP_OBJ_NEW_SMALL_INT(Ion::Keyboard::Key::Left) }, { MP_ROM_QSTR(MP_QSTR_KEY_UP), MP_OBJ_NEW_SMALL_INT(Ion::Keyboard::Key::Up) }, { MP_ROM_QSTR(MP_QSTR_KEY_DOWN), MP_OBJ_NEW_SMALL_INT(Ion::Keyboard::Key::Down) }, From 1faac23ee894ef59fa4f089eca81c7a7dd1681b2 Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Tue, 25 Jan 2022 20:17:07 +0100 Subject: [PATCH 156/355] [apps/settings] Fix assert in about submenu when username enabled (#134) --- apps/settings/sub_menu/about_controller.cpp | 1 - apps/settings/sub_menu/about_controller.h | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/settings/sub_menu/about_controller.cpp b/apps/settings/sub_menu/about_controller.cpp index 73316f2b689..901034a513b 100644 --- a/apps/settings/sub_menu/about_controller.cpp +++ b/apps/settings/sub_menu/about_controller.cpp @@ -168,7 +168,6 @@ bool AboutController::hasUsernameCell() const { void AboutController::willDisplayCellForIndex(HighlightCell * cell, int index) { int i = index + (!hasUsernameCell()); GenericSubController::willDisplayCellForIndex(cell, i); - assert(index >= 0 && index < k_totalNumberOfCell); if (m_messageTreeModel->childAtIndex(i)->label() == I18n::Message::Contributors) { MessageTableCellWithChevronAndMessage * myTextCell = (MessageTableCellWithChevronAndMessage *)cell; myTextCell->setSubtitle(I18n::Message::Default); diff --git a/apps/settings/sub_menu/about_controller.h b/apps/settings/sub_menu/about_controller.h index 9a92b70f925..f72017af001 100644 --- a/apps/settings/sub_menu/about_controller.h +++ b/apps/settings/sub_menu/about_controller.h @@ -22,7 +22,7 @@ class AboutController : public GenericSubController { int typeAtLocation(int i, int j) override; int numberOfRows() const override; private: - constexpr static int k_totalNumberOfCell = 9; + constexpr static int k_totalNumberOfCell = 8; bool hasUsernameCell() const; ContributorsController m_contributorsController; MessageTableCellWithChevronAndMessage m_contributorsCell; From 1a546e011bbc56ee0b141bef968392e55dbe988f Mon Sep 17 00:00:00 2001 From: lolocomotive <49951010+lolocomotive@users.noreply.github.com> Date: Tue, 1 Feb 2022 19:43:46 +0100 Subject: [PATCH 157/355] Upload artifacts to google cloud (#138) --- .github/workflows/ci-workflow.yml | 72 +++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 4e6ff53c393..0f3d3a77fa6 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -50,6 +50,18 @@ jobs: with: submodules: 'recursive' - run: make -j2 PLATFORM=simulator TARGET=android + - id: 'auth' + if: ${{ github.event_name == 'push' }} + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' + - id: 'upload-file' + if: ${{ github.event_name == 'push' }} + uses: 'google-github-actions/upload-cloud-storage@v0' + with: + path: 'output/release/simulator/android/epsilon.apk' + destination: 'upsilon-binfiles.appspot.com/dev/simulator/' + parent: false - uses: actions/upload-artifact@master with: name: epsilon-android.apk @@ -91,6 +103,18 @@ jobs: - run: mv output/release/device/n0100/flasher.light.bin final-output/flasher.light.bin - run: find final-output/ -type f -exec bash -c "shasum -a 256 -b {} > {}.sha256" \; - run: tar cvfz binpack-n0100.tgz final-output/* + - id: 'auth' + if: ${{ github.event_name == 'push'}} + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' + - id: 'upload-directory' + if: ${{ github.event_name == 'push'}} + uses: 'google-github-actions/upload-cloud-storage@v0' + with: + path: 'final-output/' + destination: 'upsilon-binfiles.appspot.com/dev/n100/' + parent: false - uses: actions/upload-artifact@master with: name: epsilon-binpack-n0100.tgz @@ -113,6 +137,18 @@ jobs: - run: make -j2 bench.flash.dfu - run: make -j2 binpack - run: cp output/release/device/n0110/binpack-n0110-`git rev-parse HEAD | head -c 7`.tgz output/release/device/n0110/binpack-n0110.tgz + - id: 'auth' + if: ${{ github.event_name == 'push' }} + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' + - id: 'upload-directory' + if: ${{ github.event_name == 'push' }} + uses: 'google-github-actions/upload-cloud-storage@v0' + with: + path: 'output/release/device/n0110/binpack/' + destination: 'upsilon-binfiles.appspot.com/dev/n110/' + parent: false - uses: actions/upload-artifact@master with: name: epsilon-binpack-n0110.tgz @@ -131,6 +167,18 @@ jobs: - run: make -j2 PLATFORM=simulator - run: make -j2 PLATFORM=simulator test.exe - run: cmd /c output\release\simulator\windows\test.exe --headless + - id: 'auth' + if: ${{ github.event_name == 'push' }} + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' + - id: 'upload-file' + if: ${{ github.event_name == 'push' }} + uses: 'google-github-actions/upload-cloud-storage@v0' + with: + path: 'output/release/simulator/windows/epsilon.exe' + destination: 'upsilon-binfiles.appspot.com/dev/simulator/' + parent: false - uses: actions/upload-artifact@master with: name: epsilon-windows.exe @@ -147,6 +195,18 @@ jobs: - run: make -j2 PLATFORM=simulator TARGET=web - run: make -j2 PLATFORM=simulator TARGET=web test.js - run: node output/release/simulator/web/test.js --headless + - id: 'auth' + if: ${{ github.event_name == 'push' }} + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' + - id: 'upload-file' + if: ${{ github.event_name == 'push' }} + uses: 'google-github-actions/upload-cloud-storage@v0' + with: + path: 'output/release/simulator/web/epsilon.js' + destination: 'upsilon-binfiles.appspot.com/dev/simulator/' + parent: false - uses: actions/upload-artifact@master with: name: epsilon-web.zip @@ -161,6 +221,18 @@ jobs: - run: make -j2 PLATFORM=simulator - run: make -j2 PLATFORM=simulator test.bin - run: output/release/simulator/linux/test.bin --headless + - id: 'auth' + if: ${{ github.event_name == 'push' }} + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' + - id: 'upload-file' + if: ${{ github.event_name == 'push' }} + uses: 'google-github-actions/upload-cloud-storage@v0' + with: + path: 'output/release/simulator/linux/epsilon.bin' + destination: 'upsilon-binfiles.appspot.com/dev/simulator/' + parent: false - uses: actions/upload-artifact@master with: name: epsilon-linux.bin From 814e2dee066a3e6b250e96904c7b12d79f6f9276 Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Wed, 2 Feb 2022 22:18:04 +0100 Subject: [PATCH 158/355] [python] Enable math.factorial (#141) --- python/port/genhdr/qstrdefs.in.h | 1 + python/port/mpconfigport.h | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index 322b23c0c6b..53c2f0b85c3 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -181,6 +181,7 @@ Q(exp) Q(expm1) Q(extend) Q(fabs) +Q(factorial) Q(filter) Q(find) Q(float) diff --git a/python/port/mpconfigport.h b/python/port/mpconfigport.h index 38e98145ac1..9175793b25b 100644 --- a/python/port/mpconfigport.h +++ b/python/port/mpconfigport.h @@ -82,6 +82,12 @@ // Whether to provide special math functions: math.{erf,erfc,gamma,lgamma} #define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1) +// Whether to provide math.factorial function +#define MICROPY_PY_MATH_FACTORIAL (1) + +// Whether math.factorial is large, fast and recursive (1) or small and slow (0). +#define MICROPY_OPT_MATH_FACTORIAL (0) + // Whether to provide "cmath" module #define MICROPY_PY_CMATH (1) From 100917f29f4e1c189977c7eaea71708f88144e15 Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Wed, 2 Feb 2022 22:18:45 +0100 Subject: [PATCH 159/355] [build] Add auto translate script (#140) --- Makefile | 5 + build/utilities/translate.py | 436 +++++++++++++++++++++++++++++++++++ 2 files changed, 441 insertions(+) create mode 100755 build/utilities/translate.py diff --git a/Makefile b/Makefile index fa4c53c50c7..1a424b3ef42 100644 --- a/Makefile +++ b/Makefile @@ -209,3 +209,8 @@ clean_run: cleanandcompile .PHONY: run run: compile ${MAKE} start + +.PHONY: translations +translations: + @echo "TRANSLATIONS" + $(Q) ${PYTHON} build/utilities/translate.py diff --git a/build/utilities/translate.py b/build/utilities/translate.py new file mode 100755 index 00000000000..72f851e454c --- /dev/null +++ b/build/utilities/translate.py @@ -0,0 +1,436 @@ +#!/usr/bin/env python3 +"""Generate missing translations for i18n files.""" +# Build-in library imports +import os +import re +import sys +import argparse + +# Deep translator import +try: + from deep_translator import GoogleTranslator +except ModuleNotFoundError: + print('Module deep_translator not found. Please install it with ', + '"pip3 install deep_translator"') + sys.exit(1) + + +# Initialize the parser +parser = argparse.ArgumentParser(description='Generate missing translations' + + ' for i18n files', epilog='You can execute' + + ' this script without arguments') +# Add arguments to the parser +parser.add_argument("--default-input-language", default="en", + help="The language code of the input language") +parser.add_argument("--base-path", default=".", + help="The base path of the i18n files") +parser.add_argument("--no-recursive", default=False, action="store_true", + help="Disable finding the i18n files recursively") +parser.add_argument("--dry-run", default=False, action="store_true", + help="Disable saving the i18n files") +# Parse command line arguments +args = parser.parse_args() + +# Save the user language into the global variable +DEFAULT_INPUT_LANGUAGE = args.default_input_language +# Save the base path into the global variable +BASE_PATH = args.base_path +# Save if i18n files can be found recursively +FIND_RECURSIVE = not args.no_recursive +# Save if dryrun is enabled +DRYRUN = args.dry_run + + +def translate(text: str, output_language: str, input_language: str = 'auto')\ + -> str: + """Translate the given text. + + Args: + text (str): The text to translate + output_language (str): The output language + input_language (str): The input language + + Returns: + str: The translated text + + """ + # Ensure of the text exists (not empty) + if not text: + return "" + # Initialize the translator + translator = GoogleTranslator(source=input_language, + target=output_language) + # Translate and return the translated text + return translator.translate(text) + + +def get_all_files_in_directory(directory: str) -> list[str]: + """Get all files in the given directory recursively. + + Args: + directory (str): The directory + + Returns: + List[str]: The list of files in the directory + + """ + # Initialize the list of files + files = [] + # Iterate over all files in the directory + for i in os.listdir(directory): + # Add the current directory to the file name + i = directory + '/' + i + # If the file is a directory + if FIND_RECURSIVE and os.path.isdir(i): + # Iter the directory and add the files to the list + files.extend(get_all_files_in_directory(i)) + # Else the file is a file + else: + # Add the file to the list + files.append(i) + # Return the list of files + return files + + +def get_i18n_files(directory: str = '.') -> dict[str, list[str]]: + """Get the list of i18n files in the given directory. + + Args: + directory (str, optional): The directory to find the i18n files. + Defaults to '.'. + + Returns: + dict[list[str]]: The list of i18n files in a dictionary of languages. + + """ + # Get all files in the directory recursively + files = get_all_files_in_directory(directory) + # Initialize the dictionary + i18n_files_language: dict[str, list[str]] = {} + # Iterate over all files in the directory + for i in files: + # If the file is an i18n file + if ".i18n" in i: + # Get the language of the file + file_language = i.split('.')[-2] + # If the dictionary already contains the language + if file_language in i18n_files_language: + # Append the file to the dictionary + i18n_files_language[file_language].append(i) + # Else add the language to the dictionary + else: + # Add the file to the dictionary + i18n_files_language[file_language] = [i] + # Return the dictionary + return i18n_files_language + + +def need_to_be_translated(keys: dict[str, list[list[str]]])\ + -> dict[str, list[list[str]]]: + """Return the key that needs to be translated by locale. + + Args: + keys (dict[str, list[str]]): The keys of the i18n files + + Returns: + dict[str, list[str]]: The keys that needs to be translated, + sorted by locale + + """ + # Initialize the list of keys + keys_list: list[list[str]] = [] + keys_to_translate: dict[str, list[list[str]]] = {} + # Iterate over all locales + for value_ in keys.values(): + # Iterate on keys of the locale + for key in value_: + # Skip if the key is already in the list + if key[0] in keys_list: + continue + # Else add the key to the list + keys_list.append(key) + for locale, value in keys.items(): + # Initialize the list of keys in the locale + keys_in_locale: list[str] = [i[0] for i in value] + # Get the keys of keys that need to be translated + keys_to_translate_in_locale: list[list[str]] = [ + key for key in keys_list if key[0] not in keys_in_locale + ] + # Remove duplicates from the list + # Initialize the deduplicated list + keys_to_translate_in_locale_deduplicated: list[list[str]] = [] + # Iterate over the duplicated list + for item in keys_to_translate_in_locale: + # If the key is not in the deduplicated list, add it + if item not in keys_to_translate_in_locale_deduplicated: + keys_to_translate_in_locale_deduplicated.append(item) + # Else, ignore the key, because it is already in the list + # Save the deduplicated list into the dictionary + keys_to_translate[locale] = keys_to_translate_in_locale_deduplicated + return keys_to_translate + + +def get_keys_in_file(filename: str) -> list[list[str]]: + """Return a list of keys in the file. + + Args: + filename (str): The name of the file to read + + Returns: + list[str]: The keys in the file + + """ + # Initialize the list of keys in the file + keys = [] + # Open the file read only + with open(filename, 'r', encoding='utf-8') as file_read: + # Read the content of the file line by line + for line in file_read.readlines(): + # Ignore commented lines + if re.match(r"^#(.*)$", line): + continue + # Ignore lines without = + if '=' not in line: + continue + # Get the key by spliting the line by = + key = line.split("=")[0] + # Remove spaces from the start of the key + while key[0] == " ": + key = key[1:] + # Remove spaces from the end of the key + while key[-1] == " ": + key = key[:-1] + # Get generic filename into a list separated by dots + generic_filename_list = filename.split(".")[:-2] + # Get the filename as string with a trailing dot at the end + generic_filename = "".join(i + "." for i in generic_filename_list) + # Remove trailing dot from the end of the generic filename + generic_filename = generic_filename[:-1] + # Add the key and the generic filename to the list of keys + keys.append([key, generic_filename]) + return keys + + +def list_keys(i18n_files: dict[str, list[str]]) -> dict[str, list[list[str]]]: + """List all keys in the i18n files. + + Args: + i18n_files (dict[str, list[str]]): I18n files list + + Returns: + dict[str, list[str]]: The dictionnary of keys in the i18n files by + locale. + + """ + # Initialize the list of keys in the i18n files + keys_dict: dict[str, list[list[str]]] = {} + # Iterate on the locales + for locale in i18n_files: + # Initialize the dictionary for the locale + keys_dict[locale] = [] + # Iterate on the locale's files + for actual_file in i18n_files[locale]: + # Get the keys in the file and add them to the list + keys_dict[locale].extend(get_keys_in_file(actual_file)) + # Return the dictionary of keys in the i18n files sorted by locale + return keys_dict + + +def get_value_from_key(key_to_find: str, filename_generic: str, locale: str)\ + -> str: + """Get the value of a key in the i18n files from the given locale. + + Args: + key (str): The key to fetch the value + filename_generic (str): The path to the file where the value will be + fetched + locale (str): The locale from the value will be retrieved + + Returns: + str: The value of the key from the given file + + """ + # Get the filename from the generic filename + file_path = filename_generic + '.' + locale + '.i18n' + # Exit if the file does not exist + if not os.path.exists(file_path): + return "" + # Open the file for reading + with open(file_path, 'r', encoding='utf-8') as file_read: + # Read the file line by line + for line in file_read.readlines(): + # Ignore commented lines + if re.match(r"^#(.*)$", line): + continue + # Ignore lines without = + if '=' not in line: + continue + # Get the key and value by spliting the line by = + key = line.split("=")[0] + value = "".join(i + " " for i in line.split("=")[1:]) + # print(key, value) + # Remove spaces from the start of the key + while key[0] == " ": + key = key[1:] + # Remove spaces from the end of the key + while key[-1] == " ": + key = key[:-1] + # Remove spaces from the start of the value + while value[0] == " ": + value = value[1:] + # Remove spaces from the end of the value + while value[-1] in [" ", "\n"]: + value = value[:-1] + # Remove the quotes from the value + value = value[1:][:-1] + if key == key_to_find: + # Add a space to the end of the value if the key is empty, + # because an empty value is parsed as a error + if not value: + value = " " + return value + # Return a empty string if no value were found because we can't use bools + # because it made that the linters crash + return "" + + +def translate_missing_keys(keys_to_translate: dict[str, list[list[str]]], + input_language: str) -> dict[str, list[list[str]]]: + """Get a dictionary of file with the keys and translations to add. + + Args: + keys_to_translate (dict[str, list[list[str]]]): The list of keys to + translate + input_language (str): The language to get the text that will be + translated + + Returns: + dict[str, list[str]]: The dictionary of files with translations + + """ + # Initialize the dictionary of translations + output: dict[str, list[list[str]]] = {} + # Initialize the variable to store the number of translated keys + keys_translated: int = 0 + # Iterate over the locales of the dictionary of untranslated keys + for locale in keys_to_translate: + # Iterate over the untranslated keys in the locale + for key in keys_to_translate[locale]: + # Get the filename from the generic filename + filename = key[1] + '.' + locale + '.i18n' + # Get the value of the key + value = get_value_from_key(key[0], key[1], input_language) + # If the value was not found in the locale, try other languages + if not value: + print(f"Key '{key[0]}' not found in locale {input_language}") + print("Iterating over all known languages") + # Initialize the variable to store if a value was found to false + value_found = False + # Iterate over all the known languages + for language in keys_to_translate: + # Try to get the value from the language + value = get_value_from_key(key[0], key[1], language) + # If the value was found in the language, break + if value: + print(f"Value found in the language '{language}'") + # Mark the value as been found + value_found = True + # Save the language + input_language = language + break + # Else, continue + # Crash if the value is not available all the known languages + if not value_found: + raise ValueError(f"Key '{key[0]}' not found in all locales") + + # Restore the default value if a space have been + # inserted + if value == ' ': + value = '' + # Translate the value into the requested language + value_translated = translate(value, locale, input_language) + # If the file is already in the dictionary, append the translation + # to it + if filename in output: + output[filename].append([key[0], value_translated]) + # Else create the list for the file in the dictionary + else: + output[filename] = [[key[0], value_translated]] + # Print the key and value + print(key[0] + ' = "' + value_translated + '"') + # Increment the number of translated keys + keys_translated += 1 + # If no keys were translated, print a specific message + if not keys_translated: + print('No keys were translated') + # Else, if only one key was translated, print a specific message + elif keys_translated == 1: + print('One key was translated') + # Else, print a generic message + else: + print(f'{keys_translated} keys were translated') + # Return the dictionary of translations + return output + + +def save_translations(missing_keys_translation: dict[str, list[list[str]]]): + """Save the translations. + + Args: + missing_keys_translation (dict[str, list[list[str]]]): The dictionary + of translations + + """ + # Read the dictionary of files + for filename, keys_for_file in missing_keys_translation.items(): + # Initialize add new line variable to false + add_new_line = False + # Open the file for reading only if it exists + if os.path.exists(filename): + # Open the file for reading + with open(filename, 'r', encoding='utf-8') as file_read: + # Save content of the file into the content variable and + # ensure sure there is a first character (not a empty file) + if content := str(file_read.read()): + # If the last char isn't a newline + if content[-1] != '\n': + # Mark that a newline should be added + add_new_line = True + # Else, print of the file will be created + else: + print(f"{filename} is not found, creating an empty file") + # Open the file for writing + with open(filename, 'a', encoding='utf-8') as file_write: + # If add new line is true, add a new line at the end of the file + if add_new_line: + file_write.write('\n') + # Iterate on the values to be save in the file + for value in keys_for_file: + # Generate the in the style Key = "Value" + line = value[0] + ' = "' + value[1] + '"' + # Write the line at the end of the file + file_write.write(line + '\n') + + +def main(): + """Run the program.""" + # Get the list of i18n files + i18n_files = get_i18n_files(BASE_PATH) + # Get the list of keys in the i18n files + keys = list_keys(i18n_files) + # Delete universals keys from the list of keys + if 'universal' in keys: + del keys['universal'] + # Get the list of keys to translate in the i18n files + keys_to_translate = need_to_be_translated(keys) + # Translate the missings keys + missing_keys_translation = translate_missing_keys(keys_to_translate, + DEFAULT_INPUT_LANGUAGE) + # Save the translations + if not DRYRUN: + save_translations(missing_keys_translation) + else: + print("Dry run enabled, translations aren't saved") + + +main() From a0afbe9916fb5951eca93768fde5fdd67d3dc4e4 Mon Sep 17 00:00:00 2001 From: LeMoustachu Date: Wed, 2 Feb 2022 22:19:36 +0100 Subject: [PATCH 160/355] Update building docs and french readme (#136) --- README.fr.md | 294 +++++++++++++++++++++++++++++++++++------- README.md | 60 +++++---- docs/build/index.md | 307 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 564 insertions(+), 97 deletions(-) diff --git a/README.fr.md b/README.fr.md index 58555c9c808..d30ce8eedd5 100644 --- a/README.fr.md +++ b/README.fr.md @@ -26,74 +26,275 @@ Upsilon est un fork d'Omega, un fork d'Epsilon, l'OS de Numworks tournant sur le ## Installation ### Site web -Rendez-vous sur le [site d'Upsilon](https://lolocomotive.github.io/Upsilon-website) à la section "Installer". +Rendez-vous sur le [site d'Upsilon](https://lolocomotive.github.io/Upsilon-website) à la section "Installer". Si votre calculatrice est reconnue, qu'elle contient une version d'Epsilon inférieure à 16 et que votre navigateur accepte WebUSB, la page vous proposera d'installer Upsilon. Ne débranchez votre calculatrice qu'une fois l'installation terminée. ### Manuelle -Tout d'abord, suivez **la première étape** [ici](https://www.numworks.com/resources/engineering/software/build/), puis : + *Vous pouvez vous référer à ce [site internet](https://www.numworks.com/resources/engineering/software/build/)pour la première étape si vous avez des erreurs* + + + +### 1. Installation du SDK + +
    + +
    + +1.1 Linux + +
    +
    - Modèle n0100 -(note : vous pouvez changer `EPSILON_I18N=fr` en `en`, `nl`, `pt`, `it`, `de`, `es` ou `hu`). +Debian ou Ubuntu + +
    + +Il suffit juste d'installer les dépendances en tapant ces commandes dans un Terminal en mode super-utilisateur. ```bash -git clone --recursive https://github.com/Lauryy06/Upsilon.git -cd Upsilon -git checkout omega-master -make MODEL=n0100 clean -make MODEL=n0100 EPSILON_I18N=fr OMEGA_USERNAME="{Votre nom ici, 15 caractères max}" -j4 -make MODEL=n0100 epsilon_flash +apt-get install build-essential git imagemagick libx11-dev libxext-dev libfreetype6-dev libpng-dev libjpeg-dev pkg-config gcc-arm-none-eabi binutils-arm-none-eabi ``` -Important : N'oubliez pas l'argument `--recursive`, Upsilon a besoin de sous-modules. -Vous pouvez aussi changer le nombre de processus parallèles pendant la compilation en changeant la valeur suivant `-j`. +C'est fait! Vous pouvez aller à l'étape 2. + +
    - Modèle n0110 + +Fedora + +
    + +Installez tout d'abord des outils de développement. ```bash -git clone --recursive https://github.com/Lauryy06/Upsilon.git -cd Upsilon -git checkout omega-master -make clean -make OMEGA_USERNAME="{Votre nom ici, 15 caractères max}" -j4 -make epsilon_flash +dnf install make automake gcc gcc-c++ kernel-devel +``` + +Puis les pquets requis: + +```bash +dnf install git ImageMagick libX11-devel libXext-devel freetype-devel libpng-devel libjpeg-devel pkg-config +``` + +Et enfin la version pour ARM de GCC: + +```bash +dnf install arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++ ``` -Important : N'oubliez pas l'argument `--recursive`, Upsilon a besoin de sous-modules. -Vous pouvez aussi changer le nombre de processus parallèles pendant la compilation en changeant la valeur suivant `-j`. +
    + +
    - Fichiers binaires - -Ces fichiers peuvent être utilisés pour distribuer Upsilon (pour que tout le monde puisse le flasher via [Webdfu_Numworks](https://ti-planet.github.io/webdfu_numworks/)). + +1.2 Mac + +
    + +Il est recommandé d'utiliser [Homebrew](https://brew.sh/). Une fois intsallé, utilisez: +```bash +brew install numworks/tap/epsilon-sdk +``` +Et toutes les dependances seront installées. + +
    + +Vous pouvez aller à l'étape 2. + +
    + +
    + +
    + +1.3 Windows + +[Git](http://git-scm.com) doit être installé. + +
    + +
    + +Avec Msys2/Mingw (Supportés par Numwoks bien qu'il y ait beaucoup de bugs) + +L'environnement de compilation [Msys2](https://www.msys2.org/) est recommandé par Numworks pour obtenir la plupart des outils requis facilement. C'est ici que vous allez copier-colletoutes lecommandes de ce tutoriel. Une fois installé, copier-coller ces deux commandes dans le terminal: + +```bash +pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-pkg-config mingw-w64-x86_64-libusb git make python +echo "export PATH=/mingw64/bin:$PATH" >> .bashrc +``` + +Ensuite, vous devrez installer [GCC toolchain for ARM](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads). Quand il vouest demandde choisir u dossier d'installation, choisissez `C:\msys64\home\User\gcc-arm\`. Il vous faudra ensuite ajouter ce dossier à votre $PATH. Tapez juste: + +```bash +echo "export PATH=$PATH:$HOME/gcc-arm/bin" >> .bashrc +``` +Redémarrez votre terminal et vous pouvez aller à l'étape 2! + +
    + +
    + +Avec WSL 2 + +WSL est un système qui virtualise un environnement GNU/Linux dans Windows. + +Votre version de windows doit être >= 1903. + +#### Installation de WSL + +1. Apuyez simulatanément sur les touches "windows" et "x" puis cliquez sur "Powershell administrateur". Entrez ensuite ceci dans la nouvelle fenêtre: +```powershell +dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux all /norestart +``` +Cette commande active WSL + +```powershell +dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart +``` +Cette commande permet d'autoriser le démarrage des machines signées par Microsoft. + +2. Redémarrez votre ordinateur. + +3. Téléchargez ce fichier [this file](https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi) et suivez les instructions d'installation. + +4. Ouvrez votre fenêtre powershell comme avant et tapez: +```powershell +wsl --set-default-version 2 +``` +5. téléchargez [Ubuntu](https://www.microsoft.com/store/apps/9n6svws3rx71) depuis le Microsoft store. Vous pouvez aussi installer [Debian](https://www.microsoft.com/store/productI9MSVKQC78PK6). + +WSL est maintenant installé. + +### Installation d'usbipd pour connecter la calculatrice à WSL (facultatif) +Pour connecter la calculatrice, il faut installer cet [outil](https://github.com/dorssel/usbipd-win/releases/download/v1.3.0/usbipd-win_1.3.0.msi). Il permet de connecter deperiphériques USpar internet.Suivez les instructions pour installer. + +#### Ubuntu +1. Dans un terminal WSL Ubuntu, tapez: +```bash +sudo apt install linux-tools-5.4.0-77-generic hwdata +``` +2. Editez /etc/sudoers pour que l'on puisse utiliser la commande usbip. Sur Ubutu, cele est fait de cette manière: +```bash +sudo visudo +``` +3. Ajoutez `/usr/lib/linux-tools/5.4.0-77-generic` au début du secure_path. Après édition, la ligne devrait ressembler à: +`Defaults secure_path="/usr/lib/linux-tools/5.4.0-77-generic:/usr/local/sbin:..."` + +#### Debian +1.Si vous utiliser Debian, utilisez cette commande: +```bash +sudo apt install usbip hwdata usbutils +``` + +### Pour connecter la calculatrice à WSL +1. Ouvrez encore un powershell en mode administrateur et tapez: +```powershell + usbipd wsl list +``` +Ceci va lister les périphériques USB connectés à l'ordinateur. Reagrdez le BUSID de votre "Numworks Calculator". + +2. Maintenant, lancez cette commande en remplçant par celui de votre caculatrice: +```powershell +usbipd wsl attach --busid +``` + +Le mot de passe de votre machine WSL vous sera demandé. + +Vous pouvez aller à l'étape 2. + +
    + +
    + +
    + +### 2. Récupérer le code source + + +Le code source est disponible dans une repository git. Récupérez-le de cette manière: ```bash git clone --recursive https://github.com/Lauryy06/Upsilon.git cd Upsilon -git checkout omega-master +git checkout upsilon-dev +``` +
    + + +### 3. Choisissez le système à compiler + + +
    + +Model n0100 + +(note: vous pouvez changer l'argument `EPSILON_I18N=en` avec `fr`, `nl`, `pt`, `it`, `de`, `es` or `hu`). + +```bash +make MODEL=n0100 clean +make MODEL=n0100 EPSILON_I18N=en OMEGA_USERNAME="{Votre nom, maximum 15 caractères}" -j4 +``` + +Maintenant, lancez soit: + +```bash +make MODEL=n0100 epsilon_flash +``` +pour directement flasher la calculatrice après avoir appuyé simultanément sur `reset` et `6` et avoir branché la calculatrice à l'ordinateur. + +
    + +soit: + +```bash +make MODEL=n0100 OMEGA_USERNAME="" binpack -j4 +``` +pour compiler les binpacks que vous pouvez distribuer et flasher depuis le [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). + +
    + +
    + +Model n0110 + +```bash make clean -make MODEL=n0100 OMEGA_USERNAME="" -j8 -make MODEL=n0100 OMEGA_USERNAME="" binpack -j8 -make OMEGA_USERNAME="" -j8 -make OMEGA_USERNAME="" binpack -j8 +make OMEGA_USERNAME="{Votre nom, maximum 15 caractères}" -j4 +`` + +Maintenant, lancez soit: + +```bash +make epsilon_flash +``` +pour directement flasher la calculatrice après avoir appuyé simultanément sur `reset` et `6` et avoir branché la calculatrice à l'ordinateur. + +
    + +soit: + +```bash +make OMEGA_USERNAME="" binpack -j4 ``` +pour compiler les binpacks que vous pouvez distribuer et flasher depuis le [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). -Important : N'oubliez pas l'argument `--recursive`, Upsilon a besoin de sous-modules. -Vous pouvez aussi changer le nombre de processus parallèles pendant la compilation en changeant la valeur suivant `-j`. -
    - Simulateur web - + +Simulateur web + D'abord, installez emsdk : ```bash @@ -107,24 +308,20 @@ source emsdk_env.sh Puis, compilez Upsilon : ```bash -git clone --recursive https://github.com/Lauryy06/Upsilon.git -cd Upsilon -git checkout omega-master make clean -make PLATFORM=simulator TARGET=web OMEGA_USERNAME="{Votre nom ici, 15 caractères max}" -j4 +make PLATFORM=simulator TARGET=web OMEGA_USERNAME="{Votre nom, maximum 15 caractères}" -j4 ``` Le simulateur se trouve dans `output/release/simulator/web/simulator.zip` -Important : N'oubliez pas l'argument `--recursive`, Upsilon a besoin de sous-modules. -Vous pouvez aussi changer le nombre de processus parallèles pendant la compilation en changeant la valeur suivant `-j`.
    - Simulateur 3DS - -Vous aurez besoin de devkitPro et de devkitARM disponible dans votre `$PATH` (instructions [ici](https://devkitpro.org/wiki/Getting_Started) (en anglais)) + +Simulateur pour 3DS + +Il vous faut devkitPro et devkitARM installés et dans votre path (les instructions sont [ici](https://devkitpro.org/wiki/Getting_Started)) ```bash git clone --recursive https://github.com/Lauryy06/Upsilon.git @@ -132,15 +329,20 @@ cd Upsilon git checkout --recursive upsilon-dev make PLATFORM=simulator TARGET=3ds -j ``` - -Vous pouvez ensuite copier epsilon.3dsx sur une carte SD pour l'exécuter depuis le HBC ou utiliser 3dslink pour le lancer via le réseau : +Vous pouvez ensuite mettre epsilon.3dsx sur une carte SDpour le lancer depuis le HBC ou utilisez 3dslink pour le lancer via le réseau: ```bash -3dslink output/release/simulator/3ds/epsilon.3dsx -a +3dslink output/release/simulator/3ds/epsilon.3dsx -a <3DS' IP ADDRESS> ```
    +
    + +Important: n'oubliez pas l'argument `--recursive` Parce qu'Upsilon dépend de submodules. +Aussi, vous pouvez changer le nombre de processus de compilation en parallèles en changeant le nombre après l'argument `-j`. +N'oubliez pas de mettre votre nom à la place `{Votre nom, maximum 15 caractères}`.Si vous n'en voulez pas, enlevez l'argument `OMEGA_USERNAME`. + Si vous avez besoin d'aide, n'hésitez pas à rejoindre notre serveur discord : https://discord.gg/Q9buEMduXG

    Omega Banner Discord

    diff --git a/README.md b/README.md index fb5f3144c39..592dbdbeee9 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Upsilon is a fork of Omega, an user-made OS that runs on the Numworks calculator ### Installer -Go to the [Upsilon website](https://lolocomotive.github.io/Upsilon-website) to the "Install" section. +Go to the [Upsilon website](https://lolocomotive.github.io/Upsilon-website) to the "Install" section. If your calculator is recognized, contains a version of Epsilon lower than 16 and your browser accepts WebUSB, the page will suggest you to install Upsilon. Do not disconnect your calculator until the installation is complete. @@ -46,12 +46,13 @@ Do not disconnect your calculator until the installation is complete.
    + 1.1 Linux
    -
    + Debian or Ubuntu
    @@ -69,26 +70,27 @@ And there you can go to step 2!
    + Fedora
    -First install basics dev tools. +To install basics dev tools: ```bash dnf install make automake gcc gcc-c++ kernel-devel ``` -Then install required packages. +And then install required packages. ```bash -dnf install git ImageMagick libX11-devel libXext-devel freetype-devel libpng-devel libjpeg-devel pkg-config +install git ImageMagick libX11-devel libXext-devel freetype-devel libpng-devel libjpeg-devel pkg-config ``` Then, install GCC cross compiler for ARM. ```bash -dnf install arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++ + dnf install arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++ ```
    @@ -100,14 +102,17 @@ dnf install arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++
    + 1.2 Mac
    It's recommended to use [Homebrew](https://brew.sh/). Once it's installed, just run: + ```bash brew install numworks/tap/epsilon-sdk ``` + and it will install all dependencies.
    @@ -116,24 +121,26 @@ And there you can go to step 2!
    - -
    + 1.3 Windows
    +
    + With Msys2/Mingw (officialized by numworks but with a lot of bugs) -[Msys2](https://www.msys2.org/) environment is recommended by Numworks to get most of the required tools on Windows easily. It's where you'll paste all the commands of this tutorial. Once it's installed, paste these commands into the Msys2 terminal. + +[Msys2](https://www.msys2.org/) environment is recommended by Numworks to get most of the required tools on Windows easily. It's where you'll paste all the commands of this tutorial. Once it'sinstalled, paste these commands into the Msys2 terminal. ```bash pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-pkg-config mingw-w64-x86_64-libusb git make python echo "export PATH=/mingw64/bin:$PATH" >> .bashrc ``` -Next, you'll need to install the [GCC toolchain for ARM](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads). When prompted for an install location, choose `C:\msys64\home\User\gcc-arm\`. You'll then need to add this folder to your $PATH. Just enter: +Next, you'll need to install the [GCC toolchain for ARM](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads). When prompted for aninstall location, choose `C:\msys64\home\User\gcc-arm\`. You'll then need to add this folder to your $PATH. Just enter: ```bash echo "export PATH=$PATH:$HOME/gcc-arm/bin" >> .bashrc @@ -143,8 +150,8 @@ Just restart terminal and you can go to step 2!
    -With WSL 2 +With WSL 2 You need a windows version >= 1903. @@ -168,13 +175,13 @@ This one allows virtual machines developed by Microsoft. 4. Now open powershell admin like before and type: ```powershell wsl --set-default-version 2 -``` + ``` 5. Download [Ubuntu](https://www.microsoft.com/store/apps/9n6svws3rx71) from Microsoft store. WSL is now installed. ### Usbipd installation to connect your calculator -If you want to connect to the calculator, you have to connect to install this [tool](https://github.com/dorssel/usbipd-win/releases/download/v1.3.0/usbipd-win_1.3.0.msi). This will allow you to connect WSL to the calculator through internet. Follow the on screen information to install. +If you want to connect to the calculator, you have to connect to install this [tool](https://github.com/dorssel/usbipd-win/releases/download/v1.3.0/usbipd-win_1.3.0.msi). This will allow you toconnect WSL to the calculator through internet. Follow the on screen information to install. #### Ubuntu 1. In a WSL Ubuntu command prompt, type: ```bash @@ -188,6 +195,7 @@ sudo visudo `Defaults secure_path="/usr/lib/linux-tools/5.4.0-77-generic:/usr/local/sbin:..."` #### Debian + 1. If you use debian for your WSL distro, use this command instead: ```bash sudo apt install usbip hwdata usbutils @@ -207,12 +215,14 @@ usbipd wsl attach --busid It will ask you to type your wsl's password and will connect your calculator to WSL. You can now go to step 2! +

    + ### 2. Set up repo @@ -230,7 +240,8 @@ git checkout upsilon-dev
    - Model n0100 + +Model n0100 (note: you can change the `EPSILON_I18N=en` flag to `fr`, `nl`, `pt`, `it`, `de`, `es` or `hu`). @@ -253,12 +264,13 @@ or: ```bash make MODEL=n0100 OMEGA_USERNAME="" binpack -j4 ``` -to make binpack witch you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). Binpacks are a great way to share a custom build of Upsilon to friends. - +to make binpack which you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). Binpacks are a great way to share a custom build of Upsilonto friends. +
    - Model n0110 + +Model n0110 ```bash @@ -285,8 +297,9 @@ to make binpack witch you can flash to the calculator from [Ti-planet's webDFU](
    - Web simulator - + +Web simulator + First, install emsdk : ```bash @@ -306,12 +319,12 @@ make PLATFORM=simulator TARGET=web OMEGA_USERNAME="{Your name, max 15 characters The simulator is now in `output/release/simulator/web/simulator.zip` -
    - 3DS Simulator - + +3DS Simulator + You need devkitPro and devkitARM installed and in your path (instructions [here](https://devkitpro.org/wiki/Getting_Started)) ```bash @@ -347,7 +360,7 @@ If you need help, you can join our Discord server here : https://discord.gg/NFvz ## Contributing To contribute, please refer to [Omega's Wiki](https://github.com/Omega-Numworks/Omega/wiki/Contributing), the same rules apply here. - + ## Related repositories Here are the main links toward Omega's different websites and repositories, that have been used for the creation of Upsilon. @@ -378,4 +391,3 @@ NumWorks SAS and Nintendo of America Inc aren't associated in any shape or form * NumWorks Epsilon is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). * Omega is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). * Upsilon is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). - diff --git a/docs/build/index.md b/docs/build/index.md index 1fd79621d34..dc3976b5a92 100644 --- a/docs/build/index.md +++ b/docs/build/index.md @@ -4,71 +4,324 @@ breadcrumb: SDK --- # Build and run your own version of Upsilon -## Install the SDK +### Manual -### Windows + *You can refer to this [website](https://www.numworks.com/resources/engineering/software/build/) for the first step if you get errors.* -We recommend using the [Msys2](https://www.msys2.org/) environment to install most of the required tools. We support Windows 7 and up. Once Msys2 has been installed, launch the Msys2 terminal application, and enter the following commands + +### 1. Install SDK + +
    + +
    + +1.1 Linux + +
    + +
    + +Debian or Ubuntu + +
    + +You just have to install dependencies by running these command with superuser privileges in a Terminal: + +```bash +apt-get install build-essential git imagemagick libx11-dev libxext-dev libfreetype6-dev libpng-dev libjpeg-dev pkg-config gcc-arm-none-eabi binutils-arm-none-eabi ``` -pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-pkg-config mingw-w64-x86_64-libusb git make python -echo "export PATH=/mingw64/bin:$PATH" >> .bashrc + +And there you can go to step 2! + +
    + +
    + +
    + +Fedora + +
    + +To install basics dev tools: + +```bash +dnf install make automake gcc gcc-c++ kernel-devel ``` -Last but not least, download and install the latest [GCC toolchain from ARM](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads). When prompted for an install location, choose `C:\msys64\home\User\gcc-arm\`. You'll then need to add this folder to your $PATH in Msys2 by running this command: `echo "export PATH=$PATH:$HOME/gcc-arm/bin" >> .bashrc` and restarting Msys2. +And then install required packages. -### macOS +```bash +install git ImageMagick libX11-devel libXext-devel freetype-devel libpng-devel libjpeg-devel pkg-config +``` -We recommend using [Homebrew](https://brew.sh) to install all dependencies. Once you have installed Homebrew, install all the dependencies with the following command: +Then, install GCC cross compiler for ARM. +```bash + dnf install arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++ ``` + +
    + +
    + +
    + +
    + +
    + +1.2 Mac + +
    + +It's recommended to use [Homebrew](https://brew.sh/). Once it's installed, just run: + +```bash brew install numworks/tap/epsilon-sdk ``` -### Debian or Ubuntu +and it will install all dependencies. + +
    + +And there you can go to step 2! + +
    + +
    + +
    -Most of the required tools are available as apt packages: +1.3 Windows +
    + +
    + +With Msys2/Mingw (officialized by numworks but with a lot of bugs) + +[Msys2](https://www.msys2.org/) environment is recommended by Numworks to get most of the required tools on Windows easily. It's where you'll paste all the commands of this tutorial. Once it'sinstalled, paste these commands into the Msys2 terminal. + +```bash +pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-pkg-config mingw-w64-x86_64-libusb git make python +echo "export PATH=/mingw64/bin:$PATH" >> .bashrc ``` -apt-get install build-essential git imagemagick libx11-dev libxext-dev libfreetype6-dev libpng-dev libjpeg-dev pkg-config + +Next, you'll need to install the [GCC toolchain for ARM](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads). When prompted for aninstall location, choose `C:\msys64\home\User\gcc-arm\`. You'll then need to add this folder to your $PATH. Just enter: + +```bash +echo "export PATH=$PATH:$HOME/gcc-arm/bin" >> .bashrc ``` +Just restart terminal and you can go to step 2! + +
    + +
    + +With WSL 2 -You'll also need to install the latest version of GCC and make it available in your $PATH: +You need a windows version >= 1903. -1. Download the [GCC toolchain distributed by ARM](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads). You should obtain a `gcc-arm-none-eabi-x-linux.tar.bz2` file. -2. Decompress that file with `tar xvfj gcc-arm-none-eabi-*-linux.tar.bz2` -3. Add the resulting folder to your $PATH. If you use bash, ``echo "export PATH=\$PATH:`find $(pwd)/gcc-arm-none-eabi-*-update/bin -type d`" >> ~/.bashrc`` should do what you need (you'll need to restart your terminal afterwards). +#### WSL Installation -Alternatively, on Debian 10 and later you can directly install a sufficiently modern cross-toolchain: +1. Use simultaneously win + X keys and then click on "admin powershell". +```powershell +dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux all /norestart ``` -apt-get install gcc-arm-none-eabi binutils-arm-none-eabi +This command activate WSL functionalities. + +```powershell +dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart ``` +This one allows virtual machines developed by Microsoft. + +2. Restart your computer. + +3. Download [this file](https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi) and follow instructions. -## Retrieve the source code +4. Now open powershell admin like before and type: +```powershell +wsl --set-default-version 2 + ``` +5. Download [Ubuntu](https://www.microsoft.com/store/apps/9n6svws3rx71) from Microsoft store. -The code is hosted on GitHub. You can retrieve it using the following command. +WSL is now installed. +### Usbipd installation to connect your calculator +If you want to connect to the calculator, you have to connect to install this [tool](https://github.com/dorssel/usbipd-win/releases/download/v1.3.0/usbipd-win_1.3.0.msi). This will allow you toconnect WSL to the calculator through internet. Follow the on screen information to install. +#### Ubuntu +1. In a WSL Ubuntu command prompt, type: +```bash +sudo apt install linux-tools-5.4.0-77-generic hwdata ``` -git clone https://github.com/Lauryy06/Upsilon.git +2. Edit /etc/sudoers so that root can find the usbip command. On Ubuntu, run this command. +```bash +sudo visudo ``` +3. Add `/usr/lib/linux-tools/5.4.0-77-generic` to the beginning of secure_path. After editing, the line should look similar to this. +`Defaults secure_path="/usr/lib/linux-tools/5.4.0-77-generic:/usr/local/sbin:..."` -## Run Upsilon on your computer +#### Debian -Once the SDK has been installed, just open your terminal (Msys2, Terminal.app, xterm…) and type the following commands: +1. If you use debian for your WSL distro, use this command instead: +```bash +sudo apt install usbip hwdata usbutils +``` +And that's all for installation and set up. +### To connect your calculator +1. Open an Admin powershell and type: +```powershell + usbipd wsl list ``` -make PLATFORM=simulator clean -make PLATFORM=simulator epsilon_run +This will list your usb devices connected. Look at the BUSID column and remember the one for your calculator (it should be called "Numworks Calculator"). +2. Now run this command replacing by your calculator's usb port id: +```powershell +usbipd wsl attach --busid ``` +It will ask you to type your wsl's password and will connect your calculator to WSL. + +You can now go to step 2! + +
    + +
    + +
    + -## Run Upsilon on your calculator +### 2. Set up repo -You can also update your NumWorks calculator easily. Note that you'll need to press the Reset button and that all data on your calculator will be lost. +Clone repo and use 'upsilon-dev' branch by pasting these two commands: + +```bash +git clone --recursive https://github.com/Lauryy06/Upsilon.git +cd Upsilon +git checkout upsilon-dev +``` +
    + + +### 3. Choose the target + + +
    + +Model n0100 + +(note: you can change the `EPSILON_I18N=en` flag to `fr`, `nl`, `pt`, `it`, `de`, `es` or `hu`). + +```bash +make MODEL=n0100 clean +make MODEL=n0100 EPSILON_I18N=en OMEGA_USERNAME="{Your name, max 15 characters}" -j4 ``` + +Now, run either: + +```bash +make MODEL=n0100 epsilon_flash +``` +to directly flash the calculator after pressing simultaneously `reset` and `6` buttons and plugging in. + +
    + +or: + +```bash +make MODEL=n0100 OMEGA_USERNAME="" binpack -j4 +``` +to make binpack which you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). Binpacks are a great way to share a custom build of Upsilonto friends. + +
    + +
    + +Model n0110 + + +```bash make clean -make +make OMEGA_USERNAME="{Your name, max 15 characters}" -j4 +``` + +Now, run either: + +```bash make epsilon_flash ``` +to directly flash the calculator after pressing simultaneously `reset` and `6` buttons and plugging in. + +
    + +or: + +```bash +make OMEGA_USERNAME="" binpack -j4 +``` +to make binpack witch you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0110/). Binpacks are a great way to share a custom build of Upsilon to friends. + +
    + +
    + +Web simulator + +First, install emsdk : + +```bash +git clone https://github.com/emscripten-core/emsdk.git +cd emsdk +./emsdk install latest-fastcomp +./emsdk activate latest-fastcomp +source emsdk_env.sh +``` + +Then, compile Upsilon : + +```bash +make clean +make PLATFORM=simulator TARGET=web OMEGA_USERNAME="{Your name, max 15 characters}" -j4 +``` + +The simulator is now in `output/release/simulator/web/simulator.zip` + +
    + +
    + +3DS Simulator + +You need devkitPro and devkitARM installed and in your path (instructions [here](https://devkitpro.org/wiki/Getting_Started)) + +```bash +git clone --recursive https://github.com/Lauryy06/Upsilon.git +cd Upsilon +git checkout --recursive upsilon-dev +make PLATFORM=simulator TARGET=3ds -j +``` +You can then put epsilon.3dsx on a SD card to run it from the HBC or use 3dslink to launch it over the network: + +```bash +3dslink output/release/simulator/3ds/epsilon.3dsx -a <3DS' IP ADDRESS> +``` + +
    + +
    + +Important: Don't forget the `--recursive` tag, because Upsilon relies on submodules. +Also, you can change the number of processes that run in parallel during the build by changing the value of the `-j` flag. +Don't forget to put your pseudo instead of `{your pseudo, max 15 char}`. If you don't want one, just remove the `OMEGA_USERNAME=""` argument. + +
    Congratulations, you're running your very own version of Upsilon! + +To build with a special theme, please refer to this [page](../../themes/README.md). + +If you need help, you can join our Discord server here : https://discord.gg/NFvzdCBTQn + +

    Omega Banner Discord

    From 9632b59b67da9873a49866c21611e86454e5e9c8 Mon Sep 17 00:00:00 2001 From: Laury Date: Wed, 2 Feb 2022 23:00:53 +0100 Subject: [PATCH 161/355] [reader] Separated .urt and .txt files --- apps/reader/list_book_controller.cpp | 4 +- apps/reader/word_wrap_view.cpp | 150 ++++++++++++++++++++++++--- apps/reader/word_wrap_view.h | 4 + 3 files changed, 142 insertions(+), 16 deletions(-) diff --git a/apps/reader/list_book_controller.cpp b/apps/reader/list_book_controller.cpp index d727ab9ee49..445811a9e3c 100644 --- a/apps/reader/list_book_controller.cpp +++ b/apps/reader/list_book_controller.cpp @@ -31,13 +31,13 @@ KDCoordinate ListBookController::cellHeight() { HighlightCell * ListBookController::reusableCell(int index) { return &m_cells[index]; } - + int ListBookController::reusableCellCount() const { return k_cellsNumber; } void ListBookController::willDisplayCellForIndex(HighlightCell * cell, int index) { - MessageTableCell* myTextCell = static_cast(cell); + MessageTableCell* myTextCell = static_cast(cell); MessageTextView* textView = static_cast(myTextCell->labelView()); textView->setText(m_files[index].name); myTextCell->setMessageFont(KDFont::LargeFont); //TODO set cell font at building ? diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index f3a6d890be6..367e2b03991 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -9,7 +9,7 @@ namespace Reader { -WordWrapTextView::WordWrapTextView() : +WordWrapTextView::WordWrapTextView() : PointerTextView(GlobalPreferences::sharedGlobalPreferences()->font()), m_pageOffset(0), m_nextPageOffset(0), @@ -49,10 +49,16 @@ void WordWrapTextView::previousPage() { m_lastPagesOffsetsIndex = offsetToCheck; m_pageOffset = m_lastPagesOffsets[offsetToCheck]; m_lastPagesOffsets[offsetToCheck] = -1; - markRectAsDirty(bounds()); - return; + } else if (m_isRichTextFile) { + richTextPreviousPage(); + } else { + plainTextPreviousPage(); } + markRectAsDirty(bounds()); +} + +void WordWrapTextView::richTextPreviousPage() { const int charWidth = m_font->glyphSize().width(); const int charHeight = m_font->glyphSize().height(); @@ -140,7 +146,7 @@ void WordWrapTextView::previousPage() { // We will check if we must change page below } textBottomEndPosition = KDPoint(textBottomEndPosition.x() - textSize.width(), textBottomEndPosition.y()); - + // 7. We update height of the line if needed if (textSize.height() > lineHeight) { lineHeight = textSize.height(); @@ -158,14 +164,68 @@ void WordWrapTextView::previousPage() { } else { m_pageOffset = endOfWord - text(); } +} - // Because we ask for a redraw, m_endTextPosition must auto update at the bottom of drawRect... - markRectAsDirty(bounds()); +void WordWrapTextView::plainTextPreviousPage() { + const int charWidth = m_font->glyphSize().width(); + const int charHeight = m_font->glyphSize().height(); + + const char * endOfWord = text() + m_pageOffset - 1; + const char * startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); + + KDPoint textEndPosition(m_frame.width() - k_margin, m_frame.height() - k_margin); + + while(startOfWord>=text()) { + startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); + endOfWord = UTF8Helper::EndOfWord(startOfWord); + KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + KDPoint textStartPosition = KDPoint(textEndPosition.x()-textSize.width(), textEndPosition.y()); + + if (textStartPosition.x() < k_margin) { + textEndPosition = KDPoint(m_frame.width() - k_margin, textEndPosition.y() - charHeight); + textStartPosition = KDPoint(textEndPosition.x() - textSize.width(), textEndPosition.y()); + } + if (textEndPosition.y() - textSize.height() < k_margin) { + break; + } + + --startOfWord; + while (startOfWord >= text() && (*startOfWord == ' ' || *startOfWord == '\n')) { + if (*startOfWord == ' ') { + textStartPosition = KDPoint(textStartPosition.x() - charWidth, textStartPosition.y()); + } else { + textStartPosition = KDPoint(m_frame.width() - k_margin, textStartPosition.y() - charHeight); + } + --startOfWord; + } + + if (textStartPosition.y() < k_margin) { // If out of page, quit + break; + } + + if (textStartPosition.y() != textEndPosition.y()) { // If line changed, x is at start of line + textStartPosition = KDPoint(m_frame.width() - k_margin, textStartPosition.y()); + } + if (textStartPosition.x() < k_margin) { // Go to line if left overflow + textStartPosition = KDPoint(m_frame.width() - k_margin, textStartPosition.y() - charHeight); + } + + textEndPosition = textStartPosition; + endOfWord = startOfWord + 1; + } } void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(KDRect(0, 0, bounds().width(), bounds().height()), m_backgroundColor); + if (m_isRichTextFile) { + richTextDrawRect(ctx, rect); + } else { + plainTextDrawRect(ctx, rect); + } +} + +void WordWrapTextView::richTextDrawRect(KDContext * ctx, KDRect rect) const { enum class ToDraw { Text, Expression @@ -185,7 +245,7 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { Layout layout; KDPoint textPosition = KDPoint(k_margin, k_margin); - + while (!endOfPage && startOfWord < endOfFile) { // We process line by line @@ -196,7 +256,7 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { KDCoordinate baseline = charHeight / 2; while (firstReadIndex < endOfFile) { - + KDSize textSize = KDSizeZero; // 1.1. And we check if we are at the end of the line @@ -247,9 +307,9 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { } const char * endOfWord = EndOfPrintableWord(firstReadIndex + 1, endOfFile); - + textSize = m_font->stringSizeUntil(firstReadIndex, endOfWord); - + firstReadIndex = endOfWord; } @@ -289,7 +349,7 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { //2.1. We check if we are at the end of the line if (*startOfWord == '\n') { - startOfWord++; + startOfWord++; textPosition = KDPoint(k_margin, textPosition.y() + lineSize.height()); break; // We aren't supposed to be at the end of the page, else the loop on top would have stopped drawing @@ -297,7 +357,7 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { const char * endOfWord; - + // 2.2. Check if we are in a color change if (*startOfWord == '%') { if (updateTextColorForward(startOfWord)) { @@ -349,7 +409,7 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { // 2.4 We decide where to draw and if we must change line KDPoint endTextPosition = KDPoint(textPosition.x() + textSize.width(), textPosition.y()); - + // 2.4.1. Check if we need to go to the next line if(endTextPosition.x() > m_frame.width() - k_margin) { textPosition = KDPoint(k_margin, textPosition.y() + lineSize.height()); @@ -375,8 +435,70 @@ void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { textPosition = KDPoint(textPosition.x() + charWidth, textPosition.y()); } startOfWord = endOfWord; - } + } + } + m_nextPageOffset = startOfWord - text(); +} + + +void WordWrapTextView::plainTextDrawRect(KDContext * ctx, KDRect rect) const { + ctx->fillRect(KDRect(0, 0, bounds().width(), bounds().height()), m_backgroundColor); + + const char * endOfFile = text() + m_length; + const char * startOfWord = text() + m_pageOffset; + const char * endOfWord = UTF8Helper::EndOfWord(startOfWord); + KDPoint textPosition(k_margin, k_margin); + + const int wordMaxLength = 128; + char word[wordMaxLength]; + + const int charWidth = m_font->glyphSize().width(); + const int charHeight = m_font->glyphSize().height(); + + while(startOfWord < endOfFile) { + KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + KDPoint nextTextPosition = KDPoint(textPosition.x()+textSize.width(), textPosition.y()); + + if(nextTextPosition.x() > m_frame.width() - k_margin) { // Right overflow + textPosition = KDPoint(k_margin, textPosition.y() + textSize.height()); + nextTextPosition = KDPoint(k_margin + textSize.width(), textPosition.y()); + } + + if(textPosition.y() + textSize.height() > m_frame.height() - k_margin) { // Bottom overflow + break; + } + + stringNCopy(word, wordMaxLength, startOfWord, endOfWord-startOfWord); + ctx->drawString(word, textPosition, m_font, m_textColor, m_backgroundColor); + + while(*endOfWord == ' ' || *endOfWord == '\n') { + if(*endOfWord == ' ') { + nextTextPosition = KDPoint(nextTextPosition.x() + charWidth, nextTextPosition.y()); + } + else { + nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + charHeight); + } + ++endOfWord; + } + + //We must change value of startOfWord now to avoid having + //two times the same word if the break below is used + startOfWord = endOfWord; + + if(nextTextPosition.y() + textSize.height() > m_frame.height() - k_margin) { // If out of page, quit + break; + } + if(nextTextPosition.y() != textPosition.y()) { // If line changed, x is at start of line + nextTextPosition = KDPoint(k_margin, nextTextPosition.y()); + } + if(nextTextPosition.x() > m_frame.width() - k_margin) { // Go to line if right overflow + nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + textSize.height()); + } + + textPosition = nextTextPosition; + endOfWord = UTF8Helper::EndOfWord(startOfWord); } + m_nextPageOffset = startOfWord - text(); } diff --git a/apps/reader/word_wrap_view.h b/apps/reader/word_wrap_view.h index fca07759ea8..714f1a5f5f5 100644 --- a/apps/reader/word_wrap_view.h +++ b/apps/reader/word_wrap_view.h @@ -22,6 +22,10 @@ class WordWrapTextView : public PointerTextView { BookSave getBookSave() const; void setBookSave(BookSave save); private: + void richTextPreviousPage(); + void plainTextPreviousPage(); + void richTextDrawRect(KDContext * ctx, KDRect rect) const; + void plainTextDrawRect(KDContext * ctx, KDRect rect) const; bool updateTextColorForward(const char * colorStart) const; bool updateTextColorBackward(const char * colorStart) const; static const int k_margin = 10; From 708070c284882e3b6d805bebb9ce37d49acf2f2b Mon Sep 17 00:00:00 2001 From: Laury Date: Wed, 2 Feb 2022 23:01:26 +0100 Subject: [PATCH 162/355] [calculation] Fix (again) a bug in second degree additional output --- .../second_degree_list_controller.cpp | 100 ++++++++---------- 1 file changed, 43 insertions(+), 57 deletions(-) diff --git a/apps/calculation/additional_outputs/second_degree_list_controller.cpp b/apps/calculation/additional_outputs/second_degree_list_controller.cpp index 657792239ee..afd9f1efa9d 100644 --- a/apps/calculation/additional_outputs/second_degree_list_controller.cpp +++ b/apps/calculation/additional_outputs/second_degree_list_controller.cpp @@ -20,20 +20,20 @@ using namespace Poincare; using namespace Shared; namespace Calculation { - + void SecondDegreeListController::setExpression(Poincare::Expression e) { ExpressionsListController::setExpression(e); assert(!m_expression.isUninitialized()); Expression polynomialCoefficients[Expression::k_maxNumberOfPolynomialCoefficients]; - + Context * context = App::app()->localContext(); Preferences * preferences = Preferences::sharedPreferences(); - Poincare::ExpressionNode::ReductionContext reductionContext = Poincare::ExpressionNode::ReductionContext(context, - preferences->complexFormat(), preferences->angleUnit(), - GlobalPreferences::sharedGlobalPreferences()->unitFormat(), - ExpressionNode::ReductionTarget::SystemForApproximation, - ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, + Poincare::ExpressionNode::ReductionContext reductionContext = Poincare::ExpressionNode::ReductionContext(context, + preferences->complexFormat(), preferences->angleUnit(), + GlobalPreferences::sharedGlobalPreferences()->unitFormat(), + ExpressionNode::ReductionTarget::SystemForApproximation, + ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::Default); PoincareHelpers::Reduce(&m_expression, context, ExpressionNode::ReductionTarget::SystemForAnalysis); @@ -52,7 +52,7 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { Expression a = polynomialCoefficients[2]; Expression b = polynomialCoefficients[1]; Expression c = polynomialCoefficients[0]; - + Expression delta = Subtraction::Builder(Power::Builder(b.clone(), Rational::Builder(2)), Multiplication::Builder(Rational::Builder(4), a.clone(), c.clone())); PoincareHelpers::Simplify(&delta, context, ExpressionNode::ReductionTarget::SystemForApproximation); @@ -72,25 +72,22 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { }; MultiplicationTypeForA multiplicationTypeForA; - + if (a.type() == ExpressionNode::Type::Rational && static_cast(a).isOne()) { multiplicationTypeForA = MultiplicationTypeForA::Nothing; - } - else if(a.type() == ExpressionNode::Type::Rational && static_cast(a).isMinusOne()){ + } else if(a.type() == ExpressionNode::Type::Rational && static_cast(a).isMinusOne()){ multiplicationTypeForA = MultiplicationTypeForA::Minus; - } - else if (a.type() == ExpressionNode::Type::Addition) { + } else if (a.type() == ExpressionNode::Type::Addition) { multiplicationTypeForA = MultiplicationTypeForA::Parenthesis; - } - else { + } else { multiplicationTypeForA = MultiplicationTypeForA::Normal; } PoincareHelpers::Simplify(&a, context, ExpressionNode::ReductionTarget::User); - /* - * Because when can't apply reduce or simplify to keep the - * canonized form we must beautify the expression manually + /* + * Because when can't apply reduce or simplify to keep the + * canonized form we must beautify the expression manually */ Expression xMinusAlphaPowerTwo; @@ -99,12 +96,11 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { if (alpha.isUninitialized()) { PoincareHelpers::Simplify(&minusAlpha, context, ExpressionNode::ReductionTarget::User); xMinusAlphaPowerTwo = Power::Builder(Parenthesis::Builder(Addition::Builder(Symbol::Builder("x", strlen("x")), minusAlpha)), Rational::Builder(2)); - } - else { + } else { PoincareHelpers::Simplify(&alpha, context, ExpressionNode::ReductionTarget::User); xMinusAlphaPowerTwo = Power::Builder(Parenthesis::Builder(Subtraction::Builder(Symbol::Builder("x", strlen("x")), alpha)), Rational::Builder(2)); } - + Expression xMinusAlphaPowerTwoWithFactor; switch (multiplicationTypeForA) @@ -125,18 +121,21 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { assert(false); break; } - + Expression canonized; + PoincareHelpers::Simplify(&minusBeta, context, ExpressionNode::ReductionTarget::User); Expression beta = getOppositeIfExists(minusBeta, &reductionContext); if (beta.isUninitialized()) { - PoincareHelpers::Simplify(&minusBeta, context, ExpressionNode::ReductionTarget::User); - canonized = Subtraction::Builder(xMinusAlphaPowerTwoWithFactor, minusBeta); - } - else { + if (minusBeta.type() == ExpressionNode::Type::Addition || minusBeta.type() == ExpressionNode::Type::Subtraction) { + canonized = Subtraction::Builder(xMinusAlphaPowerTwoWithFactor, Parenthesis::Builder(minusBeta)); + } else { + canonized = Subtraction::Builder(xMinusAlphaPowerTwoWithFactor, minusBeta); + } + } else { PoincareHelpers::Simplify(&beta, context, ExpressionNode::ReductionTarget::User); canonized = Addition::Builder(xMinusAlphaPowerTwoWithFactor, beta); } - + Expression x0; Expression x1; @@ -145,8 +144,7 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { x0 = Division::Builder(Opposite::Builder(b.clone()), Multiplication::Builder(Rational::Builder(2), a.clone())); m_numberOfSolutions = 1; PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::SystemForApproximation); - } - else { + } else { // x0 = (-b-sqrt(delta))/(2a) x0 = Division::Builder(Subtraction::Builder(Opposite::Builder(b.clone()), SquareRoot::Builder(delta.clone())), Multiplication::Builder(Rational::Builder(2), a.clone())); // x1 = (-b+sqrt(delta))/(2a) @@ -171,16 +169,14 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::User); if (x0.type() == ExpressionNode::Type::Addition || x0.type() == ExpressionNode::Type::Subtraction) { firstFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), Parenthesis::Builder(x0.clone())); - } - else { + } else { firstFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0.clone()); } - } - else { + } else { if (x0Opposite.type() == ExpressionNode::Type::Addition || x0Opposite.type() == ExpressionNode::Type::Subtraction) { x0Opposite = Parenthesis::Builder(x0Opposite.clone()); } - secondFactor = Addition::Builder(Symbol::Builder("x", strlen("x")), x0Opposite.clone()); + firstFactor = Addition::Builder(Symbol::Builder("x", strlen("x")), x0Opposite.clone()); } Expression x1Opposite = getOppositeIfExists(x1, &reductionContext); @@ -188,12 +184,10 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { PoincareHelpers::Simplify(&x1, context, ExpressionNode::ReductionTarget::User); if (x1.type() == ExpressionNode::Type::Addition || x1.type() == ExpressionNode::Type::Subtraction) { secondFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), Parenthesis::Builder(x1.clone())); - } - else { + } else { secondFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x1.clone()); } - } - else { + } else { PoincareHelpers::Simplify(&x1Opposite, context, ExpressionNode::ReductionTarget::User); if (x1Opposite.type() == ExpressionNode::Type::Addition || x1Opposite.type() == ExpressionNode::Type::Subtraction) { x1Opposite = Parenthesis::Builder(x1Opposite.clone()); @@ -220,21 +214,18 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { assert(false); break; } - } - else if (m_numberOfSolutions == 1) { + } else if (m_numberOfSolutions == 1) { Expression x0Opposite = getOppositeIfExists(x0, &reductionContext); Expression factor; - + if (x0Opposite.isUninitialized()) { PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::User); if (x0.type() == ExpressionNode::Type::Addition || x0.type() == ExpressionNode::Type::Subtraction) { factor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), Parenthesis::Builder(x0.clone())); - } - else { + } else { factor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0.clone()); } - } - else { + } else { PoincareHelpers::Simplify(&x0Opposite, context, ExpressionNode::ReductionTarget::User); factor = Addition::Builder(Symbol::Builder("x", strlen("x")), x0Opposite.clone()); } @@ -259,7 +250,7 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { break; } } - + PoincareHelpers::Simplify(&delta, context, ExpressionNode::ReductionTarget::User); m_layouts[0] = PoincareHelpers::CreateLayout(canonized); @@ -270,8 +261,7 @@ void SecondDegreeListController::setExpression(Poincare::Expression e) { if (m_numberOfSolutions > 1) { m_layouts[4] = PoincareHelpers::CreateLayout(x1); } - } - else { + } else { m_layouts[1] = PoincareHelpers::CreateLayout(delta); } } @@ -280,11 +270,9 @@ Expression SecondDegreeListController::getOppositeIfExists(Expression e, Poincar if (e.isNumber() && e.sign(reductionContext->context()) == ExpressionNode::Sign::Negative) { Number n = static_cast(e); return std::move(n.setSign(ExpressionNode::Sign::Positive)); - } - else if(e.type() == ExpressionNode::Type::Opposite) { + } else if(e.type() == ExpressionNode::Type::Opposite) { return std::move(e.childAtIndex(0).clone()); - } - else if (e.type() == ExpressionNode::Type::Multiplication && e.numberOfChildren() > 0 && e.childAtIndex(0).isNumber() && e.childAtIndex(0).sign(reductionContext->context()) == ExpressionNode::Sign::Negative) { + } else if (e.type() == ExpressionNode::Type::Multiplication && e.numberOfChildren() > 0 && e.childAtIndex(0).isNumber() && e.childAtIndex(0).sign(reductionContext->context()) == ExpressionNode::Sign::Negative) { Multiplication m = static_cast(e); if (m.childAtIndex(0).type() == ExpressionNode::Type::Rational && static_cast(e).isMinusOne()) { // The negative numeral factor is -1, we just remove it @@ -304,7 +292,7 @@ I18n::Message SecondDegreeListController::messageAtIndex(int index) { if (m_numberOfSolutions > 0) { if (index == 0) { return I18n::Message::CanonicalForm; - } + } if (index == 1) { return I18n::Message::FactorizedForm; } @@ -314,14 +302,12 @@ I18n::Message SecondDegreeListController::messageAtIndex(int index) { if (index == 3) { if (m_numberOfSolutions == 1) { return I18n::Message::OnlyRoot; - } - else { + } else { return I18n::Message::FirstRoot; } } return I18n::Message::SecondRoot; - } - else { + } else { switch (index) { case 0: return I18n::Message::CanonicalForm; From d8f93709b80d4ab4518a1222cbb13b10bd56356b Mon Sep 17 00:00:00 2001 From: Jules <77736557+JulesGrd@users.noreply.github.com> Date: Fri, 4 Feb 2022 13:14:32 +0100 Subject: [PATCH 163/355] Update README.fr.md (#142) --- README.fr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.fr.md b/README.fr.md index d30ce8eedd5..8177ddfb590 100644 --- a/README.fr.md +++ b/README.fr.md @@ -271,7 +271,7 @@ pour compiler les binpacks que vous pouvez distribuer et flasher depuis le [Ti-p ```bash make clean make OMEGA_USERNAME="{Votre nom, maximum 15 caractères}" -j4 -`` +``` Maintenant, lancez soit: From 9ab3e6c71559134f5057f5ac5caa58b1cbf2dd64 Mon Sep 17 00:00:00 2001 From: LeMoustachu Date: Sat, 5 Feb 2022 13:49:47 +0100 Subject: [PATCH 164/355] remove white reader icon background in omega_dark (#144) --- .../local/omega_dark/apps/reader_icon.png | Bin 1750 -> 12233 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/themes/themes/local/omega_dark/apps/reader_icon.png b/themes/themes/local/omega_dark/apps/reader_icon.png index 35cc4b5101c09a84e2e35115197598eff4594838..95441fd9f06e83ccc4cd0be52a3560f214e3b661 100644 GIT binary patch literal 12233 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 delta 1735 zcmV;&1~~c2U)BwfBYyx1a7bBm000id000id0mpBsWB>pF8FWQhbW?9;ba!ELWdL_~ zcP?peYja~^aAhuUa%Y?FJQ@H124hJ?K~!jg-I`BmQ+pQ2Klk3INwiu!Nla5aO(`K} zKp5$pszWuT-Mkh>T!|oGz%Sx*xg3WM9U_@b5{tzs6bh^Cwss^*!tHirv)KrT!&t3W zM1W8zL^7G=<;$19N?`znLV;*BN-CA2tE&ruiowA_;_*1KSZtNuTNy@2M_IpqJwro7 zNP-~rM59sOzkk2leys?Kq992Ui9`Z1kw}ouW>?#%6@O)Na*}vFP6fdH{Cv?LNRmWH zM+fcg?KCzvQdwD9awk8L$z(Ws^r$L7;iDc!QK%3_v1k&pTCJQpa|WN!M?RltY;3IL ziN|KMQBzX`pgGYM3I#+_{1~}nba!{-^Z6JU7$6)DGd(@6&tsLC&1NoLxD!g?%lgwy?RyGBwA@}Ys2Yu(%09gTNpsm+G_ju?TDhtz`%g+ zNwm_^(n2bg;@-V`hU^rLoWtQjmSx_&d9&oXD2iOTZ~=$IVaS2yuv)D=d-lw5>8EPX z34(wi2uq$Rio)~f&zYK*LOyJNW&6wrtsg z*=*MBtX9(Lw649UO57(RkqD7UMDrx8#o9d}SpmLlahjIp=ln*J__3;rZDup;%X~_9 zYif%B&dz>wjBatw77IVwY*@?m?OV(TMSr27;!~tc+!m9GKiO=U1OXHU$|&&Z+#HwF zX_6|kr!H}OtEw;w0s@pxm<+tj=lM%A$-K$}Lf1z5gT+F*7o!OLdwN>6FwhY>vmmfR zl9qk<=;xpLXC_m!Xr}XdP2)93PFjTo@(Hcgtmeqg$}%}cSvE`lV6pIn%ca$%YJdKg z$uKlCqk6oqJ@>y{PV-;UC<-4FnU-ki68Ep^X=Jo76C((~Z%rotY`0VMl|BDCy2M52 z=lOd&z3?F|r^w%1Ec~^$mOuV-P4z8wS1A9Po#pxbJU>=d@jJ5_bD0y3n#5+PwtSn< z^N-0%BPL49cl}?%cP&oSvh;erw12g=(b(9C#bPOyPhXHur|IqO)qK>TN}MQ)ghC-Y zJ3DnbXOvVbrRxk+mAJ!)57XJ%$>Yb5>F@97>C>m=HP(4r5Ji!5=g#5vdbxG$7U6K1 z4<9~gwo_Exh7B7yaNq!uNQBd;PnV^2W7n=-_TgaDVk7PT7O!otgNKJ zzrSofDXp!o%*@Pi`}S=^c8W&M>2v}txhH^+mm}TX-E7{x*^mRv;qiEg#bUaa;$n)5 zGn>uGvi!wFf*|1WcuI1GWwBVwbIUU^F@Y$GIGs)w-zmtl%<<#L4Lh(LJv}|t*Vh}i zQ*_TgdGZ8+j*bq)lc{BRcz+nD)5(q8bBM=CnC<@VNw4|Q_fY+~I z)85|B?%lh2_39Ph^6zV?zU8T|uBNxQmxhK0=H})YA0IDytA^cfXVa!l06cp1h_h$U zYMQgTxtR)CmWzJsPMl!>{{8sKjVyz=d5_Y?tBS(%P z1_A+GE*EaMd$s*Cj35ZO-EQjY>hSyhOWS}`sT8B5qrb{-+2z`Rmo#v9b#*a5K29>3 zTxj54%R(hdT4><5+d%Eq)zxwN^5vxs+>3uOG&IEZ>(`0L<0y)<=3DOtL7=v_mS8Z5 d-|t`Y{{PPGe&472*MI;3002ovPDHLkV1k?ca%KPk From 034fc65bc2d216a6cb0fdea911e9032ca22ad8e7 Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 5 Feb 2022 19:00:33 +0100 Subject: [PATCH 165/355] [settings] Updated contributors ! --- apps/settings/main_controller.cpp | 2 +- apps/settings/main_controller.h | 2 +- apps/settings/sub_menu/about_controller.cpp | 12 +++---- apps/settings/sub_menu/about_controller.h | 3 +- .../sub_menu/contributors_controller.cpp | 35 ++++++------------- apps/shared.universal.i18n | 26 ++++++-------- 6 files changed, 30 insertions(+), 50 deletions(-) diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index df488099020..cfdbbcc2a33 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -20,7 +20,7 @@ constexpr SettingsMessageTree s_usbProtectionLevelChildren[3] = {SettingsMessage constexpr SettingsMessageTree s_symbolFunctionChildren[3] = {SettingsMessageTree(I18n::Message::SymbolDefaultFunction), SettingsMessageTree(I18n::Message::SymbolArgDefaultFunction), SettingsMessageTree(I18n::Message::SymbolArgFunction)}; constexpr SettingsMessageTree s_modelMathOptionsChildren[6] = {SettingsMessageTree(I18n::Message::AngleUnit, s_modelAngleChildren), SettingsMessageTree(I18n::Message::DisplayMode, s_modelFloatDisplayModeChildren), SettingsMessageTree(I18n::Message::EditionMode, s_modelEditionModeChildren), SettingsMessageTree(I18n::Message::SymbolFunction, s_symbolFunctionChildren), SettingsMessageTree(I18n::Message::ComplexFormat, s_modelComplexFormatChildren), SettingsMessageTree(I18n::Message::SymbolMultiplication, s_symbolChildren)}; constexpr SettingsMessageTree s_accessibilityChildren[6] = {SettingsMessageTree(I18n::Message::AccessibilityInvertColors), SettingsMessageTree(I18n::Message::AccessibilityMagnify),SettingsMessageTree(I18n::Message::AccessibilityGamma),SettingsMessageTree(I18n::Message::AccessibilityGammaRed),SettingsMessageTree(I18n::Message::AccessibilityGammaGreen),SettingsMessageTree(I18n::Message::AccessibilityGammaBlue)}; -constexpr SettingsMessageTree s_contributorsChildren[23] = {SettingsMessageTree(I18n::Message::Developers), SettingsMessageTree(I18n::Message::QuentinGuidee), SettingsMessageTree(I18n::Message::JoachimLeFournis), SettingsMessageTree(I18n::Message::MaximeFriess), SettingsMessageTree(I18n::Message::JeanBaptisteBoric), SettingsMessageTree(I18n::Message::SandraSimmons), SettingsMessageTree(I18n::Message::David), SettingsMessageTree(I18n::Message::DamienNicolet), SettingsMessageTree(I18n::Message::EvannDreumont), SettingsMessageTree(I18n::Message::SzaboLevente), SettingsMessageTree(I18n::Message::VenceslasDuet), SettingsMessageTree(I18n::Message::CharlotteThomas), SettingsMessageTree(I18n::Message::AntoninLoubiere), SettingsMessageTree(I18n::Message::CyprienMejat), SettingsMessageTree(I18n::Message::BetaTesters), SettingsMessageTree(I18n::Message::TimeoArnouts), SettingsMessageTree(I18n::Message::JulieC), SettingsMessageTree(I18n::Message::LelahelHideux), SettingsMessageTree(I18n::Message::Madil), SettingsMessageTree(I18n::Message::HilaireLeRoux), SettingsMessageTree(I18n::Message::HectorNussbaumer), SettingsMessageTree(I18n::Message::RaphaelDyda), SettingsMessageTree(I18n::Message::ThibautC)}; +constexpr SettingsMessageTree s_contributorsChildren[18] = {SettingsMessageTree(I18n::Message::LaurianFournier), SettingsMessageTree(I18n::Message::YannCouturier), SettingsMessageTree(I18n::Message::LoicE), SettingsMessageTree(I18n::Message::DavidLuca), SettingsMessageTree(I18n::Message::VictorKretz), SettingsMessageTree(I18n::Message::QuentinGuidee), SettingsMessageTree(I18n::Message::JoachimLeFournis), SettingsMessageTree(I18n::Message::MaximeFriess), SettingsMessageTree(I18n::Message::JeanBaptisteBoric), SettingsMessageTree(I18n::Message::SandraSimmons), SettingsMessageTree(I18n::Message::David), SettingsMessageTree(I18n::Message::DamienNicolet), SettingsMessageTree(I18n::Message::EvannDreumont), SettingsMessageTree(I18n::Message::SzaboLevente), SettingsMessageTree(I18n::Message::VenceslasDuet), SettingsMessageTree(I18n::Message::CharlotteThomas), SettingsMessageTree(I18n::Message::AntoninLoubiere), SettingsMessageTree(I18n::Message::CyprienMejat)}; // Code Settings #ifdef HAS_CODE diff --git a/apps/settings/main_controller.h b/apps/settings/main_controller.h index cb3aae2f966..28c5b07d779 100644 --- a/apps/settings/main_controller.h +++ b/apps/settings/main_controller.h @@ -27,7 +27,7 @@ extern const Shared::SettingsMessageTree s_modelFontChildren[2]; extern const Shared::SettingsMessageTree s_codeChildren[4]; extern const Shared::SettingsMessageTree s_modelDateTimeChildren[3]; extern const Shared::SettingsMessageTree s_accessibilityChildren[6]; -extern const Shared::SettingsMessageTree s_contributorsChildren[23]; +extern const Shared::SettingsMessageTree s_contributorsChildren[18]; extern const Shared::SettingsMessageTree s_modelAboutChildren[10]; extern const Shared::SettingsMessageTree s_usbProtectionChildren[2]; extern const Shared::SettingsMessageTree s_usbProtectionLevelChildren[3]; diff --git a/apps/settings/sub_menu/about_controller.cpp b/apps/settings/sub_menu/about_controller.cpp index 901034a513b..09debd6e93d 100644 --- a/apps/settings/sub_menu/about_controller.cpp +++ b/apps/settings/sub_menu/about_controller.cpp @@ -26,7 +26,7 @@ AboutController::AboutController(Responder * parentResponder) : m_contributorsCell(KDFont::LargeFont, KDFont::SmallFont) //m_view(&m_selectableTableView) { - for (int i = 0; i < k_totalNumberOfCell; i++) { + for (int i = 0; i < k_totalNumberOfCell - 1; i++) { m_cells[i].setMessageFont(KDFont::LargeFont); m_cells[i].setAccessoryFont(KDFont::SmallFont); m_cells[i].setAccessoryTextColor(Palette::SecondaryText); @@ -138,8 +138,8 @@ int AboutController::numberOfRows() const { HighlightCell * AboutController::reusableCell(int index, int type) { assert(index >= 0); if (type == 0) { - assert(index < k_totalNumberOfCell-1-(!hasUsernameCell())); - return &m_cells[index+(!hasUsernameCell())]; + assert(index < k_totalNumberOfCell-1); + return &m_cells[index]; } assert(index == 0); return &m_contributorsCell; @@ -152,7 +152,7 @@ int AboutController::typeAtLocation(int i, int j) { int AboutController::reusableCellCount(int type) { switch (type) { case 0: - return k_totalNumberOfCell-1-(!hasUsernameCell()); + return k_totalNumberOfCell-1; case 1: return 1; default: @@ -177,12 +177,12 @@ void AboutController::willDisplayCellForIndex(HighlightCell * cell, int index) { memUseBuffer[len] = 'k'; memUseBuffer[len+1] = 'B'; memUseBuffer[len+2] = '/'; - + len = Poincare::Integer((int)((float) Ion::Storage::k_storageSize / 1024.f)).serialize(memUseBuffer + len + 3, 4) + len + 3; memUseBuffer[len] = 'k'; memUseBuffer[len+1] = 'B'; memUseBuffer[len+2] = '\0'; - + MessageTableCellWithBuffer * myCell = (MessageTableCellWithBuffer *)cell; myCell->setAccessoryText(memUseBuffer); } else { diff --git a/apps/settings/sub_menu/about_controller.h b/apps/settings/sub_menu/about_controller.h index f72017af001..b661ac9e399 100644 --- a/apps/settings/sub_menu/about_controller.h +++ b/apps/settings/sub_menu/about_controller.h @@ -11,7 +11,6 @@ namespace Settings { class AboutController : public GenericSubController { public: AboutController(Responder * parentResponder); - //View * view() override { return &m_view; } View * view() override { return &m_selectableTableView; } void viewWillAppear() override; TELEMETRY_ID("About"); @@ -26,7 +25,7 @@ class AboutController : public GenericSubController { bool hasUsernameCell() const; ContributorsController m_contributorsController; MessageTableCellWithChevronAndMessage m_contributorsCell; - MessageTableCellWithBuffer m_cells[k_totalNumberOfCell]; + MessageTableCellWithBuffer m_cells[k_totalNumberOfCell - 1]; HardwareTest::PopUpController m_hardwareTestPopUpController; }; diff --git a/apps/settings/sub_menu/contributors_controller.cpp b/apps/settings/sub_menu/contributors_controller.cpp index 26bab3326a5..378bfc3f6c1 100644 --- a/apps/settings/sub_menu/contributors_controller.cpp +++ b/apps/settings/sub_menu/contributors_controller.cpp @@ -28,8 +28,14 @@ int ContributorsController::reusableCellCount(int type) { return k_totalNumberOfCell; } -constexpr static int s_numberOfDevelopers = 13; +constexpr static int s_numberOfDevelopers = 18; +constexpr static int s_numberOfUpsilonDevelopers = 5; constexpr static I18n::Message s_developersUsernames[s_numberOfDevelopers] = { + I18n::Message::PLaurianFournier, + I18n::Message::PYannCouturier, + I18n::Message::PDavidLuca, + I18n::Message::PLoicE, + I18n::Message::PVictorKretz, I18n::Message::PQuentinGuidee, I18n::Message::PJoachimLeFournis, I18n::Message::PMaximeFriess, @@ -45,32 +51,13 @@ constexpr static I18n::Message s_developersUsernames[s_numberOfDevelopers] = { I18n::Message::PCyprienMejat, }; -constexpr static int s_numberOfBetaTesters = 8; -constexpr static I18n::Message s_betaTestersUsernames[s_numberOfBetaTesters] = { - I18n::Message::PTimeoArnouts, - I18n::Message::PJulieC, - I18n::Message::PLelahelHideux, - I18n::Message::PMadil, - I18n::Message::PHilaireLeRoux, - I18n::Message::PHectorNussbaumer, - I18n::Message::PRaphaelDyda, - I18n::Message::PThibautC, -}; - void ContributorsController::willDisplayCellForIndex(HighlightCell * cell, int index) { MessageTableCellWithBuffer * myTextCell = (MessageTableCellWithBuffer *)cell; - if (index == 0) { - myTextCell->setAccessoryText(""); - myTextCell->setTextColor(KDColor::RGB24(0xC03535)); - } else if (index > 0 && index <= s_numberOfDevelopers) { - myTextCell->setAccessoryText(I18n::translate(s_developersUsernames[index - 1])); - myTextCell->setTextColor(Palette::PrimaryText); - } else if (index == s_numberOfDevelopers + 1) { - myTextCell->setAccessoryText(""); - myTextCell->setTextColor(KDColor::RGB24(0x1ABC9A)); + myTextCell->setAccessoryText(I18n::translate(s_developersUsernames[index])); + if (index < s_numberOfUpsilonDevelopers) { + myTextCell->setTextColor(KDColor::RGB24(0x5e81ac)); } else { - myTextCell->setAccessoryText(I18n::translate(s_betaTestersUsernames[index - 2 - s_numberOfDevelopers])); - myTextCell->setTextColor(Palette::PrimaryText); + myTextCell->setTextColor(KDColor::RGB24(0xc53431)); } myTextCell->setAccessoryTextColor(Palette::SecondaryText); GenericSubController::willDisplayCellForIndex(cell, index); diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index c93abe957bd..d1d3fc9b5b9 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -315,6 +315,16 @@ ElementTsMass = "294" ElementOgMass = "294" ElementUueMass = "295" ElementUbnMass = "297" +LaurianFournier = "Laurian Fournier" +PLaurianFournier = "@Lauryy06" +YannCouturier = "Yann Couturier" +PYannCouturier = "@Yaya-Cout" +LoicE = "Loïc E." +PLoicE = "@lolocomotive" +DavidLuca = "David Luca" +PDavidLuca = "@dl11" +VictorKretz = "Victor Kretz" +PVictorKretz = "@Mino" QuentinGuidee = "Quentin Guidée" PQuentinGuidee = "@quentinguidee" SandraSimmons = "Sandra Simmons" @@ -341,22 +351,6 @@ VenceslasDuet = "Venceslas Duet" PVenceslasDuet = "@Citorva" CyprienMejat = "Cyprien Méjat" PCyprienMejat = "@A2drien" -TimeoArnouts = "Timéo Arnouts" -PTimeoArnouts = "@Dogm" -JulieC = "Julie C." -PJulieC = "@windows9x95" -LelahelHideux = "Lélahel Hideux" -PLelahelHideux = "@Lelahelry" -Madil = "Madil" -PMadil = "@le-grand-mannitout" -HilaireLeRoux = "Hilaire Le Roux" -PHilaireLeRoux = "@0Babass2" -HectorNussbaumer = "Hector Nussbaumer" -PHectorNussbaumer = "@Sycorax" -RaphaelDyda = "Raphaël Dyda" -PRaphaelDyda = "@Trixciel" -ThibautC = "Thibaut Cellier" -PThibautC = "@Tibo_C" SpeedOfLight = "2.99792458·10^8_m_s^-1" YearLight = "9.461·10^15_m" Boltzmann = "1.380649·10^-23_J_K^-1" From 43b56f3a89cf62c6b094e093f0fcbec890f96c67 Mon Sep 17 00:00:00 2001 From: LeMoustachu Date: Sat, 5 Feb 2022 19:01:47 +0100 Subject: [PATCH 166/355] Update doc titlebar to match upsilon color. (#145) --- build/doc/Doxyfile | 211 +++++++++++++++++++++++++++--------- build/doc/customdoxygen.css | 4 +- build/doc/logo.png | Bin 11463 -> 17280 bytes 3 files changed, 161 insertions(+), 54 deletions(-) diff --git a/build/doc/Doxyfile b/build/doc/Doxyfile index f903ba0f1cc..421deb4639e 100644 --- a/build/doc/Doxyfile +++ b/build/doc/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.8.18 +# Doxyfile 1.9.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -227,6 +227,14 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. @@ -315,7 +323,10 @@ OPTIMIZE_OUTPUT_SLICE = NO # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. EXTENSION_MAPPING = @@ -449,6 +460,19 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -512,6 +536,13 @@ EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation @@ -549,11 +580,18 @@ HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# (including Cygwin) ands Mac users are advised to set this option to NO. +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. # The default value is: system dependent. CASE_SENSE_NAMES = NO @@ -792,7 +830,10 @@ WARN_IF_DOC_ERROR = NO WARN_NO_PARAMDOC = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. # The default value is: NO. WARN_AS_ERROR = NO @@ -836,8 +877,8 @@ INPUT = apps \ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: https://www.gnu.org/software/libiconv/) for the list of -# possible encodings. +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 @@ -850,13 +891,15 @@ INPUT_ENCODING = UTF-8 # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, # *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), -# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen -# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, -# *.vhdl, *.ucf, *.qsf and *.ice. +# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, +# *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ @@ -1114,6 +1157,44 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to +# YES then doxygen will add the directory of each input to the include path. +# The default value is: YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- @@ -1125,13 +1206,6 @@ VERBATIM_HEADERS = YES ALPHABETICAL_INDEX = YES -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored @@ -1302,10 +1376,11 @@ HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: https://developer.apple.com/xcode/), introduced with OSX -# 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. @@ -1347,8 +1422,8 @@ DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. +# (see: +# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML @@ -1378,7 +1453,7 @@ CHM_FILE = HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). +# (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1423,7 +1498,8 @@ QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1431,8 +1507,8 @@ QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- -# folders). +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1440,16 +1516,16 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = @@ -1461,9 +1537,9 @@ QHP_CUST_FILTER_ATTRS = QHP_SECT_FILTER_ATTRS = -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = @@ -1544,8 +1620,8 @@ EXT_LINKS_IN_WINDOW = NO # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for # the HTML output. These images will generally look nicer at scaled resolutions. -# Possible values are: png The default and svg Looks nicer but requires the -# pdf2svg tool. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). # The default value is: png. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1590,7 +1666,7 @@ USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. @@ -1620,7 +1696,8 @@ MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1667,7 +1744,8 @@ SERVER_BASED_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). +# Xapian (see: +# https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1680,8 +1758,9 @@ EXTERNAL_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). See the section "External Indexing and -# Searching" for details. +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. # This tag requires that the tag SEARCHENGINE is set to YES. SEARCHENGINE_URL = @@ -1845,9 +1924,11 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES, to get a -# higher quality PDF documentation. +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. # The default value is: YES. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2279,7 +2360,7 @@ HIDE_UNDOC_RELATIONS = YES # http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO -# The default value is: NO. +# The default value is: YES. HAVE_DOT = NO @@ -2358,10 +2439,32 @@ UML_LOOK = NO # but if the number exceeds 15, the total amount of fields shown is limited to # 10. # Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. +# This tag requires that the tag UML_LOOK is set to YES. UML_LIMIT_NUM_FIELDS = 10 +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. @@ -2435,7 +2538,9 @@ DIRECTORY_GRAPH = YES # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). -# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd, +# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo, +# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo, # png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and # png:gdiplus:gdiplus. # The default value is: png. @@ -2551,9 +2656,11 @@ DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate # files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc and +# plantuml temporary files. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES diff --git a/build/doc/customdoxygen.css b/build/doc/customdoxygen.css index 40f9b1253d0..a11967f9616 100644 --- a/build/doc/customdoxygen.css +++ b/build/doc/customdoxygen.css @@ -1869,7 +1869,7 @@ u { } #titlearea { - background: #c53431; + background: #5E81AC; border: none; } @@ -2140,4 +2140,4 @@ div.line { padding-right: 12px; } -/* Custom ends HERE */ \ No newline at end of file +/* Custom ends HERE */ diff --git a/build/doc/logo.png b/build/doc/logo.png index 8a7615d61716a468a992b7191bb034ca0182c719..e178106cfbd9868d88a2d820636da598af6f3051 100644 GIT binary patch literal 17280 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 Date: Sun, 6 Feb 2022 19:06:48 +0100 Subject: [PATCH 167/355] [apps/code] Add documentation on math.factorial (#146) --- apps/code/catalog.de.i18n | 1 + apps/code/catalog.en.i18n | 1 + apps/code/catalog.es.i18n | 1 + apps/code/catalog.fr.i18n | 1 + apps/code/catalog.hu.i18n | 1 + apps/code/catalog.it.i18n | 1 + apps/code/catalog.nl.i18n | 1 + apps/code/catalog.pt.i18n | 1 + apps/code/catalog.universal.i18n | 1 + apps/code/python_toolbox.cpp | 1 + 10 files changed, 10 insertions(+) diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index e66c309a70f..15492dbd9ca 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -53,6 +53,7 @@ PythonErfc = "Komplementäre Fehlerfunktion" PythonEval = "Rückgabe ausgewerteter Ausdruck" PythonExp = "Exponentialfunktion" PythonExpm1 = "Berechne exp(x)-1" +PythonFactorial = "Fakultät von x" PythonFabs = "Absoluter Wert" PythonFillRect = "Gefülltes Rechteck bei Pixel (x,y)" PythonFillCircle = "Füllt einen Kreis" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index 651954ffb90..298b16d2b24 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -53,6 +53,7 @@ PythonErfc = "Complementary error function" PythonEval = "Return the evaluated expression" PythonExp = "Exponential function" PythonExpm1 = "Compute exp(x)-1" +PythonFactorial = "Factorial of x" PythonFabs = "Absolute value" PythonFillCircle = "Fill a circle" PythonFillPolygon = "Fill a polygon" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index c58b97e8087..c2209a3f52a 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -53,6 +53,7 @@ PythonErfc = "Complementary error function" PythonEval = "Return the evaluated expression" PythonExp = "Exponential function" PythonExpm1 = "Compute exp(x)-1" +PythonFactorial = "factorial de x" PythonFabs = "Absolute value" PythonFillCircle = "Fill a circle" PythonFillPolygon = "Fill a polygon" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index 1e348cda30f..5b2b4116649 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -53,6 +53,7 @@ PythonErfc = "Fonction d'erreur complémentaire" PythonEval = "Evalue l'expression en argument " PythonExp = "Fonction exponentielle" PythonExpm1 = "Calcul de exp(x)-1" +PythonFactorial = "Factorielle de x" PythonFabs = "Valeur absolue" PythonFillCircle = "Remplit un cercle" PythonFillPolygon = "Remplit un polygone" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index c3c8cb00ed4..570537a6eab 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -53,6 +53,7 @@ PythonErfc = "Kiegészítö hiba funkció" PythonEval = "Visszaadja az értékelt kifejezést" PythonExp = "Exponenciális függvény" PythonExpm1 = "exp(x)-1 sámitása" +PythonFactorial = "x faktorál" PythonFabs = "Abszolút érték" PythonFillRect = "Téglalap töltése" PythonFillCircle = "Kitölti a kört" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index c029afabc4b..8aa01572959 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -53,6 +53,7 @@ PythonErfc = "Funzione d'errore complementare" PythonEval = "Valuta l'espressione nell'argomento " PythonExp = "Funzione esponenziale" PythonExpm1 = "Calcola exp(x)-1" +PythonFactorial = "Fattoriale di x" PythonFabs = "Valore assoluto" PythonFillCircle = "Riempire un cerchio" PythonFillPolygon = "Riempire un poligono" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index 66a9b934453..17c9f9eb7dd 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -53,6 +53,7 @@ PythonErfc = "Complementaire error functie" PythonEval = "Geef de geëvalueerde uitdrukking" PythonExp = "Exponentiële functie" PythonExpm1 = "Bereken exp(x)-1" +PythonFactorial = "faculteit van x" PythonFabs = "Absolute waarde" PythonFillCircle = "Vul een cirkel" PythonFillPolygon = "Vul een veelhoek" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index c7f8e2ab49c..3243a1e0c01 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -53,6 +53,7 @@ PythonErfc = "Função erro complementar" PythonEval = "Devolve a expressão avaliada" PythonExp = "Função exponencial" PythonExpm1 = "Calcular exp(x)-1" +PythonFactorial = "Fatorial de x" PythonFabs = "Valor absoluto" PythonFillCircle = "Preencher um círculo" PythonFillPolygon = "Preencher um polígono" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index 967125d72fa..8159d966ba0 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -60,6 +60,7 @@ PythonCommandEval = "eval(\"expression\")" PythonCommandExp = "exp(x)" PythonCommandExpComplex = "exp(z)" PythonCommandExpm1 = "expm1(x)" +PythonCommandFactorial = "factorial(x)" PythonCommandFabs = "fabs(x)" PythonCommandFillCircle = "fill_circle(x,y,r,color)" PythonCommandFillPolygon = "fill_polygon([(x1,y1),...],color)" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 3f073401e03..35a453b22ad 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -75,6 +75,7 @@ const ToolboxMessageTree MathModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAtan2, I18n::Message::PythonAtan2), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandCeil, I18n::Message::PythonCeil), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandCopySign, I18n::Message::PythonCopySign), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFactorial, I18n::Message::PythonFactorial), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFabs, I18n::Message::PythonFabs), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFloor, I18n::Message::PythonFloor), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFmod, I18n::Message::PythonFmod), From 4f0f41be56dc8d398d142e892430f4d0cff10ce2 Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Wed, 9 Feb 2022 22:21:07 +0100 Subject: [PATCH 168/355] [python] Add FStrings support (#155) --- python/port/mpconfigport.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/port/mpconfigport.h b/python/port/mpconfigport.h index 9175793b25b..7f5fe20e29a 100644 --- a/python/port/mpconfigport.h +++ b/python/port/mpconfigport.h @@ -52,6 +52,9 @@ // Support for async/await/async for/async with #define MICROPY_PY_ASYNC_AWAIT (0) +// Support for literal string interpolation, f-strings (see PEP 498, Python 3.6+) +#define MICROPY_PY_FSTRINGS (1) + // Whether to support bytearray object #define MICROPY_PY_BUILTINS_BYTEARRAY (1) From 893a2a550d1c925d133374d29c96de285ecef97f Mon Sep 17 00:00:00 2001 From: Laury Date: Mon, 14 Feb 2022 20:52:33 +0100 Subject: [PATCH 169/355] [reader] Fixed a bug in list of books --- apps/reader/utility.cpp | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/apps/reader/utility.cpp b/apps/reader/utility.cpp index 51fdc043a3d..6ffc5820a21 100644 --- a/apps/reader/utility.cpp +++ b/apps/reader/utility.cpp @@ -4,9 +4,9 @@ #ifndef DEVICE #include -#include +#include #include -#endif +#endif namespace Reader { @@ -49,12 +49,10 @@ void stringNCopy(char* dest, int max, const char* src, int len) { int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize) { size_t nbTotalFiles = External::Archive::numberOfFiles(); int nbFiles = 0; - for(size_t i=0; i < nbTotalFiles; ++i) - { + for(size_t i=0; i < nbTotalFiles; ++i) { External::Archive::File file; External::Archive::fileAtIndex(i, file); - if(stringEndsWith(file.name, ".txt")) - { + if(stringEndsWith(file.name, extension)) { files[nbFiles] = file; nbFiles++; if(nbFiles == filesSize) @@ -65,28 +63,28 @@ int filesWithExtension(const char* extension, External::Archive::File* files, in } #else -static void fillFileData(External::Archive::File& file) { +static void fillFileData(External::Archive::File& file) { file.data = nullptr; - file.dataLength = 0; + file.dataLength = 0; struct stat info; if (stat(file.name, &info) != 0) { return; - } - + } + unsigned char* content = new unsigned char[info.st_size]; if (content == NULL) { return; - } + } FILE *fp = fopen(file.name, "rb"); if (fp == NULL) { return ; } - - fread(content, info.st_size, 1, fp); + + fread(content, info.st_size, 1, fp); fclose(fp); file.data = content; - file.dataLength = info.st_size; + file.dataLength = info.st_size; } int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize) { From f3d632c462c7342742eec6ca6b85f4b3248d5227 Mon Sep 17 00:00:00 2001 From: Laury Date: Tue, 15 Feb 2022 22:24:50 +0100 Subject: [PATCH 170/355] [apps/calculation] Add a "ctrl-z" system --- apps/calculation/calculation_store.cpp | 41 +++++++++++++++---- apps/calculation/calculation_store.h | 7 +++- .../edit_expression_controller.cpp | 9 ++++ apps/calculation/edit_expression_controller.h | 1 + apps/calculation/history_controller.h | 1 + ion/include/ion/events.h | 1 + .../keyboard/layout_B2/layout_events.cpp | 4 +- .../keyboard/layout_B3/layout_events.cpp | 4 +- 8 files changed, 55 insertions(+), 13 deletions(-) diff --git a/apps/calculation/calculation_store.cpp b/apps/calculation/calculation_store.cpp index a2ae0b3e833..55a87653b22 100644 --- a/apps/calculation/calculation_store.cpp +++ b/apps/calculation/calculation_store.cpp @@ -16,14 +16,24 @@ CalculationStore::CalculationStore(char * buffer, int size) : m_buffer(buffer), m_bufferSize(size), m_calculationAreaEnd(m_buffer), - m_numberOfCalculations(0) + m_numberOfCalculations(0), + m_trashIndex(-1) { assert(m_buffer != nullptr); assert(m_bufferSize > 0); } -// Returns an expiring pointer to the calculation of index i +// Returns an expiring pointer to the calculation of index i, and ignore the trash ExpiringPointer CalculationStore::calculationAtIndex(int i) { + if (m_trashIndex == -1 || i < m_trashIndex) { + return realCalculationAtIndex(i); + } else { + return realCalculationAtIndex(i + 1); + } +} + +// Returns an expiring pointer to the real calculation of index i +ExpiringPointer CalculationStore::realCalculationAtIndex(int i) { assert(i >= 0 && i < m_numberOfCalculations); // m_buffer is the address of the oldest calculation in calculation store Calculation * c = (Calculation *) m_buffer; @@ -36,12 +46,13 @@ ExpiringPointer CalculationStore::calculationAtIndex(int i) { // Pushes an expression in the store ExpiringPointer CalculationStore::push(const char * text, Context * context, HeightComputer heightComputer) { + emptyTrash(); /* Compute ans now, before the buffer is updated and before the calculation * might be deleted */ Expression ans = ansExpression(context); /* Prepare the buffer for the new calculation - *The minimal size to store the new calculation is the minimal size of a calculation plus the pointer to its end */ + * The minimal size to store the new calculation is the minimal size of a calculation plus the pointer to its end */ int minSize = Calculation::MinimalSize() + sizeof(Calculation *); assert(m_bufferSize > minSize); while (remainingBufferSize() < minSize) { @@ -132,15 +143,23 @@ ExpiringPointer CalculationStore::push(const char * text, Context * // Delete the calculation of index i void CalculationStore::deleteCalculationAtIndex(int i) { + if (m_trashIndex != -1) { + emptyTrash(); + } + m_trashIndex = i; +} + +// Delete the calculation of index i, internal algorithm +void CalculationStore::realDeleteCalculationAtIndex(int i) { assert(i >= 0 && i < m_numberOfCalculations); if (i == 0) { - ExpiringPointer lastCalculationPointer = calculationAtIndex(0); + ExpiringPointer lastCalculationPointer = realCalculationAtIndex(0); m_calculationAreaEnd = (char *)(lastCalculationPointer.pointer()); m_numberOfCalculations--; return; } - char * calcI = (char *)calculationAtIndex(i).pointer(); - char * nextCalc = (char *) calculationAtIndex(i-1).pointer(); + char * calcI = (char *)realCalculationAtIndex(i).pointer(); + char * nextCalc = (char *) realCalculationAtIndex(i-1).pointer(); assert(m_calculationAreaEnd >= nextCalc); size_t slidingSize = m_calculationAreaEnd - nextCalc; // Slide the i-1 most recent calculations right after the i+1'th @@ -200,6 +219,12 @@ bool CalculationStore::pushSerializeExpression(Expression e, char * location, ch return expressionIsPushed; } +void CalculationStore::emptyTrash() { + if (m_trashIndex != -1) { + realDeleteCalculationAtIndex(m_trashIndex); + m_trashIndex = -1; + } +} Shared::ExpiringPointer CalculationStore::emptyStoreAndPushUndef(Context * context, HeightComputer heightComputer) { @@ -211,9 +236,9 @@ Shared::ExpiringPointer CalculationStore::emptyStoreAndPushUndef(Co // Recompute memoized pointers to the calculations after index i void CalculationStore::recomputeMemoizedPointersAfterCalculationIndex(int index) { - assert(index < m_numberOfCalculations); + assert(index < numberOfCalculations()); // Clear pointer and recompute new ones - Calculation * c = calculationAtIndex(index).pointer(); + Calculation * c = realCalculationAtIndex(index).pointer(); Calculation * nextCalc; while (index != 0) { nextCalc = c->next(); diff --git a/apps/calculation/calculation_store.h b/apps/calculation/calculation_store.h index 53bc3fdf4f6..134a9fbb853 100644 --- a/apps/calculation/calculation_store.h +++ b/apps/calculation/calculation_store.h @@ -41,11 +41,15 @@ class CalculationStore { void deleteCalculationAtIndex(int i); void deleteAll(); int remainingBufferSize() const { assert(m_calculationAreaEnd >= m_buffer); return m_bufferSize - (m_calculationAreaEnd - m_buffer) - m_numberOfCalculations*sizeof(Calculation*); } - int numberOfCalculations() const { return m_numberOfCalculations; } + int numberOfCalculations() const { return m_numberOfCalculations - (m_trashIndex != -1); } Poincare::Expression ansExpression(Poincare::Context * context); int bufferSize() { return m_bufferSize; } + void reinsertTrash() { m_trashIndex = -1; } private: + void emptyTrash(); + Shared::ExpiringPointer realCalculationAtIndex(int i); + void realDeleteCalculationAtIndex(int i); class CalculationIterator { public: @@ -70,6 +74,7 @@ class CalculationStore { int m_bufferSize; const char * m_calculationAreaEnd; int m_numberOfCalculations; + int m_trashIndex; size_t deleteOldestCalculation(); char * addressOfPointerToCalculationOfIndex(int i) {return m_buffer + m_bufferSize - (m_numberOfCalculations - i)*sizeof(Calculation *);} diff --git a/apps/calculation/edit_expression_controller.cpp b/apps/calculation/edit_expression_controller.cpp index 7f747d3598f..9d999bf196e 100644 --- a/apps/calculation/edit_expression_controller.cpp +++ b/apps/calculation/edit_expression_controller.cpp @@ -68,6 +68,15 @@ void EditExpressionController::memoizeInput() { *m_cacheBufferInformation = m_contentView.expressionField()->moveCursorAndDumpContent(m_cacheBuffer, k_cacheBufferSize); } +bool EditExpressionController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::ShiftBack) { + m_historyController->reinsertTrash(); + m_historyController->reload(); + return true; + } + return false; +} + void EditExpressionController::viewWillAppear() { m_historyController->viewWillAppear(); } diff --git a/apps/calculation/edit_expression_controller.h b/apps/calculation/edit_expression_controller.h index 32a6ec84cb2..9e788faa361 100644 --- a/apps/calculation/edit_expression_controller.h +++ b/apps/calculation/edit_expression_controller.h @@ -31,6 +31,7 @@ class EditExpressionController : public ViewController, public Shared::TextField void insertTextBody(const char * text); void restoreInput(); void memoizeInput(); + bool handleEvent(Ion::Events::Event event) override; /* TextFieldDelegate */ bool textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) override; diff --git a/apps/calculation/history_controller.h b/apps/calculation/history_controller.h index b919e823c79..8b7c3bf53ef 100644 --- a/apps/calculation/history_controller.h +++ b/apps/calculation/history_controller.h @@ -35,6 +35,7 @@ class HistoryController : public ViewController, public ListViewDataSource, publ int typeAtLocation(int i, int j) override; void setSelectedSubviewType(SubviewType subviewType, bool sameCell, int previousSelectedX = -1, int previousSelectedY = -1) override; void tableViewDidChangeSelectionAndDidScroll(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false) override; + void reinsertTrash() { m_calculationStore->reinsertTrash(); } private: int storeIndex(int i) { return numberOfRows() - i - 1; } Shared::ExpiringPointer calculationAtIndex(int i); diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index 4dccae5be95..394271cf299 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -142,6 +142,7 @@ constexpr Event ShiftRight = Event::ShiftKey(Keyboard::Key::Right); constexpr Event ShiftUp = Event::ShiftKey(Keyboard::Key::Up); constexpr Event ShiftDown = Event::ShiftKey(Keyboard::Key::Down); constexpr Event ShiftOK = Event::ShiftKey(Keyboard::Key::OK); +constexpr Event ShiftBack = Event::ShiftKey(Keyboard::Key::Back); constexpr Event AlphaLock = Event::ShiftKey(Keyboard::Key::Alpha); constexpr Event Cut = Event::ShiftKey(Keyboard::Key::XNT); diff --git a/ion/src/shared/keyboard/layout_B2/layout_events.cpp b/ion/src/shared/keyboard/layout_B2/layout_events.cpp index 6ff1b74b7ee..035d4509054 100644 --- a/ion/src/shared/keyboard/layout_B2/layout_events.cpp +++ b/ion/src/shared/keyboard/layout_B2/layout_events.cpp @@ -16,7 +16,7 @@ const EventData s_dataForEvent[4 * Event::PageSize] = { T("1"), T("2"), T("3"), T("+"), T("-"), U(), T("0"), T("."), T("ᴇ"), TL(), TL(), U(), // Shift - TL(), TL(), TL(), TL(), TL(), U(), + TL(), TL(), TL(), TL(), TL(), TL(), TL(), U(), U(), U(), U(), U(), U(), U(), TL(), TL(), TL(), TL(), T("["), T("]"), T("{"), T("}"), T("_"), T("→"), @@ -61,7 +61,7 @@ const char * const s_nameForEvent[255] = { "One", "Two", "Three", "Plus", "Minus", nullptr, "Zero", "Dot", "EE", "Ans", "EXE", nullptr, //Shift, - "ShiftLeft", "ShiftUp", "ShiftDown", "ShiftRight", "ShiftOK", nullptr, + "ShiftLeft", "ShiftUp", "ShiftDown", "ShiftRight", "ShiftOK", "ShiftBack", "ShiftHome", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "AlphaLock", "Cut", "Copy", "Paste", "Clear", "LeftBracket", "RightBracket", "LeftBrace", "RightBrace", "Underscore", "Sto", diff --git a/ion/src/shared/keyboard/layout_B3/layout_events.cpp b/ion/src/shared/keyboard/layout_B3/layout_events.cpp index 6ccf51c3b44..2ea81e4a79b 100644 --- a/ion/src/shared/keyboard/layout_B3/layout_events.cpp +++ b/ion/src/shared/keyboard/layout_B3/layout_events.cpp @@ -16,7 +16,7 @@ const EventData s_dataForEvent[4 * Event::PageSize] = { T("1"), T("2"), T("3"), T("+"), T("-"), U(), T("0"), T("."), T("ᴇ"), TL(), TL(), U(), // Shift - TL(), TL(), TL(), TL(), TL(), U(), + TL(), TL(), TL(), TL(), TL(), TL(), TL(), U(), U(), U(), U(), U(), U(), U(), TL(), TL(), TL(), TL(), T("["), T("]"), T("{"), T("}"), T("_"), T("→"), @@ -61,7 +61,7 @@ const char * const s_nameForEvent[255] = { "One", "Two", "Three", "Plus", "Minus", nullptr, "Zero", "Dot", "EE", "Ans", "EXE", nullptr, //Shift, - "ShiftLeft", "ShiftUp", "ShiftDown", "ShiftRight", "ShiftOK", nullptr, + "ShiftLeft", "ShiftUp", "ShiftDown", "ShiftRight", "ShiftOK", "ShiftBack", "ShiftHome", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "AlphaLock", "Cut", "Copy", "Paste", "Clear", "LeftBracket", "RightBracket", "LeftBrace", "RightBrace", "Underscore", "Sto", From af5f9ea578694a1500b4a888f0c138c1c0273c49 Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 19 Feb 2022 12:09:02 +0100 Subject: [PATCH 171/355] [ion/storage] Added the "ctrl-z" system everywhere --- apps/calculation/calculation_store.cpp | 1 + apps/code/menu_controller.cpp | 5 + apps/graph/list/list_controller.h | 2 + apps/sequence/list/list_controller.h | 2 + apps/shared/expression_model_handle.h | 1 - .../expression_model_list_controller.cpp | 19 ++-- .../shared/expression_model_list_controller.h | 1 + apps/shared/function_list_controller.cpp | 5 + apps/shared/function_list_controller.h | 1 + apps/solver/equation.h | 3 - apps/solver/list_controller.h | 3 + ion/include/ion/storage.h | 13 ++- ion/src/shared/storage.cpp | 96 ++++++++++++++++--- 13 files changed, 126 insertions(+), 26 deletions(-) diff --git a/apps/calculation/calculation_store.cpp b/apps/calculation/calculation_store.cpp index 55a87653b22..5293474c98a 100644 --- a/apps/calculation/calculation_store.cpp +++ b/apps/calculation/calculation_store.cpp @@ -180,6 +180,7 @@ size_t CalculationStore::deleteOldestCalculation() { // Delete all calculations void CalculationStore::deleteAll() { + m_trashIndex = -1; m_calculationAreaEnd = m_buffer; m_numberOfCalculations = 0; } diff --git a/apps/code/menu_controller.cpp b/apps/code/menu_controller.cpp index a857fc539a2..63a50c0a882 100644 --- a/apps/code/menu_controller.cpp +++ b/apps/code/menu_controller.cpp @@ -90,6 +90,11 @@ bool MenuController::handleEvent(Ion::Events::Event event) { footer()->setSelectedButton(0); return true; } + if (event == Ion::Events::ShiftBack) { + Ion::Storage::sharedStorage()->reinsertTrash("py"); + m_selectableTableView.reloadData(); + return true; + } if (event == Ion::Events::Up) { if (footer()->selectedButton() == 0) { footer()->setSelectedButton(-1); diff --git a/apps/graph/list/list_controller.h b/apps/graph/list/list_controller.h index 12deaf33bb2..b116f34a172 100644 --- a/apps/graph/list/list_controller.h +++ b/apps/graph/list/list_controller.h @@ -21,6 +21,8 @@ class ListController : public Shared::FunctionListController, public Shared::Tex bool textFieldDidAbortEditing(TextField * textField) override; bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; +protected: + virtual const char * recordExtension() const override { return Ion::Storage::funcExtension; } private: constexpr static int k_maxNumberOfDisplayableRows = 5; Shared::ListParameterController * parameterController() override; diff --git a/apps/sequence/list/list_controller.h b/apps/sequence/list/list_controller.h index d16af18d9cd..9aef7c4adf0 100644 --- a/apps/sequence/list/list_controller.h +++ b/apps/sequence/list/list_controller.h @@ -25,6 +25,8 @@ class ListController : public Shared::FunctionListController, public Shared::Inp Toolbox * toolboxForInputEventHandler(InputEventHandler * handler) override; void selectPreviousNewSequenceCell(); void editExpression(int sequenceDefinitionIndex, Ion::Events::Event event); +protected: + virtual const char * recordExtension() const override { return Ion::Storage::seqExtension; } private: static constexpr KDCoordinate k_expressionCellVerticalMargin = 3; bool editInitialConditionOfSelectedRecordWithText(const char * text, bool firstInitialCondition); diff --git a/apps/shared/expression_model_handle.h b/apps/shared/expression_model_handle.h index 8d6229a1825..812f218a6e8 100644 --- a/apps/shared/expression_model_handle.h +++ b/apps/shared/expression_model_handle.h @@ -23,7 +23,6 @@ class ExpressionModelHandle : public Ion::Storage::Record { * not defined. We thus have to keep both methods. */ virtual bool isDefined(); virtual bool isEmpty(); - virtual bool shouldBeClearedBeforeRemove() { return !isEmpty(); } /* tidy is responsible to tidy the whole model whereas tidyExpressionModel * tidies only the members associated with the ExpressionModel. In * ExpressionModel, tidy and tidyExpressionModel trigger the same diff --git a/apps/shared/expression_model_list_controller.cpp b/apps/shared/expression_model_list_controller.cpp index dc1c18d0f52..3defcde2e2e 100644 --- a/apps/shared/expression_model_list_controller.cpp +++ b/apps/shared/expression_model_list_controller.cpp @@ -184,18 +184,19 @@ bool ExpressionModelListController::handleEventOnExpression(Ion::Events::Event e } if (event == Ion::Events::Backspace && !isAddEmptyRow(selectedRow())) { Ion::Storage::Record record = modelStore()->recordAtIndex(modelIndexForRow(selectedRow())); - ExpiringPointer model = modelStore()->modelForRecord(record); - if (model->shouldBeClearedBeforeRemove()) { - reinitSelectedExpression(model); - } else { - if (removeModelRow(record)) { - int newSelectedRow = selectedRow() >= numberOfExpressionRows() ? numberOfExpressionRows()-1 : selectedRow(); - selectCellAtLocation(selectedColumn(), newSelectedRow); - selectableTableView()->reloadData(); - } + ExpiringPointer model = modelStore()->modelForRecord(record); + if (removeModelRow(record)) { + int newSelectedRow = selectedRow() >= numberOfExpressionRows() ? numberOfExpressionRows()-1 : selectedRow(); + selectCellAtLocation(selectedColumn(), newSelectedRow); + selectableTableView()->reloadData(); } return true; } + if (event == Ion::Events::ShiftBack) { + Ion::Storage::sharedStorage()->reinsertTrash(recordExtension()); + selectableTableView()->reloadData(); + return true; + } if ((event.hasText() || event == Ion::Events::XNT || event == Ion::Events::Paste || event == Ion::Events::Toolbox || event == Ion::Events::Var) && !isAddEmptyRow(selectedRow())) { editExpression(event); diff --git a/apps/shared/expression_model_list_controller.h b/apps/shared/expression_model_list_controller.h index 45162107c9d..9ced3980a18 100644 --- a/apps/shared/expression_model_list_controller.h +++ b/apps/shared/expression_model_list_controller.h @@ -11,6 +11,7 @@ class ExpressionModelListController : public ViewController, public SelectableTa public: ExpressionModelListController(Responder * parentResponder, I18n::Message text); protected: + virtual const char * recordExtension() const = 0; static constexpr KDCoordinate k_expressionMargin = 5; // SelectableTableViewDelegate void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; diff --git a/apps/shared/function_list_controller.cpp b/apps/shared/function_list_controller.cpp index d8dbf2ad7a3..4083ab9551d 100644 --- a/apps/shared/function_list_controller.cpp +++ b/apps/shared/function_list_controller.cpp @@ -200,6 +200,11 @@ bool FunctionListController::handleEvent(Ion::Events::Event event) { } return true; } + if (event == Ion::Events::ShiftBack) { + Ion::Storage::sharedStorage()->reinsertTrash(recordExtension()); + selectableTableView()->reloadData(); + return true; + } return false; } diff --git a/apps/shared/function_list_controller.h b/apps/shared/function_list_controller.h index da6a7036a26..be62a843544 100644 --- a/apps/shared/function_list_controller.h +++ b/apps/shared/function_list_controller.h @@ -7,6 +7,7 @@ #include "list_parameter_controller.h" #include "expression_model_list_controller.h" #include +#include namespace Shared { diff --git a/apps/solver/equation.h b/apps/solver/equation.h index a43db56c822..cae60c7ce77 100644 --- a/apps/solver/equation.h +++ b/apps/solver/equation.h @@ -8,9 +8,6 @@ namespace Solver { class Equation : public Shared::ExpressionModelHandle { public: Equation(Ion::Storage::Record record = Record()) : ExpressionModelHandle(record) {} - bool shouldBeClearedBeforeRemove() override { - return false; - } Poincare::Expression standardForm(Poincare::Context * context, bool replaceFunctionsButNotSymbols, Poincare::ExpressionNode::ReductionTarget reductionTarget) const { return m_model.standardForm(this, context, replaceFunctionsButNotSymbols, reductionTarget); } bool containsIComplex(Poincare::Context * context) const; diff --git a/apps/solver/list_controller.h b/apps/solver/list_controller.h index 93e9173fda7..a64cc6ec024 100644 --- a/apps/solver/list_controller.h +++ b/apps/solver/list_controller.h @@ -9,6 +9,7 @@ #include "equation_list_view.h" #include "equation_models_parameter_controller.h" #include +#include namespace Solver { @@ -45,6 +46,8 @@ class ListController : public Shared::ExpressionModelListController, public Butt bool layoutFieldDidFinishEditing(LayoutField * layoutField, Poincare::Layout layout, Ion::Events::Event event) override; /* Specific to Solver */ void resolveEquations(); +protected: + virtual const char * recordExtension() const override { return Ion::Storage::eqExtension; } private: constexpr static int k_maxNumberOfRows = 5; // Ion::Display::Height / Metric::StoreRowHeight = 4.8; SelectableTableView * selectableTableView() override; diff --git a/ion/include/ion/storage.h b/ion/include/ion/storage.h index 56ee0fc26e9..3cca0c4ca06 100644 --- a/ion/include/ion/storage.h +++ b/ion/include/ion/storage.h @@ -125,7 +125,10 @@ class Storage { // Used by Python OS module int numberOfRecords(); - Record recordAtIndex(int index); + Record recordAtIndex(int index); // Unlike realRecordAtIndex, this ignore trash + + // Trash + void reinsertTrash(const char * extension); private: constexpr static uint32_t Magic = 0xEE0BDDBA; @@ -140,6 +143,7 @@ class Storage { Record::Data valueOfRecord(const Record record); Record::ErrorStatus setValueOfRecord(const Record record, Record::Data data); void destroyRecord(const Record record); + void realDestroyRecord(const Record record); /* Getters on address in buffer */ char * pointerOfRecord(const Record record) const; @@ -147,12 +151,18 @@ class Storage { const char * fullNameOfRecordStarting(char * start) const; const void * valueOfRecordStarting(char * start) const; + /* Trash */ + void emptyTrash(); + Storage::Record realRecordAtIndex(int index); + int realNumberOfRecords(); + /* Overriders */ size_t overrideSizeAtPosition(char * position, record_size_t size); size_t overrideFullNameAtPosition(char * position, const char * fullName); size_t overrideBaseNameWithExtensionAtPosition(char * position, const char * baseName, const char * extension); size_t overrideValueAtPosition(char * position, const void * data, record_size_t size); + size_t realAvailableSize(); bool isFullNameTaken(const char * fullName, const Record * recordToExclude = nullptr); bool isBaseNameWithExtensionTaken(const char * baseName, const char * extension, Record * recordToExclude = nullptr); bool isNameOfRecordTaken(Record r, const Record * recordToExclude); @@ -182,6 +192,7 @@ class Storage { uint32_t m_magicHeader; char m_buffer[k_storageSize]; + Record m_trashRecord; uint32_t m_magicFooter; StorageDelegate * m_delegate; mutable Record m_lastRecordRetrieved; diff --git a/ion/src/shared/storage.cpp b/ion/src/shared/storage.cpp index 3419e3d8c53..200b41865c6 100644 --- a/ion/src/shared/storage.cpp +++ b/ion/src/shared/storage.cpp @@ -97,6 +97,14 @@ void Storage::log() { #endif size_t Storage::availableSize() { + if (m_trashRecord != NULL) { + return realAvailableSize() + sizeof(record_size_t) + m_trashRecord.value().size; + } else { + return realAvailableSize(); + } +} + +size_t Storage::realAvailableSize() { /* TODO maybe do: availableSize(char ** endBuffer) to get the endBuffer if it * is needed after calling availableSize */ assert(k_storageSize >= (endBuffer() - m_buffer) + sizeof(record_size_t)); @@ -106,7 +114,7 @@ size_t Storage::availableSize() { size_t Storage::putAvailableSpaceAtEndOfRecord(Storage::Record r) { char * p = pointerOfRecord(r); size_t previousRecordSize = sizeOfRecordStarting(p); - size_t availableStorageSize = availableSize(); + size_t availableStorageSize = realAvailableSize(); char * nextRecord = p + previousRecordSize; memmove(nextRecord + availableStorageSize, nextRecord, @@ -146,6 +154,7 @@ Storage::Record::ErrorStatus Storage::notifyFullnessToDelegate() const { } Storage::Record::ErrorStatus Storage::createRecordWithFullName(const char * fullName, const void * data, size_t size) { + emptyTrash(); size_t recordSize = sizeOfRecordWithFullName(fullName, size); if (recordSize >= k_maxRecordSize || recordSize > availableSize()) { return notifyFullnessToDelegate(); @@ -172,6 +181,7 @@ Storage::Record::ErrorStatus Storage::createRecordWithFullName(const char * full } Storage::Record::ErrorStatus Storage::createRecordWithExtension(const char * baseName, const char * extension, const void * data, size_t size) { + emptyTrash(); size_t recordSize = sizeOfRecordWithBaseNameAndExtension(baseName, extension, size); if (recordSize >= k_maxRecordSize || recordSize > availableSize()) { return notifyFullnessToDelegate(); @@ -202,7 +212,7 @@ int Storage::numberOfRecordsWithExtension(const char * extension) { size_t extensionLength = strlen(extension); for (char * p : *this) { const char * name = fullNameOfRecordStarting(p); - if (FullNameHasExtension(name, extension, extensionLength)) { + if (FullNameHasExtension(name, extension, extensionLength) && Record(name) != m_trashRecord) { count++; } } @@ -210,6 +220,10 @@ int Storage::numberOfRecordsWithExtension(const char * extension) { } int Storage::numberOfRecords() { + return realNumberOfRecords() - (m_trashRecord == NULL ? 0 : 1); +} + +int Storage::realNumberOfRecords() { int count = 0; for (char * p : *this) { const char * name = fullNameOfRecordStarting(p); @@ -218,7 +232,7 @@ int Storage::numberOfRecords() { return count; } -Storage::Record Storage::recordAtIndex(int index) { +Storage::Record Storage::realRecordAtIndex(int index) { int currentIndex = -1; const char * name = nullptr; char * recordAddress = nullptr; @@ -240,16 +254,18 @@ Storage::Record Storage::recordAtIndex(int index) { return Record(name); } -Storage::Record Storage::recordWithExtensionAtIndex(const char * extension, int index) { + +Storage::Record Storage::recordAtIndex(int index) { int currentIndex = -1; const char * name = nullptr; - size_t extensionLength = strlen(extension); char * recordAddress = nullptr; for (char * p : *this) { const char * currentName = fullNameOfRecordStarting(p); - if (FullNameHasExtension(currentName, extension, extensionLength)) { - currentIndex++; + Record r = Record(currentName); + if (r == m_trashRecord) { + continue; } + currentIndex++; if (currentIndex == index) { recordAddress = p; name = currentName; @@ -265,6 +281,38 @@ Storage::Record Storage::recordWithExtensionAtIndex(const char * extension, int return Record(name); } +void Storage::emptyTrash() { + if (m_trashRecord != NULL) { + realDestroyRecord(m_trashRecord); + m_trashRecord = NULL; + } +} + +Storage::Record Storage::recordWithExtensionAtIndex(const char * extension, int index) { + int currentIndex = -1; + const char * name = nullptr; + size_t extensionLength = strlen(extension); + char * recordAddress = nullptr; + for (char * p : *this) { + const char * currentName = fullNameOfRecordStarting(p); + if (FullNameHasExtension(currentName, extension, extensionLength) && Record(currentName) != m_trashRecord) { + currentIndex++; + if (currentIndex == index) { + recordAddress = p; + name = currentName; + break; + } + } + } + if (name == nullptr) { + return Record(); + } + Record r = Record(name); + m_lastRecordRetrieved = r; + m_lastRecordRetrievedPointer = recordAddress; + return Record(name); +} + Storage::Record Storage::recordNamed(const char * fullName) { if (fullName == nullptr) { return Record(); @@ -328,7 +376,8 @@ Storage::Storage() : m_magicFooter(Magic), m_delegate(nullptr), m_lastRecordRetrieved(nullptr), - m_lastRecordRetrievedPointer(nullptr) + m_lastRecordRetrievedPointer(nullptr), + m_trashRecord(NULL) { assert(m_magicHeader == Magic); assert(m_magicFooter == Magic); @@ -430,7 +479,24 @@ Storage::Record::ErrorStatus Storage::setValueOfRecord(Record record, Record::Da return Record::ErrorStatus::RecordDoesNotExist; } + +void Storage::reinsertTrash(const char * extension) { + if (m_trashRecord != NULL) { + char * p = pointerOfRecord(m_trashRecord); + const char * fullName = fullNameOfRecordStarting(p); + if (FullNameHasExtension(fullName, extension, strlen(extension))) { + m_trashRecord = NULL; + } + } +} + + void Storage::destroyRecord(Record record) { + emptyTrash(); + m_trashRecord = record; +} + +void Storage::realDestroyRecord(Record record) { if (record.isNull()) { return; } @@ -520,7 +586,7 @@ bool Storage::isNameOfRecordTaken(Record r, const Record * recordToExclude) { if (recordToExclude && s == *recordToExclude) { continue; } - if (s == r) { + if (s == r && s != m_trashRecord) { return true; } } @@ -576,8 +642,11 @@ size_t Storage::sizeOfRecordWithFullName(const char * fullName, size_t dataSize) } bool Storage::slideBuffer(char * position, int delta) { - if (delta > (int)availableSize()) { - return false; + if (delta > (int)realAvailableSize()) { + emptyTrash(); + if (delta > (int)realAvailableSize()) { + return false; + } } memmove(position+delta, position, endBuffer()+sizeof(record_size_t)-position); return true; @@ -587,7 +656,7 @@ Storage::Record Storage::privateRecordAndExtensionOfRecordBaseNamedWithExtension size_t nameLength = baseNameLength < 0 ? strlen(baseName) : baseNameLength; { const char * lastRetrievedRecordFullName = fullNameOfRecordStarting(m_lastRecordRetrievedPointer); - if (m_lastRecordRetrievedPointer != nullptr && strncmp(baseName, lastRetrievedRecordFullName, nameLength) == 0) { + if (m_lastRecordRetrievedPointer != nullptr && strncmp(baseName, lastRetrievedRecordFullName, nameLength) == 0 && Record(lastRetrievedRecordFullName) == m_trashRecord) { for (size_t i = 0; i < numberOfExtensions; i++) { if (strcmp(lastRetrievedRecordFullName+nameLength+1 /*+1 to pass the dot*/, extensions[i]) == 0) { assert(UTF8Helper::CodePointIs(lastRetrievedRecordFullName + nameLength, '.')); @@ -602,6 +671,9 @@ Storage::Record Storage::privateRecordAndExtensionOfRecordBaseNamedWithExtension for (char * p : *this) { const char * currentName = fullNameOfRecordStarting(p); if (strncmp(baseName, currentName, nameLength) == 0) { + if (Record(currentName) == m_trashRecord) { + continue; + } for (size_t i = 0; i < numberOfExtensions; i++) { if (strcmp(currentName+nameLength+1 /*+1 to pass the dot*/, extensions[i]) == 0) { assert(UTF8Helper::CodePointIs(currentName + nameLength, '.')); From 23a94b7f5005affbbe8448f6f909643b95e973d1 Mon Sep 17 00:00:00 2001 From: Laury Date: Mon, 21 Feb 2022 17:30:27 +0100 Subject: [PATCH 172/355] [calculation] Simplify angles in additional outputs and fix an assert in calculation store --- .../trigonometry_list_controller.cpp | 53 +++++++++++++++++-- apps/calculation/calculation_store.cpp | 2 +- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/apps/calculation/additional_outputs/trigonometry_list_controller.cpp b/apps/calculation/additional_outputs/trigonometry_list_controller.cpp index b8bdcb0764e..354cbc7391f 100644 --- a/apps/calculation/additional_outputs/trigonometry_list_controller.cpp +++ b/apps/calculation/additional_outputs/trigonometry_list_controller.cpp @@ -1,16 +1,63 @@ #include "trigonometry_list_controller.h" #include "../app.h" +#include +#include "../../shared/poincare_helpers.h" using namespace Poincare; namespace Calculation { -void TrigonometryListController::setExpression(Poincare::Expression e) { +static constexpr int s_fullCircle[] = { + 360, + 2, + 400 +}; + +Poincare::Constant toConstant(Expression e) { + return static_cast(e); +} + +void TrigonometryListController::setExpression(Expression e) { assert(e.type() == ExpressionNode::Type::Cosine || e.type() == ExpressionNode::Type::Sine); - IllustratedListController::setExpression(e.childAtIndex(0)); - // Fill calculation store Poincare::Context * context = App::app()->localContext(); + Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences(); + Preferences::AngleUnit angleUnit = preferences->angleUnit(); + + Expression angleExpression = e.childAtIndex(0); + + Shared::PoincareHelpers::Reduce(&angleExpression, context, Poincare::ExpressionNode::ReductionTarget::SystemForAnalysis); + + if ((angleUnit == Preferences::AngleUnit::Radian + && angleExpression.type() == ExpressionNode::Type::Multiplication + && angleExpression.numberOfChildren() == 2 + && angleExpression.childAtIndex(1).type() == ExpressionNode::Type::Constant + && toConstant(angleExpression.childAtIndex(1)).isPi() + && angleExpression.childAtIndex(0).type() == ExpressionNode::Type::Rational) + || ((angleUnit == Preferences::AngleUnit::Degree || angleUnit == Preferences::AngleUnit::Gradian) + && angleExpression.type() == ExpressionNode::Type::Rational)) { + + Expression extracted = angleUnit == Preferences::AngleUnit::Radian ? angleExpression.childAtIndex(0) : angleExpression; + Rational r = static_cast(extracted); + + Integer denominator = Integer::Multiplication(r.integerDenominator(), Integer(s_fullCircle[(int) angleUnit])); + IntegerDivision division = Integer::Division(r.signedIntegerNumerator(), denominator); + + Integer remainder = division.remainder; + + Expression newAngle; + Integer rDenominator = r.integerDenominator(); + Rational newCoefficient = Rational::Builder(remainder, rDenominator); + if (angleUnit == Preferences::AngleUnit::Radian) { + angleExpression = Multiplication::Builder(newCoefficient, angleExpression.childAtIndex(1)); + } else { + angleExpression = newCoefficient; + } + } + + IllustratedListController::setExpression(angleExpression); + + // Fill calculation store m_calculationStore.push("sin(θ)", context, CalculationHeight); m_calculationStore.push("cos(θ)", context, CalculationHeight); m_calculationStore.push("θ", context, CalculationHeight); diff --git a/apps/calculation/calculation_store.cpp b/apps/calculation/calculation_store.cpp index 5293474c98a..72bc57d343a 100644 --- a/apps/calculation/calculation_store.cpp +++ b/apps/calculation/calculation_store.cpp @@ -237,7 +237,7 @@ Shared::ExpiringPointer CalculationStore::emptyStoreAndPushUndef(Co // Recompute memoized pointers to the calculations after index i void CalculationStore::recomputeMemoizedPointersAfterCalculationIndex(int index) { - assert(index < numberOfCalculations()); + assert(index < m_numberOfCalculations); // Clear pointer and recompute new ones Calculation * c = realCalculationAtIndex(index).pointer(); Calculation * nextCalc; From c7516f1464623a088f38681f86a60dff2f873375 Mon Sep 17 00:00:00 2001 From: Laury Date: Tue, 22 Feb 2022 20:44:16 +0100 Subject: [PATCH 173/355] [settings] Fixed name of contributors --- apps/settings/main_controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index cfdbbcc2a33..1b2e484689f 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -20,7 +20,7 @@ constexpr SettingsMessageTree s_usbProtectionLevelChildren[3] = {SettingsMessage constexpr SettingsMessageTree s_symbolFunctionChildren[3] = {SettingsMessageTree(I18n::Message::SymbolDefaultFunction), SettingsMessageTree(I18n::Message::SymbolArgDefaultFunction), SettingsMessageTree(I18n::Message::SymbolArgFunction)}; constexpr SettingsMessageTree s_modelMathOptionsChildren[6] = {SettingsMessageTree(I18n::Message::AngleUnit, s_modelAngleChildren), SettingsMessageTree(I18n::Message::DisplayMode, s_modelFloatDisplayModeChildren), SettingsMessageTree(I18n::Message::EditionMode, s_modelEditionModeChildren), SettingsMessageTree(I18n::Message::SymbolFunction, s_symbolFunctionChildren), SettingsMessageTree(I18n::Message::ComplexFormat, s_modelComplexFormatChildren), SettingsMessageTree(I18n::Message::SymbolMultiplication, s_symbolChildren)}; constexpr SettingsMessageTree s_accessibilityChildren[6] = {SettingsMessageTree(I18n::Message::AccessibilityInvertColors), SettingsMessageTree(I18n::Message::AccessibilityMagnify),SettingsMessageTree(I18n::Message::AccessibilityGamma),SettingsMessageTree(I18n::Message::AccessibilityGammaRed),SettingsMessageTree(I18n::Message::AccessibilityGammaGreen),SettingsMessageTree(I18n::Message::AccessibilityGammaBlue)}; -constexpr SettingsMessageTree s_contributorsChildren[18] = {SettingsMessageTree(I18n::Message::LaurianFournier), SettingsMessageTree(I18n::Message::YannCouturier), SettingsMessageTree(I18n::Message::LoicE), SettingsMessageTree(I18n::Message::DavidLuca), SettingsMessageTree(I18n::Message::VictorKretz), SettingsMessageTree(I18n::Message::QuentinGuidee), SettingsMessageTree(I18n::Message::JoachimLeFournis), SettingsMessageTree(I18n::Message::MaximeFriess), SettingsMessageTree(I18n::Message::JeanBaptisteBoric), SettingsMessageTree(I18n::Message::SandraSimmons), SettingsMessageTree(I18n::Message::David), SettingsMessageTree(I18n::Message::DamienNicolet), SettingsMessageTree(I18n::Message::EvannDreumont), SettingsMessageTree(I18n::Message::SzaboLevente), SettingsMessageTree(I18n::Message::VenceslasDuet), SettingsMessageTree(I18n::Message::CharlotteThomas), SettingsMessageTree(I18n::Message::AntoninLoubiere), SettingsMessageTree(I18n::Message::CyprienMejat)}; +constexpr SettingsMessageTree s_contributorsChildren[18] = {SettingsMessageTree(I18n::Message::LaurianFournier), SettingsMessageTree(I18n::Message::YannCouturier), SettingsMessageTree(I18n::Message::DavidLuca), SettingsMessageTree(I18n::Message::LoicE), SettingsMessageTree(I18n::Message::VictorKretz), SettingsMessageTree(I18n::Message::QuentinGuidee), SettingsMessageTree(I18n::Message::JoachimLeFournis), SettingsMessageTree(I18n::Message::MaximeFriess), SettingsMessageTree(I18n::Message::JeanBaptisteBoric), SettingsMessageTree(I18n::Message::SandraSimmons), SettingsMessageTree(I18n::Message::David), SettingsMessageTree(I18n::Message::DamienNicolet), SettingsMessageTree(I18n::Message::EvannDreumont), SettingsMessageTree(I18n::Message::SzaboLevente), SettingsMessageTree(I18n::Message::VenceslasDuet), SettingsMessageTree(I18n::Message::CharlotteThomas), SettingsMessageTree(I18n::Message::AntoninLoubiere), SettingsMessageTree(I18n::Message::CyprienMejat)}; // Code Settings #ifdef HAS_CODE From 23af100ed5a5d94c83269f78f498be2b011e8838 Mon Sep 17 00:00:00 2001 From: Laury Date: Thu, 24 Feb 2022 18:05:30 +0100 Subject: [PATCH 174/355] [poincare] Allow replacement of sequences by their definition if rank is an integer --- apps/math_variable_box_controller.cpp | 2 +- ion/include/ion/storage.h | 2 +- poincare/include/poincare/context.h | 3 +- .../poincare/integer_variable_context.h | 24 +++++ poincare/include/poincare/sequence.h | 2 + poincare/include/poincare/sum_and_product.h | 3 + poincare/src/function.cpp | 2 +- poincare/src/sequence.cpp | 91 +++++++++++++++++-- poincare/src/sum_and_product.cpp | 34 +++++++ 9 files changed, 153 insertions(+), 10 deletions(-) create mode 100644 poincare/include/poincare/integer_variable_context.h diff --git a/apps/math_variable_box_controller.cpp b/apps/math_variable_box_controller.cpp index 87b671788ec..28f179e26a9 100644 --- a/apps/math_variable_box_controller.cpp +++ b/apps/math_variable_box_controller.cpp @@ -124,7 +124,7 @@ void MathVariableBoxController::willDisplayCellForIndex(HighlightCell * cell, in symbolName, Shared::Sequence::k_maxNameWithArgumentSize ); - Expression symbolExpression = Expression::ParseAndSimplify(symbolName, AppsContainer::sharedAppsContainer()->globalContext(), Poincare::Preferences::sharedPreferences()->complexFormat(), Poincare::Preferences::sharedPreferences()->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat()); + Expression symbolExpression = Expression::Parse(symbolName, AppsContainer::sharedAppsContainer()->globalContext()); symbolLayout = symbolExpression.createLayout(Poincare::Preferences::sharedPreferences()->displayMode(), Poincare::Preferences::sharedPreferences()->numberOfSignificantDigits()); } if (symbolLayout.isUninitialized()) { diff --git a/ion/include/ion/storage.h b/ion/include/ion/storage.h index 3cca0c4ca06..f1a091d49bd 100644 --- a/ion/include/ion/storage.h +++ b/ion/include/ion/storage.h @@ -28,7 +28,7 @@ class Storage { static constexpr char funcExtension[] = "func"; static constexpr char seqExtension[] = "seq"; - class Record { + class Record { /* A Record is identified by the CRC32 on its fullName because: * - A record is identified by its fullName, which is unique * - We cannot keep the address pointing to the fullName because if another diff --git a/poincare/include/poincare/context.h b/poincare/include/poincare/context.h index 5b23367c7fb..2b46c62aa66 100644 --- a/poincare/include/poincare/context.h +++ b/poincare/include/poincare/context.h @@ -16,7 +16,8 @@ class Context { None, Function, Sequence, - Symbol + Symbol, + Integer // Used to simplify sequences }; virtual SymbolAbstractType expressionTypeForIdentifier(const char * identifier, int length) = 0; virtual const Expression expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone, float unknownSymbolValue = NAN) = 0; diff --git a/poincare/include/poincare/integer_variable_context.h b/poincare/include/poincare/integer_variable_context.h new file mode 100644 index 00000000000..f5ec81fa822 --- /dev/null +++ b/poincare/include/poincare/integer_variable_context.h @@ -0,0 +1,24 @@ +#ifndef POINCARE_INTEGER_VARIABLE_CONTEXT_H +#define POINCARE_INTEGER_VARIABLE_CONTEXT_H + +#include +#include + +namespace Poincare { + +class IntegerVariableContext : public ContextWithParent { +public: + IntegerVariableContext(const char * name, Context * parentContext) : + ContextWithParent(parentContext), + m_name(name) + {} + + SymbolAbstractType expressionTypeForIdentifier(const char * identifier, int length) override { return strcmp(m_name, identifier) == 0 ? SymbolAbstractType::Integer : ContextWithParent::expressionTypeForIdentifier(identifier, length); } + +private: + const char * m_name; +}; + +} + +#endif diff --git a/poincare/include/poincare/sequence.h b/poincare/include/poincare/sequence.h index a5e7656def0..cbc04d3c650 100644 --- a/poincare/include/poincare/sequence.h +++ b/poincare/include/poincare/sequence.h @@ -18,6 +18,7 @@ class SequenceNode : public SymbolAbstractNode { #endif Type type() const override { return Type::Sequence; } + virtual Expression deepReplaceReplaceableSymbols(Context * context, bool * didReplace, bool replaceFunctionsOnly, int parameteredAncestorsCount) override; Expression replaceSymbolWithExpression(const SymbolAbstract & symbol, const Expression & expression) override; int simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; @@ -46,6 +47,7 @@ friend SequenceNode; static Sequence Builder(const char * name, size_t length, Expression child = Expression()); // Simplification + Expression replacedByDefinitionIfPossible(Context * reductionContext); Expression replaceSymbolWithExpression(const SymbolAbstract & symbol, const Expression & expression); Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); Expression deepReplaceReplaceableSymbols(Context * context, bool * didReplace, bool replaceFunctionsOnly, int parameteredAncestorsCount); diff --git a/poincare/include/poincare/sum_and_product.h b/poincare/include/poincare/sum_and_product.h index 54f038bfed6..a28d00f9ae6 100644 --- a/poincare/include/poincare/sum_and_product.h +++ b/poincare/include/poincare/sum_and_product.h @@ -10,6 +10,8 @@ namespace Poincare { class SumAndProductNode : public ParameteredExpressionNode { public: int numberOfChildren() const override { return 4; } + virtual void deepReduceChildren(ReductionContext reductionContext) override; + virtual Expression deepReplaceReplaceableSymbols(Context * context, bool * didReplace, bool replaceFunctionsOnly, int parameteredAncestorsCount) override; private: Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; virtual Layout createSumAndProductLayout(Layout argumentLayout, Layout symbolLayout, Layout subscriptLayout, Layout superscriptLayout) const = 0; @@ -28,6 +30,7 @@ class SumAndProductNode : public ParameteredExpressionNode { class SumAndProduct : public Expression { public: SumAndProduct(const SumAndProductNode * n) : Expression(n) {} + Expression deepReplaceReplaceableSymbols(Context * context, bool * didReplace, bool replaceFunctionsOnly, int parameteredAncestorsCount); Expression shallowReduce(Context * context); }; diff --git a/poincare/src/function.cpp b/poincare/src/function.cpp index 16832db3322..23bf8550cc7 100644 --- a/poincare/src/function.cpp +++ b/poincare/src/function.cpp @@ -133,7 +133,7 @@ Expression Function::shallowReduce(ExpressionNode::ReductionContext reductionCon Expression Function::deepReplaceReplaceableSymbols(Context * context, bool * didReplace, bool replaceFunctionsOnly, int parameteredAncestorsCount) { { // Replace replaceable symbols in child - Expression self = defaultReplaceReplaceableSymbols(context, didReplace, replaceFunctionsOnly ,parameteredAncestorsCount); + Expression self = defaultReplaceReplaceableSymbols(context, didReplace, replaceFunctionsOnly, parameteredAncestorsCount); if (self.isUninitialized()) { // if the child is circularly defined, escape return self; } diff --git a/poincare/src/sequence.cpp b/poincare/src/sequence.cpp index bae19166c4e..666b418c766 100644 --- a/poincare/src/sequence.cpp +++ b/poincare/src/sequence.cpp @@ -9,6 +9,10 @@ #include #include #include +#include +#include +#include +#include namespace Poincare { @@ -106,19 +110,94 @@ Expression Sequence::replaceSymbolWithExpression(const SymbolAbstract & symbol, } Expression Sequence::shallowReduce(ExpressionNode::ReductionContext reductionContext) { - Expression e = Expression::defaultShallowReduce(); - e = e.defaultHandleUnitsInChildren(); - if (e.isUndefined()) { - return e; - } if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined) { return replaceWithUndefinedInPlace(); } - return *this; + + if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::DoNotReplaceAnySymbol) { + return *this; + } + + Expression result = replacedByDefinitionIfPossible(reductionContext.context()); + result = Expression::ExpressionWithoutSymbols(result, reductionContext.context()); + + if (result.isUninitialized()) { + return *this; + } + + replaceWithInPlace(result); + + // We simplify the expression entered by the user + return result.deepReduce(reductionContext); +} + +Expression SequenceNode::deepReplaceReplaceableSymbols(Context * context, bool * didReplace, bool replaceFunctionsOnly, int parameteredAncestorsCount) { + return Sequence(this).deepReplaceReplaceableSymbols(context, didReplace, replaceFunctionsOnly, parameteredAncestorsCount); } Expression Sequence::deepReplaceReplaceableSymbols(Context * context, bool * didReplace, bool replaceFunctionsOnly, int parameteredAncestorsCount) { + { + // Replace replaceable symbols in child + Expression self = defaultReplaceReplaceableSymbols(context, didReplace, replaceFunctionsOnly, parameteredAncestorsCount); + if (self.isUninitialized()) { // if the child is circularly defined, escape + return self; + } + assert(*this == self); + } + Expression e = replacedByDefinitionIfPossible(context); + if (e.isUninitialized()) { + return *this; + } + // If the function contains itself, return undefined + if (e.hasExpression([](Expression e, const void * context) { + if (e.type() != ExpressionNode::Type::Sequence) { + return false; + } + return strcmp(static_cast(e).name(), reinterpret_cast(context)) == 0; + }, reinterpret_cast(name()))) + { + return Expression(); + } + replaceWithInPlace(e); + *didReplace = true; + return e; return *this; } +Expression Sequence::replacedByDefinitionIfPossible(Context * context) { + // We try to replace the sequence by his definition ONLY if the index is an integer + bool canBeReplacedByExpression = false; + + if (childAtIndex(0).type() == ExpressionNode::Type::Symbol) { + const char * symbolName = (childAtIndex(0).convert()).name(); + if (context->expressionTypeForIdentifier(symbolName, strlen(symbolName)) == Context::SymbolAbstractType::Integer) { + canBeReplacedByExpression = true; + } + } else if (childAtIndex(0).type() == ExpressionNode::Type::Rational) { + Rational r = childAtIndex(0).convert(); + if (r.isInteger()) { + canBeReplacedByExpression = true; + } + } + + if (!canBeReplacedByExpression) { + return Expression(); + } + + Ion::Storage::Record r = Ion::Storage::sharedStorage()->recordBaseNamedWithExtension(name(), Ion::Storage::seqExtension); + + if (r.isNull()) { + return Expression(); + } + + Shared::Sequence seq(r); + + if (seq.type() != Shared::Sequence::Type::Explicit) { + return Expression(); + } + + Expression result = seq.expressionClone(); + return result.replaceSymbolWithExpression(Symbol::Builder(UCodePointUnknown), childAtIndex(0)); +} + } diff --git a/poincare/src/sum_and_product.cpp b/poincare/src/sum_and_product.cpp index 2cf0f1f4f11..a05e3e9557a 100644 --- a/poincare/src/sum_and_product.cpp +++ b/poincare/src/sum_and_product.cpp @@ -2,6 +2,10 @@ #include #include #include +#include +#include +#include +#include extern "C" { #include #include @@ -56,6 +60,36 @@ Evaluation SumAndProductNode::templatedApproximate(ApproximationContext appro return result; } +Expression SumAndProductNode::deepReplaceReplaceableSymbols(Context * context, bool * didReplace, bool replaceFunctionsOnly, int parameteredAncestorsCount) { + return SumAndProduct(this).deepReplaceReplaceableSymbols(context, didReplace, replaceFunctionsOnly, parameteredAncestorsCount); +} + +Expression SumAndProduct::deepReplaceReplaceableSymbols(Context * context, bool * didReplace, bool replaceFunctionsOnly, int parameteredAncestorsCount) { + int nbChildren = numberOfChildren(); + for (int i = 1; i < nbChildren; i++) { + Expression c = childAtIndex(i).deepReplaceReplaceableSymbols(context, didReplace, replaceFunctionsOnly, parameteredAncestorsCount); + if (c.isUninitialized()) { + return Expression(); + } + } + + Symbol symbol = childAtIndex(1).convert(); + IntegerVariableContext newContext = IntegerVariableContext(symbol.name(), context); + Expression c = childAtIndex(0).deepReplaceReplaceableSymbols(&newContext, didReplace, replaceFunctionsOnly, parameteredAncestorsCount); + if (c.isUninitialized()) { + return Expression(); + } + + return *this; +} + +void SumAndProductNode::deepReduceChildren(ReductionContext reductionContext) { + SymbolNode * symbol = static_cast(childAtIndex(1)); + IntegerVariableContext newContext = IntegerVariableContext(symbol->name(), reductionContext.context()); + reductionContext.setContext(&newContext); + ExpressionNode::deepReduceChildren(reductionContext); +} + Expression SumAndProduct::shallowReduce(Context * context) { { Expression e = Expression::defaultShallowReduce(); From f95f9ebb2da25090c22493e602c87a5bc1d6f6d7 Mon Sep 17 00:00:00 2001 From: Laury Date: Thu, 24 Feb 2022 18:48:10 +0100 Subject: [PATCH 175/355] [poincare] Better simplification of sequences --- poincare/include/poincare/addition.h | 1 + poincare/include/poincare/expression.h | 1 + poincare/include/poincare/expression_node.h | 6 +++++- poincare/include/poincare/multiplication.h | 1 + poincare/include/poincare/rational.h | 1 + poincare/include/poincare/symbol.h | 1 + poincare/src/addition.cpp | 10 ++++++++++ poincare/src/multiplication.cpp | 10 ++++++++++ poincare/src/sequence.cpp | 15 +-------------- poincare/src/sum_and_product.cpp | 6 ------ 10 files changed, 31 insertions(+), 21 deletions(-) diff --git a/poincare/include/poincare/addition.h b/poincare/include/poincare/addition.h index 73860051042..759facf232b 100644 --- a/poincare/include/poincare/addition.h +++ b/poincare/include/poincare/addition.h @@ -24,6 +24,7 @@ class AdditionNode final : public NAryInfixExpressionNode { // Properties Type type() const override { return Type::Addition; } + virtual ExpressionNode::IntegerStatus integerStatus(Context * context) const override; int polynomialDegree(Context * context, const char * symbolName) const override; int getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[], ExpressionNode::SymbolicComputation symbolicComputation) const override; diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 452c078d17a..4ac1ba70774 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -152,6 +152,7 @@ class Expression : public TreeHandle { bool isOfType(ExpressionNode::Type * types, int length) const { return node()->isOfType(types, length); } ExpressionNode::Sign sign(Context * context) const { return node()->sign(context); } ExpressionNode::NullStatus nullStatus(Context * context) const { return node()->nullStatus(context); } + ExpressionNode::IntegerStatus integerStatus(Context * context) const { return node()->integerStatus(context); } bool isStrictly(ExpressionNode::Sign s, Context * context) const { return s == node()->sign(context) && node()->nullStatus(context) == ExpressionNode::NullStatus::NonNull; } bool isUndefined() const { return node()->type() == ExpressionNode::Type::Undefined || node()->type() == ExpressionNode::Type::Unreal; } bool isNumber() const { return node()->isNumber(); } diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index ce0a702cf1b..862e10e7384 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -162,7 +162,10 @@ class ExpressionNode : public TreeNode { NonNull = 0, Null = 1, }; - + enum class IntegerStatus { + Unknown = -1, + Integer = 1, + }; class ComputationContext { public: ComputationContext( @@ -233,6 +236,7 @@ class ExpressionNode : public TreeNode { virtual Sign sign(Context * context) const { return Sign::Unknown; } virtual NullStatus nullStatus(Context * context) const { return NullStatus::Unknown; } + virtual IntegerStatus integerStatus(Context * context) const { return IntegerStatus::Unknown; } virtual bool isNumber() const { return false; } virtual bool isRandom() const { return false; } virtual bool isParameteredExpression() const { return false; } diff --git a/poincare/include/poincare/multiplication.h b/poincare/include/poincare/multiplication.h index d8b80bf0b9c..744471134e1 100644 --- a/poincare/include/poincare/multiplication.h +++ b/poincare/include/poincare/multiplication.h @@ -21,6 +21,7 @@ class MultiplicationNode final : public NAryInfixExpressionNode { // Properties Type type() const override { return Type::Multiplication; } + virtual IntegerStatus integerStatus(Context * context) const override; Sign sign(Context * context) const override; int polynomialDegree(Context * context, const char * symbolName) const override; int getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[], ExpressionNode::SymbolicComputation symbolicComputation) const override; diff --git a/poincare/include/poincare/rational.h b/poincare/include/poincare/rational.h index 0e1b3837470..34615a6cab0 100644 --- a/poincare/include/poincare/rational.h +++ b/poincare/include/poincare/rational.h @@ -18,6 +18,7 @@ class RationalNode final : public NumberNode { void setNegative(bool negative) { m_negative = negative; } bool isInteger() const { return denominator().isOne(); } NullStatus nullStatus(Context * context) const override { return isZero() ? NullStatus::Null : NullStatus::NonNull; } + virtual IntegerStatus integerStatus(Context * context) const { return isInteger() ? IntegerStatus::Integer : IntegerStatus::Unknown; } // TreeNode size_t size() const override; diff --git a/poincare/include/poincare/symbol.h b/poincare/include/poincare/symbol.h index 4f18e6176e4..43c4dd3fb69 100644 --- a/poincare/include/poincare/symbol.h +++ b/poincare/include/poincare/symbol.h @@ -21,6 +21,7 @@ class SymbolNode final : public SymbolAbstractNode { // Expression Properties Type type() const override { return Type::Symbol; } + virtual IntegerStatus integerStatus(Context * context) const { return context->expressionTypeForIdentifier(m_name, strlen(m_name)) == Context::SymbolAbstractType::Integer ? IntegerStatus::Integer : IntegerStatus::Unknown; } Expression replaceSymbolWithExpression(const SymbolAbstract & symbol, const Expression & expression) override; int polynomialDegree(Context * context, const char * symbolName) const override; int getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[], ExpressionNode::SymbolicComputation symbolicComputation) const override; diff --git a/poincare/src/addition.cpp b/poincare/src/addition.cpp index 5352cd85169..2032b7babdd 100644 --- a/poincare/src/addition.cpp +++ b/poincare/src/addition.cpp @@ -57,6 +57,16 @@ bool AdditionNode::derivate(ReductionContext reductionContext, Expression symbol // Addition +ExpressionNode::IntegerStatus AdditionNode::integerStatus(Context * context) const { + int nbOfChildren = numberOfChildren(); + for (int i = 0; i < nbOfChildren; i++) { + if (childAtIndex(i)->integerStatus(context) != IntegerStatus::Integer) { + return IntegerStatus::Unknown; + } + } + return IntegerStatus::Integer; +} + const Number Addition::NumeralFactor(const Expression & e) { if (e.type() == ExpressionNode::Type::Multiplication && e.childAtIndex(0).isNumber()) { Number result = e.childAtIndex(0).convert(); diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index a5ba391fc15..2f93440ce9c 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -42,6 +42,16 @@ ExpressionNode::Sign MultiplicationNode::sign(Context * context) const { return (Sign)sign; } +ExpressionNode::IntegerStatus MultiplicationNode::integerStatus(Context * context) const { + int nbOfChildren = numberOfChildren(); + for (int i = 0; i < nbOfChildren; i++) { + if (childAtIndex(i)->integerStatus(context) != IntegerStatus::Integer) { + return IntegerStatus::Unknown; + } + } + return IntegerStatus::Integer; +} + int MultiplicationNode::polynomialDegree(Context * context, const char * symbolName) const { int degree = 0; for (ExpressionNode * c : children()) { diff --git a/poincare/src/sequence.cpp b/poincare/src/sequence.cpp index 666b418c766..5c24602d755 100644 --- a/poincare/src/sequence.cpp +++ b/poincare/src/sequence.cpp @@ -166,21 +166,8 @@ Expression Sequence::deepReplaceReplaceableSymbols(Context * context, bool * did Expression Sequence::replacedByDefinitionIfPossible(Context * context) { // We try to replace the sequence by his definition ONLY if the index is an integer - bool canBeReplacedByExpression = false; - if (childAtIndex(0).type() == ExpressionNode::Type::Symbol) { - const char * symbolName = (childAtIndex(0).convert()).name(); - if (context->expressionTypeForIdentifier(symbolName, strlen(symbolName)) == Context::SymbolAbstractType::Integer) { - canBeReplacedByExpression = true; - } - } else if (childAtIndex(0).type() == ExpressionNode::Type::Rational) { - Rational r = childAtIndex(0).convert(); - if (r.isInteger()) { - canBeReplacedByExpression = true; - } - } - - if (!canBeReplacedByExpression) { + if (childAtIndex(0).integerStatus(context) != ExpressionNode::IntegerStatus::Integer) { return Expression(); } diff --git a/poincare/src/sum_and_product.cpp b/poincare/src/sum_and_product.cpp index a05e3e9557a..b5e7c68739d 100644 --- a/poincare/src/sum_and_product.cpp +++ b/poincare/src/sum_and_product.cpp @@ -45,12 +45,6 @@ Evaluation SumAndProductNode::templatedApproximate(ApproximationContext appro } nContext.setApproximationForVariable((T)i); Expression child = Expression(childAtIndex(0)).clone(); - if (child.type() == ExpressionNode::Type::Sequence) { - /* Since we cannot get the expression of a sequence term like we would for - * a function, we replace its potential abstract rank by the value it should - * have. We can then evaluate its value */ - child.childAtIndex(0).replaceSymbolWithExpression(symbol, Float::Builder(i)); - } approximationContext.setContext(&nContext); result = evaluateWithNextTerm(T(), result, child.node()->approximate(T(), approximationContext), approximationContext.complexFormat()); if (result.isUndefined()) { From 9edf0261103bae07c79565e3010271624f509300 Mon Sep 17 00:00:00 2001 From: "Yaya.Cout" <67095734+Yaya-Cout@users.noreply.github.com> Date: Tue, 1 Mar 2022 18:59:22 +0100 Subject: [PATCH 176/355] [python] Fix brackets in get_keys() --- python/port/mod/ion/modion.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/port/mod/ion/modion.cpp b/python/port/mod/ion/modion.cpp index c9694a69ef6..f698c0a21a7 100644 --- a/python/port/mod/ion/modion.cpp +++ b/python/port/mod/ion/modion.cpp @@ -69,8 +69,8 @@ const static key2mp keyMapping[] = { Ion::Keyboard::Key::Seven, MP_ROM_QSTR(MP_QSTR_7) }, { Ion::Keyboard::Key::Eight, MP_ROM_QSTR(MP_QSTR_8) }, { Ion::Keyboard::Key::Nine, MP_ROM_QSTR(MP_QSTR_9) }, - { Ion::Keyboard::Key::RightParenthesis, MP_ROM_QSTR(MP_QSTR__paren_open_) }, - { Ion::Keyboard::Key::LeftParenthesis, MP_ROM_QSTR(MP_QSTR__paren_close_) }, + { Ion::Keyboard::Key::LeftParenthesis, MP_ROM_QSTR(MP_QSTR__paren_open_) }, + { Ion::Keyboard::Key::RightParenthesis, MP_ROM_QSTR(MP_QSTR__paren_close_) }, { Ion::Keyboard::Key::Four, MP_ROM_QSTR(MP_QSTR_4) }, { Ion::Keyboard::Key::Five, MP_ROM_QSTR(MP_QSTR_5) }, From 19e5562228913ce46c83ebad0513eea1f5e024a3 Mon Sep 17 00:00:00 2001 From: BAALBAKYA <62015246+AlexandreArduino@users.noreply.github.com> Date: Thu, 3 Mar 2022 17:16:24 +0100 Subject: [PATCH 177/355] 2 corrections of bugs about the battery level in the settings (#148) * added battery level handler for simulator * added battery level > 100% when charging * corrected version * laury version but with float type * Update apps/settings/sub_menu/about_controller.cpp * Update apps/settings/sub_menu/about_controller.cpp Co-authored-by: = <=> Co-authored-by: Lauryy06 <80424145+Lauryy06@users.noreply.github.com> --- apps/settings/sub_menu/about_controller.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/settings/sub_menu/about_controller.cpp b/apps/settings/sub_menu/about_controller.cpp index 09debd6e93d..812862ab9ba 100644 --- a/apps/settings/sub_menu/about_controller.cpp +++ b/apps/settings/sub_menu/about_controller.cpp @@ -112,11 +112,21 @@ bool AboutController::handleEvent(Ion::Events::Event event) { if(childLabel == I18n::Message::Battery){ MessageTableCellWithBuffer * myCell = (MessageTableCellWithBuffer *)m_selectableTableView.selectedCell(); char batteryLevel[5]; - if(strchr(myCell->accessoryText(), '%') == NULL){ - int batteryLen = Poincare::Integer((int) ((Ion::Battery::voltage() - 3.6) * 166)).serialize(batteryLevel, 5); - batteryLevel[batteryLen] = '%'; - batteryLevel[batteryLen+1] = '\0'; - }else{ + if(strchr(myCell->accessoryText(), '%') == NULL) { + float voltage = (Ion::Battery::voltage() - 3.6) * 166; + if(voltage < 0.0) { + myCell->setAccessoryText("1%"); // We cheat... + return true; + } else if (voltage >= 100.0) { + myCell->setAccessoryText("100%"); + return true; + } else { + int batteryLen = Poincare::Integer((int) voltage).serialize(batteryLevel, 5); + batteryLevel[batteryLen] = '%'; + batteryLevel[batteryLen+1] = '\0'; + } + } + else { int batteryLen = Poincare::Number::FloatNumber(Ion::Battery::voltage()).serialize(batteryLevel, 5, Poincare::Preferences::PrintFloatMode::Decimal, 3); batteryLevel[batteryLen] = 'V'; batteryLevel[batteryLen+1] = '\0'; From 72a25ec26d256f6c4020c0fed792f46b8b5c9bf4 Mon Sep 17 00:00:00 2001 From: Laury Date: Sun, 13 Mar 2022 21:31:10 +0100 Subject: [PATCH 178/355] [ion] Rework of storage trash --- ion/Makefile | 1 + ion/include/ion/internal_storage.h | 218 +++++++++ ion/include/ion/storage.h | 219 +-------- ion/src/shared/internal_storage.cpp | 647 ++++++++++++++++++++++++++ ion/src/shared/storage.cpp | 685 ++++------------------------ python/port/mod/ion/file.cpp | 16 +- 6 files changed, 967 insertions(+), 819 deletions(-) create mode 100644 ion/include/ion/internal_storage.h create mode 100644 ion/src/shared/internal_storage.cpp diff --git a/ion/Makefile b/ion/Makefile index 1b04baf698f..c53ae60ef79 100644 --- a/ion/Makefile +++ b/ion/Makefile @@ -31,6 +31,7 @@ ion_src += $(addprefix ion/src/shared/, \ events.cpp \ events_keyboard.cpp \ events_modifier.cpp \ + internal_storage.cpp \ platform_info.cpp \ rtc.cpp \ stack_position.cpp \ diff --git a/ion/include/ion/internal_storage.h b/ion/include/ion/internal_storage.h new file mode 100644 index 00000000000..ead06746a9a --- /dev/null +++ b/ion/include/ion/internal_storage.h @@ -0,0 +1,218 @@ +#ifndef ION_INTERNAL_STORAGE_H +#define ION_INTERNAL_STORAGE_H + +#include +#include +#include "storage.h" + +namespace Ion { + +/* Storage : | Magic | Record1 | Record2 | ... | Magic | + * | Magic | Size1(uint16_t) | FullName1 | Body1 | Size2(uint16_t) | FullName2 | Body2 | ... | Magic | + * + * A record's fullName is baseName.extension. */ + +class StorageDelegate; + +class InternalStorage { +public: + typedef uint16_t record_size_t; + + static constexpr char eqExtension[] = "eq"; + static constexpr char expExtension[] = "exp"; + static constexpr char funcExtension[] = "func"; + static constexpr char seqExtension[] = "seq"; + + constexpr static size_t k_storageSize = 64000; + static_assert(UINT16_MAX >= k_storageSize, "record_size_t not big enough"); + + constexpr static char k_dotChar = '.'; + + class Record { + /* A Record is identified by the CRC32 on its fullName because: + * - A record is identified by its fullName, which is unique + * - We cannot keep the address pointing to the fullName because if another + * record is modified, it might alter our record's fullName address. + * Keeping a buffer with the fullNames will waste memory as we cannot + * forsee the size of the fullNames. */ + friend class Storage; + public: + enum class ErrorStatus { + None = 0, + NameTaken = 1, + NonCompliantName = 2, + NotEnoughSpaceAvailable = 3, + RecordDoesNotExist = 4 + }; + struct Data { + const void * buffer; + size_t size; + }; + Record(const char * fullName = nullptr); + Record(const char * basename, const char * extension); + bool operator==(const Record & other) const { + return m_fullNameCRC32 == other.m_fullNameCRC32; + } + bool operator!=(const Record & other) const { + return !(*this == other); + } +#if ION_STORAGE_LOG + void log(); +#endif + uint32_t checksum(); + bool isNull() const { + return m_fullNameCRC32 == 0; + } + const char * fullName() const; + ErrorStatus setBaseNameWithExtension(const char * baseName, const char * extension); + ErrorStatus setName(const char * fullName); + Data value() const; + ErrorStatus setValue(Data data); + void destroy(); + private: + Record(const char * basename, int basenameLength, const char * extension, int extensionLength); + uint32_t m_fullNameCRC32; + }; + +#if ION_STORAGE_LOG + void log(); +#endif + + size_t availableSize(); + size_t putAvailableSpaceAtEndOfRecord(Record r); + void getAvailableSpaceFromEndOfRecord(Record r, size_t recordAvailableSpace); + uint32_t checksum(); + + // Delegate + void setDelegate(StorageDelegate * delegate) { m_delegate = delegate; } + void notifyChangeToDelegate(const Record r = Record()) const; + Record::ErrorStatus notifyFullnessToDelegate() const; + + int numberOfRecordsWithExtension(const char * extension); + static bool FullNameHasExtension(const char * fullName, const char * extension, size_t extensionLength); + + // Record creation + Record::ErrorStatus createRecordWithFullName(const char * fullName, const void * data, size_t size); + Record::ErrorStatus createRecordWithExtension(const char * baseName, const char * extension, const void * data, size_t size); + + // Record getters + bool hasRecord(Record r) { return pointerOfRecord(r) != nullptr; } + Record recordWithExtensionAtIndex(const char * extension, int index); + Record recordNamed(const char * fullName); + Record recordBaseNamedWithExtension(const char * baseName, const char * extension); + Record recordBaseNamedWithExtensions(const char * baseName, const char * const extension[], size_t numberOfExtensions); + const char * extensionOfRecordBaseNamedWithExtensions(const char * baseName, int baseNameLength, const char * const extension[], size_t numberOfExtensions); + + // Record destruction + void destroyAllRecords(); + void destroyRecordWithBaseNameAndExtension(const char * baseName, const char * extension); + void destroyRecordsWithExtension(const char * extension); + + // Useful + static bool FullNameCompliant(const char * name); + + // Used by Python OS module + int numberOfRecords(); + Record recordAtIndex(int index); +protected: + InternalStorage(); + /* Getters on address in buffer */ + char * pointerOfRecord(const Record record) const; + record_size_t sizeOfRecordStarting(char * start) const; + const char * fullNameOfRecordStarting(char * start) const; + const void * valueOfRecordStarting(char * start) const; + void destroyRecord(const Record record); + + class RecordIterator { + public: + RecordIterator(char * start) : m_recordStart(start) {} + char * operator*() { return m_recordStart; } + RecordIterator& operator++(); + bool operator!=(const RecordIterator& it) const { return m_recordStart != it.m_recordStart; } + private: + char * m_recordStart; + }; + RecordIterator begin() const { + if (sizeOfRecordStarting((char *)m_buffer) == 0) { + return nullptr; + } + return RecordIterator((char *)m_buffer); + }; + RecordIterator end() const { return RecordIterator(nullptr); } + + mutable Record m_lastRecordRetrieved; + mutable char * m_lastRecordRetrievedPointer; +private: + constexpr static uint32_t Magic = 0xEE0BDDBA; + constexpr static size_t k_maxRecordSize = (1 << sizeof(record_size_t)*8); + + /* Getters/Setters on recordID */ + const char * fullNameOfRecord(const Record record); + Record::ErrorStatus setFullNameOfRecord(const Record record, const char * fullName); + Record::ErrorStatus setBaseNameWithExtensionOfRecord(const Record record, const char * baseName, const char * extension); + Record::Data valueOfRecord(const Record record); + Record::ErrorStatus setValueOfRecord(const Record record, Record::Data data); + + /* Overriders */ + size_t overrideSizeAtPosition(char * position, record_size_t size); + size_t overrideFullNameAtPosition(char * position, const char * fullName); + size_t overrideBaseNameWithExtensionAtPosition(char * position, const char * baseName, const char * extension); + size_t overrideValueAtPosition(char * position, const void * data, record_size_t size); + + bool isFullNameTaken(const char * fullName, const Record * recordToExclude = nullptr); + bool isBaseNameWithExtensionTaken(const char * baseName, const char * extension, Record * recordToExclude = nullptr); + bool isNameOfRecordTaken(Record r, const Record * recordToExclude); + char * endBuffer(); + size_t sizeOfBaseNameAndExtension(const char * baseName, const char * extension) const; + size_t sizeOfRecordWithBaseNameAndExtension(const char * baseName, const char * extension, size_t size) const; + size_t sizeOfRecordWithFullName(const char * fullName, size_t size) const; + bool slideBuffer(char * position, int delta); + + Record privateRecordAndExtensionOfRecordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions, const char * * extensionResult = nullptr, int baseNameLength = -1); + + uint32_t m_magicHeader; + char m_buffer[k_storageSize]; + uint32_t m_magicFooter; + StorageDelegate * m_delegate; +}; + +/* Some apps memoize records and need to be notified when a record might have + * become invalid. For instance in the Graph app, if f(x) = A+x, and A changed, + * f(x) memoization which stores the reduced expression of f is outdated. + * We could have computed and compared the checksum of the storage to detect + * storage invalidity, but profiling showed that this slows down the execution + * (for example when scrolling the functions list). + * We thus decided to notify a delegate when the storage changes. */ + +class StorageDelegate { +public: + virtual void storageDidChangeForRecord(const InternalStorage::Record record) = 0; + virtual void storageIsFull() = 0; +}; + +// emscripten read and writes must be aligned. +class StorageHelper { +public: + static uint16_t unalignedShort(char * address) { +#if __EMSCRIPTEN__ + uint8_t f1 = *(address); + uint8_t f2 = *(address+1); + uint16_t f = (uint16_t)f1 + (((uint16_t)f2)<<8); + return f; +#else + return *(uint16_t *)address; +#endif + } + static void writeUnalignedShort(uint16_t value, char * address) { +#if __EMSCRIPTEN__ + *((uint8_t *)address) = (uint8_t)(value & ((1 << 8) - 1)); + *((uint8_t *)address+1) = (uint8_t)(value >> 8); +#else + *((uint16_t *)address) = value; +#endif + } +}; + +} + +#endif diff --git a/ion/include/ion/storage.h b/ion/include/ion/storage.h index f1a091d49bd..5385f4608d3 100644 --- a/ion/include/ion/storage.h +++ b/ion/include/ion/storage.h @@ -1,239 +1,50 @@ #ifndef ION_STORAGE_H #define ION_STORAGE_H -#include -#include +#include "internal_storage.h" namespace Ion { -/* Storage : | Magic | Record1 | Record2 | ... | Magic | - * | Magic | Size1(uint16_t) | FullName1 | Body1 | Size2(uint16_t) | FullName2 | Body2 | ... | Magic | - * - * A record's fullName is baseName.extension. */ - -class StorageDelegate; - -class Storage { +class Storage : public InternalStorage { public: - typedef uint16_t record_size_t; + using InternalStorage::Record; - constexpr static size_t k_storageSize = 64000; - static_assert(UINT16_MAX >= k_storageSize, "record_size_t not big enough"); + using InternalStorage::expExtension; + using InternalStorage::funcExtension; + using InternalStorage::seqExtension; + using InternalStorage::eqExtension; static Storage * sharedStorage(); - constexpr static char k_dotChar = '.'; - - static constexpr char eqExtension[] = "eq"; - static constexpr char expExtension[] = "exp"; - static constexpr char funcExtension[] = "func"; - static constexpr char seqExtension[] = "seq"; - - class Record { - /* A Record is identified by the CRC32 on its fullName because: - * - A record is identified by its fullName, which is unique - * - We cannot keep the address pointing to the fullName because if another - * record is modified, it might alter our record's fullName address. - * Keeping a buffer with the fullNames will waste memory as we cannot - * forsee the size of the fullNames. */ - friend class Storage; - public: - enum class ErrorStatus { - None = 0, - NameTaken = 1, - NonCompliantName = 2, - NotEnoughSpaceAvailable = 3, - RecordDoesNotExist = 4 - }; - struct Data { - const void * buffer; - size_t size; - }; - Record(const char * fullName = nullptr); - Record(const char * basename, const char * extension); - bool operator==(const Record & other) const { - return m_fullNameCRC32 == other.m_fullNameCRC32; - } - bool operator!=(const Record & other) const { - return !(*this == other); - } -#if ION_STORAGE_LOG - void log(); -#endif - uint32_t checksum(); - bool isNull() const { - return m_fullNameCRC32 == 0; - } - const char * fullName() const { - return Storage::sharedStorage()->fullNameOfRecord(*this); - } - ErrorStatus setBaseNameWithExtension(const char * baseName, const char * extension) { - return Storage::sharedStorage()->setBaseNameWithExtensionOfRecord(*this, baseName, extension); - } - ErrorStatus setName(const char * fullName) { - return Storage::sharedStorage()->setFullNameOfRecord(*this, fullName); - } - Data value() const { - return Storage::sharedStorage()->valueOfRecord(*this); - } - ErrorStatus setValue(Data data) { - return Storage::sharedStorage()->setValueOfRecord(*this, data); - } - void destroy() { - return Storage::sharedStorage()->destroyRecord(*this); - } - private: - Record(const char * basename, int basenameLength, const char * extension, int extensionLength); - uint32_t m_fullNameCRC32; - }; - -#if ION_STORAGE_LOG - void log(); -#endif size_t availableSize(); size_t putAvailableSpaceAtEndOfRecord(Record r); void getAvailableSpaceFromEndOfRecord(Record r, size_t recordAvailableSpace); - uint32_t checksum(); - - // Delegate - void setDelegate(StorageDelegate * delegate) { m_delegate = delegate; } - void notifyChangeToDelegate(const Record r = Record()) const; - Record::ErrorStatus notifyFullnessToDelegate() const; int numberOfRecordsWithExtension(const char * extension); - static bool FullNameHasExtension(const char * fullName, const char * extension, size_t extensionLength); - // Record creation Record::ErrorStatus createRecordWithFullName(const char * fullName, const void * data, size_t size); Record::ErrorStatus createRecordWithExtension(const char * baseName, const char * extension, const void * data, size_t size); - // Record getters - bool hasRecord(Record r) { return pointerOfRecord(r) != nullptr; } + bool hasRecord(Record r); + Record recordWithExtensionAtIndex(const char * extension, int index); Record recordNamed(const char * fullName); Record recordBaseNamedWithExtension(const char * baseName, const char * extension); Record recordBaseNamedWithExtensions(const char * baseName, const char * const extension[], size_t numberOfExtensions); const char * extensionOfRecordBaseNamedWithExtensions(const char * baseName, int baseNameLength, const char * const extension[], size_t numberOfExtensions); - // Record destruction - void destroyAllRecords(); - void destroyRecordWithBaseNameAndExtension(const char * baseName, const char * extension); - void destroyRecordsWithExtension(const char * extension); - - // Useful - static bool FullNameCompliant(const char * name); - - // Used by Python OS module int numberOfRecords(); - Record recordAtIndex(int index); // Unlike realRecordAtIndex, this ignore trash + Record recordAtIndex(int index); + void destroyRecord(Record record); - // Trash void reinsertTrash(const char * extension); - -private: - constexpr static uint32_t Magic = 0xEE0BDDBA; - constexpr static size_t k_maxRecordSize = (1 << sizeof(record_size_t)*8); - - Storage(); - - /* Getters/Setters on recordID */ - const char * fullNameOfRecord(const Record record); - Record::ErrorStatus setFullNameOfRecord(const Record record, const char * fullName); - Record::ErrorStatus setBaseNameWithExtensionOfRecord(const Record record, const char * baseName, const char * extension); - Record::Data valueOfRecord(const Record record); - Record::ErrorStatus setValueOfRecord(const Record record, Record::Data data); - void destroyRecord(const Record record); - void realDestroyRecord(const Record record); - - /* Getters on address in buffer */ - char * pointerOfRecord(const Record record) const; - record_size_t sizeOfRecordStarting(char * start) const; - const char * fullNameOfRecordStarting(char * start) const; - const void * valueOfRecordStarting(char * start) const; - - /* Trash */ void emptyTrash(); - Storage::Record realRecordAtIndex(int index); - int realNumberOfRecords(); - - /* Overriders */ - size_t overrideSizeAtPosition(char * position, record_size_t size); - size_t overrideFullNameAtPosition(char * position, const char * fullName); - size_t overrideBaseNameWithExtensionAtPosition(char * position, const char * baseName, const char * extension); - size_t overrideValueAtPosition(char * position, const void * data, record_size_t size); - - size_t realAvailableSize(); - bool isFullNameTaken(const char * fullName, const Record * recordToExclude = nullptr); - bool isBaseNameWithExtensionTaken(const char * baseName, const char * extension, Record * recordToExclude = nullptr); - bool isNameOfRecordTaken(Record r, const Record * recordToExclude); - char * endBuffer(); - size_t sizeOfBaseNameAndExtension(const char * baseName, const char * extension) const; - size_t sizeOfRecordWithBaseNameAndExtension(const char * baseName, const char * extension, size_t size) const; - size_t sizeOfRecordWithFullName(const char * fullName, size_t size) const; - bool slideBuffer(char * position, int delta); - class RecordIterator { - public: - RecordIterator(char * start) : m_recordStart(start) {} - char * operator*() { return m_recordStart; } - RecordIterator& operator++(); - bool operator!=(const RecordIterator& it) const { return m_recordStart != it.m_recordStart; } - private: - char * m_recordStart; - }; - RecordIterator begin() const { - if (sizeOfRecordStarting((char *)m_buffer) == 0) { - return nullptr; - } - return RecordIterator((char *)m_buffer); - }; - RecordIterator end() const { return RecordIterator(nullptr); } - - Record privateRecordAndExtensionOfRecordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions, const char * * extensionResult = nullptr, int baseNameLength = -1); - - uint32_t m_magicHeader; - char m_buffer[k_storageSize]; - Record m_trashRecord; - uint32_t m_magicFooter; - StorageDelegate * m_delegate; - mutable Record m_lastRecordRetrieved; - mutable char * m_lastRecordRetrievedPointer; -}; - -/* Some apps memoize records and need to be notified when a record might have - * become invalid. For instance in the Graph app, if f(x) = A+x, and A changed, - * f(x) memoization which stores the reduced expression of f is outdated. - * We could have computed and compared the checksum of the storage to detect - * storage invalidity, but profiling showed that this slows down the execution - * (for example when scrolling the functions list). - * We thus decided to notify a delegate when the storage changes. */ -class StorageDelegate { -public: - virtual void storageDidChangeForRecord(const Storage::Record record) = 0; - virtual void storageIsFull() = 0; -}; +private: + Storage(): + InternalStorage() {} -// emscripten read and writes must be aligned. -class StorageHelper { -public: - static uint16_t unalignedShort(char * address) { -#if __EMSCRIPTEN__ - uint8_t f1 = *(address); - uint8_t f2 = *(address+1); - uint16_t f = (uint16_t)f1 + (((uint16_t)f2)<<8); - return f; -#else - return *(uint16_t *)address; -#endif - } - static void writeUnalignedShort(uint16_t value, char * address) { -#if __EMSCRIPTEN__ - *((uint8_t *)address) = (uint8_t)(value & ((1 << 8) - 1)); - *((uint8_t *)address+1) = (uint8_t)(value >> 8); -#else - *((uint16_t *)address) = value; -#endif - } + Record m_trashRecord; }; } diff --git a/ion/src/shared/internal_storage.cpp b/ion/src/shared/internal_storage.cpp new file mode 100644 index 00000000000..5f12eb460b0 --- /dev/null +++ b/ion/src/shared/internal_storage.cpp @@ -0,0 +1,647 @@ +#include +#include +#include +#include +#include "storage.h" +#if ION_STORAGE_LOG +#include +#endif + +namespace Ion { + +/* We want to implement a simple singleton pattern, to make sure the storage is + * initialized on first use, therefore preventing the static init order fiasco. + * That being said, we rely on knowing where the storage resides in the device's + * memory at compile time. Indeed, we want to advertise the static storage's + * memory address in the PlatformInfo structure (so that we can read and write + * it in DFU). + * Using a "static Storage storage;" variable makes it a local symbol at best, + * preventing the PlatformInfo from retrieving its address. And making the + * Storage variable global yields the static init fiasco issue. We're working + * around both issues by creating a global staticStorageArea buffer, and by + * placement-newing the Storage into that area on first use. */ + +/* The InternalStorage is handle all records. Then the "normal" Storage handle + * a trash */ + +constexpr char InternalStorage::expExtension[]; +constexpr char InternalStorage::funcExtension[]; +constexpr char InternalStorage::seqExtension[]; +constexpr char InternalStorage::eqExtension[]; + +// RECORD + +const char * InternalStorage::Record::fullName() const { + return Storage::sharedStorage()->fullNameOfRecord(*this); +} +InternalStorage::Record::ErrorStatus InternalStorage::Record::setBaseNameWithExtension(const char * baseName, const char * extension) { + return Storage::sharedStorage()->setBaseNameWithExtensionOfRecord(*this, baseName, extension); +} +InternalStorage::Record::ErrorStatus InternalStorage::Record::setName(const char * fullName) { + return Storage::sharedStorage()->setFullNameOfRecord(*this, fullName); +} +InternalStorage::Record::Data InternalStorage::Record::value() const { + return Storage::sharedStorage()->valueOfRecord(*this); +} +InternalStorage::Record::ErrorStatus InternalStorage::Record::setValue(Data data) { + return Storage::sharedStorage()->setValueOfRecord(*this, data); +} +void InternalStorage::Record::destroy() { + return Storage::sharedStorage()->destroyRecord(*this); +} + +InternalStorage::Record::Record(const char * fullName) { + if (fullName == nullptr) { + m_fullNameCRC32 = 0; + return; + } + const char * dotChar = UTF8Helper::CodePointSearch(fullName, k_dotChar); + // If no extension, return empty record + if (*dotChar == 0 || *(dotChar+1) == 0) { + m_fullNameCRC32 = 0; + return; + } + new (this) Record(fullName, dotChar - fullName, dotChar+1, (fullName + strlen(fullName)) - (dotChar+1)); +} + +InternalStorage::Record::Record(const char * baseName, const char * extension) { + if (baseName == nullptr) { + assert(extension == nullptr); + m_fullNameCRC32 = 0; + return; + } + new (this) Record(baseName, strlen(baseName), extension, strlen(extension)); +} + + +#if ION_STORAGE_LOG + +void InternalStorage::Record::log() { + std::cout << "Name: " << fullName() << std::endl; + std::cout << " Value (" << value().size << "): " << (char *)value().buffer << "\n\n" << std::endl; +} +#endif + +uint32_t InternalStorage::Record::checksum() { + uint32_t crc32Results[2]; + crc32Results[0] = m_fullNameCRC32; + Data data = value(); + crc32Results[1] = Ion::crc32Byte((const uint8_t *)data.buffer, data.size); + return Ion::crc32Word(crc32Results, 2); +} + +InternalStorage::Record::Record(const char * basename, int basenameLength, const char * extension, int extensionLength) { + assert(basename != nullptr); + assert(extension != nullptr); + + // We compute the CRC32 of the CRC32s of the basename and the extension + uint32_t crc32Results[2]; + crc32Results[0] = Ion::crc32Byte((const uint8_t *)basename, basenameLength); + crc32Results[1] = Ion::crc32Byte((const uint8_t *)extension, extensionLength); + m_fullNameCRC32 = Ion::crc32Word(crc32Results, 2); +} + +// STORAGE + +#if ION_STORAGE_LOG +void InternalStorage::log() { + for (char * p : *this) { + const char * currentName = fullNameOfRecordStarting(p); + Record(currentName).log(); + } +} +#endif + +size_t InternalStorage::availableSize() { + /* TODO maybe do: availableSize(char ** endBuffer) to get the endBuffer if it + * is needed after calling availableSize */ + assert(k_storageSize >= (endBuffer() - m_buffer) + sizeof(record_size_t)); + return k_storageSize-(endBuffer()-m_buffer)-sizeof(record_size_t); +} + +size_t InternalStorage::putAvailableSpaceAtEndOfRecord(InternalStorage::Record r) { + char * p = pointerOfRecord(r); + size_t previousRecordSize = sizeOfRecordStarting(p); + size_t availableStorageSize = availableSize(); + char * nextRecord = p + previousRecordSize; + memmove(nextRecord + availableStorageSize, + nextRecord, + (m_buffer + k_storageSize - availableStorageSize) - nextRecord); + size_t newRecordSize = previousRecordSize + availableStorageSize; + overrideSizeAtPosition(p, (record_size_t)newRecordSize); + return newRecordSize; +} + +void InternalStorage::getAvailableSpaceFromEndOfRecord(Record r, size_t recordAvailableSpace) { + char * p = pointerOfRecord(r); + size_t previousRecordSize = sizeOfRecordStarting(p); + char * nextRecord = p + previousRecordSize; + memmove(nextRecord - recordAvailableSpace, + nextRecord, + m_buffer + k_storageSize - nextRecord); + overrideSizeAtPosition(p, (record_size_t)(previousRecordSize - recordAvailableSpace)); +} + +uint32_t InternalStorage::checksum() { + return Ion::crc32Byte((const uint8_t *) m_buffer, endBuffer()-m_buffer); +} + +void InternalStorage::notifyChangeToDelegate(const Record record) const { + m_lastRecordRetrieved = Record(nullptr); + m_lastRecordRetrievedPointer = nullptr; + if (m_delegate != nullptr) { + m_delegate->storageDidChangeForRecord(record); + } +} + +InternalStorage::Record::ErrorStatus InternalStorage::notifyFullnessToDelegate() const { + if (m_delegate != nullptr) { + m_delegate->storageIsFull(); + } + return Record::ErrorStatus::NotEnoughSpaceAvailable; +} + +InternalStorage::Record::ErrorStatus InternalStorage::createRecordWithFullName(const char * fullName, const void * data, size_t size) { + size_t recordSize = sizeOfRecordWithFullName(fullName, size); + if (recordSize >= k_maxRecordSize || recordSize > availableSize()) { + return notifyFullnessToDelegate(); + } + if (isFullNameTaken(fullName)) { + return Record::ErrorStatus::NameTaken; + } + // Find the end of data + char * newRecordAddress = endBuffer(); + char * newRecord = newRecordAddress; + // Fill totalSize + newRecord += overrideSizeAtPosition(newRecord, (record_size_t)recordSize); + // Fill name + newRecord += overrideFullNameAtPosition(newRecord, fullName); + // Fill data + newRecord += overrideValueAtPosition(newRecord, data, size); + // Next Record is null-sized + overrideSizeAtPosition(newRecord, 0); + Record r = Record(fullName); + notifyChangeToDelegate(r); + m_lastRecordRetrieved = r; + m_lastRecordRetrievedPointer = newRecordAddress; + return Record::ErrorStatus::None; +} + +InternalStorage::Record::ErrorStatus InternalStorage::createRecordWithExtension(const char * baseName, const char * extension, const void * data, size_t size) { + size_t recordSize = sizeOfRecordWithBaseNameAndExtension(baseName, extension, size); + if (recordSize >= k_maxRecordSize || recordSize > availableSize()) { + return notifyFullnessToDelegate(); + } + if (isBaseNameWithExtensionTaken(baseName, extension)) { + return Record::ErrorStatus::NameTaken; + } + // Find the end of data + char * newRecordAddress = endBuffer(); + char * newRecord = newRecordAddress; + // Fill totalSize + newRecord += overrideSizeAtPosition(newRecord, (record_size_t)recordSize); + // Fill name + newRecord += overrideBaseNameWithExtensionAtPosition(newRecord, baseName, extension); + // Fill data + newRecord += overrideValueAtPosition(newRecord, data, size); + // Next Record is null-sized + overrideSizeAtPosition(newRecord, 0); + Record r = Record(fullNameOfRecordStarting(newRecordAddress)); + notifyChangeToDelegate(r); + m_lastRecordRetrieved = r; + m_lastRecordRetrievedPointer = newRecordAddress; + return Record::ErrorStatus::None; +} + +int InternalStorage::numberOfRecordsWithExtension(const char * extension) { + int count = 0; + size_t extensionLength = strlen(extension); + for (char * p : *this) { + const char * name = fullNameOfRecordStarting(p); + if (FullNameHasExtension(name, extension, extensionLength)) { + count++; + } + } + return count; +} + +int InternalStorage::numberOfRecords() { + int count = 0; + for (char * p : *this) { + const char * name = fullNameOfRecordStarting(p); + count++; + } + return count; +} + +InternalStorage::Record InternalStorage::recordAtIndex(int index) { + int currentIndex = -1; + const char * name = nullptr; + char * recordAddress = nullptr; + for (char * p : *this) { + const char * currentName = fullNameOfRecordStarting(p); + currentIndex++; + if (currentIndex == index) { + recordAddress = p; + name = currentName; + break; + } + } + if (name == nullptr) { + return Record(); + } + Record r = Record(name); + m_lastRecordRetrieved = r; + m_lastRecordRetrievedPointer = recordAddress; + return Record(name); +} + +InternalStorage::Record InternalStorage::recordWithExtensionAtIndex(const char * extension, int index) { + int currentIndex = -1; + const char * name = nullptr; + size_t extensionLength = strlen(extension); + char * recordAddress = nullptr; + for (char * p : *this) { + const char * currentName = fullNameOfRecordStarting(p); + if (FullNameHasExtension(currentName, extension, extensionLength)) { + currentIndex++; + } + if (currentIndex == index) { + recordAddress = p; + name = currentName; + break; + } + } + if (name == nullptr) { + return Record(); + } + Record r = Record(name); + m_lastRecordRetrieved = r; + m_lastRecordRetrievedPointer = recordAddress; + return Record(name); +} + +InternalStorage::Record InternalStorage::recordNamed(const char * fullName) { + if (fullName == nullptr) { + return Record(); + } + Record r = Record(fullName); + char * p = pointerOfRecord(r); + if (p != nullptr) { + return r; + } + return Record(); +} + +InternalStorage::Record InternalStorage::recordBaseNamedWithExtension(const char * baseName, const char * extension) { + const char * extensions[1] = {extension}; + return recordBaseNamedWithExtensions(baseName, extensions, 1); +} + +InternalStorage::Record InternalStorage::recordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions) { + return privateRecordAndExtensionOfRecordBaseNamedWithExtensions(baseName, extensions, numberOfExtensions); +} + +const char * InternalStorage::extensionOfRecordBaseNamedWithExtensions(const char * baseName, int baseNameLength, const char * const extensions[], size_t numberOfExtensions) { + const char * result = nullptr; + privateRecordAndExtensionOfRecordBaseNamedWithExtensions(baseName, extensions, numberOfExtensions, &result, baseNameLength); + return result; +} + +void InternalStorage::destroyAllRecords() { + overrideSizeAtPosition(m_buffer, 0); + notifyChangeToDelegate(); +} + +void InternalStorage::destroyRecordWithBaseNameAndExtension(const char * baseName, const char * extension) { + recordBaseNamedWithExtension(baseName, extension).destroy(); +} + +void InternalStorage::destroyRecordsWithExtension(const char * extension) { + size_t extensionLength = strlen(extension); + char * currentRecordStart = (char *)m_buffer; + bool didChange = false; + while (currentRecordStart != nullptr && sizeOfRecordStarting(currentRecordStart) != 0) { + const char * currentFullName = fullNameOfRecordStarting(currentRecordStart); + if (FullNameHasExtension(currentFullName, extension, extensionLength)) { + Record currentRecord(currentFullName); + currentRecord.destroy(); + didChange = true; + continue; + } + currentRecordStart = *(RecordIterator(currentRecordStart).operator++()); + } + if (didChange) { + notifyChangeToDelegate(); + } +} + +InternalStorage::InternalStorage() : + m_magicHeader(Magic), + m_buffer(), + m_magicFooter(Magic), + m_delegate(nullptr), + m_lastRecordRetrieved(nullptr), + m_lastRecordRetrievedPointer(nullptr) +{ + assert(m_magicHeader == Magic); + assert(m_magicFooter == Magic); + // Set the size of the first record to 0 + overrideSizeAtPosition(m_buffer, 0); +} + +// PRIVATE + +const char * InternalStorage::fullNameOfRecord(const Record record) { + char * p = pointerOfRecord(record); + if (p != nullptr) { + return fullNameOfRecordStarting(p); + } + return nullptr; +} + +InternalStorage::Record::ErrorStatus InternalStorage::setFullNameOfRecord(const Record record, const char * fullName) { + if (!FullNameCompliant(fullName)) { + return Record::ErrorStatus::NonCompliantName; + } + if (isFullNameTaken(fullName, &record)) { + return Record::ErrorStatus::NameTaken; + } + size_t nameSize = strlen(fullName) + 1; + char * p = pointerOfRecord(record); + if (p != nullptr) { + size_t previousNameSize = strlen(fullNameOfRecordStarting(p))+1; + record_size_t previousRecordSize = sizeOfRecordStarting(p); + size_t newRecordSize = previousRecordSize-previousNameSize+nameSize; + if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+sizeof(record_size_t)+previousNameSize, nameSize-previousNameSize)) { + return notifyFullnessToDelegate(); + } + overrideSizeAtPosition(p, newRecordSize); + overrideFullNameAtPosition(p+sizeof(record_size_t), fullName); + notifyChangeToDelegate(record); + m_lastRecordRetrieved = record; + m_lastRecordRetrievedPointer = p; + return Record::ErrorStatus::None; + } + return Record::ErrorStatus::RecordDoesNotExist; +} + +InternalStorage::Record::ErrorStatus InternalStorage::setBaseNameWithExtensionOfRecord(Record record, const char * baseName, const char * extension) { + if (isBaseNameWithExtensionTaken(baseName, extension, &record)) { + return Record::ErrorStatus::NameTaken; + } + size_t nameSize = sizeOfBaseNameAndExtension(baseName, extension); + char * p = pointerOfRecord(record); + if (p != nullptr) { + size_t previousNameSize = strlen(fullNameOfRecordStarting(p))+1; + record_size_t previousRecordSize = sizeOfRecordStarting(p); + size_t newRecordSize = previousRecordSize-previousNameSize+nameSize; + if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+sizeof(record_size_t)+previousNameSize, nameSize-previousNameSize)) { + return notifyFullnessToDelegate(); + } + overrideSizeAtPosition(p, newRecordSize); + char * fullNamePosition = p + sizeof(record_size_t); + overrideBaseNameWithExtensionAtPosition(fullNamePosition, baseName, extension); + // Recompute the CRC32 + record = Record(fullNamePosition); + notifyChangeToDelegate(record); + m_lastRecordRetrieved = record; + m_lastRecordRetrievedPointer = p; + return Record::ErrorStatus::None; + } + return Record::ErrorStatus::RecordDoesNotExist; +} + +InternalStorage::Record::Data InternalStorage::valueOfRecord(const Record record) { + char * p = pointerOfRecord(record); + if (p != nullptr) { + const char * fullName = fullNameOfRecordStarting(p); + record_size_t size = sizeOfRecordStarting(p); + const void * value = valueOfRecordStarting(p); + return {.buffer = value, .size = size-strlen(fullName)-1-sizeof(record_size_t)}; + } + return {.buffer= nullptr, .size= 0}; +} + +InternalStorage::Record::ErrorStatus InternalStorage::setValueOfRecord(Record record, Record::Data data) { + char * p = pointerOfRecord(record); + /* TODO: if data.buffer == p, assert that size hasn't change and do not do any + * memcopy, but still notify the delegate. Beware of scripts and the accordion + * routine.*/ + if (p != nullptr) { + record_size_t previousRecordSize = sizeOfRecordStarting(p); + const char * fullName = fullNameOfRecordStarting(p); + size_t newRecordSize = sizeOfRecordWithFullName(fullName, data.size); + if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+previousRecordSize, newRecordSize-previousRecordSize)) { + return notifyFullnessToDelegate(); + } + record_size_t fullNameSize = strlen(fullName)+1; + overrideSizeAtPosition(p, newRecordSize); + overrideValueAtPosition(p+sizeof(record_size_t)+fullNameSize, data.buffer, data.size); + notifyChangeToDelegate(record); + m_lastRecordRetrieved = record; + m_lastRecordRetrievedPointer = p; + return Record::ErrorStatus::None; + } + return Record::ErrorStatus::RecordDoesNotExist; +} + +void InternalStorage::destroyRecord(Record record) { + if (record.isNull()) { + return; + } + char * p = pointerOfRecord(record); + if (p != nullptr) { + record_size_t previousRecordSize = sizeOfRecordStarting(p); + slideBuffer(p+previousRecordSize, -previousRecordSize); + notifyChangeToDelegate(); + } +} + +char * InternalStorage::pointerOfRecord(const Record record) const { + if (record.isNull()) { + return nullptr; + } + if (!m_lastRecordRetrieved.isNull() && record == m_lastRecordRetrieved) { + assert(m_lastRecordRetrievedPointer != nullptr); + return m_lastRecordRetrievedPointer; + } + for (char * p : *this) { + Record currentRecord(fullNameOfRecordStarting(p)); + if (record == currentRecord) { + m_lastRecordRetrieved = record; + m_lastRecordRetrievedPointer = p; + return p; + } + } + return nullptr; +} + +InternalStorage::record_size_t InternalStorage::sizeOfRecordStarting(char * start) const { + return StorageHelper::unalignedShort(start); +} + +const char * InternalStorage::fullNameOfRecordStarting(char * start) const { + return start+sizeof(record_size_t); +} + +const void * InternalStorage::valueOfRecordStarting(char * start) const { + char * currentChar = start+sizeof(record_size_t); + size_t fullNameLength = strlen(currentChar); + return currentChar+fullNameLength+1; +} + +size_t InternalStorage::overrideSizeAtPosition(char * position, record_size_t size) { + StorageHelper::writeUnalignedShort(size, position); + return sizeof(record_size_t); +} + +size_t InternalStorage::overrideFullNameAtPosition(char * position, const char * fullName) { + return strlcpy(position, fullName, strlen(fullName)+1) + 1; +} + +size_t InternalStorage::overrideBaseNameWithExtensionAtPosition(char * position, const char * baseName, const char * extension) { + size_t result = strlcpy(position, baseName, strlen(baseName)+1); // strlcpy copies the null terminating char + assert(UTF8Decoder::CharSizeOfCodePoint(k_dotChar) == 1); + *(position+result) = k_dotChar; // Replace the null terminating char with a dot + result++; + result += strlcpy(position+result, extension, strlen(extension)+1); + return result+1; +} + +size_t InternalStorage::overrideValueAtPosition(char * position, const void * data, record_size_t size) { + memcpy(position, data, size); + return size; +} + +bool InternalStorage::isFullNameTaken(const char * fullName, const Record * recordToExclude) { + Record r = Record(fullName); + return isNameOfRecordTaken(r, recordToExclude); +} + +bool InternalStorage::isBaseNameWithExtensionTaken(const char * baseName, const char * extension, Record * recordToExclude) { + Record r = Record(baseName, extension); + return isNameOfRecordTaken(r, recordToExclude); +} + +bool InternalStorage::isNameOfRecordTaken(Record r, const Record * recordToExclude) { + if (r == Record()) { + /* If the CRC32 of fullName is 0, we want to refuse the name as it would + * interfere with our escape case in the Record constructor, when the given + * name is nullptr. */ + return true; + } + for (char * p : *this) { + Record s(fullNameOfRecordStarting(p)); + if (recordToExclude && s == *recordToExclude) { + continue; + } + if (s == r) { + return true; + } + } + return false; +} + +bool InternalStorage::FullNameCompliant(const char * fullName) { + // We check that there is one dot and one dot only. + const char * dotChar = UTF8Helper::CodePointSearch(fullName, k_dotChar); + if (*dotChar == 0) { + return false; + } + if (*(UTF8Helper::CodePointSearch(dotChar+1, k_dotChar)) == 0) { + return true; + } + return false; +} + +bool InternalStorage::FullNameHasExtension(const char * fullName, const char * extension, size_t extensionLength) { + if (fullName == nullptr) { + return false; + } + size_t fullNameLength = strlen(fullName); + if (fullNameLength > extensionLength) { + const char * ext = fullName + fullNameLength - extensionLength; + if (UTF8Helper::PreviousCodePointIs(fullName, ext, k_dotChar) && strcmp(ext, extension) == 0) { + return true; + } + } + return false; +} + +char * InternalStorage::endBuffer() { + char * currentBuffer = m_buffer; + for (char * p : *this) { + currentBuffer += sizeOfRecordStarting(p); + } + return currentBuffer; +} + +size_t InternalStorage::sizeOfBaseNameAndExtension(const char * baseName, const char * extension) const { + // +1 for the dot and +1 for the null terminating char + return strlen(baseName)+1+strlen(extension)+1; +} + +size_t InternalStorage::sizeOfRecordWithBaseNameAndExtension(const char * baseName, const char * extension, size_t dataSize) const { + return sizeOfBaseNameAndExtension(baseName, extension) + dataSize + sizeof(record_size_t); +} + +size_t InternalStorage::sizeOfRecordWithFullName(const char * fullName, size_t dataSize) const { + size_t nameSize = strlen(fullName)+1; + return nameSize+dataSize+sizeof(record_size_t); +} + +bool InternalStorage::slideBuffer(char * position, int delta) { + if (delta > (int)availableSize()) { + return false; + } + memmove(position+delta, position, endBuffer()+sizeof(record_size_t)-position); + return true; +} + +InternalStorage::Record InternalStorage::privateRecordAndExtensionOfRecordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions, const char * * extensionResult, int baseNameLength) { + size_t nameLength = baseNameLength < 0 ? strlen(baseName) : baseNameLength; + { + const char * lastRetrievedRecordFullName = fullNameOfRecordStarting(m_lastRecordRetrievedPointer); + if (m_lastRecordRetrievedPointer != nullptr && strncmp(baseName, lastRetrievedRecordFullName, nameLength) == 0) { + for (size_t i = 0; i < numberOfExtensions; i++) { + if (strcmp(lastRetrievedRecordFullName+nameLength+1 /*+1 to pass the dot*/, extensions[i]) == 0) { + assert(UTF8Helper::CodePointIs(lastRetrievedRecordFullName + nameLength, '.')); + if (extensionResult != nullptr) { + *extensionResult = extensions[i]; + } + return m_lastRecordRetrieved; + } + } + } + } + for (char * p : *this) { + const char * currentName = fullNameOfRecordStarting(p); + if (strncmp(baseName, currentName, nameLength) == 0) { + for (size_t i = 0; i < numberOfExtensions; i++) { + if (strcmp(currentName+nameLength+1 /*+1 to pass the dot*/, extensions[i]) == 0) { + assert(UTF8Helper::CodePointIs(currentName + nameLength, '.')); + if (extensionResult != nullptr) { + *extensionResult = extensions[i]; + } + return Record(currentName); + } + } + } + } + if (extensionResult != nullptr) { + *extensionResult = nullptr; + } + return Record(); +} + +InternalStorage::RecordIterator & InternalStorage::RecordIterator::operator++() { + assert(m_recordStart); + record_size_t size = StorageHelper::unalignedShort(m_recordStart); + char * nextRecord = m_recordStart+size; + record_size_t newRecordSize = StorageHelper::unalignedShort(nextRecord); + m_recordStart = (newRecordSize == 0 ? nullptr : nextRecord); + return *this; +} + +} diff --git a/ion/src/shared/storage.cpp b/ion/src/shared/storage.cpp index 200b41865c6..c3dd9fcc078 100644 --- a/ion/src/shared/storage.cpp +++ b/ion/src/shared/storage.cpp @@ -1,291 +1,61 @@ -#include -#include +#include #include #include -#if ION_STORAGE_LOG -#include -#endif +#include +#include namespace Ion { -/* We want to implement a simple singleton pattern, to make sure the storage is - * initialized on first use, therefore preventing the static init order fiasco. - * That being said, we rely on knowing where the storage resides in the device's - * memory at compile time. Indeed, we want to advertise the static storage's - * memory address in the PlatformInfo structure (so that we can read and write - * it in DFU). - * Using a "static Storage storage;" variable makes it a local symbol at best, - * preventing the PlatformInfo from retrieving its address. And making the - * Storage variable global yields the static init fiasco issue. We're working - * around both issues by creating a global staticStorageArea buffer, and by - * placement-newing the Storage into that area on first use. */ - uint32_t staticStorageArea[sizeof(Storage)/sizeof(uint32_t)] = {0}; -constexpr char Storage::expExtension[]; -constexpr char Storage::funcExtension[]; -constexpr char Storage::seqExtension[]; -constexpr char Storage::eqExtension[]; - Storage * Storage::sharedStorage() { static Storage * storage = new (staticStorageArea) Storage(); return storage; } -// RECORD - -Storage::Record::Record(const char * fullName) { - if (fullName == nullptr) { - m_fullNameCRC32 = 0; - return; - } - const char * dotChar = UTF8Helper::CodePointSearch(fullName, k_dotChar); - // If no extension, return empty record - if (*dotChar == 0 || *(dotChar+1) == 0) { - m_fullNameCRC32 = 0; - return; - } - new (this) Record(fullName, dotChar - fullName, dotChar+1, (fullName + strlen(fullName)) - (dotChar+1)); -} - -Storage::Record::Record(const char * baseName, const char * extension) { - if (baseName == nullptr) { - assert(extension == nullptr); - m_fullNameCRC32 = 0; - return; - } - new (this) Record(baseName, strlen(baseName), extension, strlen(extension)); -} - - -#if ION_STORAGE_LOG - -void Storage::Record::log() { - std::cout << "Name: " << fullName() << std::endl; - std::cout << " Value (" << value().size << "): " << (char *)value().buffer << "\n\n" << std::endl; -} -#endif - -uint32_t Storage::Record::checksum() { - uint32_t crc32Results[2]; - crc32Results[0] = m_fullNameCRC32; - Data data = value(); - crc32Results[1] = Ion::crc32Byte((const uint8_t *)data.buffer, data.size); - return Ion::crc32Word(crc32Results, 2); -} - -Storage::Record::Record(const char * basename, int basenameLength, const char * extension, int extensionLength) { - assert(basename != nullptr); - assert(extension != nullptr); - - // We compute the CRC32 of the CRC32s of the basename and the extension - uint32_t crc32Results[2]; - crc32Results[0] = Ion::crc32Byte((const uint8_t *)basename, basenameLength); - crc32Results[1] = Ion::crc32Byte((const uint8_t *)extension, extensionLength); - m_fullNameCRC32 = Ion::crc32Word(crc32Results, 2); -} - -// STORAGE - -#if ION_STORAGE_LOG -void Storage::log() { - for (char * p : *this) { - const char * currentName = fullNameOfRecordStarting(p); - Record(currentName).log(); - } -} -#endif - size_t Storage::availableSize() { if (m_trashRecord != NULL) { - return realAvailableSize() + sizeof(record_size_t) + m_trashRecord.value().size; + return InternalStorage::availableSize() + sizeof(record_size_t) + m_trashRecord.value().size; } else { - return realAvailableSize(); + return InternalStorage::availableSize(); } } -size_t Storage::realAvailableSize() { - /* TODO maybe do: availableSize(char ** endBuffer) to get the endBuffer if it - * is needed after calling availableSize */ - assert(k_storageSize >= (endBuffer() - m_buffer) + sizeof(record_size_t)); - return k_storageSize-(endBuffer()-m_buffer)-sizeof(record_size_t); -} - -size_t Storage::putAvailableSpaceAtEndOfRecord(Storage::Record r) { - char * p = pointerOfRecord(r); - size_t previousRecordSize = sizeOfRecordStarting(p); - size_t availableStorageSize = realAvailableSize(); - char * nextRecord = p + previousRecordSize; - memmove(nextRecord + availableStorageSize, - nextRecord, - (m_buffer + k_storageSize - availableStorageSize) - nextRecord); - size_t newRecordSize = previousRecordSize + availableStorageSize; - overrideSizeAtPosition(p, (record_size_t)newRecordSize); - return newRecordSize; +size_t Storage::putAvailableSpaceAtEndOfRecord(Record r) { + emptyTrash(); + return InternalStorage::putAvailableSpaceAtEndOfRecord(r); } void Storage::getAvailableSpaceFromEndOfRecord(Record r, size_t recordAvailableSpace) { - char * p = pointerOfRecord(r); - size_t previousRecordSize = sizeOfRecordStarting(p); - char * nextRecord = p + previousRecordSize; - memmove(nextRecord - recordAvailableSpace, - nextRecord, - m_buffer + k_storageSize - nextRecord); - overrideSizeAtPosition(p, (record_size_t)(previousRecordSize - recordAvailableSpace)); -} - -uint32_t Storage::checksum() { - return Ion::crc32Byte((const uint8_t *) m_buffer, endBuffer()-m_buffer); -} - -void Storage::notifyChangeToDelegate(const Record record) const { - m_lastRecordRetrieved = Record(nullptr); - m_lastRecordRetrievedPointer = nullptr; - if (m_delegate != nullptr) { - m_delegate->storageDidChangeForRecord(record); - } + emptyTrash(); + InternalStorage::getAvailableSpaceFromEndOfRecord(r, recordAvailableSpace); } -Storage::Record::ErrorStatus Storage::notifyFullnessToDelegate() const { - if (m_delegate != nullptr) { - m_delegate->storageIsFull(); +int Storage::numberOfRecordsWithExtension(const char * extension) { + int trashRecord = 0; + if (FullNameHasExtension(m_trashRecord.fullName(), extension, strlen(extension))) { + trashRecord = 1; } - return Record::ErrorStatus::NotEnoughSpaceAvailable; + return InternalStorage::numberOfRecordsWithExtension(extension) - trashRecord; } Storage::Record::ErrorStatus Storage::createRecordWithFullName(const char * fullName, const void * data, size_t size) { emptyTrash(); - size_t recordSize = sizeOfRecordWithFullName(fullName, size); - if (recordSize >= k_maxRecordSize || recordSize > availableSize()) { - return notifyFullnessToDelegate(); - } - if (isFullNameTaken(fullName)) { - return Record::ErrorStatus::NameTaken; - } - // Find the end of data - char * newRecordAddress = endBuffer(); - char * newRecord = newRecordAddress; - // Fill totalSize - newRecord += overrideSizeAtPosition(newRecord, (record_size_t)recordSize); - // Fill name - newRecord += overrideFullNameAtPosition(newRecord, fullName); - // Fill data - newRecord += overrideValueAtPosition(newRecord, data, size); - // Next Record is null-sized - overrideSizeAtPosition(newRecord, 0); - Record r = Record(fullName); - notifyChangeToDelegate(r); - m_lastRecordRetrieved = r; - m_lastRecordRetrievedPointer = newRecordAddress; - return Record::ErrorStatus::None; + return InternalStorage::createRecordWithFullName(fullName, data, size); } Storage::Record::ErrorStatus Storage::createRecordWithExtension(const char * baseName, const char * extension, const void * data, size_t size) { emptyTrash(); - size_t recordSize = sizeOfRecordWithBaseNameAndExtension(baseName, extension, size); - if (recordSize >= k_maxRecordSize || recordSize > availableSize()) { - return notifyFullnessToDelegate(); - } - if (isBaseNameWithExtensionTaken(baseName, extension)) { - return Record::ErrorStatus::NameTaken; - } - // Find the end of data - char * newRecordAddress = endBuffer(); - char * newRecord = newRecordAddress; - // Fill totalSize - newRecord += overrideSizeAtPosition(newRecord, (record_size_t)recordSize); - // Fill name - newRecord += overrideBaseNameWithExtensionAtPosition(newRecord, baseName, extension); - // Fill data - newRecord += overrideValueAtPosition(newRecord, data, size); - // Next Record is null-sized - overrideSizeAtPosition(newRecord, 0); - Record r = Record(fullNameOfRecordStarting(newRecordAddress)); - notifyChangeToDelegate(r); - m_lastRecordRetrieved = r; - m_lastRecordRetrievedPointer = newRecordAddress; - return Record::ErrorStatus::None; + return InternalStorage::createRecordWithExtension(baseName, extension, data, size); } -int Storage::numberOfRecordsWithExtension(const char * extension) { - int count = 0; - size_t extensionLength = strlen(extension); - for (char * p : *this) { - const char * name = fullNameOfRecordStarting(p); - if (FullNameHasExtension(name, extension, extensionLength) && Record(name) != m_trashRecord) { - count++; - } - } - return count; -} - -int Storage::numberOfRecords() { - return realNumberOfRecords() - (m_trashRecord == NULL ? 0 : 1); -} - -int Storage::realNumberOfRecords() { - int count = 0; - for (char * p : *this) { - const char * name = fullNameOfRecordStarting(p); - count++; - } - return count; +bool Storage::hasRecord(Record r) { + return InternalStorage::hasRecord(r) && r != m_trashRecord; } -Storage::Record Storage::realRecordAtIndex(int index) { - int currentIndex = -1; - const char * name = nullptr; - char * recordAddress = nullptr; - for (char * p : *this) { - const char * currentName = fullNameOfRecordStarting(p); - currentIndex++; - if (currentIndex == index) { - recordAddress = p; - name = currentName; - break; - } - } - if (name == nullptr) { - return Record(); - } - Record r = Record(name); - m_lastRecordRetrieved = r; - m_lastRecordRetrievedPointer = recordAddress; - return Record(name); -} - - -Storage::Record Storage::recordAtIndex(int index) { - int currentIndex = -1; - const char * name = nullptr; - char * recordAddress = nullptr; - for (char * p : *this) { - const char * currentName = fullNameOfRecordStarting(p); - Record r = Record(currentName); - if (r == m_trashRecord) { - continue; - } - currentIndex++; - if (currentIndex == index) { - recordAddress = p; - name = currentName; - break; - } - } - if (name == nullptr) { - return Record(); - } - Record r = Record(name); - m_lastRecordRetrieved = r; - m_lastRecordRetrievedPointer = recordAddress; - return Record(name); -} - -void Storage::emptyTrash() { - if (m_trashRecord != NULL) { - realDestroyRecord(m_trashRecord); - m_trashRecord = NULL; - } +void Storage::destroyRecord(Record record) { + emptyTrash(); + m_trashRecord = record; } Storage::Record Storage::recordWithExtensionAtIndex(const char * extension, int index) { @@ -307,397 +77,98 @@ Storage::Record Storage::recordWithExtensionAtIndex(const char * extension, int if (name == nullptr) { return Record(); } - Record r = Record(name); + Storage::Record r = Record(name); m_lastRecordRetrieved = r; m_lastRecordRetrievedPointer = recordAddress; return Record(name); } Storage::Record Storage::recordNamed(const char * fullName) { - if (fullName == nullptr) { - return Record(); - } - Record r = Record(fullName); - char * p = pointerOfRecord(r); - if (p != nullptr) { - return r; - } - return Record(); + Storage::Record r = InternalStorage::recordNamed(fullName); + return r == m_trashRecord ? Record() : r; } Storage::Record Storage::recordBaseNamedWithExtension(const char * baseName, const char * extension) { - const char * extensions[1] = {extension}; - return recordBaseNamedWithExtensions(baseName, extensions, 1); -} - -Storage::Record Storage::recordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions) { - return privateRecordAndExtensionOfRecordBaseNamedWithExtensions(baseName, extensions, numberOfExtensions); + Storage::Record r = InternalStorage::recordBaseNamedWithExtension(baseName, extension); + return r == m_trashRecord ? Record() : r; } -const char * Storage::extensionOfRecordBaseNamedWithExtensions(const char * baseName, int baseNameLength, const char * const extensions[], size_t numberOfExtensions) { - const char * result = nullptr; - privateRecordAndExtensionOfRecordBaseNamedWithExtensions(baseName, extensions, numberOfExtensions, &result, baseNameLength); - return result; +Storage::Record Storage::recordBaseNamedWithExtensions(const char * baseName, const char * const extension[], size_t numberOfExtensions) { + Storage::Record r = InternalStorage::recordBaseNamedWithExtensions(baseName, extension, numberOfExtensions); + return r == m_trashRecord ? Record() : r; } -void Storage::destroyAllRecords() { - overrideSizeAtPosition(m_buffer, 0); - notifyChangeToDelegate(); -} - -void Storage::destroyRecordWithBaseNameAndExtension(const char * baseName, const char * extension) { - recordBaseNamedWithExtension(baseName, extension).destroy(); -} - -void Storage::destroyRecordsWithExtension(const char * extension) { - size_t extensionLength = strlen(extension); - char * currentRecordStart = (char *)m_buffer; - bool didChange = false; - while (currentRecordStart != nullptr && sizeOfRecordStarting(currentRecordStart) != 0) { - const char * currentFullName = fullNameOfRecordStarting(currentRecordStart); - if (FullNameHasExtension(currentFullName, extension, extensionLength)) { - Record currentRecord(currentFullName); - currentRecord.destroy(); - didChange = true; - continue; +const char * Storage::extensionOfRecordBaseNamedWithExtensions(const char * baseName, int baseNameLength, const char * const extension[], size_t numberOfExtensions) { + size_t nameLength = baseNameLength < 0 ? strlen(baseName) : baseNameLength; + { + const char * lastRetrievedRecordFullName = fullNameOfRecordStarting(m_lastRecordRetrievedPointer); + if (m_lastRecordRetrievedPointer != nullptr && strncmp(baseName, lastRetrievedRecordFullName, nameLength) == 0 && Record(lastRetrievedRecordFullName) != m_trashRecord) { + for (size_t i = 0; i < numberOfExtensions; i++) { + if (strcmp(lastRetrievedRecordFullName+nameLength+1 /*+1 to pass the dot*/, extension[i]) == 0) { + assert(UTF8Helper::CodePointIs(lastRetrievedRecordFullName + nameLength, '.')); + return extension[i]; + } + } } - currentRecordStart = *(RecordIterator(currentRecordStart).operator++()); } - if (didChange) { - notifyChangeToDelegate(); + for (char * p : *this) { + const char * currentName = fullNameOfRecordStarting(p); + if (strncmp(baseName, currentName, nameLength) == 0 && Record(currentName) != m_trashRecord) { + for (size_t i = 0; i < numberOfExtensions; i++) { + if (strcmp(currentName+nameLength+1 /*+1 to pass the dot*/, extension[i]) == 0) { + assert(UTF8Helper::CodePointIs(currentName + nameLength, '.')); + return extension[i]; + } + } + } } -} - -// PRIVATE -Storage::Storage() : - m_magicHeader(Magic), - m_buffer(), - m_magicFooter(Magic), - m_delegate(nullptr), - m_lastRecordRetrieved(nullptr), - m_lastRecordRetrievedPointer(nullptr), - m_trashRecord(NULL) -{ - assert(m_magicHeader == Magic); - assert(m_magicFooter == Magic); - // Set the size of the first record to 0 - overrideSizeAtPosition(m_buffer, 0); -} - -const char * Storage::fullNameOfRecord(const Record record) { - char * p = pointerOfRecord(record); - if (p != nullptr) { - return fullNameOfRecordStarting(p); - } return nullptr; } -Storage::Record::ErrorStatus Storage::setFullNameOfRecord(const Record record, const char * fullName) { - if (!FullNameCompliant(fullName)) { - return Record::ErrorStatus::NonCompliantName; - } - if (isFullNameTaken(fullName, &record)) { - return Record::ErrorStatus::NameTaken; - } - size_t nameSize = strlen(fullName) + 1; - char * p = pointerOfRecord(record); - if (p != nullptr) { - size_t previousNameSize = strlen(fullNameOfRecordStarting(p))+1; - record_size_t previousRecordSize = sizeOfRecordStarting(p); - size_t newRecordSize = previousRecordSize-previousNameSize+nameSize; - if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+sizeof(record_size_t)+previousNameSize, nameSize-previousNameSize)) { - return notifyFullnessToDelegate(); - } - overrideSizeAtPosition(p, newRecordSize); - overrideFullNameAtPosition(p+sizeof(record_size_t), fullName); - notifyChangeToDelegate(record); - m_lastRecordRetrieved = record; - m_lastRecordRetrievedPointer = p; - return Record::ErrorStatus::None; - } - return Record::ErrorStatus::RecordDoesNotExist; +int Storage::numberOfRecords() { + return InternalStorage::numberOfRecords() - (m_trashRecord != NULL ? 1 : 0); } -Storage::Record::ErrorStatus Storage::setBaseNameWithExtensionOfRecord(Record record, const char * baseName, const char * extension) { - if (isBaseNameWithExtensionTaken(baseName, extension, &record)) { - return Record::ErrorStatus::NameTaken; - } - size_t nameSize = sizeOfBaseNameAndExtension(baseName, extension); - char * p = pointerOfRecord(record); - if (p != nullptr) { - size_t previousNameSize = strlen(fullNameOfRecordStarting(p))+1; - record_size_t previousRecordSize = sizeOfRecordStarting(p); - size_t newRecordSize = previousRecordSize-previousNameSize+nameSize; - if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+sizeof(record_size_t)+previousNameSize, nameSize-previousNameSize)) { - return notifyFullnessToDelegate(); +InternalStorage::Record Storage::recordAtIndex(int index) { + int currentIndex = -1; + const char * name = nullptr; + char * recordAddress = nullptr; + for (char * p : *this) { + const char * currentName = fullNameOfRecordStarting(p); + if (Record(currentName) != m_trashRecord) { + currentIndex++; + if (currentIndex == index) { + recordAddress = p; + name = currentName; + break; + } } - overrideSizeAtPosition(p, newRecordSize); - char * fullNamePosition = p + sizeof(record_size_t); - overrideBaseNameWithExtensionAtPosition(fullNamePosition, baseName, extension); - // Recompute the CRC32 - record = Record(fullNamePosition); - notifyChangeToDelegate(record); - m_lastRecordRetrieved = record; - m_lastRecordRetrievedPointer = p; - return Record::ErrorStatus::None; - } - return Record::ErrorStatus::RecordDoesNotExist; -} - -Storage::Record::Data Storage::valueOfRecord(const Record record) { - char * p = pointerOfRecord(record); - if (p != nullptr) { - const char * fullName = fullNameOfRecordStarting(p); - record_size_t size = sizeOfRecordStarting(p); - const void * value = valueOfRecordStarting(p); - return {.buffer = value, .size = size-strlen(fullName)-1-sizeof(record_size_t)}; } - return {.buffer= nullptr, .size= 0}; -} - -Storage::Record::ErrorStatus Storage::setValueOfRecord(Record record, Record::Data data) { - char * p = pointerOfRecord(record); - /* TODO: if data.buffer == p, assert that size hasn't change and do not do any - * memcopy, but still notify the delegate. Beware of scripts and the accordion - * routine.*/ - if (p != nullptr) { - record_size_t previousRecordSize = sizeOfRecordStarting(p); - const char * fullName = fullNameOfRecordStarting(p); - size_t newRecordSize = sizeOfRecordWithFullName(fullName, data.size); - if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+previousRecordSize, newRecordSize-previousRecordSize)) { - return notifyFullnessToDelegate(); - } - record_size_t fullNameSize = strlen(fullName)+1; - overrideSizeAtPosition(p, newRecordSize); - overrideValueAtPosition(p+sizeof(record_size_t)+fullNameSize, data.buffer, data.size); - notifyChangeToDelegate(record); - m_lastRecordRetrieved = record; - m_lastRecordRetrievedPointer = p; - return Record::ErrorStatus::None; + if (name == nullptr) { + return Record(); } - return Record::ErrorStatus::RecordDoesNotExist; + Record r = Record(name); + m_lastRecordRetrieved = r; + m_lastRecordRetrievedPointer = recordAddress; + return Record(name); } - void Storage::reinsertTrash(const char * extension) { - if (m_trashRecord != NULL) { + if (!m_trashRecord.isNull()) { char * p = pointerOfRecord(m_trashRecord); const char * fullName = fullNameOfRecordStarting(p); if (FullNameHasExtension(fullName, extension, strlen(extension))) { - m_trashRecord = NULL; - } - } -} - - -void Storage::destroyRecord(Record record) { - emptyTrash(); - m_trashRecord = record; -} - -void Storage::realDestroyRecord(Record record) { - if (record.isNull()) { - return; - } - char * p = pointerOfRecord(record); - if (p != nullptr) { - record_size_t previousRecordSize = sizeOfRecordStarting(p); - slideBuffer(p+previousRecordSize, -previousRecordSize); - notifyChangeToDelegate(); - } -} - -char * Storage::pointerOfRecord(const Record record) const { - if (record.isNull()) { - return nullptr; - } - if (!m_lastRecordRetrieved.isNull() && record == m_lastRecordRetrieved) { - assert(m_lastRecordRetrievedPointer != nullptr); - return m_lastRecordRetrievedPointer; - } - for (char * p : *this) { - Record currentRecord(fullNameOfRecordStarting(p)); - if (record == currentRecord) { - m_lastRecordRetrieved = record; - m_lastRecordRetrievedPointer = p; - return p; - } - } - return nullptr; -} - -Storage::record_size_t Storage::sizeOfRecordStarting(char * start) const { - return StorageHelper::unalignedShort(start); -} - -const char * Storage::fullNameOfRecordStarting(char * start) const { - return start+sizeof(record_size_t); -} - -const void * Storage::valueOfRecordStarting(char * start) const { - char * currentChar = start+sizeof(record_size_t); - size_t fullNameLength = strlen(currentChar); - return currentChar+fullNameLength+1; -} - -size_t Storage::overrideSizeAtPosition(char * position, record_size_t size) { - StorageHelper::writeUnalignedShort(size, position); - return sizeof(record_size_t); -} - -size_t Storage::overrideFullNameAtPosition(char * position, const char * fullName) { - return strlcpy(position, fullName, strlen(fullName)+1) + 1; -} - -size_t Storage::overrideBaseNameWithExtensionAtPosition(char * position, const char * baseName, const char * extension) { - size_t result = strlcpy(position, baseName, strlen(baseName)+1); // strlcpy copies the null terminating char - assert(UTF8Decoder::CharSizeOfCodePoint(k_dotChar) == 1); - *(position+result) = k_dotChar; // Replace the null terminating char with a dot - result++; - result += strlcpy(position+result, extension, strlen(extension)+1); - return result+1; -} - -size_t Storage::overrideValueAtPosition(char * position, const void * data, record_size_t size) { - memcpy(position, data, size); - return size; -} - -bool Storage::isFullNameTaken(const char * fullName, const Record * recordToExclude) { - Record r = Record(fullName); - return isNameOfRecordTaken(r, recordToExclude); -} - -bool Storage::isBaseNameWithExtensionTaken(const char * baseName, const char * extension, Record * recordToExclude) { - Record r = Record(baseName, extension); - return isNameOfRecordTaken(r, recordToExclude); -} - -bool Storage::isNameOfRecordTaken(Record r, const Record * recordToExclude) { - if (r == Record()) { - /* If the CRC32 of fullName is 0, we want to refuse the name as it would - * interfere with our escape case in the Record constructor, when the given - * name is nullptr. */ - return true; - } - for (char * p : *this) { - Record s(fullNameOfRecordStarting(p)); - if (recordToExclude && s == *recordToExclude) { - continue; + m_trashRecord = Record(); } - if (s == r && s != m_trashRecord) { - return true; - } - } - return false; -} - -bool Storage::FullNameCompliant(const char * fullName) { - // We check that there is one dot and one dot only. - const char * dotChar = UTF8Helper::CodePointSearch(fullName, k_dotChar); - if (*dotChar == 0) { - return false; } - if (*(UTF8Helper::CodePointSearch(dotChar+1, k_dotChar)) == 0) { - return true; - } - return false; } -bool Storage::FullNameHasExtension(const char * fullName, const char * extension, size_t extensionLength) { - if (fullName == nullptr) { - return false; - } - size_t fullNameLength = strlen(fullName); - if (fullNameLength > extensionLength) { - const char * ext = fullName + fullNameLength - extensionLength; - if (UTF8Helper::PreviousCodePointIs(fullName, ext, k_dotChar) && strcmp(ext, extension) == 0) { - return true; - } - } - return false; -} - -char * Storage::endBuffer() { - char * currentBuffer = m_buffer; - for (char * p : *this) { - currentBuffer += sizeOfRecordStarting(p); - } - return currentBuffer; -} - -size_t Storage::sizeOfBaseNameAndExtension(const char * baseName, const char * extension) const { - // +1 for the dot and +1 for the null terminating char - return strlen(baseName)+1+strlen(extension)+1; -} - -size_t Storage::sizeOfRecordWithBaseNameAndExtension(const char * baseName, const char * extension, size_t dataSize) const { - return sizeOfBaseNameAndExtension(baseName, extension) + dataSize + sizeof(record_size_t); -} - -size_t Storage::sizeOfRecordWithFullName(const char * fullName, size_t dataSize) const { - size_t nameSize = strlen(fullName)+1; - return nameSize+dataSize+sizeof(record_size_t); -} - -bool Storage::slideBuffer(char * position, int delta) { - if (delta > (int)realAvailableSize()) { - emptyTrash(); - if (delta > (int)realAvailableSize()) { - return false; - } - } - memmove(position+delta, position, endBuffer()+sizeof(record_size_t)-position); - return true; -} - -Storage::Record Storage::privateRecordAndExtensionOfRecordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions, const char * * extensionResult, int baseNameLength) { - size_t nameLength = baseNameLength < 0 ? strlen(baseName) : baseNameLength; - { - const char * lastRetrievedRecordFullName = fullNameOfRecordStarting(m_lastRecordRetrievedPointer); - if (m_lastRecordRetrievedPointer != nullptr && strncmp(baseName, lastRetrievedRecordFullName, nameLength) == 0 && Record(lastRetrievedRecordFullName) == m_trashRecord) { - for (size_t i = 0; i < numberOfExtensions; i++) { - if (strcmp(lastRetrievedRecordFullName+nameLength+1 /*+1 to pass the dot*/, extensions[i]) == 0) { - assert(UTF8Helper::CodePointIs(lastRetrievedRecordFullName + nameLength, '.')); - if (extensionResult != nullptr) { - *extensionResult = extensions[i]; - } - return m_lastRecordRetrieved; - } - } - } - } - for (char * p : *this) { - const char * currentName = fullNameOfRecordStarting(p); - if (strncmp(baseName, currentName, nameLength) == 0) { - if (Record(currentName) == m_trashRecord) { - continue; - } - for (size_t i = 0; i < numberOfExtensions; i++) { - if (strcmp(currentName+nameLength+1 /*+1 to pass the dot*/, extensions[i]) == 0) { - assert(UTF8Helper::CodePointIs(currentName + nameLength, '.')); - if (extensionResult != nullptr) { - *extensionResult = extensions[i]; - } - return Record(currentName); - } - } - } - } - if (extensionResult != nullptr) { - *extensionResult = nullptr; +void Storage::emptyTrash() { + if (!m_trashRecord.isNull()) { + InternalStorage::destroyRecord(m_trashRecord); + m_trashRecord = Record(); } - return Record(); -} - -Storage::RecordIterator & Storage::RecordIterator::operator++() { - assert(m_recordStart); - record_size_t size = StorageHelper::unalignedShort(m_recordStart); - char * nextRecord = m_recordStart+size; - record_size_t newRecordSize = StorageHelper::unalignedShort(nextRecord); - m_recordStart = (newRecordSize == 0 ? nullptr : nextRecord); - return *this; } } diff --git a/python/port/mod/ion/file.cpp b/python/port/mod/ion/file.cpp index 721d79eee04..c9eb9bc6636 100644 --- a/python/port/mod/ion/file.cpp +++ b/python/port/mod/ion/file.cpp @@ -725,12 +725,12 @@ STATIC mp_obj_t file_write(mp_obj_t o_in, mp_obj_t o_s) { size_t previous_size = file->record.value().size; - // Claim avaliable space. - size_t avaliable_size = Ion::Storage::sharedStorage()->putAvailableSpaceAtEndOfRecord(file->record); + // Claim available space. + size_t available_size = Ion::Storage::sharedStorage()->putAvailableSpaceAtEndOfRecord(file->record); // Check if there is enough space left - if (file->position + len > avaliable_size) { - Ion::Storage::sharedStorage()->getAvailableSpaceFromEndOfRecord(file->record, avaliable_size - previous_size); + if (file->position + len > available_size) { + Ion::Storage::sharedStorage()->getAvailableSpaceFromEndOfRecord(file->record, available_size - previous_size); mp_raise_OSError(28); } @@ -974,12 +974,12 @@ STATIC mp_obj_t file_truncate(size_t n_args, const mp_obj_t* args) { size_t previous_size = file->record.value().size; - // Claim avaliable space. - size_t avaliable_size = Ion::Storage::sharedStorage()->putAvailableSpaceAtEndOfRecord(file->record); + // Claim available space. + size_t available_size = Ion::Storage::sharedStorage()->putAvailableSpaceAtEndOfRecord(file->record); // Check if there is enough space left - if (new_end > avaliable_size) { - Ion::Storage::sharedStorage()->getAvailableSpaceFromEndOfRecord(file->record, avaliable_size - previous_size); + if (new_end > available_size) { + Ion::Storage::sharedStorage()->getAvailableSpaceFromEndOfRecord(file->record, available_size - previous_size); mp_raise_OSError(28); } From 1c918f549cd48c7d891b03fbf543f6a8de24045d Mon Sep 17 00:00:00 2001 From: Laury Date: Sun, 13 Mar 2022 21:43:14 +0100 Subject: [PATCH 179/355] [ion] Try a fix to make compilation work --- ion/src/shared/internal_storage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/shared/internal_storage.cpp b/ion/src/shared/internal_storage.cpp index 5f12eb460b0..5fc4a90d10a 100644 --- a/ion/src/shared/internal_storage.cpp +++ b/ion/src/shared/internal_storage.cpp @@ -2,9 +2,9 @@ #include #include #include -#include "storage.h" +#include #if ION_STORAGE_LOG -#include +#include #endif namespace Ion { From 6dc31401fe4fd1072478237e10174f08c8fe588f Mon Sep 17 00:00:00 2001 From: Laury Date: Thu, 17 Mar 2022 20:05:20 +0100 Subject: [PATCH 180/355] [bootloader] Compatibility with Omega 2.0 --- .github/workflows/ci-workflow.yml | 19 + Makefile | 7 +- apps/code/app.h | 2 +- apps/external/app.h | 2 +- apps/settings/sub_menu/about_controller.cpp | 12 +- bootloader/Makefile | 21 + bootloader/boot.cpp | 82 +++ bootloader/boot.h | 28 + bootloader/interface.cpp | 83 +++ bootloader/interface.h | 22 + bootloader/jump_to_firmware.s | 9 + bootloader/kernel_header.cpp | 25 + bootloader/kernel_header.h | 32 ++ bootloader/main.cpp | 42 ++ bootloader/slot.cpp | 33 ++ bootloader/slot.h | 33 ++ bootloader/trampoline.cpp | 42 ++ bootloader/trampoline.h | 14 + bootloader/usb_desc.cpp | 12 + bootloader/userland_header.cpp | 33 ++ bootloader/userland_header.h | 50 ++ build/config.mak | 2 +- build/device/secure_ext.py | 13 +- build/platform.device.bootloader.mak | 3 + build/rules.mk | 4 +- build/targets.device.bootloader.mak | 56 ++ build/targets.device.mak | 19 - build/targets.device.n0100.mak | 15 + build/targets.device.n0110.mak | 34 +- ion/Makefile | 3 +- ion/include/ion.h | 5 +- ion/include/ion/storage.h | 2 +- ion/src/device/Makefile | 4 +- ion/src/device/bootloader/Makefile | 19 + ion/src/device/bootloader/boot/rt0.cpp | 283 ++++++++++ ion/src/device/bootloader/bootloader.A.ld | 28 + ion/src/device/bootloader/bootloader.B.ld | 28 + .../device/bootloader/bootloader_common.ld | 128 +++++ ion/src/device/bootloader/drivers/board.cpp | 423 ++++++++++++++ ion/src/device/bootloader/drivers/cache.cpp | 104 ++++ ion/src/device/bootloader/drivers/cache.h | 46 ++ .../bootloader/drivers/config/backlight.h | 25 + .../bootloader/drivers/config/battery.h | 30 + .../device/bootloader/drivers/config/clocks.h | 68 +++ .../bootloader/drivers/config/console.h | 32 ++ .../bootloader/drivers/config/display.h | 38 ++ .../bootloader/drivers/config/exam_mode.h | 30 + .../drivers/config/external_flash.h | 45 ++ .../drivers/config/internal_flash.h | 31 ++ .../bootloader/drivers/config/keyboard.h | 75 +++ .../device/bootloader/drivers/config/led.h | 28 + .../bootloader/drivers/config/serial_number.h | 18 + .../device/bootloader/drivers/config/swd.h | 24 + .../device/bootloader/drivers/config/timing.h | 19 + .../device/bootloader/drivers/config/usb.h | 31 ++ .../bootloader/drivers/external_flash.cpp | 521 ++++++++++++++++++ .../drivers/external_flash_tramp.cpp | 386 +++++++++++++ ion/src/device/bootloader/drivers/led.cpp | 23 + ion/src/device/bootloader/drivers/power.cpp | 87 +++ ion/src/device/bootloader/drivers/power.h | 16 + ion/src/device/bootloader/drivers/reset.cpp | 13 + .../device/bootloader/drivers/trampoline.cpp | 14 + .../device/bootloader/drivers/trampoline.h | 22 + ion/src/device/bootloader/drivers/usb.cpp | 27 + ion/src/device/bootloader/platform_info.cpp | 183 ++++++ .../device/bootloader/regs/config/cortex.h | 6 + ion/src/device/bootloader/regs/config/crc.h | 6 + ion/src/device/bootloader/regs/config/flash.h | 6 + ion/src/device/bootloader/regs/config/pwr.h | 6 + ion/src/device/bootloader/regs/config/rcc.h | 7 + .../device/bootloader/regs/config/syscfg.h | 6 + ion/src/device/bootloader/regs/config/usart.h | 12 + ion/src/device/n0100/Makefile | 8 + ion/src/device/n0100/boot/rt0.cpp | 264 +++++++++ ion/src/device/n0100/drivers/power.cpp | 33 ++ ion/src/device/n0100/platform_info.cpp | 143 +++++ ion/src/device/n0110/Makefile | 8 + ion/src/device/{shared => n0110}/boot/rt0.cpp | 28 +- ion/src/device/n0110/drivers/board.cpp | 22 + .../device/n0110/drivers/external_flash.cpp | 3 +- ion/src/device/n0110/drivers/power.cpp | 39 ++ ion/src/device/n0110/drivers/power.h | 1 + ion/src/device/n0110/internal_flash.ld | 36 +- .../n0110}/platform_info.cpp | 54 +- ion/src/device/shared/boot/Makefile | 1 - ion/src/device/shared/boot/isr.h | 1 - ion/src/device/shared/drivers/Makefile | 1 + ion/src/device/shared/drivers/board.h | 1 + ion/src/device/shared/drivers/flash.cpp | 2 +- ion/src/device/shared/drivers/power.cpp | 37 -- ion/src/device/shared/drivers/usb.h | 1 + ion/src/device/shared/drivers/usb_desc.cpp | 15 + ion/src/device/shared/usb/Makefile | 1 + ion/src/device/shared/usb/calculator.h | 3 +- ion/src/device/shared/usb/dfu_interface.cpp | 1 + ion/src/device/shared/usb/dfu_relocated.cpp | 2 + ion/src/device/shared/usb/dfu_xip.cpp | 2 + ion/src/shared/dummy/usb.cpp | 6 +- ion/src/simulator/Makefile | 1 + ion/src/simulator/shared/platform_info.cpp | 141 +++++ kandinsky/Makefile | 2 +- themes/icons.json | 5 +- .../local/epsilon_dark/bootloader/cable.png | Bin 0 -> 179 bytes .../epsilon_dark/bootloader/computer.png | Bin 0 -> 357 bytes .../local/epsilon_light/bootloader/cable.png | Bin 0 -> 179 bytes .../epsilon_light/bootloader/computer.png | Bin 0 -> 357 bytes .../local/omega_dark/bootloader/cable.png | Bin 0 -> 5586 bytes .../local/omega_dark/bootloader/computer.png | Bin 0 -> 10338 bytes .../local/omega_light/bootloader/cable.png | Bin 0 -> 5586 bytes .../local/omega_light/bootloader/computer.png | Bin 0 -> 10338 bytes .../local/upsilon_light/bootloader/cable.png | Bin 0 -> 179 bytes .../upsilon_light/bootloader/computer.png | Bin 0 -> 357 bytes 112 files changed, 4372 insertions(+), 147 deletions(-) create mode 100644 bootloader/Makefile create mode 100644 bootloader/boot.cpp create mode 100644 bootloader/boot.h create mode 100644 bootloader/interface.cpp create mode 100644 bootloader/interface.h create mode 100644 bootloader/jump_to_firmware.s create mode 100644 bootloader/kernel_header.cpp create mode 100644 bootloader/kernel_header.h create mode 100644 bootloader/main.cpp create mode 100644 bootloader/slot.cpp create mode 100644 bootloader/slot.h create mode 100644 bootloader/trampoline.cpp create mode 100644 bootloader/trampoline.h create mode 100644 bootloader/usb_desc.cpp create mode 100644 bootloader/userland_header.cpp create mode 100644 bootloader/userland_header.h create mode 100644 build/platform.device.bootloader.mak create mode 100644 build/targets.device.bootloader.mak create mode 100644 ion/src/device/bootloader/Makefile create mode 100644 ion/src/device/bootloader/boot/rt0.cpp create mode 100644 ion/src/device/bootloader/bootloader.A.ld create mode 100644 ion/src/device/bootloader/bootloader.B.ld create mode 100644 ion/src/device/bootloader/bootloader_common.ld create mode 100644 ion/src/device/bootloader/drivers/board.cpp create mode 100644 ion/src/device/bootloader/drivers/cache.cpp create mode 100644 ion/src/device/bootloader/drivers/cache.h create mode 100644 ion/src/device/bootloader/drivers/config/backlight.h create mode 100644 ion/src/device/bootloader/drivers/config/battery.h create mode 100644 ion/src/device/bootloader/drivers/config/clocks.h create mode 100644 ion/src/device/bootloader/drivers/config/console.h create mode 100644 ion/src/device/bootloader/drivers/config/display.h create mode 100644 ion/src/device/bootloader/drivers/config/exam_mode.h create mode 100644 ion/src/device/bootloader/drivers/config/external_flash.h create mode 100644 ion/src/device/bootloader/drivers/config/internal_flash.h create mode 100644 ion/src/device/bootloader/drivers/config/keyboard.h create mode 100644 ion/src/device/bootloader/drivers/config/led.h create mode 100644 ion/src/device/bootloader/drivers/config/serial_number.h create mode 100644 ion/src/device/bootloader/drivers/config/swd.h create mode 100644 ion/src/device/bootloader/drivers/config/timing.h create mode 100644 ion/src/device/bootloader/drivers/config/usb.h create mode 100644 ion/src/device/bootloader/drivers/external_flash.cpp create mode 100644 ion/src/device/bootloader/drivers/external_flash_tramp.cpp create mode 100644 ion/src/device/bootloader/drivers/led.cpp create mode 100644 ion/src/device/bootloader/drivers/power.cpp create mode 100644 ion/src/device/bootloader/drivers/power.h create mode 100644 ion/src/device/bootloader/drivers/reset.cpp create mode 100644 ion/src/device/bootloader/drivers/trampoline.cpp create mode 100644 ion/src/device/bootloader/drivers/trampoline.h create mode 100644 ion/src/device/bootloader/drivers/usb.cpp create mode 100644 ion/src/device/bootloader/platform_info.cpp create mode 100644 ion/src/device/bootloader/regs/config/cortex.h create mode 100644 ion/src/device/bootloader/regs/config/crc.h create mode 100644 ion/src/device/bootloader/regs/config/flash.h create mode 100644 ion/src/device/bootloader/regs/config/pwr.h create mode 100644 ion/src/device/bootloader/regs/config/rcc.h create mode 100644 ion/src/device/bootloader/regs/config/syscfg.h create mode 100644 ion/src/device/bootloader/regs/config/usart.h create mode 100644 ion/src/device/n0100/boot/rt0.cpp create mode 100644 ion/src/device/n0100/platform_info.cpp rename ion/src/device/{shared => n0110}/boot/rt0.cpp (97%) rename ion/src/{shared => device/n0110}/platform_info.cpp (72%) create mode 100644 ion/src/device/shared/drivers/usb_desc.cpp create mode 100644 ion/src/simulator/shared/platform_info.cpp create mode 100644 themes/themes/local/epsilon_dark/bootloader/cable.png create mode 100644 themes/themes/local/epsilon_dark/bootloader/computer.png create mode 100644 themes/themes/local/epsilon_light/bootloader/cable.png create mode 100644 themes/themes/local/epsilon_light/bootloader/computer.png create mode 100644 themes/themes/local/omega_dark/bootloader/cable.png create mode 100644 themes/themes/local/omega_dark/bootloader/computer.png create mode 100644 themes/themes/local/omega_light/bootloader/cable.png create mode 100644 themes/themes/local/omega_light/bootloader/computer.png create mode 100644 themes/themes/local/upsilon_light/bootloader/cable.png create mode 100644 themes/themes/local/upsilon_light/bootloader/computer.png diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 0f3d3a77fa6..2dc7f1be618 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -153,6 +153,25 @@ jobs: with: name: epsilon-binpack-n0110.tgz path: output/release/device/n0110/binpack-n0110.tgz + bootloader: + runs-on: ubuntu-latest + steps: + - run: sudo apt-get install build-essential imagemagick libfreetype6-dev libjpeg-dev libpng-dev pkg-config + - uses: numworks/setup-arm-toolchain@2020-q2 + - uses: actions/checkout@v2 + with: + submodules: 'recursive' + - run: make -j2 bootloader.dfu + - run: make MODEL=bootloader -j2 epsilon.A.dfu epsilon.B.dfu + - run: make MODEL=bootloader -j2 epsilon.onboarding.A.dfu epsilon.onboarding.B.dfu + - run: make MODEL=bootloader -j2 epsilon.onboarding.update.A.dfu epsilon.onboarding.update.B.dfu + - run: make MODEL=bootloader -j2 epsilon.onboarding.beta.A.dfu epsilon.onboarding.beta.B.dfu + - run: make -j2 binpack + - run: cp output/release/device/bootloader/binpack-bootloader-`git rev-parse HEAD | head -c 7`.tgz output/release/device/bootloader/binpack-bootloader.tgz + - uses: actions/upload-artifact@master + with: + name: epsilon-binpack-bootloader.tgz + path: output/release/device/bootloader/binpack-bootloader.tgz windows: runs-on: windows-latest defaults: diff --git a/Makefile b/Makefile index 1a424b3ef42..be01a126edf 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,11 @@ endif ifeq (${MODEL}, n0110) apps_list = ${EPSILON_APPS} else - apps_list = $(foreach i, ${EPSILON_APPS}, $(if $(filter external, $(i)),,$(i))) + ifeq (${MODEL}, bootloader) + apps_list = ${EPSILON_APPS} + else + apps_list = $(foreach i, ${EPSILON_APPS}, $(if $(filter external, $(i)),,$(i))) + endif endif ifdef FORCE_EXTERNAL @@ -137,6 +141,7 @@ include poincare/Makefile include python/Makefile include escher/Makefile # Executable Makefiles +include bootloader/Makefile include apps/Makefile include build/struct_layout/Makefile include build/scenario/Makefile diff --git a/apps/code/app.h b/apps/code/app.h index 68692a1350b..4e2ffc9aef0 100644 --- a/apps/code/app.h +++ b/apps/code/app.h @@ -75,7 +75,7 @@ class App : public Shared::InputEventHandlerDelegateApp { VariableBoxController * variableBoxController() { return &m_variableBoxController; } - static constexpr int k_pythonHeapSize = 67000; + static constexpr int k_pythonHeapSize = 70000; private: /* Python delegate: diff --git a/apps/external/app.h b/apps/external/app.h index 8077289f5c2..d1d38487b9f 100644 --- a/apps/external/app.h +++ b/apps/external/app.h @@ -29,7 +29,7 @@ class App : public ::App { MainController m_mainController; StackViewController m_stackViewController; Window * m_window; - static constexpr int k_externalHeapSize = 100000; + static constexpr int k_externalHeapSize = 99000; char m_externalHeap[k_externalHeapSize]; }; diff --git a/apps/settings/sub_menu/about_controller.cpp b/apps/settings/sub_menu/about_controller.cpp index 812862ab9ba..a95879f52ec 100644 --- a/apps/settings/sub_menu/about_controller.cpp +++ b/apps/settings/sub_menu/about_controller.cpp @@ -66,20 +66,20 @@ bool AboutController::handleEvent(Ion::Events::Event event) { } if (childLabel == I18n::Message::UpsilonVersion) { MessageTableCellWithBuffer * myCell = (MessageTableCellWithBuffer *)m_selectableTableView.selectedCell(); - if (strcmp(myCell->accessoryText(), Ion::UpsilonVersion()) == 0) { + if (strcmp(myCell->accessoryText(), Ion::upsilonVersion()) == 0) { myCell->setAccessoryText(MP_STRINGIFY(OMEGA_STATE)); //Change for public/dev return true; } - myCell->setAccessoryText(Ion::UpsilonVersion()); + myCell->setAccessoryText(Ion::upsilonVersion()); return true; } if (childLabel == I18n::Message::OmegaVersion) { MessageTableCellWithBuffer * myCell = (MessageTableCellWithBuffer *)m_selectableTableView.selectedCell(); - if (strcmp(myCell->accessoryText(), Ion::OmegaVersion()) == 0) { + if (strcmp(myCell->accessoryText(), Ion::omegaVersion()) == 0) { myCell->setAccessoryText(MP_STRINGIFY(OMEGA_STATE)); //Change for public/dev return true; } - myCell->setAccessoryText(Ion::OmegaVersion()); + myCell->setAccessoryText(Ion::omegaVersion()); return true; } if (childLabel == I18n::Message::MemUse) { @@ -206,8 +206,8 @@ void AboutController::willDisplayCellForIndex(HighlightCell * cell, int index) { static const char * messages[] = { (const char*) Ion::username(), - Ion::UpsilonVersion(), - Ion::OmegaVersion(), + Ion::upsilonVersion(), + Ion::omegaVersion(), Ion::softwareVersion(), mpVersion, batteryLevel, diff --git a/bootloader/Makefile b/bootloader/Makefile new file mode 100644 index 00000000000..83f835a36ac --- /dev/null +++ b/bootloader/Makefile @@ -0,0 +1,21 @@ + +bootloader_src += $(addprefix bootloader/,\ + boot.cpp \ + main.cpp \ + kernel_header.cpp \ + userland_header.cpp \ + slot.cpp \ + interface.cpp \ + jump_to_firmware.s \ + trampoline.cpp \ + usb_desc.cpp \ +) + +bootloader_images = $(addprefix bootloader/, \ + cable.png \ + computer.png \ +) + +bootloader_src += $(filter-out ion/src/device/shared/drivers/usb_desc.cpp,$(ion_src)) $(simple_kandinsky_src) $(liba_src) $(libaxx_src) $(bootloader_images) + +$(eval $(call depends_on_image,bootloader/interface.cpp,$(bootloader_images))) diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp new file mode 100644 index 00000000000..fa8d8282e8e --- /dev/null +++ b/bootloader/boot.cpp @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include + +#include + +namespace Bootloader { + +BootMode Boot::mode() { + // We use the exam mode driver as storage for the boot mode + uint8_t mode = Ion::ExamMode::FetchExamMode(); + + if (mode > 3) + return Unknown; + + return (BootMode) mode; +} + +void Boot::setMode(BootMode mode) { + BootMode currentMode = Boot::mode(); + if (currentMode == mode) + return; + + assert(mode != BootMode::Unknown); + int8_t deltaMode = (int8_t)mode - (int8_t)currentMode; + deltaMode = deltaMode < 0 ? deltaMode + 4 : deltaMode; + assert(deltaMode > 0); + Ion::ExamMode::IncrementExamMode(deltaMode); +} + +__attribute__((noreturn)) void Boot::boot() { + assert(mode() != BootMode::Unknown); + + if (!Slot::A().kernelHeader()->isValid() && !Slot::B().kernelHeader()->isValid()) { + // Bootloader if both invalid + bootloader(); + } else if (!Slot::A().kernelHeader()->isValid()) { + // If slot A is invalid and B valid, boot B + setMode(BootMode::SlotB); + Slot::B().boot(); + } else if (!Slot::B().kernelHeader()->isValid()) { + // If slot B is invalid and A valid, boot A + setMode(BootMode::SlotA); + Slot::A().boot(); + } else { + // Both valid, boot the selected one + if (mode() == BootMode::SlotA) { + Slot::A().boot(); + } else if (mode() == BootMode::SlotB) { + Slot::B().boot(); + } + } + + // Achivement unlocked: How Did We Get Here? + bootloader(); +} + +__attribute__ ((noreturn)) void Boot::bootloader() { + for(;;) { + // Draw the interfaces and infos + Bootloader::Interface::draw(); + + // Enable USB + Ion::USB::enable(); + + // Wait for the device to be enumerated + do { + // If we pressed back while waiting, reset. + uint64_t scan = Ion::Keyboard::scan(); + if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { + Ion::Device::Reset::core(); + } + } while (!Ion::USB::isEnumerated()); + + // Launch the DFU stack, allowing to press Back to quit and reset + Ion::USB::DFU(true); + } +} + +} diff --git a/bootloader/boot.h b/bootloader/boot.h new file mode 100644 index 00000000000..bb5df17c1b1 --- /dev/null +++ b/bootloader/boot.h @@ -0,0 +1,28 @@ +#ifndef BOOTLOADER_BOOT_H +#define BOOTLOADER_BOOT_H + +#include + +namespace Bootloader { + +enum BootMode: uint8_t { + SlotA = 0, + SlotB = 1, + // These modes exists so that you can launch the bootloader from a running slot + // They mean "Launch bootloader then go back to slot X" + SlotABootloader = 2, + SlotBBootloader = 3, + Unknown +}; + +class Boot { +public: + static BootMode mode(); + static void setMode(BootMode mode); + __attribute__ ((noreturn)) static void boot(); + __attribute__ ((noreturn)) static void bootloader(); +}; + +} + +#endif \ No newline at end of file diff --git a/bootloader/interface.cpp b/bootloader/interface.cpp new file mode 100644 index 00000000000..e9e87e50459 --- /dev/null +++ b/bootloader/interface.cpp @@ -0,0 +1,83 @@ + +#include +#include + +#include +#include +#include + +#include "computer.h" +#include "cable.h" + +namespace Bootloader { + +void Interface::drawImage(KDContext* ctx, const Image* image, int offset) { + const uint8_t* data; + size_t size; + size_t pixelBufferSize; + + if (image != nullptr) { + data = image->compressedPixelData(); + size = image->compressedPixelDataSize(); + pixelBufferSize = image->width() * image->height(); + } else { + return; + } + + KDColor pixelBuffer[4000]; + assert(pixelBufferSize <= 4000); + assert(Ion::stackSafe()); // That's a VERY big buffer we're allocating on the stack + + Ion::decompress( + data, + reinterpret_cast(pixelBuffer), + size, + pixelBufferSize * sizeof(KDColor) + ); + + KDRect bounds((320 - image->width()) / 2, offset, image->width(), image->height()); + + ctx->fillRectWithPixels(bounds, pixelBuffer, nullptr); +} + +void Interface::draw() { + KDContext * ctx = KDIonContext::sharedContext(); + ctx->fillRect(KDRect(0,0,320,240), KDColorBlack); + drawImage(ctx, ImageStore::Computer, 70); + drawImage(ctx, ImageStore::Cable, 172); + + ctx->drawString("Slot A:", KDPoint(0, 0), KDFont::SmallFont, KDColorWhite, KDColorBlack); + ctx->drawString("Slot B:", KDPoint(0, 13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + ctx->drawString("Current:", KDPoint(0, 26), KDFont::SmallFont, KDColorWhite, KDColorBlack); + + if (Boot::mode() == BootMode::SlotA) { + ctx->drawString("Slot A", KDPoint(63, 26), KDFont::SmallFont, KDColorWhite, KDColorBlack); + } else if (Boot::mode() == BootMode::SlotB) { + ctx->drawString("Slot B", KDPoint(63, 26), KDFont::SmallFont, KDColorWhite, KDColorBlack); + } + + Slot slots[2] = {Slot::A(), Slot::B()}; + + for(uint8_t i = 0; i < 2; i++) { + Slot slot = slots[i]; + + if (slot.kernelHeader()->isValid() && slot.userlandHeader()->isValid()) { + if (slot.userlandHeader()->isOmega() && slot.userlandHeader()->isUpsilon()) { + ctx->drawString("Upsilon", KDPoint(56, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + ctx->drawString(slot.userlandHeader()->upsilonVersion(), KDPoint(112, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + } else if (slot.userlandHeader()->isOmega()) { + ctx->drawString("Omega", KDPoint(56, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + ctx->drawString(slot.userlandHeader()->omegaVersion(), KDPoint(112, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + } else { + ctx->drawString("Epsilon", KDPoint(56, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + ctx->drawString(slot.userlandHeader()->version(), KDPoint(112, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + } + ctx->drawString(slot.kernelHeader()->patchLevel(), KDPoint(168, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + } else { + ctx->drawString("Invalid", KDPoint(56, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + } + } + +} + +} diff --git a/bootloader/interface.h b/bootloader/interface.h new file mode 100644 index 00000000000..c163ddc4c02 --- /dev/null +++ b/bootloader/interface.h @@ -0,0 +1,22 @@ +#ifndef BOOTLOADER_INTERFACE +#define BOOTLOADER_INTERFACE + +#include +#include +#include + +namespace Bootloader { + +class Interface { + +private: + static void drawImage(KDContext* ctx, const Image* image, int offset); + +public: + static void draw(); + +}; + +} + +#endif \ No newline at end of file diff --git a/bootloader/jump_to_firmware.s b/bootloader/jump_to_firmware.s new file mode 100644 index 00000000000..25a52171341 --- /dev/null +++ b/bootloader/jump_to_firmware.s @@ -0,0 +1,9 @@ + +.syntax unified +.section .text.jump_to_firmware +.align 2 +.thumb +.global jump_to_firmware +jump_to_firmware: + msr msp, r0 + bx r1 diff --git a/bootloader/kernel_header.cpp b/bootloader/kernel_header.cpp new file mode 100644 index 00000000000..7dac97a06b5 --- /dev/null +++ b/bootloader/kernel_header.cpp @@ -0,0 +1,25 @@ +#include + +namespace Bootloader { + +const char * KernelHeader::version() const { + return m_version; +} + +const char * KernelHeader::patchLevel() const { + return m_patchLevel; +} + +const bool KernelHeader::isValid() const { + return m_header == Magic && m_footer == Magic; +} + +const uint32_t* KernelHeader::stackPointer() const { + return m_stackPointer; +} + +const void(*KernelHeader::startPointer() const)() { + return m_startPointer; +} + +} diff --git a/bootloader/kernel_header.h b/bootloader/kernel_header.h new file mode 100644 index 00000000000..017036b62a8 --- /dev/null +++ b/bootloader/kernel_header.h @@ -0,0 +1,32 @@ +#ifndef BOOTLOADER_KERNEL_HEADER_H +#define BOOTLOADER_KERNEL_HEADER_H + +#include + +namespace Bootloader { + +class KernelHeader { +public: + const char * version() const; + const char * patchLevel() const; + const bool isValid() const; + + const uint32_t* stackPointer() const; + const void(*startPointer() const)(); + +private: + KernelHeader(); + constexpr static uint32_t Magic = 0xDEC00DF0; + const uint32_t m_unknown; + const uint32_t m_signature; + const uint32_t m_header; + const char m_version[8]; + const char m_patchLevel[8]; + const uint32_t m_footer; + const uint32_t* m_stackPointer; + const void(*m_startPointer)(); +}; + +} + +#endif diff --git a/bootloader/main.cpp b/bootloader/main.cpp new file mode 100644 index 00000000000..70c63213698 --- /dev/null +++ b/bootloader/main.cpp @@ -0,0 +1,42 @@ + +#include +#include + +#include + +__attribute__ ((noreturn)) void ion_main(int argc, const char * const argv[]) { + // Clear the screen + Ion::Display::pushRectUniform(KDRect(0,0,320,240), KDColorBlack); + // Initialize the backlight + Ion::Backlight::init(); + + // Set the mode to slot A if undefined + if (Bootloader::Boot::mode() == Bootloader::BootMode::Unknown) { + Bootloader::Boot::setMode(Bootloader::BootMode::SlotA); + } + + // Handle rebooting to bootloader + if (Bootloader::Boot::mode() == Bootloader::BootMode::SlotABootloader) { + Bootloader::Boot::setMode(Bootloader::BootMode::SlotA); + Bootloader::Boot::bootloader(); + } else if (Bootloader::Boot::mode() == Bootloader::BootMode::SlotBBootloader) { + Bootloader::Boot::setMode(Bootloader::BootMode::SlotB); + Bootloader::Boot::bootloader(); + } + + uint64_t scan = Ion::Keyboard::scan(); + + // Reset+4 => Launch bootloader + if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Four)) { + Bootloader::Boot::bootloader(); + // Reset+1 => Launch slot A + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::One)) { + Bootloader::Boot::setMode(Bootloader::BootMode::SlotA); + // Reset+2 => Launch slot B + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Two)) { + Bootloader::Boot::setMode(Bootloader::BootMode::SlotB); + } + + // Boot the firmware + Bootloader::Boot::boot(); +} diff --git a/bootloader/slot.cpp b/bootloader/slot.cpp new file mode 100644 index 00000000000..b23655aedef --- /dev/null +++ b/bootloader/slot.cpp @@ -0,0 +1,33 @@ +#include +#include + +extern "C" void jump_to_firmware(const uint32_t* stackPtr, const void(*startPtr)(void)); + +namespace Bootloader { + +const Slot Slot::A() { + return Slot(0x90000000); +} + +const Slot Slot::B() { + return Slot(0x90400000); +} + +const KernelHeader* Slot::kernelHeader() const { + return m_kernelHeader; +} + +const UserlandHeader* Slot::userlandHeader() const { + return m_userlandHeader; +} + +[[ noreturn ]] void Slot::boot() const { + // Configure the MPU for the booted firmware + Ion::Device::Board::bootloaderMPU(); + + // Jump + jump_to_firmware(kernelHeader()->stackPointer(), kernelHeader()->startPointer()); + for(;;); +} + +} diff --git a/bootloader/slot.h b/bootloader/slot.h new file mode 100644 index 00000000000..15a883f3948 --- /dev/null +++ b/bootloader/slot.h @@ -0,0 +1,33 @@ +#ifndef BOOTLOADER_SLOT_H +#define BOOTLOADER_SLOT_H + +#include + +#include "kernel_header.h" +#include "userland_header.h" + +namespace Bootloader { + +class Slot { + +public: + Slot(uint32_t address) : + m_kernelHeader(reinterpret_cast(address)), + m_userlandHeader(reinterpret_cast(address + 64 * 1024)) { } + + const KernelHeader* kernelHeader() const; + const UserlandHeader* userlandHeader() const; + [[ noreturn ]] void boot() const; + + static const Slot A(); + static const Slot B(); + +private: + const KernelHeader* m_kernelHeader; + const UserlandHeader* m_userlandHeader; + +}; + +} + +#endif \ No newline at end of file diff --git a/bootloader/trampoline.cpp b/bootloader/trampoline.cpp new file mode 100644 index 00000000000..4d1013f3491 --- /dev/null +++ b/bootloader/trampoline.cpp @@ -0,0 +1,42 @@ +#include + +#include +#include + +#include +#include + +namespace Bootloader { + +void __attribute__((noinline)) suspend() { + Ion::Device::Power::internalFlashSuspend(true); +} + +void* Trampolines[TRAMPOLINES_COUNT] + __attribute__((section(".trampolines_table"))) + __attribute__((used)) + = { + (void*) Bootloader::suspend, // Suspend + (void*) Ion::Device::ExternalFlash::EraseSector, // External erase + (void*) Ion::Device::ExternalFlash::WriteMemory, // External write + (void*) memcmp, + (void*) memcpy, + (void*) memmove, + (void*) memset, + (void*) strchr, + (void*) strcmp, + (void*) strlcat, + (void*) strlcpy, + (void*) strlen, + (void*) strncmp +}; + +void* CustomTrampolines[CUSTOM_TRAMPOLINES_COUNT] + __attribute__((section(".custom_trampolines_table"))) + __attribute__((used)) + = { + (void*) Bootloader::Boot::mode, + (void*) Bootloader::Boot::setMode +}; + +} diff --git a/bootloader/trampoline.h b/bootloader/trampoline.h new file mode 100644 index 00000000000..0bff7a6d284 --- /dev/null +++ b/bootloader/trampoline.h @@ -0,0 +1,14 @@ +#ifndef BOOTLOADER_TRAMPOLINE_H +#define BOOTLOADER_TRAMPOLINE_H + +namespace Bootloader { + +#define TRAMPOLINES_COUNT 13 +extern void* Trampolines[TRAMPOLINES_COUNT]; + +#define CUSTOM_TRAMPOLINES_COUNT 2 +extern void* CustomTrampolines[CUSTOM_TRAMPOLINES_COUNT]; + +} + +#endif \ No newline at end of file diff --git a/bootloader/usb_desc.cpp b/bootloader/usb_desc.cpp new file mode 100644 index 00000000000..be49306000b --- /dev/null +++ b/bootloader/usb_desc.cpp @@ -0,0 +1,12 @@ + +namespace Ion { +namespace Device { +namespace USB { + +const char* stringDescriptor() { + return "@Flash/0x90000000/08*004Kg,01*032Kg,63*064Kg,64*064Kg"; +} + +} +} +} diff --git a/bootloader/userland_header.cpp b/bootloader/userland_header.cpp new file mode 100644 index 00000000000..349d7708a83 --- /dev/null +++ b/bootloader/userland_header.cpp @@ -0,0 +1,33 @@ +#include + +namespace Bootloader { + +const UserlandHeader* s_UserlandHeaderA = reinterpret_cast(0x90010000); +const UserlandHeader* s_UserlandHeaderB = reinterpret_cast(0x90410000); + +const char * UserlandHeader::version() const { + return m_expectedEpsilonVersion; +} + +const bool UserlandHeader::isValid() const { + return m_header == Magic && m_footer == Magic; +} + +const bool UserlandHeader::isOmega() const { + return m_omegaMagicHeader == OmegaMagic && m_omegaMagicFooter == OmegaMagic; +} + + +const char * UserlandHeader::omegaVersion() const { + return m_omegaVersion; +} + +const bool UserlandHeader::isUpsilon() const { + return m_upsilonMagicHeader == UpsilonMagic && m_upsilonMagicFooter == UpsilonMagic; +} + +const char * UserlandHeader::upsilonVersion() const { + return m_UpsilonVersion; +} + +} diff --git a/bootloader/userland_header.h b/bootloader/userland_header.h new file mode 100644 index 00000000000..dbcfb453934 --- /dev/null +++ b/bootloader/userland_header.h @@ -0,0 +1,50 @@ +#ifndef BOOTLOADER_USERLAND_HEADER_H +#define BOOTLOADER_USERLAND_HEADER_H + +#include +#include +#include + +namespace Bootloader { + +class UserlandHeader { +public: + const char * version() const; + const bool isValid() const; + const bool isOmega() const; + const char * omegaVersion() const; + const bool isUpsilon() const; + const char * upsilonVersion() const; + +private: + UserlandHeader(); + constexpr static uint32_t Magic = 0xDEC0EDFE; + constexpr static uint32_t OmegaMagic = 0xEFBEADDE; + constexpr static uint32_t UpsilonMagic = 0x55707369; + uint32_t m_header; + const char m_expectedEpsilonVersion[8]; + void * m_storageAddressRAM; + size_t m_storageSizeRAM; + /* We store the range addresses of external apps memory because storing the + * size is complicated due to c++11 constexpr. */ + uint32_t m_externalAppsFlashStart; + uint32_t m_externalAppsFlashEnd; + uint32_t m_externalAppsRAMStart; + uint32_t m_externalAppsRAMEnd; + uint32_t m_footer; + uint32_t m_omegaMagicHeader; + const char m_omegaVersion[16]; + const volatile char m_username[16]; + uint32_t m_omegaMagicFooter; + uint32_t m_upsilonMagicHeader; + const char m_UpsilonVersion[16]; + uint32_t m_osType; + uint32_t m_upsilonMagicFooter; +}; + +extern const UserlandHeader* s_userlandHeaderA; +extern const UserlandHeader* s_userlandHeaderB; + +} + +#endif diff --git a/build/config.mak b/build/config.mak index eaa8c1db1e2..53d37a8c1d3 100644 --- a/build/config.mak +++ b/build/config.mak @@ -5,7 +5,7 @@ DEBUG ?= 0 HOME_DISPLAY_EXTERNALS ?= 1 EPSILON_VERSION ?= 15.5.0 -OMEGA_VERSION ?= 1.22.1 +OMEGA_VERSION ?= 2.0.0 UPSILON_VERSION ?= 1.0.0-dev # OMEGA_USERNAME ?= N/A OMEGA_STATE ?= public diff --git a/build/device/secure_ext.py b/build/device/secure_ext.py index e1939a5428f..13c6acc174f 100644 --- a/build/device/secure_ext.py +++ b/build/device/secure_ext.py @@ -11,15 +11,16 @@ print("Error: File not found!") sys.exit(-1) file = open(ext_path, "r+b") - first_packet = bytearray(file.read(2048)) - for b in first_packet: - if b != 255: + file.read(MAGIK_POS) + packet = bytearray(file.read(4)) + for b in packet: + if b != 0: print("Error: Invalid file! (maybe already patched?)") sys.exit(-1) for i in range(4): - first_packet[MAGIK_POS + i] = MAGIK_CODE[i] + packet[i] = MAGIK_CODE[i] - file.seek(0) - file.write(first_packet) + file.seek(MAGIK_POS) + file.write(packet) print("External bin Patched!") \ No newline at end of file diff --git a/build/platform.device.bootloader.mak b/build/platform.device.bootloader.mak new file mode 100644 index 00000000000..256dd180d59 --- /dev/null +++ b/build/platform.device.bootloader.mak @@ -0,0 +1,3 @@ +TOOLCHAIN ?= arm-gcc-m7f +ION_KEYBOARD_LAYOUT = layout_B3 +PCB_LATEST = 343 # PCB version 3.43 diff --git a/build/rules.mk b/build/rules.mk index 050c4b805c1..e6faac45035 100644 --- a/build/rules.mk +++ b/build/rules.mk @@ -32,13 +32,13 @@ $(eval $(call rule_for, \ $(eval $(call rule_for, \ OBJCOPY, %.hex, %.elf, \ - $$(OBJCOPY) -O ihex $$< $$@, \ + $$(OBJCOPY) -R .slot_info -O ihex $$< $$@, \ local \ )) $(eval $(call rule_for, \ OBJCOPY, %.bin, %.elf, \ - $$(OBJCOPY) -O binary $$< $$@, \ + $$(OBJCOPY) -R .slot_info -O binary $$< $$@, \ local \ )) diff --git a/build/targets.device.bootloader.mak b/build/targets.device.bootloader.mak new file mode 100644 index 00000000000..52e509e2c22 --- /dev/null +++ b/build/targets.device.bootloader.mak @@ -0,0 +1,56 @@ + +epsilon_flavors_bootloader = $(foreach floavor,$(epsilon_flavors),$(floavor).A $(floavor).B) + +define rule_for_epsilon_flavor_bootloader +$$(BUILD_DIR)/epsilon.$(1).A.$$(EXE): $$(call flavored_object_for,$$(epsilon_src),$(1)) +$$(BUILD_DIR)/epsilon.$(1).A.$$(EXE): LDSCRIPT = ion/src/device/bootloader/bootloader.A.ld +$$(BUILD_DIR)/epsilon.$(1).B.$$(EXE): $$(call flavored_object_for,$$(epsilon_src),$(1)) +$$(BUILD_DIR)/epsilon.$(1).B.$$(EXE): LDSCRIPT = ion/src/device/bootloader/bootloader.B.ld +$$(BUILD_DIR)/epsilon.$(1).bin: $$(BUILD_DIR)/epsilon.$(1).A.bin $$(BUILD_DIR)/epsilon.$(1).B.bin + @echo "COMBINE $$@" + $(Q) cat $$(BUILD_DIR)/epsilon.$(1).A.bin >> $$(BUILD_DIR)/epsilon.$(1).bin + $(Q) truncate -s 4MiB $$(BUILD_DIR)/epsilon.$(1).bin + $(Q) cat $$(BUILD_DIR)/epsilon.$(1).B.bin >> $$(BUILD_DIR)/epsilon.$(1).bin + $(Q) truncate -s 8MiB $$(BUILD_DIR)/epsilon.$(1).bin +endef + +$(BUILD_DIR)/epsilon.A.$(EXE): $(call flavored_object_for,$(epsilon_src)) +$(BUILD_DIR)/epsilon.A.$(EXE): LDSCRIPT = ion/src/device/bootloader/bootloader.A.ld + +$(BUILD_DIR)/epsilon.B.$(EXE): $(call flavored_object_for,$(epsilon_src)) +$(BUILD_DIR)/epsilon.B.$(EXE): LDSCRIPT = ion/src/device/bootloader/bootloader.B.ld + +$(BUILD_DIR)/epsilon.bin: $(BUILD_DIR)/epsilon.A.bin $(BUILD_DIR)/epsilon.B.bin + @echo "COMBINE $@" + $(Q) cat $(BUILD_DIR)/epsilon.A.bin >> $(BUILD_DIR)/epsilon.bin + $(Q) truncate -s 4MiB $(BUILD_DIR)/epsilon.bin + $(Q) cat $(BUILD_DIR)/epsilon.B.bin >> $(BUILD_DIR)/epsilon.bin + $(Q) truncate -s 8MiB $(BUILD_DIR)/epsilon.bin + +$(foreach flavor,$(epsilon_flavors),$(eval $(call rule_for_epsilon_flavor_bootloader,$(flavor)))) + + +HANDY_TARGETS = $(foreach flavor,$(epsilon_flavors_bootloader),epsilon.$(flavor)) +HANDY_TARGETS += epsilon.A epsilon.B + +.PHONY: epsilon +epsilon: $(BUILD_DIR)/epsilon.onboarding.bin +.DEFAULT_GOAL := epsilon + +.PHONY: %_flash +%_flash: $(BUILD_DIR)/%.dfu + @echo "DFU $@" + @echo "INFO About to flash your device. Please plug your device to your computer" + @echo " using an USB cable and press at the same time the 6 key and the RESET" + @echo " button on the back of your device." + $(Q) until $(PYTHON) build/device/dfu.py -l | grep -E "0483:a291|0483:df11" > /dev/null 2>&1; do sleep 2;done + $(Q) $(PYTHON) build/device/dfu.py -u $(word 1,$^) + +.PHONY: binpack +binpack: $(BUILD_DIR)/epsilon.onboarding.bin + rm -rf $(BUILD_DIR)/binpack + mkdir -p $(BUILD_DIR)/binpack + cp $(BUILD_DIR)/epsilon.onboarding.bin $(BUILD_DIR)/binpack + cd $(BUILD_DIR) && for binary in epsilon.onboarding.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done + cd $(BUILD_DIR) && tar cvfz binpack-$(MODEL)-`git rev-parse HEAD | head -c 7`.tgz binpack/* + $(PYTHON) build/device/secure_ext.py $(BUILD_DIR)/epsilon.onboarding.bin diff --git a/build/targets.device.mak b/build/targets.device.mak index 97293a6cdbe..f11b11f86b5 100644 --- a/build/targets.device.mak +++ b/build/targets.device.mak @@ -54,22 +54,3 @@ $(BUILD_DIR)/bench.ram.$(EXE): LDFLAGS += -Lion/src/$(PLATFORM)/bench $(BUILD_DIR)/bench.ram.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/shared/ram.ld $(BUILD_DIR)/bench.flash.$(EXE): $(call flavored_object_for,$(bench_src),consoleuart usbxip) $(BUILD_DIR)/bench.flash.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/$(MODEL)/internal_flash.ld - -.PHONY: %.two_binaries -%.two_binaries: %.elf - @echo "Building an internal and an external binary for $<" - $(Q) $(OBJCOPY) -O binary -j .text.external -j .rodata.external -j .exam_mode_buffer $< $(basename $<).external.bin - $(Q) $(OBJCOPY) -O binary -R .text.external -R .rodata.external -R .exam_mode_buffer $< $(basename $<).internal.bin - @echo "Padding $(basename $<).external.bin and $(basename $<).internal.bin" - $(Q) printf "\xFF\xFF\xFF\xFF" >> $(basename $<).external.bin - $(Q) printf "\xFF\xFF\xFF\xFF" >> $(basename $<).internal.bin - -.PHONY: binpack -binpack: $(BUILD_DIR)/flasher.light.bin $(BUILD_DIR)/epsilon.onboarding.two_binaries - rm -rf $(BUILD_DIR)/binpack - mkdir -p $(BUILD_DIR)/binpack - cp $(BUILD_DIR)/flasher.light.bin $(BUILD_DIR)/binpack - cp $(BUILD_DIR)/epsilon.onboarding.internal.bin $(BUILD_DIR)/epsilon.onboarding.external.bin $(BUILD_DIR)/binpack - cd $(BUILD_DIR) && for binary in flasher.light.bin epsilon.onboarding.internal.bin epsilon.onboarding.external.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done - cd $(BUILD_DIR) && tar cvfz binpack-$(MODEL)-`git rev-parse HEAD | head -c 7`.tgz binpack/* - $(PYTHON) build/device/secure_ext.py $(BUILD_DIR)/epsilon.onboarding.external.bin diff --git a/build/targets.device.n0100.mak b/build/targets.device.n0100.mak index aff59a280fc..ae70afbdbf2 100644 --- a/build/targets.device.n0100.mak +++ b/build/targets.device.n0100.mak @@ -5,3 +5,18 @@ @echo " using an USB cable and press the RESET button the back of your device." $(Q) until $(PYTHON) build/device/dfu.py -l | grep -E "0483:a291|0483:df11" > /dev/null 2>&1; do sleep 2;done $(Q) $(PYTHON) build/device/dfu.py -m -u $< + +.PHONY: %.two_binaries +%.two_binaries: %.elf + @echo "Building an internal binary for $<" + $(Q) $(OBJCOPY) -O binary -R .text.external -R .rodata.external -R .exam_mode_buffer $< $(basename $<).internal.bin + @echo "Padding $(basename $<).internal.bin" + $(Q) printf "\xFF\xFF\xFF\xFF" >> $(basename $<).internal.bin + +.PHONY: binpack +binpack: $(BUILD_DIR)/epsilon.onboarding.two_binaries + rm -rf $(BUILD_DIR)/binpack + mkdir -p $(BUILD_DIR)/binpack + cp $(BUILD_DIR)/epsilon.onboarding.internal.bin $(BUILD_DIR)/binpack + cd $(BUILD_DIR) && for binary in epsilon.onboarding.internal.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done + cd $(BUILD_DIR) && tar cvfz binpack-$(MODEL)-`git rev-parse HEAD | head -c 7`.tgz binpack/* diff --git a/build/targets.device.n0110.mak b/build/targets.device.n0110.mak index c71f80aaeb2..570ae0b31e5 100644 --- a/build/targets.device.n0110.mak +++ b/build/targets.device.n0110.mak @@ -1,21 +1,39 @@ -HANDY_TARGETS += test.external_flash.write test.external_flash.read +HANDY_TARGETS += test.external_flash.write test.external_flash.read bootloader $(BUILD_DIR)/test.external_flash.%.$(EXE): LDSCRIPT = ion/test/device/n0110/external_flash_tests.ld test_external_flash_src = $(ion_src) $(liba_src) $(libaxx_src) $(default_kandinsky_src) $(poincare_src) $(ion_device_dfu_relegated_src) $(runner_src) $(BUILD_DIR)/test.external_flash.read.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_external_flash_read_symbols.o $(call object_for,$(test_external_flash_src) $(test_ion_external_flash_read_src)) $(BUILD_DIR)/test.external_flash.write.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_external_flash_write_symbols.o $(call object_for,$(test_external_flash_src) $(test_ion_external_flash_write_src)) +.PHONY: bootloader +bootloader: $(BUILD_DIR)/bootloader.bin +$(BUILD_DIR)/bootloader.$(EXE): $(call flavored_object_for,$(bootloader_src),usbxip) +$(BUILD_DIR)/bootloader.$(EXE): LDSCRIPT = ion/src/device/n0110/internal_flash.ld + .PHONY: %_flash -%_flash: $(BUILD_DIR)/%.dfu $(BUILD_DIR)/flasher.light.dfu +%_flash: $(BUILD_DIR)/%.dfu @echo "DFU $@" @echo "INFO About to flash your device. Please plug your device to your computer" @echo " using an USB cable and press at the same time the 6 key and the RESET" @echo " button on the back of your device." $(Q) until $(PYTHON) build/device/dfu.py -l | grep -E "0483:a291|0483:df11" > /dev/null 2>&1; do sleep 2;done - $(eval DFU_SLAVE := $(shell $(PYTHON) build/device/dfu.py -l | grep -E "0483:a291|0483:df11")) - $(Q) if expr "$(DFU_SLAVE)" : ".*0483:df11.*" > /dev/null; \ - then \ - $(PYTHON) build/device/dfu.py -u $(word 2,$^); \ - sleep 2; \ - fi $(Q) $(PYTHON) build/device/dfu.py -u $(word 1,$^) + +.PHONY: %.two_binaries +%.two_binaries: %.elf + @echo "Building an internal and an external binary for $<" + $(Q) $(OBJCOPY) -O binary -j .text.external -j .rodata.external -j .exam_mode_buffer $< $(basename $<).external.bin + $(Q) $(OBJCOPY) -O binary -R .text.external -R .rodata.external -R .exam_mode_buffer $< $(basename $<).internal.bin + @echo "Padding $(basename $<).external.bin and $(basename $<).internal.bin" + $(Q) printf "\xFF\xFF\xFF\xFF" >> $(basename $<).external.bin + $(Q) printf "\xFF\xFF\xFF\xFF" >> $(basename $<).internal.bin + +.PHONY: binpack +binpack: $(BUILD_DIR)/flasher.light.bin $(BUILD_DIR)/epsilon.onboarding.two_binaries + rm -rf $(BUILD_DIR)/binpack + mkdir -p $(BUILD_DIR)/binpack + cp $(BUILD_DIR)/flasher.light.bin $(BUILD_DIR)/binpack + cp $(BUILD_DIR)/epsilon.onboarding.internal.bin $(BUILD_DIR)/epsilon.onboarding.external.bin $(BUILD_DIR)/binpack + cd $(BUILD_DIR) && for binary in flasher.light.bin epsilon.onboarding.internal.bin epsilon.onboarding.external.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done + cd $(BUILD_DIR) && tar cvfz binpack-$(MODEL)-`git rev-parse HEAD | head -c 7`.tgz binpack/* + $(PYTHON) build/device/secure_ext.py $(BUILD_DIR)/epsilon.onboarding.external.bin diff --git a/ion/Makefile b/ion/Makefile index 1b04baf698f..4182a922492 100644 --- a/ion/Makefile +++ b/ion/Makefile @@ -22,7 +22,7 @@ include ion/src/shared/tools/Makefile # char test[4]= "ab"; is valid and should initialize test to 'a','b',0,0). # Older versions of GCC are not conformant so we resort to an initializer list. initializer_list = $(shell echo $(1) | sed "s/\(.\)/'\1',/g")0 -$(call object_for,ion/src/shared/platform_info.cpp): SFLAGS += -DPATCH_LEVEL="$(call initializer_list,$(PATCH_LEVEL))" -DEPSILON_VERSION="$(call initializer_list,$(EPSILON_VERSION))" -DOMEGA_VERSION="$(call initializer_list,$(OMEGA_VERSION))" -DUPSILON_VERSION="$(call initializer_list,$(UPSILON_VERSION))" -DOMEGA_USERNAME="$(call initializer_list,$(OMEGA_USERNAME))" +$(call object_for,ion/src/simulator/platform_info.cpp ion/src/device/n0100/platform_info.cpp ion/src/device/n0110/platform_info.cpp ion/src/device/bootloader/platform_info.cpp ion/src/simulator/shared/platform_info.cpp): SFLAGS += -DPATCH_LEVEL="$(call initializer_list,$(PATCH_LEVEL))" -DEPSILON_VERSION="$(call initializer_list,$(EPSILON_VERSION))" -DOMEGA_VERSION="$(call initializer_list,$(OMEGA_VERSION))" -DUPSILON_VERSION="$(call initializer_list,$(UPSILON_VERSION))" -DOMEGA_USERNAME="$(call initializer_list,$(OMEGA_USERNAME))" ion_src += $(addprefix ion/src/shared/, \ console_line.cpp \ @@ -31,7 +31,6 @@ ion_src += $(addprefix ion/src/shared/, \ events.cpp \ events_keyboard.cpp \ events_modifier.cpp \ - platform_info.cpp \ rtc.cpp \ stack_position.cpp \ storage.cpp \ diff --git a/ion/include/ion.h b/ion/include/ion.h index 490e6a28b82..e6072db2686 100644 --- a/ion/include/ion.h +++ b/ion/include/ion.h @@ -35,11 +35,12 @@ namespace Ion { const char * serialNumber(); const volatile char * username(); const char * softwareVersion(); -const char * UpsilonVersion(); -const char * OmegaVersion(); +const char * upsilonVersion(); +const char * omegaVersion(); const char * patchLevel(); const char * fccId(); const char * pcbVersion(); +void updateSlotInfo(); // CRC32 : non xor-ed, non reversed, direct, polynomial 4C11DB7 uint32_t crc32Word(const uint32_t * data, size_t length); // Only accepts whole 32bit values diff --git a/ion/include/ion/storage.h b/ion/include/ion/storage.h index f1a091d49bd..fe9f3364a9e 100644 --- a/ion/include/ion/storage.h +++ b/ion/include/ion/storage.h @@ -17,7 +17,7 @@ class Storage { public: typedef uint16_t record_size_t; - constexpr static size_t k_storageSize = 64000; + constexpr static size_t k_storageSize = 61000; static_assert(UINT16_MAX >= k_storageSize, "record_size_t not big enough"); static Storage * sharedStorage(); diff --git a/ion/src/device/Makefile b/ion/src/device/Makefile index 9636904406f..37943dbe213 100644 --- a/ion/src/device/Makefile +++ b/ion/src/device/Makefile @@ -4,13 +4,13 @@ include ion/src/device/bench/Makefile include ion/src/device/flasher/Makefile include ion/src/device/$(MODEL)/Makefile -$(call object_for,ion/src/shared/platform_info.cpp): SFLAGS += -DHEADER_SECTION="__attribute__((section(\".header\")))" +$(call object_for,ion/src/device/n0100/platform_info.cpp ion/src/device/n0110/platform_info.cpp ion/src/device/bootloader/platform_info.cpp): SFLAGS += -DHEADER_SECTION="__attribute__((section(\".header\")))" ifeq ($(EPSILON_TELEMETRY),1) ion_src += ion/src/shared/telemetry_console.cpp endif -ion_src += ion/src/shared/collect_registers.cpp +ion_device_src += ion/src/shared/collect_registers.cpp IN_FACTORY ?= 0 diff --git a/ion/src/device/bootloader/Makefile b/ion/src/device/bootloader/Makefile new file mode 100644 index 00000000000..c5d7208f7ab --- /dev/null +++ b/ion/src/device/bootloader/Makefile @@ -0,0 +1,19 @@ + +ion_device_src += $(addprefix ion/src/device/bootloader/drivers/, \ + board.cpp \ + cache.cpp \ + external_flash_tramp.cpp \ + led.cpp \ + power.cpp \ + reset.cpp \ + trampoline.cpp \ + usb.cpp \ +) + +ion_device_src += $(addprefix ion/src/device/bootloader/boot/, \ + rt0.cpp \ +) + +ion_device_src += $(addprefix ion/src/device/bootloader/, \ + platform_info.cpp \ +) diff --git a/ion/src/device/bootloader/boot/rt0.cpp b/ion/src/device/bootloader/boot/rt0.cpp new file mode 100644 index 00000000000..3e503757290 --- /dev/null +++ b/ion/src/device/bootloader/boot/rt0.cpp @@ -0,0 +1,283 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef void (*cxx_constructor)(); + +extern "C" { + extern char _data_section_start_flash; + extern char _data_section_start_ram; + extern char _data_section_end_ram; + extern char _bss_section_start_ram; + extern char _bss_section_end_ram; + extern cxx_constructor _init_array_start; + extern cxx_constructor _init_array_end; + + extern char _isr_vector_table_start_flash; + extern char _isr_vector_table_start_ram; + extern char _isr_vector_table_end_ram; +} + +/* In order to ensure that this method is execute from the external flash, we + * forbid inlining it.*/ + +static void __attribute__((noinline)) external_flash_start() { + /* Init the peripherals. We do not initialize the backlight in case there is + * an on boarding app: indeed, we don't want the user to see the LCD tests + * happening during the on boarding app. The backlight will be initialized + * after the Power-On Self-Test if there is one or before switching to the + * home app otherwise. */ + Ion::Device::Board::initPeripherals(false); + + return ion_main(0, nullptr); +} + +/* This additional function call 'jump_to_external_flash' serves two purposes: + * - By default, the compiler is free to inline any function call he wants. If + * the compiler decides to inline some functions that make use of VFP + * registers, it will need to push VFP them onto the stack in calling + * function's prologue. + * Problem: in start()'s prologue, we would never had a chance to enable the + * FPU since this function is the first thing called after reset. + * We can safely assume that neither memcpy, memset, nor any Ion::Device::init* + * method will use floating-point numbers, but ion_main very well can. + * To make sure ion_main's potential usage of VFP registers doesn't bubble-up to + * start(), we isolate it in its very own non-inlined function call. + * - To avoid jumping on the external flash when it is shut down, we ensure + * there is no symbol references from the internal flash to the external + * flash except this jump. In order to do that, we isolate this + * jump in a symbol that we link in a special section separated from the + * internal flash section. We can than forbid cross references from the + * internal flash to the external flash. */ + +static void __attribute__((noinline)) jump_to_external_flash() { + external_flash_start(); +} + +void __attribute__((noinline)) abort_init() { + Ion::Device::Board::shutdownPeripherals(true); + Ion::Device::Board::initPeripherals(false); + Ion::Timing::msleep(100); + Ion::Backlight::init(); + Ion::LED::setColor(KDColorRed); + Ion::Backlight::setBrightness(180); +} + +void __attribute__((noinline)) abort_economy() { + int brightness = Ion::Backlight::brightness(); + bool plugged = Ion::USB::isPlugged(); + while (brightness > 0) { + brightness--; + Ion::Backlight::setBrightness(brightness); + Ion::Timing::msleep(50); + if(plugged || (!plugged && Ion::USB::isPlugged())){ + Ion::Backlight::setBrightness(180); + return; + } + } + Ion::Backlight::shutdown(); + while (1) { + Ion::Device::Power::sleepConfiguration(); + Ion::Device::WakeUp::onUSBPlugging(); + Ion::Device::WakeUp::onChargingEvent(); + Ion::Device::Power::internalFlashSuspend(true); + if (!plugged && Ion::USB::isPlugged()) { + break; + } + plugged = Ion::USB::isPlugged(); + }; + Ion::Device::Board::setStandardFrequency(Ion::Device::Board::Frequency::High); + Ion::Backlight::init(); + Ion::Backlight::setBrightness(180); +} + +void __attribute__((noinline)) abort_sleeping() { + if (Ion::Battery::level() != Ion::Battery::Charge::EMPTY) { + return; + } + // we don't use Ion::Power::suspend because we don't want to move the exam buffer into the internal + Ion::Device::Board::shutdownPeripherals(true); + bool plugged = Ion::USB::isPlugged(); + while (1) { + Ion::Device::Battery::initGPIO(); + Ion::Device::USB::initGPIO(); + Ion::Device::LED::init(); + Ion::Device::Power::sleepConfiguration(); + Ion::Device::Board::shutdownPeripherals(true); + Ion::Device::WakeUp::onUSBPlugging(); + Ion::Device::WakeUp::onChargingEvent(); + Ion::Device::Power::internalFlashSuspend(true); + Ion::Device::USB::initGPIO(); + if (!plugged && Ion::USB::isPlugged()) { + break; + } + plugged = Ion::USB::isPlugged(); + } + Ion::Device::Board::setStandardFrequency(Ion::Device::Board::Frequency::High); + abort_init(); +} + +void __attribute__((noinline)) abort_core(const char * text) { + Ion::Timing::msleep(100); + int counting; + while (true) { + counting = 0; + if (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) { + abort_sleeping(); + abort_screen(text); + } + Ion::USB::enable(); + Ion::Battery::Charge previous_state = Ion::Battery::level(); + while (!Ion::USB::isEnumerated()) { + if (Ion::Battery::level() == Ion::Battery::Charge::LOW) { + if (previous_state != Ion::Battery::Charge::LOW) { + previous_state = Ion::Battery::Charge::LOW; + counting = 0; + } + Ion::Timing::msleep(500); + if (counting >= 20) { + abort_sleeping(); + abort_screen(text); + counting = -1; + } + counting++; + } else { + if (previous_state == Ion::Battery::Charge::LOW) { + previous_state = Ion::Battery::level(); + counting = 0; + } + Ion::Timing::msleep(100); + if (counting >= 300) { + abort_economy(); + counting = -1; + } + counting++; + } + } + Ion::USB::DFU(false, false, 0); + } +} + +void __attribute__((noinline)) abort_screen(const char * text){ + KDRect screen = KDRect(0, 0, Ion::Display::Width, Ion::Display::Height); + Ion::Display::pushRectUniform(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height), KDColor::RGB24(0xffffff)); + KDContext* ctx = KDIonContext::sharedContext(); + ctx->setOrigin(KDPointZero); + ctx->setClippingRect(screen); + ctx->drawString("UPSILON CRASH", KDPoint(90, 10), KDFont::LargeFont, KDColorRed, KDColor::RGB24(0xffffff)); + ctx->drawString("An error occurred", KDPoint(10, 30), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("If you have some important data, please", KDPoint(10, 45), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("use bit.ly/upsiBackup to backup them.", KDPoint(10, 60), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("YOU WILL LOSE ALL YOUR DATA", KDPoint(10, 85), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("→ You can try to reboot by presssing the", KDPoint(10, 110), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("reset button at the back of the calculator", KDPoint(10, 125), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("→ If Upsilon keeps crashing, you can connect", KDPoint(10, 140), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("the calculator to a computer or a phone", KDPoint(10, 160), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("and try to reinstall Upsilon", KDPoint(10, 175), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString(text, KDPoint(220, 200), KDFont::SmallFont, KDColorRed, KDColor::RGB24(0xffffff)); +} + +void __attribute__((noinline)) abort() { + abort_init(); + abort_screen("HARDFAULT"); + abort_core("HARDFAULT"); +} + +void __attribute__((noinline)) nmi_abort() { + abort_init(); + abort_screen("NMIFAULT"); + abort_core("NMIFAULT"); +} + +void __attribute__((noinline)) bf_abort() { + abort_init(); + abort_screen("BUSFAULT"); + abort_core("BUSFAULT"); +} + +void __attribute__((noinline)) uf_abort() { + abort_init(); + abort_screen("USAGEFAULT"); + abort_core("USAGEFAULT"); +} + +/* When 'start' is executed, the external flash is supposed to be shutdown. We + * thus forbid inlining to prevent executing this code from external flash + * (just in case 'start' was to be called from the external flash). */ + +void __attribute__((noinline)) start() { + /* This is where execution starts after reset. + * Many things are not initialized yet so the code here has to pay attention. */ + + /* Initialize the FPU as early as possible. + * For example, static C++ objects are very likely to manipulate float values */ + Ion::Device::Board::initFPU(); + + /* Copy data section to RAM + * The data section is R/W but its initialization value matters. It's stored + * in Flash, but linked as if it were in RAM. Now's our opportunity to copy + * it. Note that until then the data section (e.g. global variables) contains + * garbage values and should not be used. */ + size_t dataSectionLength = (&_data_section_end_ram - &_data_section_start_ram); + memcpy(&_data_section_start_ram, &_data_section_start_flash, dataSectionLength); + + /* Zero-out the bss section in RAM + * Until we do, any uninitialized global variable will be unusable. */ + size_t bssSectionLength = (&_bss_section_end_ram - &_bss_section_start_ram); + memset(&_bss_section_start_ram, 0, bssSectionLength); + + /* Call static C++ object constructors + * The C++ compiler creates an initialization function for each static object. + * The linker then stores the address of each of those functions consecutively + * between _init_array_start and _init_array_end. So to initialize all C++ + * static objects we just have to iterate between theses two addresses and + * call the pointed function. */ +#define SUPPORT_CPP_GLOBAL_CONSTRUCTORS 0 +#if SUPPORT_CPP_GLOBAL_CONSTRUCTORS + for (cxx_constructor * c = &_init_array_start; c<&_init_array_end; c++) { + (*c)(); + } +#else + /* In practice, static initialized objects are a terrible idea. Since the init + * order is not specified, most often than not this yields the dreaded static + * init order fiasco. How about bypassing the issue altogether? */ + if (&_init_array_start != &_init_array_end) { + abort(); + } +#endif + + /* Copy isr_vector_table section to RAM + * The isr table must be within the memory mapped by the microcontroller (it + * can't live in the external flash). */ + size_t isrSectionLength = (&_isr_vector_table_end_ram - &_isr_vector_table_start_ram); + memcpy(&_isr_vector_table_start_ram, &_isr_vector_table_start_flash, isrSectionLength); + + Ion::Device::Board::init(); + + /* At this point, we initialized clocks and the external flash but no other + * peripherals. */ + + jump_to_external_flash(); + + abort(); +} + +void __attribute__((interrupt, noinline)) isr_systick() { + auto t = Ion::Device::Timing::MillisElapsed; + t++; + Ion::Device::Timing::MillisElapsed = t; +} diff --git a/ion/src/device/bootloader/bootloader.A.ld b/ion/src/device/bootloader/bootloader.A.ld new file mode 100644 index 00000000000..3e1188476d2 --- /dev/null +++ b/ion/src/device/bootloader/bootloader.A.ld @@ -0,0 +1,28 @@ +/* Linker script + * The role of this script is to take all the object files built by the compiler + * and produce a single binary suitable for execution. + * Without an explicit linker script, the linker will produce a binary file that + * would not match some of our requirements (for example, we want the code to be + * written at a specific address (in Flash ROM) and the data at another. */ + +/* Let's instruct the linker about our memory layout. + * This will let us use shortcuts such as ">FLASH" to ask for a given section to + * be stored in Flash. */ + +MEMORY { + SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K + FLASH (rx) : ORIGIN = 0x90000000, LENGTH = 4M + /* + ITCM (rwx) : ORIGIN = 0x00000000, LENGTH = 16K + DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K + SRAM1 (rwx) : ORIGIN = 0x20010000, LENGTH = 176K + SRAM2 (rwx) : ORIGIN = 0x2003C000, LENGTH = 16K + */ +} + +STACK_SIZE = 32K; +FIRST_FLASH_SECTOR_SIZE = 4K; +SIGNED_PAYLOAD_LENGTH = 8; +USERLAND_OFFSET = 64K; + +INCLUDE ion/src/device/bootloader/bootloader_common.ld; diff --git a/ion/src/device/bootloader/bootloader.B.ld b/ion/src/device/bootloader/bootloader.B.ld new file mode 100644 index 00000000000..f89ebca2f21 --- /dev/null +++ b/ion/src/device/bootloader/bootloader.B.ld @@ -0,0 +1,28 @@ +/* Linker script + * The role of this script is to take all the object files built by the compiler + * and produce a single binary suitable for execution. + * Without an explicit linker script, the linker will produce a binary file that + * would not match some of our requirements (for example, we want the code to be + * written at a specific address (in Flash ROM) and the data at another. */ + +/* Let's instruct the linker about our memory layout. + * This will let us use shortcuts such as ">FLASH" to ask for a given section to + * be stored in Flash. */ + +MEMORY { + SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K + FLASH (rx) : ORIGIN = 0x90400000, LENGTH = 4M + /* + ITCM (rwx) : ORIGIN = 0x00000000, LENGTH = 16K + DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K + SRAM1 (rwx) : ORIGIN = 0x20010000, LENGTH = 176K + SRAM2 (rwx) : ORIGIN = 0x2003C000, LENGTH = 16K + */ +} + +STACK_SIZE = 32K; +FIRST_FLASH_SECTOR_SIZE = 4K; +SIGNED_PAYLOAD_LENGTH = 8; +USERLAND_OFFSET = 64K; + +INCLUDE ion/src/device/bootloader/bootloader_common.ld; diff --git a/ion/src/device/bootloader/bootloader_common.ld b/ion/src/device/bootloader/bootloader_common.ld new file mode 100644 index 00000000000..e8f0c9bbc3f --- /dev/null +++ b/ion/src/device/bootloader/bootloader_common.ld @@ -0,0 +1,128 @@ + +SECTIONS { + .signed_payload_prefix ORIGIN(FLASH) : { + FILL(0xFF); + BYTE(0xFF) + . = ORIGIN(FLASH) + SIGNED_PAYLOAD_LENGTH; + } >FLASH + + .kernel_header : { + KEEP(*(.kernel_header)) + } >FLASH + + .slot_info : { + *(.slot_info*) + } >SRAM + + .isr_vector_table ORIGIN(SRAM) + 512 : AT(ORIGIN(FLASH) + SIZEOF(.signed_payload_prefix) + SIZEOF(.kernel_header)) { + /* When booting, the STM32F412 fetches the content of address 0x0, and + * extracts from it various key infos: the initial value of the PC register + * (program counter), the initial value of the stack pointer, and various + * entry points to interrupt service routines. This data is called the ISR + * vector table. + * + * Note that address 0x0 is always an alias. It points to the beginning of + * Flash, SRAM, or integrated bootloader depending on the boot mode chosen. + * (This mode is chosen by setting the BOOTn pins on the chip). + * + * We're generating the ISR vector table in code because it's very + * convenient: using function pointers, we can easily point to the service + * routine for each interrupt. */ + _isr_vector_table_start_flash = LOADADDR(.isr_vector_table); + _isr_vector_table_start_ram = .; + KEEP(*(.isr_vector_table)) + _isr_vector_table_end_ram = .; + } >SRAM + + .exam_mode_buffer ORIGIN(FLASH) + SIZEOF(.signed_payload_prefix) + SIZEOF(.kernel_header) + SIZEOF(.isr_vector_table) : { + . = ALIGN(4K); + _exam_mode_buffer_start = .; + KEEP(*(.exam_mode_buffer)) + /* Note: We don't increment "." here, we set it. */ + . = . + FIRST_FLASH_SECTOR_SIZE; + _exam_mode_buffer_end = .; + } >FLASH + + /* External flash memory */ + .userland_header : { + . = ORIGIN(FLASH) + USERLAND_OFFSET; + KEEP(*(.userland_header)); + } > FLASH + + .text : { + . = ALIGN(4); + *(.text) + *(.text.*) + } >FLASH + + .rodata : { + *(.rodata) + *(.rodata.*) + } >FLASH + + .init_array : { + . = ALIGN(4); + _init_array_start = .; + KEEP (*(.init_array*)) + _init_array_end = .; + } >FLASH + + .data : { + /* The data section is written to Flash but linked as if it were in RAM. + * + * This is required because its initial value matters (so it has to be in + * persistant memory in the first place), but it is a R/W area of memory + * so it will have to live in RAM upon execution (in linker lingo, that + * translates to the data section having a LMA in Flash and a VMA in RAM). + * + * This means we'll have to copy it from Flash to RAM on initialization. + * To do this, we'll need to know the source location of the data section + * (in Flash), the target location (in RAM), and the size of the section. + * That's why we're defining three symbols that we'll use in the initial- + * -ization routine. */ + . = ALIGN(4); + _data_section_start_flash = LOADADDR(.data); + _data_section_start_ram = .; + *(.data) + *(.data.*) + _data_section_end_ram = .; + } >SRAM AT> FLASH + + .bss : { + /* The bss section contains data for all uninitialized variables + * So like the .data section, it will go in RAM, but unlike the data section + * we don't care at all about an initial value. + * + * Before execution, crt0 will erase that section of memory though, so we'll + * need pointers to the beginning and end of this section. */ + . = ALIGN(4); + _bss_section_start_ram = .; + *(.bss) + *(.bss.*) + /* The compiler may choose to allocate uninitialized global variables as + * COMMON blocks. This can be disabled with -fno-common if needed. */ + *(COMMON) + _bss_section_end_ram = .; + } >SRAM + + .heap : { + _heap_start = .; + /* Note: We don't increment "." here, we set it. */ + . = (ORIGIN(SRAM) + LENGTH(SRAM) - STACK_SIZE); + _heap_end = .; + } >SRAM + + .stack : { + . = ALIGN(8); + _stack_end = .; + . += (STACK_SIZE - 8); + . = ALIGN(8); + _stack_start = .; + } >SRAM + + /DISCARD/ : { + /* exidx and extab are needed for unwinding, which we don't use */ + *(.ARM.exidx*) + *(.ARM.extab*) + } +} diff --git a/ion/src/device/bootloader/drivers/board.cpp b/ion/src/device/bootloader/drivers/board.cpp new file mode 100644 index 00000000000..03e47bc2cdb --- /dev/null +++ b/ion/src/device/bootloader/drivers/board.cpp @@ -0,0 +1,423 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +typedef void(*ISR)(void); +extern ISR InitialisationVector[]; + +// Public Ion methods + +const char * Ion::fccId() { + return "2ALWP-N0110"; +} + +// Private Ion::Device methods + +namespace Ion { +namespace Device { +namespace Board { + +using namespace Regs; + +void initMPU() { + // 1. Disable the MPU + // 1.1 Memory barrier + Cache::dmb(); + + // 1.2 Disable fault exceptions + CORTEX.SHCRS()->setMEMFAULTENA(false); + + // 1.3 Disable the MPU and clear the control register + MPU.CTRL()->setENABLE(false); + + // 2. MPU settings + // 2.1 Configure a MPU region for the FMC memory area + /* This is needed for interfacing with the LCD + * We define the whole FMC memory bank 1 as strongly ordered, non-executable + * and not accessible. We define the FMC command and data addresses as + * writeable non-cachable, non-buffereable and non shareable. */ + int sector = 0; + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x60000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_256MB); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::NoAccess); + MPU.RASR()->setXN(true); + MPU.RASR()->setTEX(2); + MPU.RASR()->setS(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x60000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_32B); + MPU.RASR()->setXN(true); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW); + MPU.RASR()->setTEX(2); + MPU.RASR()->setS(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x60000000+0x20000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_32B); + MPU.RASR()->setXN(true); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW); + MPU.RASR()->setTEX(2); + MPU.RASR()->setS(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + // 2.2 Configure MPU regions for the QUADSPI peripheral + /* L1 Cache can issue speculative reads to any memory address. But, when the + * Quad-SPI is in memory-mapped mode, if an access is made to an address + * outside of the range defined by FSIZE but still within the 256Mbytes range, + * then an AHB error is given (AN4760). To prevent this to happen, we + * configure the MPU to define the whole Quad-SPI addressable space as + * strongly ordered, non-executable and not accessible. Plus, we define the + * Quad-SPI region corresponding to the Expternal Chip as executable and + * fully accessible (AN4861). */ + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x90000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_256MB); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::NoAccess); + MPU.RASR()->setXN(true); + MPU.RASR()->setTEX(0); + MPU.RASR()->setS(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x90000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_8MB); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW); + MPU.RASR()->setXN(false); + MPU.RASR()->setTEX(0); + MPU.RASR()->setS(0); + MPU.RASR()->setC(1); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + /* 2.3 Empty sector + * We have to override the sectors configured by the bootloader. */ + while(sector < 8) { + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0); + MPU.RASR()->setENABLE(0); + } + + /* We assert that all sectors have been initialized. Otherwise, the bootloader + * configuration is still active on the last sectors when their configuration + * should be reset. */ + assert(sector == 8); + + // 2.4 Enable MPU + MPU.CTRL()->setPRIVDEFENA(true); + MPU.CTRL()->setENABLE(true); + + // 3. Data/instruction synchronisation barriers to ensure that the new MPU configuration is used by subsequent instructions. + Cache::dsb(); + Cache::isb(); +} + +void init() { + initMPU(); + initClocks(); + + // Ensure right location of interrupt vectors + CORTEX.VTOR()->setVTOR((void*)&InitialisationVector); + + // Initiate L1 cache after initiating the external flash + Cache::enable(); +} + +void initClocks() { + /* System clock + * Configure the CPU at 192 MHz and USB at 48 MHz. */ + + /* After reset, the device is using the high-speed internal oscillator (HSI) + * as a clock source, which runs at a fixed 16 MHz frequency. The HSI is not + * accurate enough for reliable USB operation, so we need to use the external + * high-speed oscillator (HSE). */ + + // Enable the HSI and wait for it to be ready + RCC.CR()->setHSION(true); + while(!RCC.CR()->getHSIRDY()) { + } + + // Enable the HSE and wait for it to be ready + RCC.CR()->setHSEON(true); + while(!RCC.CR()->getHSERDY()) { + } + + // Enable PWR peripheral clock + RCC.APB1ENR()->setPWREN(true); + + /* To pass electromagnetic compatibility tests, we activate the Spread + * Spectrum clock generation, which adds jitter to the PLL clock in order to + * "lower peak-energy on the central frequency" and its harmonics. + * It must be done before enabling the PLL. */ + class RCC::SSCGR sscgr(0); // Reset value + sscgr.setMODPER(Clocks::Config::SSCG_MODPER); + sscgr.setINCSTEP(Clocks::Config::SSCG_INCSTEP); + sscgr.setSPREADSEL(RCC::SSCGR::SPREADSEL::CenterSpread); + sscgr.setSSCGEN(true); + RCC.SSCGR()->set(sscgr); + + /* Given the crystal used on our device, the HSE will oscillate at 8 MHz. By + * piping it through a phase-locked loop (PLL) we can derive other frequencies + * for use in different parts of the system. */ + + // Configure the PLL ratios and use HSE as a PLL input + RCC.PLLCFGR()->setPLLM(Clocks::Config::PLL_M); + RCC.PLLCFGR()->setPLLN(Clocks::Config::PLL_N); + RCC.PLLCFGR()->setPLLQ(Clocks::Config::PLL_Q); + RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); + + // Enable the PLL and wait for it to be ready + RCC.CR()->setPLLON(true); + + // Enable Over-drive + PWR.CR()->setODEN(true); + while(!PWR.CSR()->getODRDY()) { + } + + PWR.CR()->setODSWEN(true); + while(!PWR.CSR()->getODSWRDY()) { + } + + // Choose Voltage scale 1 + PWR.CR()->setVOS(PWR::CR::Voltage::Scale1); + while (!PWR.CSR()->getVOSRDY()) {} + + /* After reset the Flash runs as fast as the CPU. When we clock the CPU faster + * the flash memory cannot follow and therefore flash memory accesses need to + * wait a little bit. + * The spec tells us that at 2.8V and over 210MHz the flash expects 7 WS. */ + FLASH.ACR()->setLATENCY(7); + + /* Enable prefetching flash instructions */ + /* Fetching instructions increases slightly the power consumption but the + * increase is negligible compared to the screen consumption. */ + FLASH.ACR()->setPRFTEN(true); + + /* Enable the ART */ + FLASH.ACR()->setARTEN(true); + + // 192 MHz is too fast for APB1. Divide it by four to reach 48 MHz + RCC.CFGR()->setPPRE1(Clocks::Config::APB1PrescalerReg); + // 192 MHz is too fast for APB2. Divide it by two to reach 96 MHz + RCC.CFGR()->setPPRE2(Clocks::Config::APB2PrescalerReg); + + while(!RCC.CR()->getPLLRDY()) { + } + + // Use the PLL output as a SYSCLK source + RCC.CFGR()->setSW(RCC::CFGR::SW::PLL); + while (RCC.CFGR()->getSWS() != RCC::CFGR::SW::PLL) { + } + + // Now that we don't need use it anymore, turn the HSI off + RCC.CR()->setHSION(false); + + // Peripheral clocks + + // AHB1 bus + // Our peripherals are using GPIO A, B, C, D and E. + // We're not using the CRC nor DMA engines. + class RCC::AHB1ENR ahb1enr(0x00100000); // Reset value + ahb1enr.setGPIOAEN(true); + ahb1enr.setGPIOBEN(true); + ahb1enr.setGPIOCEN(true); + ahb1enr.setGPIODEN(true); + ahb1enr.setGPIOEEN(true); + ahb1enr.setDMA2EN(true); + RCC.AHB1ENR()->set(ahb1enr); + + // AHB2 bus + RCC.AHB2ENR()->setOTGFSEN(true); + + // AHB3 bus + RCC.AHB3ENR()->setFSMCEN(true); + + // APB1 bus + // We're using TIM3 for the LEDs + RCC.APB1ENR()->setTIM3EN(true); + RCC.APB1ENR()->setPWREN(true); + RCC.APB1ENR()->setRTCAPB(true); + + // APB2 bus + class RCC::APB2ENR apb2enr(0); // Reset value + apb2enr.setADC1EN(true); + apb2enr.setSYSCFGEN(true); + apb2enr.setUSART6EN(true); // TODO required if building bench target only? + RCC.APB2ENR()->set(apb2enr); + + // Configure clocks in sleep mode + // AHB1 peripheral clock enable in low-power mode register + class RCC::AHB1LPENR ahb1lpenr(0x7EF7B7FF); // Reset value + ahb1lpenr.setGPIOALPEN(true); // Enable IO port A for Charging/USB plug/Keyboard pins + ahb1lpenr.setGPIOBLPEN(true); // Enable IO port B for LED pins + ahb1lpenr.setGPIOCLPEN(true); // Enable IO port C for LED/Keyboard pins + ahb1lpenr.setGPIODLPEN(false); // Disable IO port D (LCD...) + ahb1lpenr.setGPIOELPEN(true); // Enable IO port E for Keyboard/Battery pins + ahb1lpenr.setGPIOFLPEN(false); // Disable IO port F + ahb1lpenr.setGPIOGLPEN(false); // Disable IO port G + ahb1lpenr.setGPIOHLPEN(false); // Disable IO port H + ahb1lpenr.setGPIOILPEN(false); // Disable IO port I + ahb1lpenr.setCRCLPEN(false); + ahb1lpenr.setFLITFLPEN(false); + ahb1lpenr.setSRAM1LPEN(false); + ahb1lpenr.setDMA1LPEN(false); + ahb1lpenr.setDMA2LPEN(false); + ahb1lpenr.setAXILPEN(false); + ahb1lpenr.setSRAM2LPEN(false); + ahb1lpenr.setBKPSRAMLPEN(false); + ahb1lpenr.setDTCMLPEN(false); + ahb1lpenr.setOTGHSLPEN(false); + ahb1lpenr.setOTGHSULPILPEN(false); + RCC.AHB1LPENR()->set(ahb1lpenr); + + // AHB2 peripheral clock enable in low-power mode register + class RCC::AHB2LPENR ahb2lpenr(0x000000F1); // Reset value + ahb2lpenr.setOTGFSLPEN(false); + ahb2lpenr.setRNGLPEN(false); + ahb2lpenr.setAESLPEN(false); + RCC.AHB2LPENR()->set(ahb2lpenr); + + // AHB3 peripheral clock enable in low-power mode register + class RCC::AHB3LPENR ahb3lpenr(0x00000003); // Reset value + ahb3lpenr.setFMCLPEN(false); + ahb3lpenr.setQSPILPEN(false); + RCC.AHB3LPENR()->set(ahb3lpenr); + + // APB1 peripheral clock enable in low-power mode register + class RCC::APB1LPENR apb1lpenr(0xFFFFCBFF); // Reset value + apb1lpenr.setTIM2LPEN(false); + apb1lpenr.setTIM3LPEN(true); // Enable TIM3 in sleep mode for LEDs + apb1lpenr.setTIM4LPEN(false); + apb1lpenr.setTIM5LPEN(false); + apb1lpenr.setTIM6LPEN(false); + apb1lpenr.setTIM7LPEN(false); + apb1lpenr.setTIM12LPEN(false); + apb1lpenr.setTIM13LPEN(false); + apb1lpenr.setTIM14LPEN(false); + apb1lpenr.setRTCAPBLPEN(false); + apb1lpenr.setWWDGLPEN(false); + apb1lpenr.setSPI2LPEN(false); + apb1lpenr.setSPI3LPEN(false); + apb1lpenr.setUSART2LPEN(false); + apb1lpenr.setUSART3LPEN(false); + apb1lpenr.setI2C1LPEN(false); + apb1lpenr.setI2C2LPEN(false); + apb1lpenr.setI2C3LPEN(false); + apb1lpenr.setCAN1LPEN(false); + apb1lpenr.setPWRLPEN(false); + apb1lpenr.setLPTIM1LPEN(false); + apb1lpenr.setUSART4LPEN(false); + apb1lpenr.setUSART5LPEN(false); + apb1lpenr.setOTGHSLPEN(false); + apb1lpenr.setOTGHSULPILPEN(false); + RCC.APB1LPENR()->set(apb1lpenr); + + // APB2 peripheral clock enable in low-power mode register + class RCC::APB2LPENR apb2lpenr(0x04F77F33); // Reset value + apb2lpenr.setTIM1LPEN(false); + apb2lpenr.setTIM8LPEN(false); + apb2lpenr.setUSART1LPEN(false); + apb2lpenr.setUSART6LPEN(false); + apb2lpenr.setADC1LPEN(false); + apb2lpenr.setSPI1LPEN(false); + apb2lpenr.setSPI4LPEN(false); + apb2lpenr.setSYSCFGLPEN(false); + apb2lpenr.setTIM9LPEN(false); + apb2lpenr.setTIM10LPEN(false); + apb2lpenr.setTIM11LPEN(false); + apb2lpenr.setSPI5LPEN(false); + apb2lpenr.setSDMMC2LPEN(false); + apb2lpenr.setADC2LPEN(false); + apb2lpenr.setADC3LPEN(false); + apb2lpenr.setSAI1LPEN(false); + apb2lpenr.setSAI2LPEN(false); + RCC.APB2LPENR()->set(apb2lpenr); +} + +void shutdownClocks(bool keepLEDAwake) { + // APB2 bus + RCC.APB2ENR()->set(0); // Reset value + + // AHB2 bus + RCC.AHB2ENR()->set(0); // Reset value + + // AHB3 bus + class RCC::AHB3ENR ahb3enr(0); // Reset value + // Required by external flash + ahb3enr.setQSPIEN(true); + RCC.AHB3ENR()->set(ahb3enr); // Reset value + + // APB1 + class RCC::APB1ENR apb1enr(0); // Reset value + // AHB1 bus + class RCC::AHB1ENR ahb1enr(0x00100000); // Reset value + // GPIO B, C, D, E are used the by external flash + ahb1enr.setGPIOBEN(true); + ahb1enr.setGPIOCEN(true); + ahb1enr.setGPIODEN(true); + ahb1enr.setGPIOEEN(true); + if (keepLEDAwake) { + apb1enr.setTIM3EN(true); + ahb1enr.setGPIOBEN(true); + } + RCC.APB1ENR()->set(apb1enr); + RCC.AHB1ENR()->set(ahb1enr); +} + +constexpr int k_pcbVersionOTPIndex = 0; + +/* As we want the PCB versions to be in ascending order chronologically, and + * because the OTP are initialized with 1s, we store the bitwise-not of the + * version number. This way, devices with blank OTP are considered version 0. */ + +PCBVersion pcbVersion() { +#if IN_FACTORY + /* When flashing for the first time, we want all systems that depend on the + * PCB version to function correctly before flashing the PCB version. This + * way, flashing the PCB version can be done last. */ + return PCB_LATEST; +#else + PCBVersion version = readPCBVersionInMemory(); + return (version == k_alternateBlankVersion ? 0 : version); +#endif +} + +PCBVersion readPCBVersionInMemory() { + return ~(*reinterpret_cast(InternalFlash::Config::OTPAddress(k_pcbVersionOTPIndex))); +} + +void writePCBVersion(PCBVersion version) { + uint8_t * destination = reinterpret_cast(InternalFlash::Config::OTPAddress(k_pcbVersionOTPIndex)); + PCBVersion formattedVersion = ~version; + InternalFlash::WriteMemory(destination, reinterpret_cast(&formattedVersion), sizeof(formattedVersion)); +} + +void lockPCBVersion() { + uint8_t * destination = reinterpret_cast(InternalFlash::Config::OTPLockAddress(k_pcbVersionOTPIndex)); + uint8_t zero = 0; + InternalFlash::WriteMemory(destination, &zero, sizeof(zero)); +} + +bool pcbVersionIsLocked() { + return *reinterpret_cast(InternalFlash::Config::OTPLockAddress(k_pcbVersionOTPIndex)) == 0; +} + +} +} +} diff --git a/ion/src/device/bootloader/drivers/cache.cpp b/ion/src/device/bootloader/drivers/cache.cpp new file mode 100644 index 00000000000..0d16f5261ea --- /dev/null +++ b/ion/src/device/bootloader/drivers/cache.cpp @@ -0,0 +1,104 @@ +#include "cache.h" + +namespace Ion { +namespace Device { +namespace Cache { + +using namespace Regs; + +void privateCleanInvalidateDisableDCache(bool clean, bool invalidate, bool disable) { + // Select Level 1 data cache + CORTEX.CSSELR()->set(0); + dsb(); + + // Disable D-Cache + if (disable) { + CORTEX.CCR()->setDC(false); + dsb(); + } + + // Pick the right DC??SW register according to invalidate/disable parameters + volatile CORTEX::DCSW * target = nullptr; + if (clean && invalidate) { + target = CORTEX.DCCISW(); + } else if (clean) { + target = CORTEX.DCCSW(); + } else { + assert(invalidate); + target = CORTEX.DCISW(); + } + + class CORTEX::CCSIDR ccsidr = CORTEX.CCSIDR()->get(); + uint32_t sets = ccsidr.getNUMSETS(); + uint32_t ways = ccsidr.getASSOCIATIVITY(); + + for (int set = sets; set >= 0; set--) { + for (int way = ways; way >= 0; way--) { + class CORTEX::DCSW dcsw; + dcsw.setSET(set); + dcsw.setWAY(way); + target->set(dcsw); + } + } + + dsb(); + isb(); +} + +void enable() { + enableICache(); + enableDCache(); +} + +void disable() { + disableICache(); + disableDCache(); +} + +void invalidateDCache() { + privateCleanInvalidateDisableDCache(false, true, false); +} + +void cleanDCache() { + privateCleanInvalidateDisableDCache(true, false, false); +} + +void enableDCache() { + invalidateDCache(); + CORTEX.CCR()->setDC(true); // Enable D-cache + dsb(); + isb(); +} + +void disableDCache() { + privateCleanInvalidateDisableDCache(true, true, true); +} + +void invalidateICache() { + dsb(); + isb(); + CORTEX.ICIALLU()->set(0); // Invalidate I-cache + dsb(); + isb(); +} + +void enableICache() { + invalidateICache(); + CORTEX.CCR()->setIC(true); // Enable I-cache + dsb(); + isb(); +} + +void disableICache() { + dsb(); + isb(); + CORTEX.CCR()->setIC(false); // Disable I-cache + CORTEX.ICIALLU()->set(0); // Invalidate I-cache + dsb(); + isb(); +} + + +} +} +} diff --git a/ion/src/device/bootloader/drivers/cache.h b/ion/src/device/bootloader/drivers/cache.h new file mode 100644 index 00000000000..cc047743b30 --- /dev/null +++ b/ion/src/device/bootloader/drivers/cache.h @@ -0,0 +1,46 @@ +#ifndef ION_DEVICE_N0110_CACHE_H +#define ION_DEVICE_N0110_CACHE_H + +#include + +namespace Ion { +namespace Device { +namespace Cache { + +/* Data memory barrier + * Ensures that all explicit memory accesses that appear in program order before + * the DMB instruction are observed before any explicit memory accesses that + * appear in program order after the DMB instruction */ +inline void dmb() { + asm volatile("dmb 0xF":::"memory"); +} + +/* Data synchronisation barrier + * Ensures that the processor stalls until the memory write is complete */ +inline void dsb() { + asm volatile("dsb 0xF":::"memory"); +} + +/* Instructions synchronisation barrier + * Ensures that the subsequent instructions are loaded in the new context */ +inline void isb() { + asm volatile("isb 0xF":::"memory"); +} + +void enable(); +void disable(); + +void invalidateDCache(); +void cleanDCache(); +void enableDCache(); +void disableDCache(); + +void invalidateICache(); +void enableICache(); +void disableICache(); + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/backlight.h b/ion/src/device/bootloader/drivers/config/backlight.h new file mode 100644 index 00000000000..6f94838693c --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/backlight.h @@ -0,0 +1,25 @@ +#ifndef ION_DEVICE_N0110_CONFIG_BACKLIGHT_H +#define ION_DEVICE_N0110_CONFIG_BACKLIGHT_H + +#include + +/* Pin | Role | Mode | Function + * -----+-------------------+-----------------------+---------- + * PE0 | Backlight Enable | Output | + */ + +namespace Ion { +namespace Device { +namespace Backlight { +namespace Config { + +using namespace Regs; + +constexpr static GPIOPin BacklightPin = GPIOPin(GPIOE, 0); + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/battery.h b/ion/src/device/bootloader/drivers/config/battery.h new file mode 100644 index 00000000000..07851149b33 --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/battery.h @@ -0,0 +1,30 @@ +#ifndef ION_DEVICE_N0110_CONFIG_BATTERY_H +#define ION_DEVICE_N0110_CONFIG_BATTERY_H + +#include + +/* Pin | Role | Mode | Function + * -----+-------------------+-----------------------+---------- + * PE3 | BAT_CHRG | Input, pulled up | Low = charging, high = full + * PB1 | VBAT_SNS | Analog | ADC1_1 + */ + +namespace Ion { +namespace Device { +namespace Battery { +namespace Config { + +using namespace Regs; + +constexpr static GPIOPin ChargingPin = GPIOPin(GPIOE, 3); +constexpr static GPIOPin ADCPin = GPIOPin(GPIOB, 1); +constexpr uint8_t ADCChannel = 9; +constexpr float ADCReferenceVoltage = 2.8f; +constexpr float ADCDividerBridgeRatio = 2.0f; + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/clocks.h b/ion/src/device/bootloader/drivers/config/clocks.h new file mode 100644 index 00000000000..0cc551cb03b --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/clocks.h @@ -0,0 +1,68 @@ +#ifndef ION_DEVICE_N0110_CONFIG_CLOCKS_H +#define ION_DEVICE_N0110_CONFIG_CLOCKS_H + +#include + +namespace Ion { +namespace Device { +namespace Clocks { +namespace Config { + +/* If you want to considerably slow down the whole machine uniformely, which + * can be very useful to diagnose performance issues, change the PLL + * configuration to: + * PLL_M = 8 + * PLL_N = 192 + * PLL_P_Reg = Regs::RCC::PLLCFGR::PLLP::PLLP8 + * PLL_Q = 4 + * + * SYSCLK and HCLK will be set to 24 MHz. + * Note that even booting takes a few seconds, so don't be surprised + * if the screen is black for a short while upon booting. */ + +constexpr static int HSE = 8; +constexpr static int PLL_M = 8; +constexpr static int PLL_N = 384; +constexpr static Regs::RCC::PLLCFGR::PLLP PLL_P_Reg = Regs::RCC::PLLCFGR::PLLP::PLLP2; +constexpr static int PLL_P = ((int)PLL_P_Reg | 1) << 1; +constexpr static int PLL_Q = 8; +constexpr static int SYSCLKFrequency = ((HSE/PLL_M)*PLL_N)/PLL_P; +constexpr static int AHBPrescaler = 1; +/* To slow down the whole system, we prescale the AHB clock. + * We could divide the system clock by 512. However, the HCLK clock + * frequency must be >= 14.2MHz and <=216 MHz which forces the + * AHBPrescaler to be below 192MHz/14.2MHz~13.5. */ +constexpr static Regs::RCC::CFGR::AHBPrescaler AHBLowFrequencyPrescalerReg = Regs::RCC::CFGR::AHBPrescaler::SysClkDividedBy8; +constexpr static int AHBLowFrequencyPrescaler = 8; +constexpr static int HCLKFrequency = SYSCLKFrequency/AHBPrescaler; +static_assert(HCLKFrequency == 192, "HCLK frequency changed!"); +constexpr static int HCLKLowFrequency = SYSCLKFrequency/AHBLowFrequencyPrescaler; +constexpr static int AHBFrequency = HCLKFrequency; +//constexpr static int AHBLowFrequency = HCLKLowFrequency; +constexpr static Regs::RCC::CFGR::APBPrescaler APB1PrescalerReg = Regs::RCC::CFGR::APBPrescaler::AHBDividedBy4; +constexpr static int APB1Prescaler = 4; +//constexpr static int APB1Frequency = HCLKFrequency/APB1Prescaler; +constexpr static int APB1LowFrequency = HCLKLowFrequency/APB1Prescaler; +//constexpr static int APB1TimerFrequency = 2*APB1Frequency; +constexpr static int APB1TimerLowFrequency = 2*APB1LowFrequency; + +constexpr static Regs::RCC::CFGR::APBPrescaler APB2PrescalerReg = Regs::RCC::CFGR::APBPrescaler::AHBDividedBy2; + +/* According to AN4850 about Spread Spectrum clock generation + * MODPER = round[HSE/(4 x fMOD)] with fMOD the target modulation frequency. */ +constexpr static int fMod = 8; // in KHz. Must be <= 10KHz +constexpr static uint32_t SSCG_MODPER = HSE*1000/(4*fMod); // *1000 to put HSE in KHz +/* According to the USB specification 2, "For full-speed only functions, the + * required data-rate when transmitting (TFDRATE) is 12.000 Mb/s ±0.25%". */ +constexpr static double modulationDepth = 0.25; // Must be (0.25% <= md <= 2%) +// INCSTEP = round[(2^15 -1)xmdxPLLN)/(100x5xMODPER) +constexpr static uint32_t SSCG_INCSTEP = (32767*modulationDepth*PLL_N)/(1.0*100*5*SSCG_MODPER); +static_assert(SSCG_MODPER == 250, "SSCG_MODPER changed"); +static_assert(SSCG_INCSTEP == 25, "SSCG_INCSTEP changed"); +static_assert(SSCG_INCSTEP * SSCG_MODPER < 32767, "Wrong values for the Spread spectrun clock generator"); +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/console.h b/ion/src/device/bootloader/drivers/config/console.h new file mode 100644 index 00000000000..58a527201ab --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/console.h @@ -0,0 +1,32 @@ +#ifndef ION_DEVICE_N0110_CONFIG_CONSOLE_H +#define ION_DEVICE_N0110_CONFIG_CONSOLE_H + +#include + +namespace Ion { +namespace Device { +namespace Console { +namespace Config { + +using namespace Regs; + +constexpr static USART Port = USART(6); +constexpr static GPIOPin RxPin = GPIOPin(GPIOC, 7); +constexpr static GPIOPin TxPin = GPIOPin(GPIOC, 6); +constexpr static GPIO::AFR::AlternateFunction AlternateFunction = GPIO::AFR::AlternateFunction::AF8; + +/* The baud rate of the UART is set by the following equation: + * BaudRate = f/USARTDIV, where f is the clock frequency and USARTDIV a divider. + * In other words, USARTDIV = f/BaudRate. All frequencies in Hz. + * + * In our case, we configure the minicom to use a 115200 BaudRate and + * f = fAPB2 = 96 MHz, so USARTDIV = 833.333 */ +constexpr static int USARTDIVValue = 833; + + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/display.h b/ion/src/device/bootloader/drivers/config/display.h new file mode 100644 index 00000000000..c14f0d21bd5 --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/display.h @@ -0,0 +1,38 @@ +#ifndef ION_DEVICE_N0110_CONFIG_DISPLAY_H +#define ION_DEVICE_N0110_CONFIG_DISPLAY_H + +#include + +namespace Ion { +namespace Device { +namespace Display { +namespace Config { + +using namespace Regs; + +constexpr static GPIOPin FSMCPins[] = { + GPIOPin(GPIOD, 0), GPIOPin(GPIOD, 1), GPIOPin(GPIOD, 4), GPIOPin(GPIOD, 5), + GPIOPin(GPIOD, 7), GPIOPin(GPIOD, 8), GPIOPin(GPIOD, 9), GPIOPin(GPIOD, 10), + GPIOPin(GPIOD, 11), GPIOPin(GPIOD, 14), GPIOPin(GPIOD, 15), GPIOPin(GPIOE, 7), + GPIOPin(GPIOE, 8), GPIOPin(GPIOE, 9), GPIOPin(GPIOE, 10), GPIOPin(GPIOE, 11), + GPIOPin(GPIOE, 12), GPIOPin(GPIOE, 13), GPIOPin(GPIOE, 14), GPIOPin(GPIOE, 15), +}; + +constexpr static GPIOPin PowerPin = GPIOPin(GPIOC, 8); +constexpr static GPIOPin ResetPin = GPIOPin(GPIOE, 1); +constexpr static GPIOPin ExtendedCommandPin = GPIOPin(GPIOD, 6); +constexpr static GPIOPin TearingEffectPin = GPIOPin(GPIOB, 11); + +constexpr static DMA DMAEngine = DMA2; +constexpr static int DMAStream = 0; + +constexpr static int HCLKFrequencyInMHz = 192; + +constexpr static bool DisplayInversion = true; + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/exam_mode.h b/ion/src/device/bootloader/drivers/config/exam_mode.h new file mode 100644 index 00000000000..e2a2e2abae5 --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/exam_mode.h @@ -0,0 +1,30 @@ +#ifndef ION_DEVICE_N0110_CONFIG_EXAM_MODE_H +#define ION_DEVICE_N0110_CONFIG_EXAM_MODE_H + +namespace Ion { +namespace ExamMode { +namespace Config { + +// TODO: factorize the macro with equivalent macro on N100 + +#define byte4 0xFF, 0xFF, 0xFF, 0xFF +#define byte8 byte4, byte4 +#define byte16 byte8, byte8 +#define byte32 byte16, byte16 +#define byte64 byte32, byte32 +#define byte128 byte64, byte64 +#define byte256 byte128, byte128 +#define byte512 byte256, byte256 +#define byte1K byte512, byte512 +#define byte2K byte1K, byte1K +#define byte4K byte2K, byte2K + +#define EXAM_BUFFER_CONTENT byte4K + +constexpr static int ExamModeBufferSize = 4*1024; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/external_flash.h b/ion/src/device/bootloader/drivers/config/external_flash.h new file mode 100644 index 00000000000..f245dca2000 --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/external_flash.h @@ -0,0 +1,45 @@ +#ifndef ION_DEVICE_N0110_CONFIG_EXTERNAL_FLASH_H +#define ION_DEVICE_N0110_CONFIG_EXTERNAL_FLASH_H + +#include + +/* Pin | Role | Mode | Function + * -----+----------------------+-----------------------+----------------- + * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK + * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS + * PE2 | QUADSPI BK1_IO2/WP | Alternate Function 9 | QUADSPI_BK1_IO2 + * PC9 | QUADSPI BK1_IO0/SO | Alternate Function 9 | QUADSPI_BK1_IO0 + * PD12 | QUADSPI BK1_IO1/SI | Alternate Function 9 | QUADSPI_BK1_IO1 + * PD13 | QUADSPI BK1_IO3/HOLD | Alternate Function 9 | QUADSPI_BK1_IO3 + */ + +namespace Ion { +namespace Device { +namespace ExternalFlash { +namespace Config { + +using namespace Regs; + +constexpr static uint32_t StartAddress = 0x90000000; +constexpr static uint32_t EndAddress = 0x90800000; + +constexpr static int NumberOf4KSectors = 8; +constexpr static int NumberOf32KSectors = 1; +constexpr static int NumberOf64KSectors = 128 - 1; +constexpr static int NumberOfSectors = NumberOf4KSectors + NumberOf32KSectors + NumberOf64KSectors; + +constexpr static AFGPIOPin Pins[] = { + AFGPIOPin(GPIOB, 2, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast), + AFGPIOPin(GPIOB, 6, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast), + AFGPIOPin(GPIOC, 9, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast), + AFGPIOPin(GPIOD, 12, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast), + AFGPIOPin(GPIOD, 13, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast), + AFGPIOPin(GPIOE, 2, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast), +}; + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/internal_flash.h b/ion/src/device/bootloader/drivers/config/internal_flash.h new file mode 100644 index 00000000000..9fcbeed145d --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/internal_flash.h @@ -0,0 +1,31 @@ +#ifndef ION_DEVICE_N0110_CONFIG_INTERNAL_FLASH_H +#define ION_DEVICE_N0110_CONFIG_INTERNAL_FLASH_H + +#include + +namespace Ion { +namespace Device { +namespace InternalFlash { +namespace Config { + +constexpr static uint32_t StartAddress = 0x08000000; +constexpr static uint32_t EndAddress = 0x08010000; +constexpr static int NumberOfSectors = 4; +constexpr static uint32_t SectorAddresses[NumberOfSectors+1] = { + 0x08000000, 0x08004000, 0x08008000, 0x0800C000, + 0x08010000 +}; + +constexpr static uint32_t OTPStartAddress = 0x1FF07800; +constexpr static uint32_t OTPLocksAddress = 0x1FF07A00; +constexpr static int NumberOfOTPBlocks = 16; +constexpr static uint32_t OTPBlockSize = 0x20; +constexpr uint32_t OTPAddress(int block) { return OTPStartAddress + block * OTPBlockSize; }; +constexpr uint32_t OTPLockAddress(int block) { return OTPLocksAddress + block; } + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/keyboard.h b/ion/src/device/bootloader/drivers/config/keyboard.h new file mode 100644 index 00000000000..2c98f84a3e3 --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/keyboard.h @@ -0,0 +1,75 @@ +#ifndef ION_DEVICE_N0110_CONFIG_KEYBOARD_H +#define ION_DEVICE_N0110_CONFIG_KEYBOARD_H + +#include +#include + +/* Pin | Role | Mode + * -----+-------------------+-------------------- + * PC0 | Keyboard column 1 | Input, pulled up + * PC1 | Keyboard column 2 | Input, pulled up + * PC2 | Keyboard column 3 | Input, pulled up + * PC3 | Keyboard column 4 | Input, pulled up + * PC4 | Keyboard column 5 | Input, pulled up + * PC5 | Keyboard column 6 | Input, pulled up + * PA1 | Keyboard row A | Output, open drain + * PA0 | Keyboard row B | Output, open drain + * PA2 | Keyboard row C | Output, open drain + * PA3 | Keyboard row D | Output, open drain + * PA4 | Keyboard row E | Output, open drain + * PA5 | Keyboard row F | Output, open drain + * PA6 | Keyboard row G | Output, open drain + * PA7 | Keyboard row H | Output, open drain + * PA8 | Keyboard row I | Output, open drain + * + * The keyboard is a matrix that is laid out as follow: + * + * -+------+------+------+------+------+------+ + * | K_A1 | K_A2 | K_A3 | K_A4 | K_A5 | K_A6 | + * -+------+------+------+------+------+------+ + * | K_B1 | | K_B3 | | | | + * -+------+------+------+------+------+------+ + * | K_C1 | K_C2 | K_C3 | K_C4 | K_C5 | K_C6 | + * -+------+------+------+------+------+------+ + * | K_D1 | K_D2 | K_D3 | K_D4 | K_D5 | K_D6 | + * -+------+------+------+------+------+------+ + * | K_E1 | K_E2 | K_E3 | K_E4 | K_E5 | K_E6 | + * -+------+------+------+------+------+------+ + * | K_F1 | K_F2 | K_F3 | K_F4 | K_F5 | | + * -+------+------+------+------+------+------+ + * | K_G1 | K_G2 | K_G3 | K_G4 | K_G5 | | + * -+------+------+------+------+------+------+ + * | K_H1 | K_H2 | K_H3 | K_H4 | K_H5 | | + * -+------+------+------+------+------+------+ + * | K_I1 | K_I2 | K_I3 | K_I4 | K_I5 | | + * -+------+------+------+------+------+------| + */ + +namespace Ion { +namespace Device { +namespace Keyboard { +namespace Config { + +using namespace Regs; + +constexpr GPIO RowGPIO = GPIOA; +constexpr uint8_t numberOfRows = 9; +constexpr uint8_t RowPins[numberOfRows] = {1, 0, 2, 3, 4, 5, 6, 7, 8}; + +constexpr GPIO ColumnGPIO = GPIOC; +constexpr uint8_t numberOfColumns = 6; +constexpr uint8_t ColumnPins[numberOfColumns] = {0, 1, 2, 3, 4, 5}; + +/* Undefined keys numbers are: 7, 9, 10, 11, 35, 41, 47 and 53 + * Therefore we want to make sure those bits are forced to zero in + * whatever value we return. */ +inline uint64_t ValidKeys(uint64_t state) { + return state & 0x1F7DF7FFFFF17F; +} + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/led.h b/ion/src/device/bootloader/drivers/config/led.h new file mode 100644 index 00000000000..1fea36d4a82 --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/led.h @@ -0,0 +1,28 @@ +#ifndef ION_DEVICE_N0110_CONFIG_LED_H +#define ION_DEVICE_N0110_CONFIG_LED_H + +#include + +namespace Ion { +namespace Device { +namespace LED { +namespace Config { + +using namespace Regs; + +static constexpr int RedChannel = 1; +static constexpr int GreenChannel = 2; +static constexpr int BlueChannel = 3; + +constexpr static AFGPIOPin RGBPins[] = { + AFGPIOPin(GPIOB, 4, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Low), // RED + AFGPIOPin(GPIOB, 5, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Low), // GREEN + AFGPIOPin(GPIOB, 0, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Low) // BLUE +}; + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/serial_number.h b/ion/src/device/bootloader/drivers/config/serial_number.h new file mode 100644 index 00000000000..c5ad127c304 --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/serial_number.h @@ -0,0 +1,18 @@ +#ifndef ION_DEVICE_N0110_CONFIG_SERIAL_NUMBER_H +#define ION_DEVICE_N0110_CONFIG_SERIAL_NUMBER_H + +#include + +namespace Ion { +namespace Device { +namespace SerialNumber { +namespace Config { + +constexpr uint32_t UniqueDeviceIDAddress = 0x1FF07A10; + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/swd.h b/ion/src/device/bootloader/drivers/config/swd.h new file mode 100644 index 00000000000..1b9fcaa20aa --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/swd.h @@ -0,0 +1,24 @@ +#ifndef ION_DEVICE_N0110_CONFIG_SWD_H +#define ION_DEVICE_N0110_CONFIG_SWD_H + +#include + +namespace Ion { +namespace Device { +namespace SWD { +namespace Config { + +using namespace Regs; + +constexpr static AFGPIOPin Pins[] = { + AFGPIOPin(GPIOA, 13, GPIO::AFR::AlternateFunction::AF0, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOA, 14, GPIO::AFR::AlternateFunction::AF0, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOB, 3, GPIO::AFR::AlternateFunction::AF0, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), +}; + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/timing.h b/ion/src/device/bootloader/drivers/config/timing.h new file mode 100644 index 00000000000..fa4502000af --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/timing.h @@ -0,0 +1,19 @@ +#ifndef ION_DEVICE_N0110_CONFIG_TIMING_H +#define ION_DEVICE_N0110_CONFIG_TIMING_H + +#include + +namespace Ion { +namespace Device { +namespace Timing { +namespace Config { + +constexpr static int LoopsPerMillisecond = 4811; +constexpr static int LoopsPerMicrosecond = 38; + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/usb.h b/ion/src/device/bootloader/drivers/config/usb.h new file mode 100644 index 00000000000..61c1e739d31 --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/usb.h @@ -0,0 +1,31 @@ +#ifndef ION_DEVICE_N0110_CONFIG_USB_H +#define ION_DEVICE_N0110_CONFIG_USB_H + +#include + +namespace Ion { +namespace Device { +namespace USB { +namespace Config { + +using namespace Regs; + +/* On the STM32F730, PA9 does not actually support alternate function 10. + * However, because of the wiring of the USB connector on old N0110, detection + * of when the device is plugged required the use of this undocumented setting. + * After the revision of the USB connector and ESD protection, we can now + * follow the specification and configure the Vbus pin as a floating-input GPIO. + */ +constexpr static AFGPIOPin VbusPin = AFGPIOPin(GPIOA, 9, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast); + +constexpr static AFGPIOPin DmPin = AFGPIOPin(GPIOA, 11, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast); +constexpr static AFGPIOPin DpPin = AFGPIOPin(GPIOA, 12, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast); + +constexpr static const char * InterfaceStringDescriptor = "@Flash/0x90000000/08*004Kg,01*032Kg,63*064Kg,64*064Kg"; + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/external_flash.cpp b/ion/src/device/bootloader/drivers/external_flash.cpp new file mode 100644 index 00000000000..1c7686cd45e --- /dev/null +++ b/ion/src/device/bootloader/drivers/external_flash.cpp @@ -0,0 +1,521 @@ +#include +#include +#include +#include +#include + +namespace Ion { +namespace Device { +namespace ExternalFlash { + +using namespace Regs; + +/* The external flash and the Quad-SPI peripheral support several operating + * modes, corresponding to different numbers of signals used to communicate + * during each phase of the command sequence. + * + * Mode name for | Number of signals used during each phase: + * external flash | Instruction | Address | Alt. bytes | Data + * ----------------+-------------+---------+------------+------ + * Standard SPI | 1 | 1 | 1 | 1 + * Dual-Output SPI | 1 | 1 | 1 | 2 + * Dual-I/O SPI | 1 | 2 | 2 | 2 + * Quad-Output SPI | 1 | 1 | 1 | 4 + * Quad-I/O SPI | 1 | 4 | 4 | 4 + * QPI | 4 | 4 | 4 | 4 + * + * The external flash supports clock frequencies up to 104MHz for all + * instructions, except for Read Data (0x03) which is supported up to 50Mhz. + * + * + * Quad-SPI block diagram + * + * +----------------------+ +------------+ + * | Quad-SPI | | | + * | peripheral | | External | + * | | read | flash | + * AHB <-- | data <-- 32-byte | <-- | memory | + * matrix --> | register --> FIFO | --> | | + * +----------------------+ write +------------+ + * + * Any data transmitted to or from the external flash memory go through a + * 32-byte FIFO. + * + * Read or write operations are performed in burst mode, that is, after any data + * byte is transmitted between the Quad-SPI and the flash memory, the latter + * automatically increments the specified address and the next byte to read or + * write is respectively pushed in or popped from the FIFO. + * And so on, as long as the clock continues. + * + * If the FIFO gets full in a read operation or + * if the FIFO gets empty in a write operation, + * the operation stalls and CLK stays low until firmware services the FIFO. + * + * If the FIFO gets full in a write operation, the operation is stalled until + * the FIFO has enough space to accept the amount of data being written. + * If the FIFO does not have as many bytes as requested by the read operation + * and if BUSY=1, the operation is stalled until enough data is present or until + * the transfer is complete, whichever happens first. */ + +enum class Command : uint8_t { + WriteStatusRegister = 0x01, + PageProgram = 0x02, // Program previously erased memory areas as being "0" + ReadData = 0x03, + ReadStatusRegister1 = 0x05, + WriteEnable = 0x06, + Erase4KbyteBlock = 0x20, + WriteStatusRegister2 = 0x31, + QuadPageProgramW25Q64JV = 0x32, + QuadPageProgramAT25F641 = 0x33, + ReadStatusRegister2 = 0x35, + Erase32KbyteBlock = 0x52, + EnableReset = 0x66, + Reset = 0x99, + ReadJEDECID = 0x9F, + ReleaseDeepPowerDown = 0xAB, + DeepPowerDown = 0xB9, + ChipErase = 0xC7, // Erase the whole chip or a 64-Kbyte block as being "1" + Erase64KbyteBlock = 0xD8, + FastReadQuadIO = 0xEB +}; + +static constexpr uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; +static constexpr uint8_t NumberOfAddressBitsIn32KbyteBlock = 15; +static constexpr uint8_t NumberOfAddressBitsIn4KbyteBlock = 12; + +class ExternalFlashStatusRegister { +public: + class StatusRegister1 : public Register8 { + public: + using Register8::Register8; + REGS_BOOL_FIELD_R(BUSY, 0); + }; + class StatusRegister2 : public Register8 { + public: + using Register8::Register8; + REGS_BOOL_FIELD(QE, 1); + }; +}; + +class OperatingModes { +public: + constexpr OperatingModes( + QUADSPI::CCR::OperatingMode instruction, + QUADSPI::CCR::OperatingMode address, + QUADSPI::CCR::OperatingMode data) : + m_instructionOperatingMode(instruction), + m_addressOperatingMode(address), + m_dataOperatingMode(data) + {} + QUADSPI::CCR::OperatingMode instructionOperatingMode() const { return m_instructionOperatingMode; } + QUADSPI::CCR::OperatingMode addressOperatingMode() const { return m_addressOperatingMode; } + QUADSPI::CCR::OperatingMode dataOperatingMode() const { return m_dataOperatingMode; } +private: + QUADSPI::CCR::OperatingMode m_instructionOperatingMode; + QUADSPI::CCR::OperatingMode m_addressOperatingMode; + QUADSPI::CCR::OperatingMode m_dataOperatingMode; +}; + +/* W25Q64JV does not implement QPI-4-4-4, so we always send the instructions on + * one wire only.*/ +static constexpr OperatingModes sOperatingModes100(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::NoData, QUADSPI::CCR::OperatingMode::NoData); +static constexpr OperatingModes sOperatingModes101(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::NoData, QUADSPI::CCR::OperatingMode::Single); +static constexpr OperatingModes sOperatingModes110(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::NoData); +static constexpr OperatingModes sOperatingModes111(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single); +static constexpr OperatingModes sOperatingModes114(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Quad); +static constexpr OperatingModes sOperatingModes144(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Quad, QUADSPI::CCR::OperatingMode::Quad); + +static QUADSPI::CCR::OperatingMode sOperatingMode = QUADSPI::CCR::OperatingMode::Single; + +static constexpr int ClockFrequencyDivisor = 2; // F(QUADSPI) = F(AHB) / ClockFrequencyDivisor +static constexpr int FastReadQuadIODummyCycles = 4; // Must be 4 for W25Q64JV (Fig 24.A page 34) and for AT25F641 (table 7.19 page 28) +/* According to datasheets, the CS signal should stay high (deselect the device) + * for t_SHSL = 50ns at least. + * -> Max of 30ns (see AT25F641 Sections 8.7 and 8.8), + * 10ns and 50ns (see W25Q64JV Section 9.6). */ +static constexpr float ChipSelectHighTimeInNanoSeconds = 50.0f; + +static void send_command_full( + QUADSPI::CCR::FunctionalMode functionalMode, + OperatingModes operatingModes, + Command c, + uint8_t * address, + uint32_t altBytes, + size_t numberOfAltBytes, + uint8_t dummyCycles, + uint8_t * data, + size_t dataLength); + +static inline void send_command(Command c) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + sOperatingModes100, + c, + reinterpret_cast(FlashAddressSpaceSize), + 0, 0, + 0, + nullptr, 0); +} + +static inline void send_write_command(Command c, uint8_t * address, const uint8_t * data, size_t dataLength, OperatingModes operatingModes) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + operatingModes, + c, + address, + 0, 0, + 0, + const_cast(data), dataLength); +} + +static inline void send_read_command(Command c, uint8_t * address, uint8_t * data, size_t dataLength) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectRead, + sOperatingModes101, + c, + address, + 0, 0, + 0, + data, dataLength); +} + +static inline void wait() { + /* The DSB instruction guarantees the completion of a write operation before + * polling the status register. */ + Cache::dsb(); + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + do { + send_read_command(Command::ReadStatusRegister1, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&statusRegister1), sizeof(statusRegister1)); + } while (statusRegister1.getBUSY()); +} + +static void set_as_memory_mapped() { + /* In memory-mapped mode, all AHB masters may access the external flash memory as an internal one: + * the programmed instruction is sent automatically whenever an AHB master reads in the Quad-SPI flash bank area. + * (The QUADSPI_DLR register has no meaning and any access to QUADSPI_DR returns zero.) + * + * To anticipate sequential reads, the nCS signal is maintained low so as to + * keep the read operation active and prefetch the subsequent bytes in the FIFO. + * + * It goes low, only if the low-power timeout counter is enabled. + * (Flash memories tend to consume more when nCS is held low.) */ + send_command_full( + QUADSPI::CCR::FunctionalMode::MemoryMapped, + sOperatingModes144, + Command::FastReadQuadIO, + reinterpret_cast(FlashAddressSpaceSize), + 0xA0, 1, + FastReadQuadIODummyCycles, + nullptr, 0 + ); +} + +static void unset_memory_mapped_mode() { + /* Reset Continuous Read Mode Bits before issuing normal instructions. */ + uint8_t dummyData; + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectRead, + sOperatingModes144, + Command::FastReadQuadIO, + 0, + ~(0xA0), 1, + FastReadQuadIODummyCycles, + &dummyData, 1 + ); +} + +static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, OperatingModes operatingModes, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { + /* According to ST's Errata Sheet ES0360, "Wrong data can be read in + * memory-mapped after an indirect mode operation". This is the workaround. */ + if (functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + QUADSPI::CCR::FunctionalMode previousMode = QUADSPI.CCR()->getFMODE(); + if (previousMode == QUADSPI::CCR::FunctionalMode::IndirectWrite || previousMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { + // Reset the address register + QUADSPI.AR()->set(0); // No write to DR should be done after this + if (previousMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { + // Make an abort request to stop the reading and clear the busy bit + QUADSPI.CR()->setABORT(true); + while (QUADSPI.CR()->getABORT()) { + } + } + } + } else if (QUADSPI.CCR()->getFMODE() == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + /* "BUSY goes high as soon as the first memory-mapped access occurs. Because + * of the prefetch operations, BUSY does not fall until there is a timeout, + * there is an abort, or the peripheral is disabled". (From the Reference + * Manual) + * If we are leaving memory-mapped mode, we send an abort to clear BUSY. */ + QUADSPI.CR()->setABORT(true); + while (QUADSPI.CR()->getABORT()) { + } + } + + assert(QUADSPI.CCR()->getFMODE() != QUADSPI::CCR::FunctionalMode::MemoryMapped || QUADSPI.SR()->getBUSY() == 0); + + class QUADSPI::CCR ccr(0); + ccr.setFMODE(functionalMode); + if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setDMODE(operatingModes.dataOperatingMode()); + } + if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { + QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); + } + ccr.setDCYC(dummyCycles); + if (numberOfAltBytes > 0) { + ccr.setABMODE(operatingModes.addressOperatingMode()); // Seems to always be the same as address mode + ccr.setABSIZE(static_cast(numberOfAltBytes - 1)); + QUADSPI.ABR()->set(altBytes); + } + if (address != reinterpret_cast(FlashAddressSpaceSize) || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setADMODE(operatingModes.addressOperatingMode()); + ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); + } + ccr.setIMODE(operatingModes.instructionOperatingMode()); + ccr.setINSTRUCTION(static_cast(c)); + if (functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setSIOO(true); + /* If the SIOO bit is set, the instruction is sent only for the first command following a write to QUADSPI_CCR. + * Subsequent command sequences skip the instruction phase, until there is a write to QUADSPI_CCR. */ + } + QUADSPI.CCR()->set(ccr); + if (address != reinterpret_cast(FlashAddressSpaceSize)) { + QUADSPI.AR()->set(reinterpret_cast(address)); + } + + if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) { + for (size_t i=0; iset(data[i]); + } + } else if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { + for (size_t i=0; iget(); + } + } + + /* Wait for the command to be sent. + * "When configured in memory-mapped mode, because of the prefetch operations, + * BUSY does not fall until there is a timeout, there is an abort, or the + * peripheral is disabled.", so we do not wait if the device is in + * memory-mapped mode. */ + if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { + while (QUADSPI.SR()->getBUSY()) { + } + } +} + +static void initGPIO() { + for(const AFGPIOPin & p : Config::Pins) { + p.init(); + } +} + +static void initQSPI() { + // Enable QUADSPI AHB3 peripheral clock + RCC.AHB3ENR()->setQSPIEN(true); + + // Configure controller for target device + class QUADSPI::DCR dcr(0); + dcr.setFSIZE(NumberOfAddressBitsInChip - 1); + constexpr int ChipSelectHighTimeCycles = (ChipSelectHighTimeInNanoSeconds * static_cast(Clocks::Config::AHBFrequency)) / (static_cast(ClockFrequencyDivisor) * 1000.0f) + 1.0f; + dcr.setCSHT(ChipSelectHighTimeCycles - 1); + dcr.setCKMODE(true); + QUADSPI.DCR()->set(dcr); + class QUADSPI::CR cr(0); + cr.setPRESCALER(ClockFrequencyDivisor - 1); + cr.setEN(true); + QUADSPI.CR()->set(cr); +} + +static void initChip() { + // Release sleep deep + send_command(Command::ReleaseDeepPowerDown); + Timing::usleep(3); + + /* The chip initially expects commands in SPI mode. We need to use SPI to tell + * it to switch to QuadSPI/QPI. */ + if (sOperatingMode == QUADSPI::CCR::OperatingMode::Single) { + send_command(Command::WriteEnable); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + statusRegister2.setQE(true); + wait(); + send_write_command(Command::WriteStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&statusRegister2), sizeof(statusRegister2), sOperatingModes101); + wait(); + sOperatingMode = QUADSPI::CCR::OperatingMode::Quad; + } + set_as_memory_mapped(); +} + +void init() { + if (Config::NumberOfSectors == 0) { + return; + } + initGPIO(); + initQSPI(); + initChip(); +} + +static void shutdownGPIO() { + for(const AFGPIOPin & p : Config::Pins) { + p.group().OSPEEDR()->setOutputSpeed(p.pin(), GPIO::OSPEEDR::OutputSpeed::Low); + p.group().MODER()->setMode(p.pin(), GPIO::MODER::Mode::Analog); + p.group().PUPDR()->setPull(p.pin(), GPIO::PUPDR::Pull::None); + } +} + +static void shutdownChip() { + unset_memory_mapped_mode(); + // Reset + send_command(Command::EnableReset); + send_command(Command::Reset); + sOperatingMode = QUADSPI::CCR::OperatingMode::Single; + Timing::usleep(30); + + // Sleep deep + send_command(Command::DeepPowerDown); + Timing::usleep(3); +} + +static void shutdownQSPI() { + // Reset the controller + RCC.AHB3RSTR()->setQSPIRST(true); + RCC.AHB3RSTR()->setQSPIRST(false); + + RCC.AHB3ENR()->setQSPIEN(false); // TODO: move in Device::shutdownClocks +} + +void shutdown() { + if (Config::NumberOfSectors == 0) { + return; + } + shutdownChip(); + shutdownQSPI(); + shutdownGPIO(); +} + +int SectorAtAddress(uint32_t address) { + /* WARNING: this code assumes that the flash sectors are of increasing size: + * first all 4K sectors, then all 32K sectors, and finally all 64K sectors. */ + int i = address >> NumberOfAddressBitsIn64KbyteBlock; + if (i > Config::NumberOf64KSectors) { + return -1; + } + if (i >= 1) { + return Config::NumberOf4KSectors + Config::NumberOf32KSectors + i - 1; + } + i = address >> NumberOfAddressBitsIn32KbyteBlock; + if (i >= 1) { + i = Config::NumberOf4KSectors + i - 1; + assert(i >= 0 && i <= Config::NumberOf32KSectors); + return i; + } + i = address >> NumberOfAddressBitsIn4KbyteBlock; + assert(i <= Config::NumberOf4KSectors); + return i; +} + +void unlockFlash() { + // Warning: unset_memory_mapped_mode must be called before + send_command(Command::WriteEnable); + wait(); + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + ExternalFlashStatusRegister::StatusRegister2 currentStatusRegister2(0); + send_read_command(Command::ReadStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(¤tStatusRegister2), sizeof(currentStatusRegister2)); + statusRegister2.setQE(currentStatusRegister2.getQE()); + + uint8_t registers[] = {statusRegister1.get(), statusRegister2.get()}; + send_write_command(Command::WriteStatusRegister, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(registers), sizeof(registers), sOperatingModes101); + wait(); +} + +void MassErase() { + if (Config::NumberOfSectors == 0) { + return; + } + unset_memory_mapped_mode(); + unlockFlash(); + send_command(Command::WriteEnable); + wait(); + send_command(Command::ChipErase); + wait(); + set_as_memory_mapped(); +} + +void __attribute__((noinline)) EraseSector(int i) { + assert(i >= 0 && i < Config::NumberOfSectors); + unset_memory_mapped_mode(); + unlockFlash(); + send_command(Command::WriteEnable); + wait(); + /* WARNING: this code assumes that the flash sectors are of increasing size: + * first all 4K sectors, then all 32K sectors, and finally all 64K sectors. */ + if (i < Config::NumberOf4KSectors) { + send_write_command(Command::Erase4KbyteBlock, reinterpret_cast(i << NumberOfAddressBitsIn4KbyteBlock), nullptr, 0, sOperatingModes110); + } else if (i < Config::NumberOf4KSectors + Config::NumberOf32KSectors) { + /* If the sector is the number Config::NumberOf4KSectors, we want to write + * at the address 1 << NumberOfAddressBitsIn32KbyteBlock, hence the formula + * (i - Config::NumberOf4KSectors + 1). */ + send_write_command(Command::Erase32KbyteBlock, reinterpret_cast((i - Config::NumberOf4KSectors + 1) << NumberOfAddressBitsIn32KbyteBlock), nullptr, 0, sOperatingModes110); + } else { + /* If the sector is the number + * Config::NumberOf4KSectors - Config::NumberOf32KSectors, we want to write + * at the address 1 << NumberOfAddressBitsIn32KbyteBlock, hence the formula + * (i - Config::NumberOf4KSectors - Config::NumberOf32KSectors + 1). */ + send_write_command(Command::Erase64KbyteBlock, reinterpret_cast((i - Config::NumberOf4KSectors - Config::NumberOf32KSectors + 1) << NumberOfAddressBitsIn64KbyteBlock), nullptr, 0, sOperatingModes110); + } + wait(); + set_as_memory_mapped(); +} + +void __attribute__((noinline)) WriteMemory(uint8_t * destination, const uint8_t * source, size_t length) { + if (Config::NumberOfSectors == 0) { + return; + } + destination -= ExternalFlash::Config::StartAddress; + unset_memory_mapped_mode(); + /* Each 256-byte page of the external flash memory (contained in a previously erased area) + * may be programmed in burst mode with a single Page Program instruction. + * However, when the end of a page is reached, the addressing wraps to the beginning. + * Hence a Page Program instruction must be issued for each page. */ + static constexpr size_t PageSize = 256; + uint8_t offset = reinterpret_cast(destination) & (PageSize - 1); + size_t lengthThatFitsInPage = PageSize - offset; + while (length > 0) { + if (lengthThatFitsInPage > length) { + lengthThatFitsInPage = length; + } + send_command(Command::WriteEnable); + wait(); + + /* Some chips implement 0x32 only, others 0x33 only, we call both. This does + * not seem to affect the writing. */ + send_write_command(Command::QuadPageProgramAT25F641, destination, source, lengthThatFitsInPage, sOperatingModes144); + send_write_command(Command::QuadPageProgramW25Q64JV, destination, source, lengthThatFitsInPage, sOperatingModes114); + + length -= lengthThatFitsInPage; + destination += lengthThatFitsInPage; + source += lengthThatFitsInPage; + lengthThatFitsInPage = PageSize; + wait(); + } + set_as_memory_mapped(); +} + +void JDECid(uint8_t * manufacturerID, uint8_t * memoryType, uint8_t * capacityType) { + unset_memory_mapped_mode(); + struct JEDECId { + uint8_t manufacturerID; + uint8_t memoryType; + uint8_t capacityType; + }; + JEDECId id; + send_read_command(Command::ReadJEDECID, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&id), sizeof(id)); + *manufacturerID = id.manufacturerID; + *memoryType = id.memoryType; + *capacityType = id.capacityType; + set_as_memory_mapped(); +} + +} +} +} diff --git a/ion/src/device/bootloader/drivers/external_flash_tramp.cpp b/ion/src/device/bootloader/drivers/external_flash_tramp.cpp new file mode 100644 index 00000000000..29a2bf03e0d --- /dev/null +++ b/ion/src/device/bootloader/drivers/external_flash_tramp.cpp @@ -0,0 +1,386 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace Ion { +namespace Device { +namespace ExternalFlash { + +using namespace Regs; + +/* The external flash and the Quad-SPI peripheral support several operating + * modes, corresponding to different numbers of signals used to communicate + * during each phase of the command sequence. + * + * Mode name for | Number of signals used during each phase: + * external flash | Instruction | Address | Alt. bytes | Data + * ----------------+-------------+---------+------------+------ + * Standard SPI | 1 | 1 | 1 | 1 + * Dual-Output SPI | 1 | 1 | 1 | 2 + * Dual-I/O SPI | 1 | 2 | 2 | 2 + * Quad-Output SPI | 1 | 1 | 1 | 4 + * Quad-I/O SPI | 1 | 4 | 4 | 4 + * QPI | 4 | 4 | 4 | 4 + * + * The external flash supports clock frequencies up to 104MHz for all + * instructions, except for Read Data (0x03) which is supported up to 50Mhz. + * + * + * Quad-SPI block diagram + * + * +----------------------+ +------------+ + * | Quad-SPI | | | + * | peripheral | | External | + * | | read | flash | + * AHB <-- | data <-- 32-byte | <-- | memory | + * matrix --> | register --> FIFO | --> | | + * +----------------------+ write +------------+ + * + * Any data transmitted to or from the external flash memory go through a + * 32-byte FIFO. + * + * Read or write operations are performed in burst mode, that is, after any data + * byte is transmitted between the Quad-SPI and the flash memory, the latter + * automatically increments the specified address and the next byte to read or + * write is respectively pushed in or popped from the FIFO. + * And so on, as long as the clock continues. + * + * If the FIFO gets full in a read operation or + * if the FIFO gets empty in a write operation, + * the operation stalls and CLK stays low until firmware services the FIFO. + * + * If the FIFO gets full in a write operation, the operation is stalled until + * the FIFO has enough space to accept the amount of data being written. + * If the FIFO does not have as many bytes as requested by the read operation + * and if BUSY=1, the operation is stalled until enough data is present or until + * the transfer is complete, whichever happens first. */ + +enum class Command : uint8_t { + WriteStatusRegister = 0x01, + PageProgram = 0x02, // Program previously erased memory areas as being "0" + ReadData = 0x03, + ReadStatusRegister1 = 0x05, + WriteEnable = 0x06, + Erase4KbyteBlock = 0x20, + WriteStatusRegister2 = 0x31, + QuadPageProgramW25Q64JV = 0x32, + QuadPageProgramAT25F641 = 0x33, + ReadStatusRegister2 = 0x35, + Erase32KbyteBlock = 0x52, + EnableReset = 0x66, + Reset = 0x99, + ReadJEDECID = 0x9F, + ReleaseDeepPowerDown = 0xAB, + DeepPowerDown = 0xB9, + ChipErase = 0xC7, // Erase the whole chip or a 64-Kbyte block as being "1" + Erase64KbyteBlock = 0xD8, + FastReadQuadIO = 0xEB +}; + +static constexpr uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; +static constexpr uint8_t NumberOfAddressBitsIn32KbyteBlock = 15; +static constexpr uint8_t NumberOfAddressBitsIn4KbyteBlock = 12; + +class ExternalFlashStatusRegister { +public: + class StatusRegister1 : public Register8 { + public: + using Register8::Register8; + REGS_BOOL_FIELD_R(BUSY, 0); + }; + class StatusRegister2 : public Register8 { + public: + using Register8::Register8; + REGS_BOOL_FIELD(QE, 1); + }; +}; + +class OperatingModes { +public: + constexpr OperatingModes( + QUADSPI::CCR::OperatingMode instruction, + QUADSPI::CCR::OperatingMode address, + QUADSPI::CCR::OperatingMode data) : + m_instructionOperatingMode(instruction), + m_addressOperatingMode(address), + m_dataOperatingMode(data) + {} + QUADSPI::CCR::OperatingMode instructionOperatingMode() const { return m_instructionOperatingMode; } + QUADSPI::CCR::OperatingMode addressOperatingMode() const { return m_addressOperatingMode; } + QUADSPI::CCR::OperatingMode dataOperatingMode() const { return m_dataOperatingMode; } +private: + QUADSPI::CCR::OperatingMode m_instructionOperatingMode; + QUADSPI::CCR::OperatingMode m_addressOperatingMode; + QUADSPI::CCR::OperatingMode m_dataOperatingMode; +}; + +/* W25Q64JV does not implement QPI-4-4-4, so we always send the instructions on + * one wire only.*/ +static constexpr OperatingModes sOperatingModes100(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::NoData, QUADSPI::CCR::OperatingMode::NoData); +static constexpr OperatingModes sOperatingModes101(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::NoData, QUADSPI::CCR::OperatingMode::Single); +static constexpr OperatingModes sOperatingModes110(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::NoData); +static constexpr OperatingModes sOperatingModes111(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single); +static constexpr OperatingModes sOperatingModes114(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Quad); +static constexpr OperatingModes sOperatingModes144(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Quad, QUADSPI::CCR::OperatingMode::Quad); + +// static QUADSPI::CCR::OperatingMode sOperatingMode = QUADSPI::CCR::OperatingMode::Single; + +static constexpr int ClockFrequencyDivisor = 2; // F(QUADSPI) = F(AHB) / ClockFrequencyDivisor +static constexpr int FastReadQuadIODummyCycles = 4; // Must be 4 for W25Q64JV (Fig 24.A page 34) and for AT25F641 (table 7.19 page 28) +/* According to datasheets, the CS signal should stay high (deselect the device) + * for t_SHSL = 50ns at least. + * -> Max of 30ns (see AT25F641 Sections 8.7 and 8.8), + * 10ns and 50ns (see W25Q64JV Section 9.6). */ +static constexpr float ChipSelectHighTimeInNanoSeconds = 50.0f; + +static void send_command_full( + QUADSPI::CCR::FunctionalMode functionalMode, + OperatingModes operatingModes, + Command c, + uint8_t * address, + uint32_t altBytes, + size_t numberOfAltBytes, + uint8_t dummyCycles, + uint8_t * data, + size_t dataLength); + +static inline void send_command(Command c) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + sOperatingModes100, + c, + reinterpret_cast(FlashAddressSpaceSize), + 0, 0, + 0, + nullptr, 0); +} + +static inline void send_write_command(Command c, uint8_t * address, const uint8_t * data, size_t dataLength, OperatingModes operatingModes) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + operatingModes, + c, + address, + 0, 0, + 0, + const_cast(data), dataLength); +} + +static inline void send_read_command(Command c, uint8_t * address, uint8_t * data, size_t dataLength) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectRead, + sOperatingModes101, + c, + address, + 0, 0, + 0, + data, dataLength); +} + +static inline void wait() { + /* The DSB instruction guarantees the completion of a write operation before + * polling the status register. */ + Cache::dsb(); + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + do { + send_read_command(Command::ReadStatusRegister1, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&statusRegister1), sizeof(statusRegister1)); + } while (statusRegister1.getBUSY()); +} + +static void set_as_memory_mapped() { + /* In memory-mapped mode, all AHB masters may access the external flash memory as an internal one: + * the programmed instruction is sent automatically whenever an AHB master reads in the Quad-SPI flash bank area. + * (The QUADSPI_DLR register has no meaning and any access to QUADSPI_DR returns zero.) + * + * To anticipate sequential reads, the nCS signal is maintained low so as to + * keep the read operation active and prefetch the subsequent bytes in the FIFO. + * + * It goes low, only if the low-power timeout counter is enabled. + * (Flash memories tend to consume more when nCS is held low.) */ + send_command_full( + QUADSPI::CCR::FunctionalMode::MemoryMapped, + sOperatingModes144, + Command::FastReadQuadIO, + reinterpret_cast(FlashAddressSpaceSize), + 0xA0, 1, + FastReadQuadIODummyCycles, + nullptr, 0 + ); +} + +static void unset_memory_mapped_mode() { + /* Reset Continuous Read Mode Bits before issuing normal instructions. */ + uint8_t dummyData; + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectRead, + sOperatingModes144, + Command::FastReadQuadIO, + 0, + ~(0xA0), 1, + FastReadQuadIODummyCycles, + &dummyData, 1 + ); +} + +static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, OperatingModes operatingModes, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { + /* According to ST's Errata Sheet ES0360, "Wrong data can be read in + * memory-mapped after an indirect mode operation". This is the workaround. */ + if (functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + QUADSPI::CCR::FunctionalMode previousMode = QUADSPI.CCR()->getFMODE(); + if (previousMode == QUADSPI::CCR::FunctionalMode::IndirectWrite || previousMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { + // Reset the address register + QUADSPI.AR()->set(0); // No write to DR should be done after this + if (previousMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { + // Make an abort request to stop the reading and clear the busy bit + QUADSPI.CR()->setABORT(true); + while (QUADSPI.CR()->getABORT()) { + } + } + } + } else if (QUADSPI.CCR()->getFMODE() == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + /* "BUSY goes high as soon as the first memory-mapped access occurs. Because + * of the prefetch operations, BUSY does not fall until there is a timeout, + * there is an abort, or the peripheral is disabled". (From the Reference + * Manual) + * If we are leaving memory-mapped mode, we send an abort to clear BUSY. */ + QUADSPI.CR()->setABORT(true); + while (QUADSPI.CR()->getABORT()) { + } + } + + assert(QUADSPI.CCR()->getFMODE() != QUADSPI::CCR::FunctionalMode::MemoryMapped || QUADSPI.SR()->getBUSY() == 0); + + class QUADSPI::CCR ccr(0); + ccr.setFMODE(functionalMode); + if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setDMODE(operatingModes.dataOperatingMode()); + } + if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { + QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); + } + ccr.setDCYC(dummyCycles); + if (numberOfAltBytes > 0) { + ccr.setABMODE(operatingModes.addressOperatingMode()); // Seems to always be the same as address mode + ccr.setABSIZE(static_cast(numberOfAltBytes - 1)); + QUADSPI.ABR()->set(altBytes); + } + if (address != reinterpret_cast(FlashAddressSpaceSize) || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setADMODE(operatingModes.addressOperatingMode()); + ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); + } + ccr.setIMODE(operatingModes.instructionOperatingMode()); + ccr.setINSTRUCTION(static_cast(c)); + if (functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setSIOO(true); + /* If the SIOO bit is set, the instruction is sent only for the first command following a write to QUADSPI_CCR. + * Subsequent command sequences skip the instruction phase, until there is a write to QUADSPI_CCR. */ + } + QUADSPI.CCR()->set(ccr); + if (address != reinterpret_cast(FlashAddressSpaceSize)) { + QUADSPI.AR()->set(reinterpret_cast(address)); + } + + if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) { + for (size_t i=0; iset(data[i]); + } + } else if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { + for (size_t i=0; iget(); + } + } + + /* Wait for the command to be sent. + * "When configured in memory-mapped mode, because of the prefetch operations, + * BUSY does not fall until there is a timeout, there is an abort, or the + * peripheral is disabled.", so we do not wait if the device is in + * memory-mapped mode. */ + if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { + while (QUADSPI.SR()->getBUSY()) { + } + } +} + +void init() { + assert(false); +} + +void shutdown() { + assert(false); +} + +int SectorAtAddress(uint32_t address) { + /* WARNING: this code assumes that the flash sectors are of increasing size: + * first all 4K sectors, then all 32K sectors, and finally all 64K sectors. */ + int i = address >> NumberOfAddressBitsIn64KbyteBlock; + if (i > Config::NumberOf64KSectors) { + return -1; + } + if (i >= 1) { + return Config::NumberOf4KSectors + Config::NumberOf32KSectors + i - 1; + } + i = address >> NumberOfAddressBitsIn32KbyteBlock; + if (i >= 1) { + i = Config::NumberOf4KSectors + i - 1; + assert(i >= 0 && i <= Config::NumberOf32KSectors); + return i; + } + i = address >> NumberOfAddressBitsIn4KbyteBlock; + assert(i <= Config::NumberOf4KSectors); + return i; +} + +void unlockFlash() { + // Warning: unset_memory_mapped_mode must be called before + send_command(Command::WriteEnable); + wait(); + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + ExternalFlashStatusRegister::StatusRegister2 currentStatusRegister2(0); + send_read_command(Command::ReadStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(¤tStatusRegister2), sizeof(currentStatusRegister2)); + statusRegister2.setQE(currentStatusRegister2.getQE()); + + uint8_t registers[] = {statusRegister1.get(), statusRegister2.get()}; + send_write_command(Command::WriteStatusRegister, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(registers), sizeof(registers), sOperatingModes101); + wait(); +} + +void JDECid(uint8_t * manufacturerID, uint8_t * memoryType, uint8_t * capacityType) { + unset_memory_mapped_mode(); + struct JEDECId { + uint8_t manufacturerID; + uint8_t memoryType; + uint8_t capacityType; + }; + JEDECId id; + send_read_command(Command::ReadJEDECID, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&id), sizeof(id)); + *manufacturerID = id.manufacturerID; + *memoryType = id.memoryType; + *capacityType = id.capacityType; + set_as_memory_mapped(); +} + +void MassErase() { + // Mass erase is not enabled on kernel + assert(false); +} + +void WriteMemory(uint8_t * destination, const uint8_t * source, size_t length) { + asm("cpsid if"); + reinterpret_cast(Ion::Device::Trampoline::address(Ion::Device::Trampoline::ExternalFlashWriteMemory))(destination, source, length); + asm("cpsie if"); +} + +void EraseSector(int i) { + asm("cpsid if"); + reinterpret_cast(Ion::Device::Trampoline::address(Ion::Device::Trampoline::ExternalFlashEraseSector))(i); + asm("cpsie if"); +} + +} +} +} diff --git a/ion/src/device/bootloader/drivers/led.cpp b/ion/src/device/bootloader/drivers/led.cpp new file mode 100644 index 00000000000..676464827bf --- /dev/null +++ b/ion/src/device/bootloader/drivers/led.cpp @@ -0,0 +1,23 @@ +#include +#include +#include +#include + +namespace Ion { +namespace LED { + +KDColor updateColorWithPlugAndCharge() { + KDColor ledColor = getColor(); + if (ExamMode::FetchExamMode() == 0) { // If exam mode is on, we do not update the LED with the plugged/charging state + if (USB::isPlugged()) { + ledColor = Battery::isCharging() ? KDColorOrange : KDColorGreen; + } else { + ledColor = KDColorBlack; + } + setColor(ledColor); + } + return ledColor; +} + +} +} diff --git a/ion/src/device/bootloader/drivers/power.cpp b/ion/src/device/bootloader/drivers/power.cpp new file mode 100644 index 00000000000..f38f9e9786b --- /dev/null +++ b/ion/src/device/bootloader/drivers/power.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace Ion { +namespace Power { + +/* We isolate the standby code that needs to be executed from the internal + * flash (because the external flash is then shut down). We forbid inlining to + * avoid inlining these instructions in the external flash. */ + +void standby() { + Device::Power::waitUntilOnOffKeyReleased(); + Device::Power::standbyConfiguration(); + Device::Board::shutdownPeripherals(); + Device::Power::internalFlashStandby(); +} + +} +} + +namespace Ion { +namespace Device { +namespace Power { + +void configWakeUp() { + Device::WakeUp::onOnOffKeyDown(); + Device::WakeUp::onUSBPlugging(); + Device::WakeUp::onChargingEvent(); +} + +// Public Power methods +using namespace Device::Regs; + +void standbyConfiguration() { + PWR.CR()->setPPDS(true); // Select standby when the CPU enters deepsleep + PWR.CR()->setCSBF(true); // Clear Standby flag + PWR.CSR()->setBRE(false); // Unable back up RAM (lower power consumption in standby) + PWR.CSR()->setEIWUP(false); // Unable RTC (lower power consumption in standby) + + /* The pin A0 is about to be configured as a wakeup pin. However, the matrix + * keyboard connects pin A0 (row B) with other pins (column 1, column 3...). + * We thus shutdown this pins to avoid the potential pull-up on pin A0 due to + * a keyboard event. For example, if the "Home" key is down, pin A0 is + * pulled-up so enabling it as the wake up pin would trigger a wake up flag + * instantly. */ + Device::Keyboard::shutdown(); +#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS + PWR.CSR2()->setEWUP1(true); // Enable PA0 as wakeup pin + PWR.CR2()->setWUPP1(false); // Define PA0 (wakeup) pin polarity (rising edge) + PWR.CR2()->setCWUPF1(true); // Clear wakeup pin flag for PA0 (if device has already been in standby and woke up) +#endif + + CORTEX.SCR()->setSLEEPDEEP(true); // Allow Cortex-M7 deepsleep state +} + +void __attribute__((noinline)) internalFlashSuspend(bool isLEDActive) { + // Shutdown all clocks (except the ones used by LED if active and the one used by the flash) + Device::Board::shutdownClocks(isLEDActive); + + Device::Power::enterLowPowerMode(); + + /* A hardware event triggered a wake up, we determine if the device should + * wake up. We wake up when: + * - only the power key was down + * - the unplugged device was plugged + * - the battery stopped charging */ + Device::Board::initClocks(); +} + +void __attribute__((noinline)) internalFlashStandby() { + Device::Board::shutdownClocks(); + Device::Power::enterLowPowerMode(); + Device::Reset::coreWhilePlugged(); +} + +void enterLowPowerMode() { + reinterpret_cast(Ion::Device::Trampoline::address(Ion::Device::Trampoline::Suspend))(); +} + +} +} +} diff --git a/ion/src/device/bootloader/drivers/power.h b/ion/src/device/bootloader/drivers/power.h new file mode 100644 index 00000000000..a3d142bc1c2 --- /dev/null +++ b/ion/src/device/bootloader/drivers/power.h @@ -0,0 +1,16 @@ +#ifndef ION_DEVICE_N0110_POWER_H +#define ION_DEVICE_N0110_POWER_H + +#include + +namespace Ion { +namespace Device { +namespace Power { + +void standbyConfiguration(); + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/reset.cpp b/ion/src/device/bootloader/drivers/reset.cpp new file mode 100644 index 00000000000..f8c30d7e6be --- /dev/null +++ b/ion/src/device/bootloader/drivers/reset.cpp @@ -0,0 +1,13 @@ +#include + +namespace Ion { +namespace Device { +namespace Reset { + +void coreWhilePlugged() { + core(); +} + +} +} +} diff --git a/ion/src/device/bootloader/drivers/trampoline.cpp b/ion/src/device/bootloader/drivers/trampoline.cpp new file mode 100644 index 00000000000..be12ea05e81 --- /dev/null +++ b/ion/src/device/bootloader/drivers/trampoline.cpp @@ -0,0 +1,14 @@ +#include +#include + +namespace Ion { +namespace Device { +namespace Trampoline { + +uint32_t address(int index) { + return 0x0020E000 + sizeof(void *) * index; +} + +} +} +} diff --git a/ion/src/device/bootloader/drivers/trampoline.h b/ion/src/device/bootloader/drivers/trampoline.h new file mode 100644 index 00000000000..db10ce4bc8f --- /dev/null +++ b/ion/src/device/bootloader/drivers/trampoline.h @@ -0,0 +1,22 @@ +#ifndef ION_DEVICE_BOOTLOADER_DRIVERS_TRAMPOLINE_H +#define ION_DEVICE_BOOTLOADER_DRIVERS_TRAMPOLINE_H + +#include + +namespace Ion { +namespace Device { +namespace Trampoline { + +constexpr int Suspend = 0; +constexpr int ExternalFlashEraseSector = 1; +constexpr int ExternalFlashWriteMemory = 2; + +// TODO: Use the other available trampolines instead of liba's functions + +uint32_t address(int index); + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/usb.cpp b/ion/src/device/bootloader/drivers/usb.cpp new file mode 100644 index 00000000000..d56cee4752b --- /dev/null +++ b/ion/src/device/bootloader/drivers/usb.cpp @@ -0,0 +1,27 @@ +#include +#include +#include + +namespace Ion { +namespace Device { + +using namespace Regs; + +namespace USB { + +bool useAlternateFunctionVbus() { + return Board::pcbVersion() == 0; +} + +void initVbus() { + if (useAlternateFunctionVbus()) { + Config::VbusPin.init(); + } else { + Config::VbusPin.group().MODER()->setMode(Config::VbusPin.pin(), GPIO::MODER::Mode::Input); + Config::VbusPin.group().PUPDR()->setPull(Config::VbusPin.pin(), GPIO::PUPDR::Pull::None); + } +} + +} +} +} diff --git a/ion/src/device/bootloader/platform_info.cpp b/ion/src/device/bootloader/platform_info.cpp new file mode 100644 index 00000000000..b3b9a2a004e --- /dev/null +++ b/ion/src/device/bootloader/platform_info.cpp @@ -0,0 +1,183 @@ +#include +#include + +#ifndef PATCH_LEVEL +#error This file expects PATCH_LEVEL to be defined +#endif + +#ifndef EPSILON_VERSION +#error This file expects EPSILON_VERSION to be defined +#endif + +#ifndef OMEGA_VERSION +#error This file expects OMEGA_VERSION to be defined +#endif + +#ifndef UPSILON_VERSION +#error This file expects UPSILON_VERSION to be defined +#endif + +namespace Ion { +extern char staticStorageArea[]; +} +constexpr void * storageAddress = &(Ion::staticStorageArea); + +class KernelHeader { +public: + constexpr KernelHeader() : + m_header(Magic), + m_version{EPSILON_VERSION}, + m_patchLevel{PATCH_LEVEL}, + m_footer(Magic) { } + const char * version() const { + assert(m_header == Magic); + assert(m_footer == Magic); + return m_version; + } + const char * patchLevel() const { + assert(m_header == Magic); + assert(m_footer == Magic); + return m_patchLevel; + } +private: + constexpr static uint32_t Magic = 0xDEC00DF0; + uint32_t m_header; + const char m_version[8]; + const char m_patchLevel[8]; + uint32_t m_footer; +}; + +const KernelHeader __attribute__((section(".kernel_header"), used)) k_kernelHeader; + +class UserlandHeader { +public: + constexpr UserlandHeader(): + m_header(Magic), + m_expectedEpsilonVersion{EPSILON_VERSION}, + m_storageAddressRAM(storageAddress), + m_storageSizeRAM(Ion::Storage::k_storageSize), + m_externalAppsFlashStart(0xFFFFFFFF), + m_externalAppsFlashEnd(0xFFFFFFFF), + m_externalAppsRAMStart(0xFFFFFFFF), + m_externalAppsRAMEnd(0xFFFFFFFF), + m_footer(Magic), + m_omegaMagicHeader(OmegaMagic), + m_omegaVersion{OMEGA_VERSION}, +#ifdef OMEGA_USERNAME + m_username{OMEGA_USERNAME}, +#else + m_username{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, +#endif + m_omegaMagicFooter(OmegaMagic), + m_upsilonMagicHeader(UpsilonMagic), + m_UpsilonVersion{UPSILON_VERSION}, + m_osType(OSType), + m_upsilonMagicFooter(UpsilonMagic) { } + + const char * omegaVersion() const { + assert(m_storageAddressRAM != nullptr); + assert(m_storageSizeRAM != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_omegaVersion; + } + const char * upsilonVersion() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_UpsilonVersion; + } + const volatile char * username() const volatile { + assert(m_storageAddressRAM != nullptr); + assert(m_storageSizeRAM != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_username; + } + +private: + constexpr static uint32_t Magic = 0xDEC0EDFE; + constexpr static uint32_t OmegaMagic = 0xEFBEADDE; + constexpr static uint32_t UpsilonMagic = 0x55707369; + constexpr static uint32_t OSType = 0x79827178; + uint32_t m_header; + const char m_expectedEpsilonVersion[8]; + void * m_storageAddressRAM; + size_t m_storageSizeRAM; + /* We store the range addresses of external apps memory because storing the + * size is complicated due to c++11 constexpr. */ + uint32_t m_externalAppsFlashStart; + uint32_t m_externalAppsFlashEnd; + uint32_t m_externalAppsRAMStart; + uint32_t m_externalAppsRAMEnd; + uint32_t m_footer; + uint32_t m_omegaMagicHeader; + const char m_omegaVersion[16]; + const volatile char m_username[16]; + uint32_t m_omegaMagicFooter; + uint32_t m_upsilonMagicHeader; + const char m_UpsilonVersion[16]; + uint32_t m_osType; + uint32_t m_upsilonMagicFooter; +}; + +const UserlandHeader __attribute__((section(".userland_header"), used)) k_userlandHeader; + +class SlotInfo { + +public: + SlotInfo() : + m_header(Magic), + m_footer(Magic) {} + + void update() { + m_header = Magic; + m_kernelHeaderAddress = &k_kernelHeader; + m_userlandHeaderAddress = &k_userlandHeader; + m_footer = Magic; + } + +private: + constexpr static uint32_t Magic = 0xEFEEDBBA; + uint32_t m_header; + const KernelHeader * m_kernelHeaderAddress; + const UserlandHeader * m_userlandHeaderAddress; + uint32_t m_footer; + +}; + +const char * Ion::omegaVersion() { + return k_userlandHeader.omegaVersion(); +} + +const char * Ion::upsilonVersion() { + return k_userlandHeader.upsilonVersion(); +} + +const volatile char * Ion::username() { + return k_userlandHeader.username(); +} + +const char * Ion::softwareVersion() { + return k_kernelHeader.version(); +} + +const char * Ion::patchLevel() { + return k_kernelHeader.patchLevel(); +} + +SlotInfo * slotInfo() { + static SlotInfo __attribute__((used)) __attribute__((section(".slot_info"))) slotInformation; + return &slotInformation; +} + +void Ion::updateSlotInfo() { + slotInfo()->update(); +} diff --git a/ion/src/device/bootloader/regs/config/cortex.h b/ion/src/device/bootloader/regs/config/cortex.h new file mode 100644 index 00000000000..faee2a9ced9 --- /dev/null +++ b/ion/src/device/bootloader/regs/config/cortex.h @@ -0,0 +1,6 @@ +#ifndef ION_DEVICE_N0110_REGS_CONFIG_CORTEX_H +#define ION_DEVICE_N0110_REGS_CONFIG_CORTEX_H + +#define REGS_CORTEX_CONFIG_CACHE 1 + +#endif diff --git a/ion/src/device/bootloader/regs/config/crc.h b/ion/src/device/bootloader/regs/config/crc.h new file mode 100644 index 00000000000..faa0a263b0a --- /dev/null +++ b/ion/src/device/bootloader/regs/config/crc.h @@ -0,0 +1,6 @@ +#ifndef ION_DEVICE_N0110_REGS_CONFIG_CRC_H +#define ION_DEVICE_N0110_REGS_CONFIG_CRC_H + +#define REGS_CRC_CONFIG_BYTE_ACCESS 1 + +#endif diff --git a/ion/src/device/bootloader/regs/config/flash.h b/ion/src/device/bootloader/regs/config/flash.h new file mode 100644 index 00000000000..770fcc37ad1 --- /dev/null +++ b/ion/src/device/bootloader/regs/config/flash.h @@ -0,0 +1,6 @@ +#ifndef ION_DEVICE_N0110_REGS_CONFIG_FLASH_H +#define ION_DEVICE_N0110_REGS_CONFIG_FLASH_H + +#define REGS_FLASH_CONFIG_ART 1 + +#endif diff --git a/ion/src/device/bootloader/regs/config/pwr.h b/ion/src/device/bootloader/regs/config/pwr.h new file mode 100644 index 00000000000..85f7c0e9d1b --- /dev/null +++ b/ion/src/device/bootloader/regs/config/pwr.h @@ -0,0 +1,6 @@ +#ifndef ION_DEVICE_N0110_REGS_CONFIG_PWR_H +#define ION_DEVICE_N0110_REGS_CONFIG_PWR_H + +#define REGS_PWR_CONFIG_ADDITIONAL_FIELDS 1 + +#endif diff --git a/ion/src/device/bootloader/regs/config/rcc.h b/ion/src/device/bootloader/regs/config/rcc.h new file mode 100644 index 00000000000..54db5f2116e --- /dev/null +++ b/ion/src/device/bootloader/regs/config/rcc.h @@ -0,0 +1,7 @@ +#ifndef ION_DEVICE_N0110_REGS_CONFIG_RCC_H +#define ION_DEVICE_N0110_REGS_CONFIG_RCC_H + +#define REGS_RCC_CONFIG_F730 1 +#define REGS_RCC_CONFIG_F412 0 + +#endif diff --git a/ion/src/device/bootloader/regs/config/syscfg.h b/ion/src/device/bootloader/regs/config/syscfg.h new file mode 100644 index 00000000000..1165be7c976 --- /dev/null +++ b/ion/src/device/bootloader/regs/config/syscfg.h @@ -0,0 +1,6 @@ +#ifndef ION_DEVICE_N0110_REGS_CONFIG_SYSCFG_H +#define ION_DEVICE_N0110_REGS_CONFIG_SYSCFG_H + +#define REGS_SYSCFG_CONFIG_F412 0 + +#endif diff --git a/ion/src/device/bootloader/regs/config/usart.h b/ion/src/device/bootloader/regs/config/usart.h new file mode 100644 index 00000000000..5de196af22a --- /dev/null +++ b/ion/src/device/bootloader/regs/config/usart.h @@ -0,0 +1,12 @@ +#ifndef ION_DEVICE_N0110_REGS_CONFIG_USART_H +#define ION_DEVICE_N0110_REGS_CONFIG_USART_H + +#define REGS_USART_SR_OFFSET 0x1C +#define REGS_USART_RDR_OFFSET 0x24 +#define REGS_USART_TDR_OFFSET 0x28 +#define REGS_USART_BRR_OFFSET 0x0C +#define REGS_USART_CR1_OFFSET 0x00 + +#define REGS_USART_CR1_UE_BIT 0 + +#endif diff --git a/ion/src/device/n0100/Makefile b/ion/src/device/n0100/Makefile index ccacc84ab6f..83d810c1029 100644 --- a/ion/src/device/n0100/Makefile +++ b/ion/src/device/n0100/Makefile @@ -7,4 +7,12 @@ ion_device_src += $(addprefix ion/src/device/n0100/drivers/, \ usb.cpp \ ) +ion_device_src += $(addprefix ion/src/device/n0100/boot/, \ + rt0.cpp \ +) + +ion_device_src += $(addprefix ion/src/device/n0100/, \ + platform_info.cpp \ +) + LDSCRIPT ?= ion/src/device/n0100/flash.ld diff --git a/ion/src/device/n0100/boot/rt0.cpp b/ion/src/device/n0100/boot/rt0.cpp new file mode 100644 index 00000000000..d9b47cf51e5 --- /dev/null +++ b/ion/src/device/n0100/boot/rt0.cpp @@ -0,0 +1,264 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +typedef void (*cxx_constructor)(); + +extern "C" { +extern char _data_section_start_flash; +extern char _data_section_start_ram; +extern char _data_section_end_ram; +extern char _bss_section_start_ram; +extern char _bss_section_end_ram; +extern cxx_constructor _init_array_start; +extern cxx_constructor _init_array_end; +} + +/* In order to ensure that this method is execute from the external flash, we + * forbid inlining it.*/ + +static void __attribute__((noinline)) external_flash_start() { + /* Init the peripherals. We do not initialize the backlight in case there is + * an on boarding app: indeed, we don't want the user to see the LCD tests + * happening during the on boarding app. The backlight will be initialized + * after the Power-On Self-Test if there is one or before switching to the + * home app otherwise. */ + Ion::Device::Board::initPeripherals(false); + return ion_main(0, nullptr); +} + +/* This additional function call 'jump_to_external_flash' serves two purposes: + * - By default, the compiler is free to inline any function call he wants. If + * the compiler decides to inline some functions that make use of VFP + * registers, it will need to push VFP them onto the stack in calling + * function's prologue. + * Problem: in start()'s prologue, we would never had a chance to enable the + * FPU since this function is the first thing called after reset. + * We can safely assume that neither memcpy, memset, nor any Ion::Device::init* + * method will use floating-point numbers, but ion_main very well can. + * To make sure ion_main's potential usage of VFP registers doesn't bubble-up to + * start(), we isolate it in its very own non-inlined function call. + * - To avoid jumping on the external flash when it is shut down, we ensure + * there is no symbol references from the internal flash to the external + * flash except this jump. In order to do that, we isolate this + * jump in a symbol that we link in a special section separated from the + * internal flash section. We can than forbid cross references from the + * internal flash to the external flash. */ + +static void __attribute__((noinline)) jump_to_external_flash() { + external_flash_start(); +} + +void __attribute__((noinline)) abort_init() { + Ion::Device::Board::shutdownPeripherals(true); + Ion::Device::Board::initPeripherals(false); + Ion::Timing::msleep(100); + Ion::Backlight::init(); + Ion::LED::setColor(KDColorRed); + Ion::Backlight::setBrightness(180); +} + +void __attribute__((noinline)) abort_economy() { + int brightness = Ion::Backlight::brightness(); + bool plugged = Ion::USB::isPlugged(); + while (brightness > 0) { + brightness--; + Ion::Backlight::setBrightness(brightness); + Ion::Timing::msleep(50); + if(plugged || (!plugged && Ion::USB::isPlugged())){ + Ion::Backlight::setBrightness(180); + return; + } + } + Ion::Backlight::shutdown(); + while (1) { + Ion::Device::Power::sleepConfiguration(); + Ion::Device::WakeUp::onUSBPlugging(); + Ion::Device::WakeUp::onChargingEvent(); + Ion::Device::Power::internalFlashSuspend(true); + if (!plugged && Ion::USB::isPlugged()) { + break; + } + plugged = Ion::USB::isPlugged(); + }; + Ion::Device::Board::setStandardFrequency(Ion::Device::Board::Frequency::High); + Ion::Backlight::init(); + Ion::Backlight::setBrightness(180); +} + +void __attribute__((noinline)) abort_sleeping() { + if (Ion::Battery::level() != Ion::Battery::Charge::EMPTY) { + return; + } + // we don't use Ion::Power::suspend because we don't want to move the exam buffer into the internal + Ion::Device::Board::shutdownPeripherals(true); + bool plugged = Ion::USB::isPlugged(); + while (1) { + Ion::Device::Battery::initGPIO(); + Ion::Device::USB::initGPIO(); + Ion::Device::LED::init(); + Ion::Device::Power::sleepConfiguration(); + Ion::Device::Board::shutdownPeripherals(true); + Ion::Device::WakeUp::onUSBPlugging(); + Ion::Device::WakeUp::onChargingEvent(); + Ion::Device::Power::internalFlashSuspend(true); + Ion::Device::USB::initGPIO(); + if (!plugged && Ion::USB::isPlugged()) { + break; + } + plugged = Ion::USB::isPlugged(); + } + Ion::Device::Board::setStandardFrequency(Ion::Device::Board::Frequency::High); + abort_init(); +} + +void __attribute__((noinline)) abort_core(const char * text) { + Ion::Timing::msleep(100); + int counting; + while (true) { + counting = 0; + if (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) { + abort_sleeping(); + abort_screen(text); + } + + Ion::USB::enable(); + Ion::Battery::Charge previous_state = Ion::Battery::level(); + while (!Ion::USB::isEnumerated()) { + if (Ion::Battery::level() == Ion::Battery::Charge::LOW) { + if (previous_state != Ion::Battery::Charge::LOW) { + previous_state = Ion::Battery::Charge::LOW; + counting = 0; + } + Ion::Timing::msleep(500); + if (counting >= 20) { + abort_sleeping(); + abort_screen(text); + counting = -1; + } + counting++; + + } else { + if (previous_state == Ion::Battery::Charge::LOW) { + previous_state = Ion::Battery::level(); + counting = 0; + } + Ion::Timing::msleep(100); + if (counting >= 300) { + abort_economy(); + counting = -1; + } + counting++; + } + } + Ion::USB::DFU(false, false, 0); + } +} + +void __attribute__((noinline)) abort_screen(const char * text){ + KDRect screen = KDRect(0, 0, Ion::Display::Width, Ion::Display::Height); + Ion::Display::pushRectUniform(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height), KDColor::RGB24(0xffffff)); + KDContext* ctx = KDIonContext::sharedContext(); + ctx->setOrigin(KDPointZero); + ctx->setClippingRect(screen); + ctx->drawString("UPSILON CRASH", KDPoint(90, 10), KDFont::LargeFont, KDColorRed, KDColor::RGB24(0xffffff)); + ctx->drawString("An error occurred", KDPoint(10, 30), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("If you have some important data, please", KDPoint(10, 45), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("use bit.ly/upsiBackup to backup them.", KDPoint(10, 60), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("YOU WILL LOSE ALL YOUR DATA", KDPoint(10, 85), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("→ You can try to reboot by presssing the", KDPoint(10, 110), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("reset button at the back of the calculator", KDPoint(10, 125), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("→ If Upsilon keeps crashing, you can connect", KDPoint(10, 140), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("the calculator to a computer or a phone", KDPoint(10, 160), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("and try to reinstall Upsilon", KDPoint(10, 175), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString(text, KDPoint(220, 200), KDFont::SmallFont, KDColorRed, KDColor::RGB24(0xffffff)); +} + +void __attribute__((noinline)) abort() { + abort_init(); + abort_screen("HARDFAULT"); + abort_core("HARDFAULT"); +} + +void __attribute__((noinline)) nmi_abort() { + abort_init(); + abort_screen("NMIFAULT"); + abort_core("NMIFAULT"); +} + +void __attribute__((noinline)) bf_abort() { + abort_init(); + abort_screen("BUSFAULT"); + abort_core("BUSFAULT"); +} +void __attribute__((noinline)) uf_abort() { + abort_init(); + abort_screen("USAGEFAULT"); + abort_core("USAGEFAULT"); +} + +/* When 'start' is executed, the external flash is supposed to be shutdown. We + * thus forbid inlining to prevent executing this code from external flash + * (just in case 'start' was to be called from the external flash). */ + +void __attribute__((noinline)) start() { + /* This is where execution starts after reset. + * Many things are not initialized yet so the code here has to pay attention. */ + + /* Copy data section to RAM + * The data section is R/W but its initialization value matters. It's stored + * in Flash, but linked as if it were in RAM. Now's our opportunity to copy + * it. Note that until then the data section (e.g. global variables) contains + * garbage values and should not be used. */ + size_t dataSectionLength = (&_data_section_end_ram - &_data_section_start_ram); + memcpy(&_data_section_start_ram, &_data_section_start_flash, dataSectionLength); + + /* Zero-out the bss section in RAM + * Until we do, any uninitialized global variable will be unusable. */ + size_t bssSectionLength = (&_bss_section_end_ram - &_bss_section_start_ram); + memset(&_bss_section_start_ram, 0, bssSectionLength); + + /* Initialize the FPU as early as possible. + * For example, static C++ objects are very likely to manipulate float values */ + Ion::Device::Board::initFPU(); + + /* Call static C++ object constructors + * The C++ compiler creates an initialization function for each static object. + * The linker then stores the address of each of those functions consecutively + * between _init_array_start and _init_array_end. So to initialize all C++ + * static objects we just have to iterate between theses two addresses and + * call the pointed function. */ +#define SUPPORT_CPP_GLOBAL_CONSTRUCTORS 0 +#if SUPPORT_CPP_GLOBAL_CONSTRUCTORS + for (cxx_constructor* c = &_init_array_start; c < &_init_array_end; c++) { + (*c)(); + } +#else + /* In practice, static initialized objects are a terrible idea. Since the init + * order is not specified, most often than not this yields the dreaded static + * init order fiasco. How about bypassing the issue altogether? */ + if (&_init_array_start != &_init_array_end) { + abort(); + } +#endif + + Ion::Device::Board::init(); + + /* At this point, we initialized clocks and the external flash but no other + * peripherals. */ + + jump_to_external_flash(); + + abort(); +} + +void __attribute__((interrupt, noinline)) isr_systick() { + auto t = Ion::Device::Timing::MillisElapsed; + t++; + Ion::Device::Timing::MillisElapsed = t; +} diff --git a/ion/src/device/n0100/drivers/power.cpp b/ion/src/device/n0100/drivers/power.cpp index 4f7a5b8fdf8..e14012b7116 100644 --- a/ion/src/device/n0100/drivers/power.cpp +++ b/ion/src/device/n0100/drivers/power.cpp @@ -1,5 +1,6 @@ #include #include +#include #include namespace Ion { @@ -29,6 +30,38 @@ void configWakeUp() { Device::WakeUp::onUSBPlugging(); } +void __attribute__((noinline)) internalFlashSuspend(bool isLEDActive) { + // Shutdown all clocks (except the ones used by LED if active) + Device::Board::shutdownClocks(isLEDActive); + + Device::Power::enterLowPowerMode(); + + /* A hardware event triggered a wake up, we determine if the device should + * wake up. We wake up when: + * - only the power key was down + * - the unplugged device was plugged + * - the battery stopped charging */ + Device::Board::initClocks(); +} + +void __attribute__((noinline)) internalFlashStandby() { + Device::Board::shutdownClocks(); + Device::Power::enterLowPowerMode(); + Device::Reset::coreWhilePlugged(); +} + +void enterLowPowerMode() { + /* To enter sleep, we need to issue a WFE instruction, which waits for the + * event flag to be set and then clears it. However, the event flag might + * already be on. So the safest way to make sure we actually wait for a new + * event is to force the event flag to on (SEV instruction), use a first WFE + * to clear it, and then a second WFE to wait for a _new_ event. */ + asm("sev"); + asm("wfe"); + asm("nop"); + asm("wfe"); +} + } } } diff --git a/ion/src/device/n0100/platform_info.cpp b/ion/src/device/n0100/platform_info.cpp new file mode 100644 index 00000000000..28a9b04dc66 --- /dev/null +++ b/ion/src/device/n0100/platform_info.cpp @@ -0,0 +1,143 @@ +#include +#include + +#ifndef PATCH_LEVEL +#error This file expects PATCH_LEVEL to be defined +#endif + +#ifndef EPSILON_VERSION +#error This file expects EPSILON_VERSION to be defined +#endif + +#ifndef OMEGA_VERSION +#error This file expects OMEGA_VERSION to be defined +#endif + +#ifndef UPSILON_VERSION +#error This file expects UPSILON_VERSION to be defined +#endif + +#ifndef HEADER_SECTION +#define HEADER_SECTION +#endif + +namespace Ion { +extern char staticStorageArea[]; +} +constexpr void * storageAddress = &(Ion::staticStorageArea); + +class PlatformInfo { +public: + constexpr PlatformInfo() : + m_header(Magic), + m_version{EPSILON_VERSION}, + m_patchLevel{PATCH_LEVEL}, + m_storageAddress(storageAddress), + m_storageSize(Ion::Storage::k_storageSize), + m_footer(Magic), + m_omegaMagicHeader(OmegaMagic), + m_omegaVersion{OMEGA_VERSION}, +#ifdef OMEGA_USERNAME + m_username{OMEGA_USERNAME}, +#else + m_username{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, +#endif + m_omegaMagicFooter(OmegaMagic), + m_upsilonMagicHeader(UpsilonMagic), + m_upsilonVersion{UPSILON_VERSION}, + m_osType(OSType), + m_upsilonMagicFooter(UpsilonMagic) { } + const char * version() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_version; + } + const char * upsilonVersion() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + assert(m_upsilonMagicHeader == UpsilonMagic); + assert(m_upsilonMagicFooter == UpsilonMagic); + return m_upsilonVersion; + } + const char * omegaVersion() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_omegaVersion; + } + const volatile char * username() const volatile { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_username; + } + const char * patchLevel() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_patchLevel; + } +private: + constexpr static uint32_t Magic = 0xDEC00DF0; + constexpr static uint32_t OmegaMagic = 0xEFBEADDE; + constexpr static uint32_t UpsilonMagic = 0x55707369; + constexpr static uint32_t OSType = 0x79827178; + uint32_t m_header; + const char m_version[8]; + const char m_patchLevel[8]; + void * m_storageAddress; + size_t m_storageSize; + uint32_t m_footer; + uint32_t m_omegaMagicHeader; + const char m_omegaVersion[16]; + const volatile char m_username[16]; + uint32_t m_omegaMagicFooter; + uint32_t m_upsilonMagicHeader; + const char m_upsilonVersion[16]; + uint32_t m_osType; + uint32_t m_upsilonMagicFooter; + +}; + +const PlatformInfo HEADER_SECTION platform_infos; + +const char * Ion::softwareVersion() { + return platform_infos.version(); +} + +const char * Ion::upsilonVersion() { + return platform_infos.upsilonVersion(); +} + +const char * Ion::omegaVersion() { + return platform_infos.omegaVersion(); +} + +const volatile char * Ion::username() { + return platform_infos.username(); +} + +const char * Ion::patchLevel() { + return platform_infos.patchLevel(); +} + +void Ion::updateSlotInfo() { + +} diff --git a/ion/src/device/n0110/Makefile b/ion/src/device/n0110/Makefile index 3e6297f6aaf..11c3b224f94 100644 --- a/ion/src/device/n0110/Makefile +++ b/ion/src/device/n0110/Makefile @@ -8,4 +8,12 @@ ion_device_src += $(addprefix ion/src/device/n0110/drivers/, \ usb.cpp \ ) +ion_device_src += $(addprefix ion/src/device/n0110/boot/, \ + rt0.cpp \ +) + +ion_device_src += $(addprefix ion/src/device/n0110/, \ + platform_info.cpp \ +) + LDSCRIPT ?= ion/src/device/n0110/flash.ld diff --git a/ion/src/device/shared/boot/rt0.cpp b/ion/src/device/n0110/boot/rt0.cpp similarity index 97% rename from ion/src/device/shared/boot/rt0.cpp rename to ion/src/device/n0110/boot/rt0.cpp index a2949b3c0d4..4e2f22a5641 100644 --- a/ion/src/device/shared/boot/rt0.cpp +++ b/ion/src/device/n0110/boot/rt0.cpp @@ -1,28 +1,20 @@ -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include -#include +#include +#include #include +#include +#include +#include #include -#include -#include -#include -#include #include #include #include #include -#include -#include - -#include "../drivers/board.h" -#include "../drivers/reset.h" -#include "../drivers/rtc.h" -#include "../drivers/timing.h" -#include "isr.h" typedef void (*cxx_constructor)(); diff --git a/ion/src/device/n0110/drivers/board.cpp b/ion/src/device/n0110/drivers/board.cpp index 24b8cfc3648..ae6b8ac0638 100644 --- a/ion/src/device/n0110/drivers/board.cpp +++ b/ion/src/device/n0110/drivers/board.cpp @@ -24,6 +24,28 @@ namespace Board { using namespace Regs; +void bootloaderMPU() { + // 1. Disable the MPU + // 1.1 Memory barrier + Cache::dmb(); + + // 1.3 Disable the MPU and clear the control register + MPU.CTRL()->setENABLE(false); + + MPU.RNR()->setREGION(7); + MPU.RBAR()->setADDR(0x90000000); + MPU.RASR()->setXN(false); + MPU.RASR()->setENABLE(true); + + // 2.3 Enable MPU + MPU.CTRL()->setENABLE(true); + + // 3. Data/instruction synchronisation barriers to ensure that the new MPU configuration is used by subsequent instructions. + Cache::disable(); + Cache::dsb(); + Cache::isb(); +} + void initMPU() { // 1. Disable the MPU // 1.1 Memory barrier diff --git a/ion/src/device/n0110/drivers/external_flash.cpp b/ion/src/device/n0110/drivers/external_flash.cpp index 34de8b591a6..612efc01796 100644 --- a/ion/src/device/n0110/drivers/external_flash.cpp +++ b/ion/src/device/n0110/drivers/external_flash.cpp @@ -405,7 +405,7 @@ int SectorAtAddress(uint32_t address) { i = address >> NumberOfAddressBitsIn32KbyteBlock; if (i >= 1) { i = Config::NumberOf4KSectors + i - 1; - assert(i >= 0 && i <= Config::NumberOf32KSectors); + assert(i >= Config::NumberOf4KSectors && i <= Config::NumberOf4KSectors + Config::NumberOf32KSectors); return i; } i = address >> NumberOfAddressBitsIn4KbyteBlock; @@ -471,6 +471,7 @@ void __attribute__((noinline)) WriteMemory(uint8_t * destination, const uint8_t if (Config::NumberOfSectors == 0) { return; } + destination -= ExternalFlash::Config::StartAddress; unset_memory_mapped_mode(); /* Each 256-byte page of the external flash memory (contained in a previously erased area) * may be programmed in burst mode with a single Page Program instruction. diff --git a/ion/src/device/n0110/drivers/power.cpp b/ion/src/device/n0110/drivers/power.cpp index f8f657dea07..1286bc88363 100644 --- a/ion/src/device/n0110/drivers/power.cpp +++ b/ion/src/device/n0110/drivers/power.cpp @@ -1,6 +1,8 @@ #include +#include #include #include +#include #include #include @@ -56,6 +58,43 @@ void standbyConfiguration() { CORTEX.SCR()->setSLEEPDEEP(true); // Allow Cortex-M7 deepsleep state } +void __attribute__((noinline)) internalFlashSuspend(bool isLEDActive) { + // Shutdown the external flash + Device::ExternalFlash::shutdown(); + // Shutdown all clocks (except the ones used by LED if active) + Device::Board::shutdownClocks(isLEDActive); + + Device::Power::enterLowPowerMode(); + + /* A hardware event triggered a wake up, we determine if the device should + * wake up. We wake up when: + * - only the power key was down + * - the unplugged device was plugged + * - the battery stopped charging */ + Device::Board::initClocks(); + // Init external flash + Device::ExternalFlash::init(); +} + +void __attribute__((noinline)) internalFlashStandby() { + Device::ExternalFlash::shutdown(); + Device::Board::shutdownClocks(); + Device::Power::enterLowPowerMode(); + Device::Reset::coreWhilePlugged(); +} + +void enterLowPowerMode() { + /* To enter sleep, we need to issue a WFE instruction, which waits for the + * event flag to be set and then clears it. However, the event flag might + * already be on. So the safest way to make sure we actually wait for a new + * event is to force the event flag to on (SEV instruction), use a first WFE + * to clear it, and then a second WFE to wait for a _new_ event. */ + asm("sev"); + asm("wfe"); + asm("nop"); + asm("wfe"); +} + } } } diff --git a/ion/src/device/n0110/drivers/power.h b/ion/src/device/n0110/drivers/power.h index a3d142bc1c2..1fbc55acf93 100644 --- a/ion/src/device/n0110/drivers/power.h +++ b/ion/src/device/n0110/drivers/power.h @@ -8,6 +8,7 @@ namespace Device { namespace Power { void standbyConfiguration(); +void internalFlashSuspend(bool isLEDActive); } } diff --git a/ion/src/device/n0110/internal_flash.ld b/ion/src/device/n0110/internal_flash.ld index 2e1ce78bbb1..2094a6469d9 100644 --- a/ion/src/device/n0110/internal_flash.ld +++ b/ion/src/device/n0110/internal_flash.ld @@ -1,11 +1,15 @@ /* Same as flash.ld but everything is linked in internal flash */ MEMORY { - INTERNAL_FLASH (rx) : ORIGIN = 0x00200000, LENGTH = 64K + INTERNAL_FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K } STACK_SIZE = 32K; +TRAMPOLINES_OFFSET = 0xE000; +CUSTOM_TRAMPOLINES_OFFSET = 64K - 64; +FLASH_SECOND_SECTOR_OFFSET = 16K; +FLASH_SECOND_SECTOR_SIZE = 16K; SECTIONS { .isr_vector_table ORIGIN(INTERNAL_FLASH) : { @@ -29,6 +33,20 @@ SECTIONS { KEEP(*(.header)) } >INTERNAL_FLASH + .rodata : { + . = ALIGN(4); + *(.rodata) + *(.rodata.*) + } >INTERNAL_FLASH + + .exam_mode_buffer ORIGIN(INTERNAL_FLASH) + FLASH_SECOND_SECTOR_OFFSET : { + _exam_mode_buffer_start = .; + KEEP(*(.exam_mode_buffer)) + /* Note: We don't increment "." here, we set it. */ + . = ORIGIN(INTERNAL_FLASH) + FLASH_SECOND_SECTOR_OFFSET + FLASH_SECOND_SECTOR_SIZE; + _exam_mode_buffer_end = .; + } >INTERNAL_FLASH + .text : { . = ALIGN(4); *(.text) @@ -42,12 +60,6 @@ SECTIONS { _init_array_end = .; } >INTERNAL_FLASH - .rodata : { - . = ALIGN(4); - *(.rodata) - *(.rodata.*) - } >INTERNAL_FLASH - .data : { /* The data section is written to Flash but linked as if it were in RAM. * @@ -69,6 +81,16 @@ SECTIONS { _data_section_end_ram = .; } >SRAM AT> INTERNAL_FLASH + .trampolines_table : { + . = ORIGIN(INTERNAL_FLASH) + TRAMPOLINES_OFFSET; + KEEP(*(.trampolines_table)); + } > INTERNAL_FLASH + + .custom_trampolines_table : { + . = ORIGIN(INTERNAL_FLASH) + CUSTOM_TRAMPOLINES_OFFSET; + KEEP(*(.custom_trampolines_table)); + } > INTERNAL_FLASH + .bss : { /* The bss section contains data for all uninitialized variables * So like the .data section, it will go in RAM, but unlike the data section diff --git a/ion/src/shared/platform_info.cpp b/ion/src/device/n0110/platform_info.cpp similarity index 72% rename from ion/src/shared/platform_info.cpp rename to ion/src/device/n0110/platform_info.cpp index b5ab46842fe..5edbb65dbfe 100644 --- a/ion/src/shared/platform_info.cpp +++ b/ion/src/device/n0110/platform_info.cpp @@ -35,43 +35,43 @@ class PlatformInfo { m_storageAddress(storageAddress), m_storageSize(Ion::Storage::k_storageSize), m_footer(Magic), - m_ohm_header(OmegaMagic), + m_omegaMagicHeader(OmegaMagic), m_OmegaVersion{OMEGA_VERSION}, #ifdef OMEGA_USERNAME m_username{OMEGA_USERNAME}, #else m_username{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, #endif - m_ohm_footer(OmegaMagic), - m_ups_header(UpsilonMagic), + m_omegaMagicFooter(OmegaMagic), + m_upsilonMagicHeader(UpsilonMagic), m_UpsilonVersion{UPSILON_VERSION}, m_osType(OSType), - m_ups_footer(UpsilonMagic) { } + m_upsilonMagicFooter(UpsilonMagic) { } const char * version() const { assert(m_storageAddress != nullptr); assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_version; } - const char * UpsilonVersion() const { + const char * upsilonVersion() const { assert(m_storageAddress != nullptr); assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_UpsilonVersion; - } - const char * OmegaVersion() const { + } + const char * omegaVersion() const { assert(m_storageAddress != nullptr); assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_OmegaVersion; } const volatile char * username() const volatile { @@ -79,8 +79,8 @@ class PlatformInfo { assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_username; } const char * patchLevel() const { @@ -88,8 +88,8 @@ class PlatformInfo { assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_patchLevel; } private: @@ -103,14 +103,14 @@ class PlatformInfo { void * m_storageAddress; size_t m_storageSize; uint32_t m_footer; - uint32_t m_ohm_header; + uint32_t m_omegaMagicHeader; const char m_OmegaVersion[16]; const volatile char m_username[16]; - uint32_t m_ohm_footer; - uint32_t m_ups_header; + uint32_t m_omegaMagicFooter; + uint32_t m_upsilonMagicHeader; const char m_UpsilonVersion[16]; uint32_t m_osType; - uint32_t m_ups_footer; + uint32_t m_upsilonMagicFooter; }; @@ -120,12 +120,12 @@ const char * Ion::softwareVersion() { return platform_infos.version(); } -const char * Ion::UpsilonVersion() { - return platform_infos.UpsilonVersion(); +const char * Ion::upsilonVersion() { + return platform_infos.upsilonVersion(); } -const char * Ion::OmegaVersion() { - return platform_infos.OmegaVersion(); +const char * Ion::omegaVersion() { + return platform_infos.omegaVersion(); } const volatile char * Ion::username() { @@ -135,3 +135,7 @@ const volatile char * Ion::username() { const char * Ion::patchLevel() { return platform_infos.patchLevel(); } + +void Ion::updateSlotInfo() { + +} diff --git a/ion/src/device/shared/boot/Makefile b/ion/src/device/shared/boot/Makefile index 2bb8d22d76b..17dc4a4adbd 100644 --- a/ion/src/device/shared/boot/Makefile +++ b/ion/src/device/shared/boot/Makefile @@ -1,4 +1,3 @@ ion_device_src += $(addprefix ion/src/device/shared/boot/, \ isr.c \ - rt0.cpp \ ) diff --git a/ion/src/device/shared/boot/isr.h b/ion/src/device/shared/boot/isr.h index cec396388fb..05812cd60a3 100644 --- a/ion/src/device/shared/boot/isr.h +++ b/ion/src/device/shared/boot/isr.h @@ -8,7 +8,6 @@ extern "C" { void bf_abort(); void uf_abort(); void nmi_abort(); -// Here and below, we are doing operations on the abort handler, not the opposite void abort_init(); void abort_core(const char *); void abort_screen(const char *); diff --git a/ion/src/device/shared/drivers/Makefile b/ion/src/device/shared/drivers/Makefile index 2db043c5fa4..67bfb6ff5ad 100644 --- a/ion/src/device/shared/drivers/Makefile +++ b/ion/src/device/shared/drivers/Makefile @@ -25,5 +25,6 @@ ion_device_src += $(addprefix ion/src/device/shared/drivers/, \ swd.cpp \ timing.cpp \ usb.cpp \ + usb_desc.cpp \ wakeup.cpp \ ) diff --git a/ion/src/device/shared/drivers/board.h b/ion/src/device/shared/drivers/board.h index 61b63b2b31a..8d1d1bbb6fa 100644 --- a/ion/src/device/shared/drivers/board.h +++ b/ion/src/device/shared/drivers/board.h @@ -9,6 +9,7 @@ namespace Board { void init(); +void bootloaderMPU(); void initFPU(); void initClocks(); void shutdownClocks(bool keepLEDAwake = false); diff --git a/ion/src/device/shared/drivers/flash.cpp b/ion/src/device/shared/drivers/flash.cpp index 6f569cd8929..692bd4df8ba 100644 --- a/ion/src/device/shared/drivers/flash.cpp +++ b/ion/src/device/shared/drivers/flash.cpp @@ -46,7 +46,7 @@ void WriteMemory(uint8_t * destination, uint8_t * source, size_t length) { if (SectorAtAddress((uint32_t)destination) < InternalFlash::Config::NumberOfSectors) { InternalFlash::WriteMemory(destination, source, length); } else { - ExternalFlash::WriteMemory(destination - ExternalFlash::Config::StartAddress, source, length); + ExternalFlash::WriteMemory(destination, source, length); } } diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index 0e5b35267b5..bba6444c33d 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -100,31 +100,6 @@ namespace Power { // Public Power methods using namespace Device::Regs; -void __attribute__((noinline)) internalFlashSuspend(bool isLEDActive) { - // Shutdown the external flash - Device::ExternalFlash::shutdown(); - // Shutdown all clocks (except the ones used by LED if active) - Device::Board::shutdownClocks(isLEDActive); - - Device::Power::enterLowPowerMode(); - - /* A hardware event triggered a wake up, we determine if the device should - * wake up. We wake up when: - * - only the power key was down - * - the unplugged device was plugged - * - the battery stopped charging */ - Device::Board::initClocks(); - // Init external flash - Device::ExternalFlash::init(); -} - -void __attribute__((noinline)) internalFlashStandby() { - Device::ExternalFlash::shutdown(); - Device::Board::shutdownClocks(); - Device::Power::enterLowPowerMode(); - Device::Reset::coreWhilePlugged(); -} - void stopConfiguration() { PWR.CR()->setMRUDS(true); // Main regulator in Low Voltage and Flash memory in Deep Sleep mode when the device is in Stop mode PWR.CR()->setLPUDS(true); // Low-power regulator in under-drive mode if LPDS bit is set and Flash memory in power-down when the device is in Stop under-drive mode @@ -163,18 +138,6 @@ void waitUntilOnOffKeyReleased() { Timing::msleep(100); } -void enterLowPowerMode() { - /* To enter sleep, we need to issue a WFE instruction, which waits for the - * event flag to be set and then clears it. However, the event flag might - * already be on. So the safest way to make sure we actually wait for a new - * event is to force the event flag to on (SEV instruction), use a first WFE - * to clear it, and then a second WFE to wait for a _new_ event. */ - asm("sev"); - asm("wfe"); - asm("nop"); - asm("wfe"); -} - } } } diff --git a/ion/src/device/shared/drivers/usb.h b/ion/src/device/shared/drivers/usb.h index df27513e7d2..598aa4e9f16 100644 --- a/ion/src/device/shared/drivers/usb.h +++ b/ion/src/device/shared/drivers/usb.h @@ -12,6 +12,7 @@ void initGPIO(); void shutdownGPIO(); void initOTG(); void shutdownOTG(); +const char* stringDescriptor(); } } diff --git a/ion/src/device/shared/drivers/usb_desc.cpp b/ion/src/device/shared/drivers/usb_desc.cpp new file mode 100644 index 00000000000..3d87d72fe9a --- /dev/null +++ b/ion/src/device/shared/drivers/usb_desc.cpp @@ -0,0 +1,15 @@ +#include "usb.h" +#include +#include + +namespace Ion { +namespace Device { +namespace USB { + +const char* stringDescriptor() { + return Config::InterfaceStringDescriptor; +} + +} +} +} diff --git a/ion/src/device/shared/usb/Makefile b/ion/src/device/shared/usb/Makefile index a79991d7459..02e931b6a21 100644 --- a/ion/src/device/shared/usb/Makefile +++ b/ion/src/device/shared/usb/Makefile @@ -66,6 +66,7 @@ ion_device_dfu_src += $(addprefix ion/src/device/shared/drivers/, \ swd.cpp \ timing.cpp \ usb.cpp \ + usb_desc.cpp \ wakeup.cpp \ ) diff --git a/ion/src/device/shared/usb/calculator.h b/ion/src/device/shared/usb/calculator.h index 5a3d898ef67..a2a3581ea20 100644 --- a/ion/src/device/shared/usb/calculator.h +++ b/ion/src/device/shared/usb/calculator.h @@ -3,6 +3,7 @@ #include #include +#include #include #include "dfu_interface.h" #include "stack/device.h" @@ -94,7 +95,7 @@ class Calculator : public Device { m_manufacturerStringDescriptor("NumWorks"), m_productStringDescriptor("NumWorks Calculator"), m_serialNumberStringDescriptor(serialNumber), - m_interfaceStringDescriptor(Config::InterfaceStringDescriptor), + m_interfaceStringDescriptor(stringDescriptor()), //m_interfaceStringDescriptor("@SRAM/0x20000000/01*256Ke"), /* Switch to this descriptor to use dfu-util to write in the SRAM. * FIXME Should be an alternate Interface. */ diff --git a/ion/src/device/shared/usb/dfu_interface.cpp b/ion/src/device/shared/usb/dfu_interface.cpp index a473e142294..836153d4a66 100644 --- a/ion/src/device/shared/usb/dfu_interface.cpp +++ b/ion/src/device/shared/usb/dfu_interface.cpp @@ -296,6 +296,7 @@ void DFUInterface::writeOnMemory() { leaveDFUAndReset(false); return; } + m_largeBuffer[k_externalMagicAddress + i] = 0; } } // We only check the first packet because there is some predictable data in there, diff --git a/ion/src/device/shared/usb/dfu_relocated.cpp b/ion/src/device/shared/usb/dfu_relocated.cpp index 9b68244c488..77433ee4c7a 100644 --- a/ion/src/device/shared/usb/dfu_relocated.cpp +++ b/ion/src/device/shared/usb/dfu_relocated.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -14,6 +15,7 @@ namespace USB { typedef void (*PollFunctionPointer)(bool exitWithKeyboard, bool unlocked, int level); void DFU(bool exitWithKeyboard, bool unlocked, int level) { + Ion::updateSlotInfo(); /* DFU transfers can serve two purposes: * - Transfering RAM data between the machine and a host, e.g. Python scripts diff --git a/ion/src/device/shared/usb/dfu_xip.cpp b/ion/src/device/shared/usb/dfu_xip.cpp index dce014cd219..b94a77bd56e 100644 --- a/ion/src/device/shared/usb/dfu_xip.cpp +++ b/ion/src/device/shared/usb/dfu_xip.cpp @@ -1,9 +1,11 @@ +#include #include "calculator.h" namespace Ion { namespace USB { void DFU(bool exitWithKeyboard, bool unlocked, int level) { + Ion::updateSlotInfo(); Ion::Device::USB::Calculator::PollAndReset(exitWithKeyboard, unlocked, level); } diff --git a/ion/src/shared/dummy/usb.cpp b/ion/src/shared/dummy/usb.cpp index 4f3554dff94..36732158449 100644 --- a/ion/src/shared/dummy/usb.cpp +++ b/ion/src/shared/dummy/usb.cpp @@ -1,4 +1,4 @@ -#include +#include namespace Ion { namespace USB { @@ -25,3 +25,7 @@ void disable() { } } + +void Ion::updateSlotInfo() { + +} diff --git a/ion/src/simulator/Makefile b/ion/src/simulator/Makefile index f98981af2c5..caa9c1028a8 100644 --- a/ion/src/simulator/Makefile +++ b/ion/src/simulator/Makefile @@ -25,6 +25,7 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ keyboard.cpp \ layout.cpp \ main.cpp \ + platform_info.cpp \ random.cpp \ timing.cpp \ window.cpp \ diff --git a/ion/src/simulator/shared/platform_info.cpp b/ion/src/simulator/shared/platform_info.cpp new file mode 100644 index 00000000000..5edbb65dbfe --- /dev/null +++ b/ion/src/simulator/shared/platform_info.cpp @@ -0,0 +1,141 @@ +#include +#include + +#ifndef PATCH_LEVEL +#error This file expects PATCH_LEVEL to be defined +#endif + +#ifndef EPSILON_VERSION +#error This file expects EPSILON_VERSION to be defined +#endif + +#ifndef OMEGA_VERSION +#error This file expects OMEGA_VERSION to be defined +#endif + +#ifndef UPSILON_VERSION +#error This file expects UPSILON_VERSION to be defined +#endif + +#ifndef HEADER_SECTION +#define HEADER_SECTION +#endif + +namespace Ion { +extern char staticStorageArea[]; +} +constexpr void * storageAddress = &(Ion::staticStorageArea); + +class PlatformInfo { +public: + constexpr PlatformInfo() : + m_header(Magic), + m_version{EPSILON_VERSION}, + m_patchLevel{PATCH_LEVEL}, + m_storageAddress(storageAddress), + m_storageSize(Ion::Storage::k_storageSize), + m_footer(Magic), + m_omegaMagicHeader(OmegaMagic), + m_OmegaVersion{OMEGA_VERSION}, +#ifdef OMEGA_USERNAME + m_username{OMEGA_USERNAME}, +#else + m_username{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, +#endif + m_omegaMagicFooter(OmegaMagic), + m_upsilonMagicHeader(UpsilonMagic), + m_UpsilonVersion{UPSILON_VERSION}, + m_osType(OSType), + m_upsilonMagicFooter(UpsilonMagic) { } + const char * version() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_version; + } + const char * upsilonVersion() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_UpsilonVersion; + } + const char * omegaVersion() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_OmegaVersion; + } + const volatile char * username() const volatile { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_username; + } + const char * patchLevel() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_patchLevel; + } +private: + constexpr static uint32_t Magic = 0xDEC00DF0; + constexpr static uint32_t OmegaMagic = 0xEFBEADDE; + constexpr static uint32_t UpsilonMagic = 0x55707369; + constexpr static uint32_t OSType = 0x79827178; + uint32_t m_header; + const char m_version[8]; + const char m_patchLevel[8]; + void * m_storageAddress; + size_t m_storageSize; + uint32_t m_footer; + uint32_t m_omegaMagicHeader; + const char m_OmegaVersion[16]; + const volatile char m_username[16]; + uint32_t m_omegaMagicFooter; + uint32_t m_upsilonMagicHeader; + const char m_UpsilonVersion[16]; + uint32_t m_osType; + uint32_t m_upsilonMagicFooter; + +}; + +const PlatformInfo HEADER_SECTION platform_infos; + +const char * Ion::softwareVersion() { + return platform_infos.version(); +} + +const char * Ion::upsilonVersion() { + return platform_infos.upsilonVersion(); +} + +const char * Ion::omegaVersion() { + return platform_infos.omegaVersion(); +} + +const volatile char * Ion::username() { + return platform_infos.username(); +} + +const char * Ion::patchLevel() { + return platform_infos.patchLevel(); +} + +void Ion::updateSlotInfo() { + +} diff --git a/kandinsky/Makefile b/kandinsky/Makefile index 88cb6885af2..3b3f45628eb 100644 --- a/kandinsky/Makefile +++ b/kandinsky/Makefile @@ -94,8 +94,8 @@ kandinsky_src += $(addprefix kandinsky/fonts/, \ SmallFont.ttf \ ) -simple_kandinsky_src = $(kandinsky_src) default_kandinsky_src = $(kandinsky_src) +simple_kandinsky_src = $(kandinsky_src) $(eval $(call raster_font,SmallFont,SmallFontSimple,0,12,7,14)) $(eval $(call raster_font,LargeFont,LargeFontSimple,0,16,10,18)) diff --git a/themes/icons.json b/themes/icons.json index ee6985c86b7..953bca5cf27 100644 --- a/themes/icons.json +++ b/themes/icons.json @@ -41,5 +41,8 @@ "apps/probability/images/normal_icon.png" : "probability/normal_icon.png", "apps/probability/images/poisson_icon.png" : "probability/poisson_icon.png", "apps/probability/images/student_icon.png" : "probability/student_icon.png", - "apps/probability/images/uniform_icon.png" : "probability/uniform_icon.png" + "apps/probability/images/uniform_icon.png" : "probability/uniform_icon.png", + + "bootloader/cable.png": "bootloader/cable.png", + "bootloader/computer.png": "bootloader/computer.png" } diff --git a/themes/themes/local/epsilon_dark/bootloader/cable.png b/themes/themes/local/epsilon_dark/bootloader/cable.png new file mode 100644 index 0000000000000000000000000000000000000000..ac0996ca74685d68c185ee0bbc4cc3a05cdb6aec GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^Y(VV7!2~3A?JidUsVYww$B>FS$tej5D^{!s2nb9_ zNT?`y?&$8$F3il#+`^x3V3@$52{5#SsAJA*S1~lk!zb;$B{EN4o7dwGqi2xG= YtES2ht{+eQfv#ZiboFyt=akR{0N5@#iU0rr literal 0 HcmV?d00001 diff --git a/themes/themes/local/epsilon_dark/bootloader/computer.png b/themes/themes/local/epsilon_dark/bootloader/computer.png new file mode 100644 index 0000000000000000000000000000000000000000..d30a99af2af41be5ed19fed9df7ea01d38eb3acb GIT binary patch literal 357 zcmeAS@N?(olHy`uVBq!ia0vp^!9c9f!2~1)3=-cmFfi(Px;TbZ%z1lDvFNaYK7}4lH07&=AltcwxxJ!ozZ6At1t`aiZjyXlCbA z&vN@|r8b}I^4L0=aSo2B8^(o^QSwf_z?GwA>L^kc)t8E*qE z?Lzwar#^Om`XhiyW<9qeLqm|(|0z@NMCHYLyMC&+;D45|Yv1WjTJwYJz4UTqj=%aE zU;F*L_~++mPA$J=slN11l}}h{tTDrbIj`ql%a5}2R%Chp=eqg(ke-W{>CvzE{%0x` XaGfW8;hGOH_!vB0{an^LB{Ts5SyhFS$tej5D^{!s2nb9_ zNT?`y?&$8$F3il#+`^x3V3@$52{5#SsAJA*S1~lk!zb;$B{EN4o7dwGqi2xG= YtES2ht{+eQfv#ZiboFyt=akR{0N5@#iU0rr literal 0 HcmV?d00001 diff --git a/themes/themes/local/epsilon_light/bootloader/computer.png b/themes/themes/local/epsilon_light/bootloader/computer.png new file mode 100644 index 0000000000000000000000000000000000000000..d30a99af2af41be5ed19fed9df7ea01d38eb3acb GIT binary patch literal 357 zcmeAS@N?(olHy`uVBq!ia0vp^!9c9f!2~1)3=-cmFfi(Px;TbZ%z1lDvFNaYK7}4lH07&=AltcwxxJ!ozZ6At1t`aiZjyXlCbA z&vN@|r8b}I^4L0=aSo2B8^(o^QSwf_z?GwA>L^kc)t8E*qE z?Lzwar#^Om`XhiyW<9qeLqm|(|0z@NMCHYLyMC&+;D45|Yv1WjTJwYJz4UTqj=%aE zU;F*L_~++mPA$J=slN11l}}h{tTDrbIj`ql%a5}2R%Chp=eqg(ke-W{>CvzE{%0x` XaGfW8;hGOH_!vB0{an^LB{Ts5Syhk!b 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 literal 0 HcmV?d00001 diff --git a/themes/themes/local/omega_dark/bootloader/computer.png b/themes/themes/local/omega_dark/bootloader/computer.png new file mode 100644 index 0000000000000000000000000000000000000000..0b5a578e6962685f14dc8c43866fec08c1ac2c71 GIT binary patch literal 10338 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 Vj8me+;?_!lmYTk5g|bcf{{Zqm*T(<= literal 0 HcmV?d00001 diff --git a/themes/themes/local/omega_light/bootloader/cable.png b/themes/themes/local/omega_light/bootloader/cable.png new file mode 100644 index 0000000000000000000000000000000000000000..cf2c2f8455e68d2fcd19efeee26dd2f7f32b4891 GIT binary patch literal 5586 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 literal 0 HcmV?d00001 diff --git a/themes/themes/local/omega_light/bootloader/computer.png b/themes/themes/local/omega_light/bootloader/computer.png new file mode 100644 index 0000000000000000000000000000000000000000..0b5a578e6962685f14dc8c43866fec08c1ac2c71 GIT binary patch literal 10338 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 Vj8me+;?_!lmYTk5g|bcf{{Zqm*T(<= literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/bootloader/cable.png b/themes/themes/local/upsilon_light/bootloader/cable.png new file mode 100644 index 0000000000000000000000000000000000000000..ac0996ca74685d68c185ee0bbc4cc3a05cdb6aec GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^Y(VV7!2~3A?JidUsVYww$B>FS$tej5D^{!s2nb9_ zNT?`y?&$8$F3il#+`^x3V3@$52{5#SsAJA*S1~lk!zb;$B{EN4o7dwGqi2xG= YtES2ht{+eQfv#ZiboFyt=akR{0N5@#iU0rr literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/bootloader/computer.png b/themes/themes/local/upsilon_light/bootloader/computer.png new file mode 100644 index 0000000000000000000000000000000000000000..d30a99af2af41be5ed19fed9df7ea01d38eb3acb GIT binary patch literal 357 zcmeAS@N?(olHy`uVBq!ia0vp^!9c9f!2~1)3=-cmFfi(Px;TbZ%z1lDvFNaYK7}4lH07&=AltcwxxJ!ozZ6At1t`aiZjyXlCbA z&vN@|r8b}I^4L0=aSo2B8^(o^QSwf_z?GwA>L^kc)t8E*qE z?Lzwar#^Om`XhiyW<9qeLqm|(|0z@NMCHYLyMC&+;D45|Yv1WjTJwYJz4UTqj=%aE zU;F*L_~++mPA$J=slN11l}}h{tTDrbIj`ql%a5}2R%Chp=eqg(ke-W{>CvzE{%0x` XaGfW8;hGOH_!vB0{an^LB{Ts5Syh Date: Sat, 19 Mar 2022 17:59:07 +0100 Subject: [PATCH 181/355] [ion] Fixed CI --- ion/src/shared/storage.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ion/src/shared/storage.cpp b/ion/src/shared/storage.cpp index c3dd9fcc078..50a8a4c7007 100644 --- a/ion/src/shared/storage.cpp +++ b/ion/src/shared/storage.cpp @@ -14,8 +14,14 @@ Storage * Storage::sharedStorage() { } size_t Storage::availableSize() { - if (m_trashRecord != NULL) { - return InternalStorage::availableSize() + sizeof(record_size_t) + m_trashRecord.value().size; + if (m_trashRecord != Record()) { + int bufferSize = 0; + for (char * p : *this) { + if (Record(fullNameOfRecordStarting(p)) != m_trashRecord) { + bufferSize += sizeOfRecordStarting(p); + } + } + return k_storageSize-bufferSize-sizeof(record_size_t); } else { return InternalStorage::availableSize(); } From 42343d562c7c44f12a471a00249872ac0859096a Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Sat, 19 Mar 2022 19:01:50 +0100 Subject: [PATCH 182/355] [ code ] Gutter improvement (#71) (#168) --- apps/code/editor_view.cpp | 71 ++++++++++++++++++++++++++++----------- apps/code/editor_view.h | 18 ++++++++-- 2 files changed, 67 insertions(+), 22 deletions(-) diff --git a/apps/code/editor_view.cpp b/apps/code/editor_view.cpp index ab327fb5233..855db6ab052 100644 --- a/apps/code/editor_view.cpp +++ b/apps/code/editor_view.cpp @@ -8,6 +8,8 @@ namespace Code { /* EditorView */ +constexpr char Code::EditorView::k_eol; + EditorView::EditorView(Responder * parentResponder, App * pythonDelegate) : Responder(parentResponder), View(), @@ -27,6 +29,9 @@ void EditorView::resetSelection() { void EditorView::scrollViewDidChangeOffset(ScrollViewDataSource * scrollViewDataSource) { m_gutterView.setOffset(scrollViewDataSource->offset().y()); + if (m_gutterView.isEditorReloadNeeded()) { + redrawSubviews(); + } } View * EditorView::subviewAtIndex(int index) { @@ -43,7 +48,7 @@ void EditorView::didBecomeFirstResponder() { void EditorView::layoutSubviews(bool force) { m_gutterView.setOffset(0); - KDCoordinate gutterWidth = m_gutterView.minimalSizeForOptimalDisplay().width(); + KDCoordinate gutterWidth = m_gutterView.widthComputed().width(); m_gutterView.setFrame(KDRect(0, 0, gutterWidth, bounds().height()), force); m_textArea.setFrame(KDRect( @@ -52,6 +57,19 @@ void EditorView::layoutSubviews(bool force) { bounds().width()-gutterWidth, bounds().height()), force); + m_gutterView.loadMaxDigits(); +} + +void EditorView::redrawSubviews() { + KDCoordinate gutterWidth = m_gutterView.widthComputed().width(); + m_gutterView.setFrame(KDRect(0, 0, gutterWidth, bounds().height()), true); + m_textArea.setFrame(KDRect( + gutterWidth, + 0, + bounds().width()-gutterWidth, + bounds().height()), + true); + markRectAsDirty(bounds()); } /* EditorView::GutterView */ @@ -67,42 +85,57 @@ void EditorView::GutterView::drawRect(KDContext * ctx, KDRect rect) const { KDCoordinate firstLine = m_offset / glyphSize.height(); KDCoordinate firstLinePixelOffset = m_offset - firstLine * glyphSize.height(); - char lineNumber[k_lineNumberCharLength]; + char lineNumber[m_digits]; int numberOfLines = bounds().height() / glyphSize.height() + 1; for (int i=0; i= 10) { - line.serialize(lineNumber, k_lineNumberCharLength); - } else { - // Add a leading "0" - lineNumber[0] = '0'; - line.serialize(lineNumber + 1, k_lineNumberCharLength - 1); + + int lineDigits = getDigits(lineNumberValue); + + for (int j=0; j < (m_digits - lineDigits - 1); j++) { + lineNumber[j] = '0'; } - KDCoordinate leftPadding = (2 - strlen(lineNumber)) * glyphSize.width(); + + line.serialize(lineNumber + (m_digits-lineDigits - 1), lineDigits + 1); + + KDCoordinate leftPadding = (m_digits - strlen(lineNumber) - 1) * glyphSize.width(); ctx->drawString( - lineNumber, - KDPoint(k_margin + leftPadding, i*glyphSize.height() - firstLinePixelOffset), - m_font, - textColor, - backgroundColor + lineNumber, + KDPoint(k_margin + leftPadding, i*glyphSize.height() - firstLinePixelOffset), + m_font, + textColor, + backgroundColor ); } } +void EditorView::GutterView::loadMaxDigits() { + m_digits = getDigits((bounds().height() / m_font->glyphSize().height() + 1) + (m_offset / m_font->glyphSize().height())) + 1; +} + void EditorView::GutterView::setOffset(KDCoordinate offset) { if (m_offset == offset) { return; } m_offset = offset; + m_previousDigits = m_digits; + loadMaxDigits(); markRectAsDirty(bounds()); } +KDSize EditorView::GutterView::widthComputed() { + return KDSize(2 * k_margin + (m_digits - 1) * m_font->glyphSize().width(), 0); +} + +int EditorView::GutterView::getDigits(int value) { + int digits = 0; + while (value >= pow(10, digits)) {digits++;} + return digits; +} -KDSize EditorView::GutterView::minimalSizeForOptimalDisplay() const { - int numberOfChars = 2; // TODO: Could be computed - return KDSize(2 * k_margin + numberOfChars * m_font->glyphSize().width(), 0); +bool EditorView::GutterView::isEditorReloadNeeded() { + return m_previousDigits != m_digits; } } diff --git a/apps/code/editor_view.h b/apps/code/editor_view.h index 547f7340b53..55ae800df38 100644 --- a/apps/code/editor_view.h +++ b/apps/code/editor_view.h @@ -8,6 +8,8 @@ namespace Code { class EditorView : public Responder, public View, public ScrollViewDelegate { public: + static constexpr char k_eol = '\n'; + EditorView(Responder * parentResponder, App * pythonDelegate); PythonTextArea::AutocompletionType autocompletionType(const char ** autocompletionBeginning, const char ** autocompletionEnd) const { return m_textArea.autocompletionType(nullptr, autocompletionBeginning, autocompletionEnd); } bool isAutocompleting() const; @@ -29,6 +31,7 @@ class EditorView : public Responder, public View, public ScrollViewDelegate { void unloadSyntaxHighlighter() { m_textArea.unloadSyntaxHighlighter(); }; void scrollViewDidChangeOffset(ScrollViewDataSource * scrollViewDataSource) override; void didBecomeFirstResponder() override; + void redrawSubviews(); private: int numberOfSubviews() const override { return 2; } View * subviewAtIndex(int index) override; @@ -36,15 +39,24 @@ class EditorView : public Responder, public View, public ScrollViewDelegate { class GutterView : public View { public: - GutterView(const KDFont * font) : View(), m_font(font), m_offset(0) {} + GutterView(const KDFont * font) : View(), m_font(font), m_offset(0), m_digits(3), m_previousDigits(0) {} + void drawRect(KDContext * ctx, KDRect rect) const override; void setOffset(KDCoordinate offset); - KDSize minimalSizeForOptimalDisplay() const override; + + KDSize widthComputed(); + void loadMaxDigits(); + bool isEditorReloadNeeded(); + + static int getDigits(int value); + private: static constexpr KDCoordinate k_margin = 2; - static constexpr int k_lineNumberCharLength = 3; + const KDFont * m_font; KDCoordinate m_offset; + int m_digits; + int m_previousDigits; }; PythonTextArea m_textArea; From 97a168052bf984f6813a78dc9f7ce5fde097a05b Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 19 Mar 2022 23:39:44 +0100 Subject: [PATCH 183/355] [code] Improved code quality in gutter view resize --- apps/code/editor_view.cpp | 85 +++++++++++++++++---------------------- apps/code/editor_view.h | 19 ++++----- 2 files changed, 45 insertions(+), 59 deletions(-) diff --git a/apps/code/editor_view.cpp b/apps/code/editor_view.cpp index 855db6ab052..1e3c34af9a0 100644 --- a/apps/code/editor_view.cpp +++ b/apps/code/editor_view.cpp @@ -8,8 +8,6 @@ namespace Code { /* EditorView */ -constexpr char Code::EditorView::k_eol; - EditorView::EditorView(Responder * parentResponder, App * pythonDelegate) : Responder(parentResponder), View(), @@ -28,9 +26,8 @@ void EditorView::resetSelection() { } void EditorView::scrollViewDidChangeOffset(ScrollViewDataSource * scrollViewDataSource) { - m_gutterView.setOffset(scrollViewDataSource->offset().y()); - if (m_gutterView.isEditorReloadNeeded()) { - redrawSubviews(); + if (m_gutterView.setOffsetAndNeedResize(scrollViewDataSource->offset().y())) { + internalLayoutSubviews(true); } } @@ -47,8 +44,12 @@ void EditorView::didBecomeFirstResponder() { } void EditorView::layoutSubviews(bool force) { - m_gutterView.setOffset(0); - KDCoordinate gutterWidth = m_gutterView.widthComputed().width(); + m_gutterView.setOffsetAndNeedResize(0); // Whatever the return is, we layout the editor view + internalLayoutSubviews(force); +} + +void EditorView::internalLayoutSubviews(bool force) { + KDCoordinate gutterWidth = m_gutterView.computeWidth(); m_gutterView.setFrame(KDRect(0, 0, gutterWidth, bounds().height()), force); m_textArea.setFrame(KDRect( @@ -57,19 +58,6 @@ void EditorView::layoutSubviews(bool force) { bounds().width()-gutterWidth, bounds().height()), force); - m_gutterView.loadMaxDigits(); -} - -void EditorView::redrawSubviews() { - KDCoordinate gutterWidth = m_gutterView.widthComputed().width(); - m_gutterView.setFrame(KDRect(0, 0, gutterWidth, bounds().height()), true); - m_textArea.setFrame(KDRect( - gutterWidth, - 0, - bounds().width()-gutterWidth, - bounds().height()), - true); - markRectAsDirty(bounds()); } /* EditorView::GutterView */ @@ -85,57 +73,60 @@ void EditorView::GutterView::drawRect(KDContext * ctx, KDRect rect) const { KDCoordinate firstLine = m_offset / glyphSize.height(); KDCoordinate firstLinePixelOffset = m_offset - firstLine * glyphSize.height(); - char lineNumber[m_digits]; + char lineNumberBuffer[m_numberOfDigits + 1]; int numberOfLines = bounds().height() / glyphSize.height() + 1; for (int i=0; idrawString( - lineNumber, - KDPoint(k_margin + leftPadding, i*glyphSize.height() - firstLinePixelOffset), - m_font, - textColor, - backgroundColor + lineNumberBuffer, + KDPoint(k_margin, i*glyphSize.height() - firstLinePixelOffset), + m_font, + textColor, + backgroundColor ); } } -void EditorView::GutterView::loadMaxDigits() { - m_digits = getDigits((bounds().height() / m_font->glyphSize().height() + 1) + (m_offset / m_font->glyphSize().height())) + 1; -} - -void EditorView::GutterView::setOffset(KDCoordinate offset) { +bool EditorView::GutterView::setOffsetAndNeedResize(KDCoordinate offset) { if (m_offset == offset) { - return; + return false; } m_offset = offset; - m_previousDigits = m_digits; - loadMaxDigits(); + + int numberOfDigits = computeMaxNumberOfDigits(); + if (numberOfDigits != m_numberOfDigits) { + m_numberOfDigits = numberOfDigits; + return true; + } + markRectAsDirty(bounds()); + return false; } -KDSize EditorView::GutterView::widthComputed() { - return KDSize(2 * k_margin + (m_digits - 1) * m_font->glyphSize().width(), 0); +int EditorView::GutterView::computeWidth() { + return 2 * k_margin + (m_numberOfDigits) * m_font->glyphSize().width(); } -int EditorView::GutterView::getDigits(int value) { - int digits = 0; - while (value >= pow(10, digits)) {digits++;} - return digits; +int EditorView::GutterView::computeMaxNumberOfDigits() { + return computeNumberOfDigitsFor((bounds().height() / m_font->glyphSize().height() + 1) + (m_offset / m_font->glyphSize().height())); } -bool EditorView::GutterView::isEditorReloadNeeded() { - return m_previousDigits != m_digits; +int EditorView::GutterView::computeNumberOfDigitsFor(int value) { + int digits = 1; + while (value >= pow(10, digits)) { + digits++; + } + return digits; } } diff --git a/apps/code/editor_view.h b/apps/code/editor_view.h index 55ae800df38..6c91fdde617 100644 --- a/apps/code/editor_view.h +++ b/apps/code/editor_view.h @@ -8,8 +8,6 @@ namespace Code { class EditorView : public Responder, public View, public ScrollViewDelegate { public: - static constexpr char k_eol = '\n'; - EditorView(Responder * parentResponder, App * pythonDelegate); PythonTextArea::AutocompletionType autocompletionType(const char ** autocompletionBeginning, const char ** autocompletionEnd) const { return m_textArea.autocompletionType(nullptr, autocompletionBeginning, autocompletionEnd); } bool isAutocompleting() const; @@ -31,7 +29,7 @@ class EditorView : public Responder, public View, public ScrollViewDelegate { void unloadSyntaxHighlighter() { m_textArea.unloadSyntaxHighlighter(); }; void scrollViewDidChangeOffset(ScrollViewDataSource * scrollViewDataSource) override; void didBecomeFirstResponder() override; - void redrawSubviews(); + void internalLayoutSubviews(bool force); private: int numberOfSubviews() const override { return 2; } View * subviewAtIndex(int index) override; @@ -39,24 +37,21 @@ class EditorView : public Responder, public View, public ScrollViewDelegate { class GutterView : public View { public: - GutterView(const KDFont * font) : View(), m_font(font), m_offset(0), m_digits(3), m_previousDigits(0) {} + GutterView(const KDFont * font) : View(), m_font(font), m_offset(0), m_numberOfDigits(2) {} void drawRect(KDContext * ctx, KDRect rect) const override; - void setOffset(KDCoordinate offset); - - KDSize widthComputed(); - void loadMaxDigits(); - bool isEditorReloadNeeded(); + bool setOffsetAndNeedResize(KDCoordinate offset); // Return true if the gutter view need to be resized - static int getDigits(int value); + int computeWidth(); + int computeMaxNumberOfDigits(); + static int computeNumberOfDigitsFor(int value); private: static constexpr KDCoordinate k_margin = 2; const KDFont * m_font; KDCoordinate m_offset; - int m_digits; - int m_previousDigits; + int m_numberOfDigits; }; PythonTextArea m_textArea; From c92c4d8383747b0a6ece332cdd4c4cef4d516e4f Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Sun, 20 Mar 2022 16:08:39 +0100 Subject: [PATCH 184/355] [apps/reader] Add slanted arrows, \in, \cdot, \cdots and \ldots (#184) --- apps/reader/tex_parser.cpp | 50 ++++++++++++++++-------------- kandinsky/fonts/code_points.h | 7 +++++ kandinsky/include/kandinsky/font.h | 2 +- 3 files changed, 34 insertions(+), 25 deletions(-) diff --git a/apps/reader/tex_parser.cpp b/apps/reader/tex_parser.cpp index fe105176d92..1d6b4e87ea3 100644 --- a/apps/reader/tex_parser.cpp +++ b/apps/reader/tex_parser.cpp @@ -5,40 +5,42 @@ namespace Reader { // List of available Symbols static constexpr char const * k_SymbolsCommands[] = { - "times", "div", "forall", "partial", "exists", "pm", "approx", "infty", "neq", "equiv", "leq", "geq", - "leftarrow", "uparrow", "rightarrow", "downarrow", "leftrightarrow", "updownarrow", "Leftarrow", "Uparrow", "Rightarrow", "Downarrow", - "Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta", "Eta", "Theta", "Iota", "Kappa", "Lambda", + "times", "div", "forall", "partial", "exists", "pm", "approx", "infty", "neq", "equiv", "leq", "geq", + "leftarrow", "uparrow", "rightarrow", "downarrow","leftrightarrow", "updownarrow", "Leftarrow", "Uparrow", "Rightarrow", "Downarrow", + "nwarrow", "nearrow", "swarrow", "searrow", "in", "cdot", "cdots", "ldots", + "Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta", "Eta", "Theta", "Iota", "Kappa", "Lambda", "Mu", "Nu", "Xi", "Omicron", "Pi", "Rho", "Sigma", "Tau", "Upsilon", "Phi", "Chi", "Psi","Omega", - "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", + "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu", "nu", "xi", "omicron", "pi", "rho", "sigma", "tau", "upsilon", "phi", "chi", "psi", "omega", "sim", }; // List of the available Symbol's CodePoints in the same order of the Symbol's list static constexpr uint32_t const k_SymbolsCodePoints[] = { - 0xd7, 0xf7, 0x2200, 0x2202, 0x2203, 0xb1, 0x2248, 0x221e, 0x2260, 0x2261, 0x2264, 0x2265, - 0x2190, 0x2191, 0x2192, 0x2193, 0x2194, 0x2195, 0x21d0, 0x21d1, 0x21d2, 0x21d3, - 0x391, 0x392, 0x393, 0x394, 0x395, 0x396, 0x397, 0x398, 0x399, 0x39a, 0x39b, - 0x39c, 0x39d, 0x39e, 0x39f, 0x3a0, 0x3a1, 0x3a3, 0x3a4, 0x3a5, 0x3a6, 0x3a7, 0x3a8, 0x3a9, - 0x3b1, 0x3b2, 0x3b3, 0x3b4, 0x3b5, 0x3b6, 0x3b7, 0x3b8, 0x3b9, 0x3ba, 0x3bb, + 0xd7, 0xf7, 0x2200, 0x2202, 0x2203, 0xb1, 0x2248, 0x221e, 0x2260, 0x2261, 0x2264, 0x2265, + 0x2190, 0x2191, 0x2192, 0x2193, 0x2194, 0x2195, 0x21d0, 0x21d1, 0x21d2, 0x21d3, + 0x2196, 0x2197, 0x2198, 0x2199, 0x454, 0xb7, 0x2505, 0x2026, + 0x391, 0x392, 0x393, 0x394, 0x395, 0x396, 0x397, 0x398, 0x399, 0x39a, 0x39b, + 0x39c, 0x39d, 0x39e, 0x39f, 0x3a0, 0x3a1, 0x3a3, 0x3a4, 0x3a5, 0x3a6, 0x3a7, 0x3a8, 0x3a9, + 0x3b1, 0x3b2, 0x3b3, 0x3b4, 0x3b5, 0x3b6, 0x3b7, 0x3b8, 0x3b9, 0x3ba, 0x3bb, 0x3bc, 0x3bd, 0x3be, 0x3bf, 0x3c0, 0x3c1, 0x3c3, 0x3c4, 0x3c5, 0x3c6, 0x3c7, 0x3c8, 0x3c9, 0x7e, }; - + // List of available Function Commands that don't require a specific handling static constexpr char const * k_FunctionCommands[] = { - "arccos", "arcsin", "arctan", "arg", "cos", "cosh", "cot", "coth", - "csc", "deg", "det", "dim", "exp", "gcd", "hom", "inf", - "ker", "lg", "lim", "liminf", "limsup", "ln", "log", "max", + "arccos", "arcsin", "arctan", "arg", "cos", "cosh", "cot", "coth", + "csc", "deg", "det", "dim", "exp", "gcd", "hom", "inf", + "ker", "lg", "lim", "liminf", "limsup", "ln", "log", "max", "min", "Pr", "sec", "sin", "sinh", "sup", "tan", "tanh" }; -TexParser::TexParser(const char * text, const char * endOfText) : - m_text(text), +TexParser::TexParser(const char * text, const char * endOfText) : + m_text(text), m_endOfText(endOfText), m_hasError(false) { - + } Layout TexParser::getLayout() { @@ -78,7 +80,7 @@ Layout TexParser::popBlock() { Layout TexParser::popText(char stop) { HorizontalLayout layout = HorizontalLayout::Builder(); const char * start = m_text; - + while (m_text < m_endOfText && *m_text != stop) { switch (*m_text) { // TODO: Factorize this code @@ -121,7 +123,7 @@ Layout TexParser::popText(char stop) { if (start != m_text) { layout.addOrMergeChildAtIndex(LayoutHelper::String(start, m_text - start), layout.numberOfChildren(), false); } - + m_text ++; if (layout.numberOfChildren() == 1) { @@ -149,7 +151,7 @@ Layout TexParser::popCommand() { if (isCommandEnded(*(m_text + strlen(k_fracCommand)))) { m_text += strlen(k_fracCommand); return popFracCommand(); - } + } } if (strncmp(k_leftCommand, m_text, strlen(k_leftCommand)) == 0) { if (isCommandEnded(*(m_text + strlen(k_leftCommand)))) { @@ -199,20 +201,20 @@ Layout TexParser::popCommand() { return LayoutHelper::String(k_FunctionCommands[i], strlen(k_FunctionCommands[i])); } } - } + } - m_hasError = true; + m_hasError = true; return EmptyLayout::Builder(); } // Expressions Layout TexParser::popCeilCommand() { - Layout ceil = popBlock(); + Layout ceil = popBlock(); return CeilingLayout::Builder(ceil); } Layout TexParser::popFloorCommand() { - Layout floor = popBlock(); + Layout floor = popBlock(); return FloorLayout::Builder(floor); } @@ -255,7 +257,7 @@ Layout TexParser::popSpaceCommand() { Layout TexParser::popOverrightarrowCommand() { return VectorLayout::Builder(popBlock()); } - + Layout TexParser::popSymbolCommand(int SymbolIndex) { uint32_t codePoint = k_SymbolsCodePoints[SymbolIndex]; return CodePointLayout::Builder(codePoint); diff --git a/kandinsky/fonts/code_points.h b/kandinsky/fonts/code_points.h index ec011e91ac7..5d57021ca37 100644 --- a/kandinsky/fonts/code_points.h +++ b/kandinsky/fonts/code_points.h @@ -324,7 +324,9 @@ uint32_t ExtendedCodePoints[] = { 0x3c7, // χ // GREEK SMALL LETTER KHI 0x3c8, // ψ // GREEK SMALL LETTER PSI 0x3c9, // ω // GREEK SMALL LETTER OMEGA + 0x454, // є // CYRILLIC SMALL LETTER UKRAINIAN LE 0x1d07, // ᴇ // LATIN LETTER SMALL CAPITAL E + 0x2026, // … // HORIZONTAL ELLIPSIS 0x212f, // ℯ // SCRIPT SMALL E 0x2190, // ← // BACKWARD ARROW (leftarrow) 0x2191, // ↑ // TOP ARROW (uparrow) @@ -332,6 +334,10 @@ uint32_t ExtendedCodePoints[] = { 0x2193, // ↓ // BOTTOM ARROW (downarrow) 0x2194, // ↔ // BACKWARD FORWARD ARROW (leftrightarrow) 0x2195, // ↕ // TOP BOTTOM ARROW (updownarrow) + 0x2196, // ↖ // NORDWEST ARROW (nwarrow) + 0x2197, // ↗ // NORDEST ARROW (nearrow) + 0x2198, // ↘ // SOUTHWEST ARROW (swarrow) + 0x2199, // ↙ // SOUTHEST ARROW (searrow) 0x21d0, // ⇐ // DOUBLE BACKWARD ARROW (Leftarrow) 0x21d1, // ⇑ // DOUBLE TOP ARROW (Uparrow) 0x21d2, // ⇒ // DOUBLE FORWARD ARROW (Rightarrow) @@ -348,6 +354,7 @@ uint32_t ExtendedCodePoints[] = { 0x2261, // ≡ // IS CONGRUENT TO 0x2264, // ≤ // LESS-THAN OR EQUAL TO 0x2265, // ≥ // GREATER-THAN OR EQUAL TO + 0x2505, // ┅ // BOX DRAWING EQU HEAVY DASH HORIZONTAL 0xFFFD, // � // REPLACEMENT CHARACTER 0x1d422, // 𝐢 // MATHEMATICAL BOLD SMALL I" }; diff --git a/kandinsky/include/kandinsky/font.h b/kandinsky/include/kandinsky/font.h index 2b7921cced1..f1bbf7ec939 100644 --- a/kandinsky/include/kandinsky/font.h +++ b/kandinsky/include/kandinsky/font.h @@ -61,7 +61,7 @@ class KDFont { CodePoint m_codePoint; GlyphIndex m_glyphIndex; }; - static constexpr GlyphIndex IndexForReplacementCharacterCodePoint = 190; + static constexpr GlyphIndex IndexForReplacementCharacterCodePoint = 197; GlyphIndex indexForCodePoint(CodePoint c) const; void setGlyphGrayscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const; From 841ac7d5f4aa0b305be5a23d9d14d2a6ae521f01 Mon Sep 17 00:00:00 2001 From: Assim ZEMOUCHI Date: Sun, 20 Mar 2022 16:09:02 +0100 Subject: [PATCH 185/355] [ion] Fix win11 build for n0110 (#182) (#183) --- ion/src/device/n0110/flash.ld | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ion/src/device/n0110/flash.ld b/ion/src/device/n0110/flash.ld index e66cbbac1d8..dccce34bc41 100644 --- a/ion/src/device/n0110/flash.ld +++ b/ion/src/device/n0110/flash.ld @@ -236,6 +236,8 @@ SECTIONS { . = ALIGN(4); *(.rodata._ZN3Ion6Device13ExternalFlash*) /* 'start' dependencies */ + *(.rodata._ZN3Ion6Device8Keyboard6ConfigL10ColumnGPIOE*) + *(.rodata._ZN3Ion6Device8Keyboard6ConfigL7RowGPIOE*) *(.rodata._ZN3Ion6Device4RegsL5GPIOAE) *(.rodata._ZN3Ion6Device4RegsL5GPIOBE) *(.rodata._ZN3Ion6Device3LED6ConfigL7RGBPinsE) From 865bacf89a347112defb4a71454db5ae26c40778 Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Sun, 20 Mar 2022 16:10:54 +0100 Subject: [PATCH 186/355] [build/utilities] Add translations clean script (#179) --- Makefile | 5 + apps/code/base.universal.i18n | 1 - apps/code/catalog.de.i18n | 3 - apps/code/catalog.en.i18n | 3 - apps/code/catalog.es.i18n | 3 - apps/code/catalog.fr.i18n | 3 - apps/code/catalog.hu.i18n | 423 +++++----- apps/code/catalog.it.i18n | 3 - apps/code/catalog.nl.i18n | 3 - apps/code/catalog.pt.i18n | 3 - apps/code/catalog.universal.i18n | 54 -- apps/sequence/base.de.i18n | 1 - apps/sequence/base.en.i18n | 1 - apps/sequence/base.es.i18n | 1 - apps/sequence/base.fr.i18n | 1 - apps/sequence/base.hu.i18n | 43 +- apps/sequence/base.it.i18n | 1 - apps/sequence/base.nl.i18n | 1 - apps/sequence/base.pt.i18n | 1 - apps/settings/base.de.i18n | 1 - apps/settings/base.en.i18n | 1 - apps/settings/base.es.i18n | 1 - apps/settings/base.fr.i18n | 1 - apps/settings/base.hu.i18n | 163 ++-- apps/settings/base.it.i18n | 1 - apps/settings/base.nl.i18n | 1 - apps/settings/base.pt.i18n | 1 - apps/shared.de.i18n | 10 - apps/shared.en.i18n | 10 - apps/shared.es.i18n | 10 - apps/shared.fr.i18n | 10 - apps/shared.hu.i18n | 192 +++-- apps/shared.it.i18n | 10 - apps/shared.nl.i18n | 10 - apps/shared.pt.i18n | 10 - apps/shared.universal.i18n | 3 - apps/statistics/base.de.i18n | 1 - apps/statistics/base.en.i18n | 1 - apps/statistics/base.es.i18n | 3 +- apps/statistics/base.fr.i18n | 1 - apps/statistics/base.hu.i18n | 53 +- apps/statistics/base.it.i18n | 3 +- apps/statistics/base.nl.i18n | 3 +- apps/statistics/base.pt.i18n | 3 +- apps/toolbox.de.i18n | 7 - apps/toolbox.en.i18n | 7 - apps/toolbox.es.i18n | 7 - apps/toolbox.fr.i18n | 7 - apps/toolbox.hu.i18n | 1031 ++++++++++++------------- apps/toolbox.it.i18n | 7 - apps/toolbox.nl.i18n | 7 - apps/toolbox.pt.i18n | 7 - apps/usb/base.de.i18n | 2 - apps/usb/base.en.i18n | 2 - apps/usb/base.es.i18n | 2 - apps/usb/base.fr.i18n | 2 - apps/usb/base.hu.i18n | 26 +- apps/usb/base.it.i18n | 2 - apps/usb/base.nl.i18n | 2 - apps/usb/base.pt.i18n | 2 - build/utilities/translations_clean.py | 363 +++++++++ 61 files changed, 1325 insertions(+), 1215 deletions(-) create mode 100644 build/utilities/translations_clean.py diff --git a/Makefile b/Makefile index 1a424b3ef42..fb08d34d46e 100644 --- a/Makefile +++ b/Makefile @@ -214,3 +214,8 @@ run: compile translations: @echo "TRANSLATIONS" $(Q) ${PYTHON} build/utilities/translate.py + +.PHONY: translations_clean +translations_clean: + @echo "TRANSLATIONS CLEAN" + $(Q) ${PYTHON} build/utilities/translations_clean.py diff --git a/apps/code/base.universal.i18n b/apps/code/base.universal.i18n index dbbf69f5a3b..57acbc2ec34 100644 --- a/apps/code/base.universal.i18n +++ b/apps/code/base.universal.i18n @@ -1,3 +1,2 @@ CodeAppCapital = "PYTHON" ConsolePrompt = ">>> " -ScriptParameters = "..." diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index 15492dbd9ca..bb4c1994748 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -186,14 +186,11 @@ PythonTurtlePosition = "Aktuelle (x,y) Position zurückgeben" PythonTurtleReset = "Die Zeichnung zurücksetzen" PythonTurtleRight = "Um ein Grad nach rechts drehen" PythonTurtleSetheading = "Ausrichtung auf einen Grad setzen" -PythonTurtleSetposition = "Den Igel auf Position setzen" PythonTurtleShowturtle = "Den Igel anzeigen" PythonTurtleSpeed = "Zeichengeschwindigkeit von 0 bis 10" PythonTurtleWrite = "Einen Text anzeigen" PythonUniform = "Fließkommazahl in [a,b]" PythonImportTime = "Time-Modul importieren" -PythonTimePrefix = "Zeitmodul-Funktionspräfix" -PythonTimeSleep = "Warte für n Sekunden" PythonMonotonic = "Monotone Zeit zurückgeben" PythonFileOpen = "Öffnet eine Datei" PythonFileSeekable = "Kann Datei durchsucht werden?" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index 298b16d2b24..7241ff9b142 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -172,7 +172,6 @@ PythonTurtlePosition = "Return the current (x,y) location" PythonTurtleReset = "Reset the drawing" PythonTurtleRight = "Turn right by a degrees" PythonTurtleSetheading = "Set the orientation to a degrees" -PythonTurtleSetposition = "Positioning the turtle" PythonTurtleShowturtle = "Show the turtle" PythonTurtleSpeed = "Drawing speed between 0 and 10" PythonTurtleWrite = "Display a text" @@ -192,8 +191,6 @@ PythonSysImplementation = "Information about Python" PythonSysModules = "Dictionary of loaded modules" PythonSysVersion = "Python language version (string)" PythonSysVersioninfo = "Python language version (tuple)" -PythonTimePrefix = "time module function prefix" -PythonTimeSleep = "Wait for n second" PythonMonotonic = "Return monotonic time" PythonFileOpen = "Opens a file" PythonFileSeekable = "Tells if seek can be used on a file" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index c2209a3f52a..4bc9eb2dc6a 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -172,7 +172,6 @@ PythonTurtlePosition = "Return the current (x,y) location" PythonTurtleReset = "Reset the drawing" PythonTurtleRight = "Turn right by a degrees" PythonTurtleSetheading = "Set the orientation to a degrees" -PythonTurtleSetposition = "Colocar la tortuga" PythonTurtleShowturtle = "Show the turtle" PythonTurtleSpeed = "Drawing speed between 0 and 10" PythonTurtleWrite = "Display a text" @@ -192,8 +191,6 @@ PythonSysImplementation = "Information about Python" PythonSysModules = "Dictionary of loaded modules" PythonSysVersion = "Python language version (string)" PythonSysVersioninfo = "Python language version (tuple)" -PythonTimePrefix = "time module function prefix" -PythonTimeSleep = "Esperar n segundos" PythonMonotonic = "Tiempo monótono de retorno" PythonFileOpen = "Opens a file" PythonFileSeekable = "Tells if seek can be used on a file" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index 5b2b4116649..c43c9a2c22a 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -172,7 +172,6 @@ PythonTurtlePosition = "Renvoie la position (x,y)" PythonTurtleReset = "Réinitialise le dessin" PythonTurtleRight = "Pivote de a degrés vers la droite" PythonTurtleSetheading = "Met un cap de a degrés" -PythonTurtleSetposition = "Positionne la tortue" PythonTurtleShowturtle = "Affiche la tortue" PythonTurtleSpeed = "Vitesse du tracé entre 0 et 10" PythonTurtleWrite = "Affiche un texte" @@ -192,8 +191,6 @@ PythonSysImplementation = "Information sur Python" PythonSysModules = "Dictionnaire des modules chargés" PythonSysVersion = "Version du langage Python (string)" PythonSysVersioninfo = "Version du langage Python (tuple)" -PythonTimePrefix = "Préfixe fonction du module temps" -PythonTimeSleep = "Attendre n secondes" PythonMonotonic = "Retourne le temps monotone" PythonFileOpen = "Ouvre un fichier" PythonFileSeekable = "Indique si seek peut être utilisé" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index 570537a6eab..ad7eb410f74 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -1,213 +1,210 @@ -PythonPound = "Megjegyzés" -PythonPercent = "Modulo" -Python1J = "Képzeletbeli i" -PythonLF = "Enter" -PythonTab = "Táblázat" -PythonAmpersand = "Logikus és" -PythonSymbolExp = "logikus exkluzív vagy pedig" -PythonVerticalBar = "logikus vagy pedig" -PythonImag = "z képzeletbeli része" -PythonReal = "z valódi része" -PythonSingleQuote = "apostróf" -PythonAbs = "Abszolút érték/nagyság" -PythonAcos = "Ív (arc) koszinusz" -PythonAcosh = "Hiperbolikus arc koszinusz" -PythonAppend = "Lista végére hozzáadni x-et" -PythonArrow = "(x,y) nyíla (x+dx,y+dy) nyílához" -PythonAsin = "Ív (arc) szinusz" -PythonAsinh = "Hiperbolikus ív (arc) szinusz" -PythonAtan = "Ív (arc) érintö (tan)" -PythonAtan2 = "atan(y/x) sámolása" -PythonAtanh = "Hiperbolikus ív (arc) érintö (atan)" -PythonAxis = "Tengelyeket (xmin,xmax,ymin,ymax)-ra állitani" -PythonBar = "Az x lista oszlopdiagramja" -PythonBin = "Egész szám konvertálása binárisra" -PythonCeil = "Mennyezet" -PythonChoice = "Véletlenszerü szám a listában" -PythonClear = "A lista ürítése" -PythonCmathFunction = "cmath modul funkció elötag" -PythonColor = "Rgb (pzk) szín allítása" -PythonColorBlack = "Fekete szín" -PythonColorBlue = "Kék szín" -PythonColorBrown = "Barna szín" -PythonColorGreen = "Zöld szín" -PythonColorGray = "Szürke szín" -PythonColorOrange = "Narancssárga szín" -PythonColorPink = "Rózsaszín szín" -PythonColorPurple = "Lila szín" -PythonColorRed = "Piros szín" -PythonColorWhite = "Fehér szín" -PythonColorYellow = "Sárga szín" -PythonComplex = "A + ib visszaadása" -PythonCopySign = "X visszaadása y jelével" -PythonCos = "Koszinusz" -PythonCosh = "Hiperbolikus koszinusz" -PythonCount = "Számolja az x elöfordulását" -PythonDegrees = "x konvertálása radiánokrol fokokra" -PythonDivMod = "Hányados és maradék" -PythonDrawCircle = "Rajzolj egy kört" -PythonDrawLine = "Húzzon egy vonalat " -PythonDrawString = "Szöveg megjelenítése (x, y)-en" -PythonErf = "Hiba funkció" -PythonErfc = "Kiegészítö hiba funkció" -PythonEval = "Visszaadja az értékelt kifejezést" -PythonExp = "Exponenciális függvény" -PythonExpm1 = "exp(x)-1 sámitása" -PythonFactorial = "x faktorál" -PythonFabs = "Abszolút érték" -PythonFillRect = "Téglalap töltése" -PythonFillCircle = "Kitölti a kört" -PythonFillPolygon = "Kitölti a poligont" -PythonFloat = "Konvertálása tizedes számra" -PythonFloor = "Egész része" -PythonFmod = "a modulo b" -PythonFrExp = "X mantissája és kiállítója" -PythonGamma = "Gamma funkció" -PythonGetKeys = "Billentyűk lenyomva" -PythonGetPalette = "Téma paletta beszerzése" -PythonGetPixel = "Visszatéríti (x,y) színét" -PythonGetrandbits = "Váletlenszám visszatérítése k biten" -PythonGrid = "Rács megjelenítése/elrejtése" -PythonHex = "Decimális szám konvertálása hexadecimális számra" -PythonHist = "x hisztográmiája" -PythonImportCmath = "cmath modul importálása" -PythonImportIon = "Ion modul importálása" -PythonImportKandinsky = "Kandinsky modul importálása" -PythonImportRandom = "Véletlenszerü modul importálása" -PythonImportMath = "math modul importálása" -PythonImportMatplotlibPyplot = "matplotlib.pyplot modul importálása" -PythonImportNumpy = "ulab.numpy modul importálása" -PythonImportScipy = "ulab.scipy modul importálása" -PythonImportTurtle = "turtle modul importálása" -PythonImportTime = "time modul importálása" -PythonIndex = "Az elsö x esemény indexe" -PythonInput = "Irjon egy értéket (számot)" -PythonInsert = "x-et i. pozícióra helyezze a listában" -PythonInt = "egész számra konvertálás" -PythonIonFunction = "ion modul funkció elötag" -PythonIsFinite = "x véges-e" -PythonIsInfinite = "x végtelen-e" -PythonIsKeyDown = "True-t válaszol ha a k gomb le van nyomva" -PythonBattery = "Az akkumulátor feszültségének visszaadása" -PythonBatteryLevel = "Az akkumulátor töltöttségi szintjének visszaadása" -PythonBatteryIscharging = "Visszaadja, ha az akkumulátor töltődik" -PythonSetBrightness = "Fényerőszint beállítása" -PythonGetBrightness = "Get brightness level" -PythonIsNaN = "Ellenörizze hogy x nem NaN" -PythonKandinskyFunction = "kandinsky modul funkció elötag" -PythonLdexp = "frexp ellentéte : x*(2**i)" -PythonLength = "Egy targy hossza" -PythonLgamma = "Gamma funkció logaritmusa" -PythonLog = "a alapú logaritmus" -PythonLog10 = "Decimális logaritmus" -PythonLog2 = "Bináris logaritmus" -PythonMathFunction = "math modul funkció elötag" -PythonMatplotlibPyplotFunction = "matplotlib.pyplot elötag" -PythonMax = "Maximum" -PythonMin = "Minimum" -PythonModf = "x-nek tört és egész részei" -PythonMonotonic = "Az óra értékét adja vissza" -PythonNumpyFunction = "numpy elötag" -PythonNumpyFftFunction = "numpy.fft elötag" -PythonNumpyLinalgFunction = "numpy.linalg elötag" -PythonScipyFunction = "scipy elötag" -PythonScipyLinalgFunction = "scipy.linalg elötag" -PythonScipyOptimizeFunction = "scipy.optimize elötag" -PythonScipySignalFunction = "scipy.signal elötag" -PythonScipySpecialFunction = "scipy.special elötag" -PythonOct = "Decimális szám konvertálása octális számra" -PythonPhase = "z fázisa" -PythonPlot = "y-t jelöli x függvényében" -PythonPolar = "Verctorizálni" -PythonPop = "Az utolsó elemet el törölni" -PythonPower = "x y. kitevö" -PythonPrint = "Ki irni a elemeket" -PythonRadians = "Fokról radiánra konvertálni" -PythonRandint = "Véletlen egész szám [a;b] -ban" -PythonRandom = "Decimális szám [0;1] -ban" -PythonRandomFunction = "random modul funkció elötag" -PythonRandrange = "Véletlen szám range(start,stop)-ban" -PythonRangeStartStop = "start-tol stop-ig listája" -PythonRangeStop = "0 tol stop-ig lista" -PythonRect = "Algebrai számra konvertálni" -PythonRemove = "Elsö x elöfordulását törolni" -PythonReverse = "A lista elemeit megfordítani (másik irány)" -PythonRound = "N számjegyre kerekítni" -PythonScatter = "(x,y) halmaza" -PythonSeed = "Inicializálni a véletlenszám-választót" -PythonSetPixel = "Az (x,y) pixel-t ki szinezni" -PythonShow = "Mutassa az ábrát" -PythonSin = "Szinusz" -PythonSinh = "Hiperbolikus szinusz" -PythonSleep = "t másodpercre meg állitani a programmot" -PythonLocalTime = "Idő konvertálása csomóvá" -PythonMktime = "A tuple konvertálása az időben" -PythonTime = "Az aktuális időbélyeg letöltése" -PythonSetLocaltime = "Idő beállítása egy csomóból" -PythonRTCmode = "Aktuális RTC mód" -PythonSetRTCmode = "RTC mód beállítása" -PythonSort = "A listát rendezni" -PythonSqrt = "Négyzetgyök" -PythonSum = "Összeadni a lista elemeit" -PythonTan = "Érintö (tan)" -PythonTanh = "Hiperbolikus érintö (tan)" -PythonText = "(x,y) nél egy szöveget irni" -PythonTimeFunction = "time funkció elötag" -PythonTrunc = "Egész csonka (?)" -PythonTurtleBackward = "x pixelt hátra" -PythonTurtleCircle = "r pixel sugarú kört rajzolni" -PythonTurtleColor = "Toll szinét beállitani" -PythonTurtleColorMode = "Szin módot 1.0-ra vagy 255-ra állitani" -PythonTurtleForward = "x pixelt elölre" -PythonTurtleFunction = "turtle modul funkció elötag" -PythonTurtleGoto = "Menjen a (x,y) koordinátákra" -PythonTurtleHeading = "Visszaadja az aktuális irányt" -PythonTurtleHideturtle = "A teknös elrejtése" -PythonTurtleIsdown = "True-t válaszol ha a toll irás pozícióban van" -PythonTurtleLeft = "a fokkot forduljon balra" -PythonTurtlePendown = "Húzza le a tollat" -PythonTurtlePensize = "Állítsa a vonalvastagságot x pixelre" -PythonTurtlePenup = "Húzza fel a tollat" -PythonTurtlePosition = "Az aktuális (x,y) pozíciót visszaadása" -PythonTurtleReset = "Visszaállitani a rajzot (torléssel)" -PythonTurtleRight = "a fokkot forduljon jobbra" -PythonTurtleSetheading = "a fokokra állítja be az irányt" -PythonTurtleSetposition = "A teknös pozicioját allitja" -PythonTurtleShowturtle = "A teknöst meg mutatni" -PythonTurtleSpeed = "Rajzolási sebesség 0 és 10 között" -PythonTurtleWrite = "Szöveg irás" -PythonUniform = "Lebegöpontos szám [a,b] -ban" -PythonImportTime = "time modul importálása" -PythonTimePrefix = "time funkció elötag" -PythonTimeSleep = "n másodpercet várni" -PythonMonotonic = "Meg fordítani a monoton idö" -PythonFileOpen = "Fájl megnyitása" -PythonFileSeekable = "Seek-et lehete használni" -PythonFileSeek = "A kurzort áthelyezni" -PythonFileTell = "Visszaadja a kurzor helye" -PythonFileClose = "Bezárni egy fájlt" -PythonFileClosed = "True ha a fájl bezárva" -PythonFileRead = "Olvas 16 bájtig" -PythonFileWrite = "b-t irjon a fájlba" -PythonFileReadline = "Olvas egy sort vagy 16 bájtig" -PythonFileReadlines = "Olvas több sort" -PythonFileTruncate = "A fájl átméretezése" -PythonFileWritelines = "Irjon több sort" -PythonFileName = "A fájl neve" -PythonFileMode = "A fájl nyitott módja" -PythonFileReadable = "read-et lehete használni" -PythonFileWritable = "write-ot lehete használni" -PythonImportOs = "os modul importálása" -PythonOsUname = "Rendszer informaciók" -PythonOsGetlogin = "Get username" -PythonOsRemove = "Fájl törlése" -PythonOsRename = "Fájl átnevezése" -PythonOsListdir = "Fájlok listája" -PythonImportSys = "sys modul importálása" -PythonSysExit = "Terminate current program" -PythonSysPrintexception = "Print exception" -PythonSysByteorder = "The byte order of the system" -PythonSysImplementation = "Information about Python" -PythonSysModules = "Dictionary of loaded modules" -PythonSysVersion = "Python language version (string)" -PythonSysVersioninfo = "Python language version (tuple)" +PythonPound = "Megjegyzés" +PythonPercent = "Modulo" +Python1J = "Képzeletbeli i" +PythonLF = "Enter" +PythonTab = "Táblázat" +PythonAmpersand = "Logikus és" +PythonSymbolExp = "logikus exkluzív vagy pedig" +PythonVerticalBar = "logikus vagy pedig" +PythonImag = "z képzeletbeli része" +PythonReal = "z valódi része" +PythonSingleQuote = "apostróf" +PythonAbs = "Abszolút érték/nagyság" +PythonAcos = "Ív (arc) koszinusz" +PythonAcosh = "Hiperbolikus arc koszinusz" +PythonAppend = "Lista végére hozzáadni x-et" +PythonArrow = "(x,y) nyíla (x+dx,y+dy) nyílához" +PythonAsin = "Ív (arc) szinusz" +PythonAsinh = "Hiperbolikus ív (arc) szinusz" +PythonAtan = "Ív (arc) érintö (tan)" +PythonAtan2 = "atan(y/x) sámolása" +PythonAtanh = "Hiperbolikus ív (arc) érintö (atan)" +PythonAxis = "Tengelyeket (xmin,xmax,ymin,ymax)-ra állitani" +PythonBar = "Az x lista oszlopdiagramja" +PythonBin = "Egész szám konvertálása binárisra" +PythonCeil = "Mennyezet" +PythonChoice = "Véletlenszerü szám a listában" +PythonClear = "A lista ürítése" +PythonCmathFunction = "cmath modul funkció elötag" +PythonColor = "Rgb (pzk) szín allítása" +PythonColorBlack = "Fekete szín" +PythonColorBlue = "Kék szín" +PythonColorBrown = "Barna szín" +PythonColorGreen = "Zöld szín" +PythonColorGray = "Szürke szín" +PythonColorOrange = "Narancssárga szín" +PythonColorPink = "Rózsaszín szín" +PythonColorPurple = "Lila szín" +PythonColorRed = "Piros szín" +PythonColorWhite = "Fehér szín" +PythonColorYellow = "Sárga szín" +PythonComplex = "A + ib visszaadása" +PythonCopySign = "X visszaadása y jelével" +PythonCos = "Koszinusz" +PythonCosh = "Hiperbolikus koszinusz" +PythonCount = "Számolja az x elöfordulását" +PythonDegrees = "x konvertálása radiánokrol fokokra" +PythonDivMod = "Hányados és maradék" +PythonDrawCircle = "Rajzolj egy kört" +PythonDrawLine = "Húzzon egy vonalat " +PythonDrawString = "Szöveg megjelenítése (x, y)-en" +PythonErf = "Hiba funkció" +PythonErfc = "Kiegészítö hiba funkció" +PythonEval = "Visszaadja az értékelt kifejezést" +PythonExp = "Exponenciális függvény" +PythonExpm1 = "exp(x)-1 sámitása" +PythonFactorial = "x faktorál" +PythonFabs = "Abszolút érték" +PythonFillRect = "Téglalap töltése" +PythonFillCircle = "Kitölti a kört" +PythonFillPolygon = "Kitölti a poligont" +PythonFloat = "Konvertálása tizedes számra" +PythonFloor = "Egész része" +PythonFmod = "a modulo b" +PythonFrExp = "X mantissája és kiállítója" +PythonGamma = "Gamma funkció" +PythonGetKeys = "Billentyűk lenyomva" +PythonGetPalette = "Téma paletta beszerzése" +PythonGetPixel = "Visszatéríti (x,y) színét" +PythonGetrandbits = "Váletlenszám visszatérítése k biten" +PythonGrid = "Rács megjelenítése/elrejtése" +PythonHex = "Decimális szám konvertálása hexadecimális számra" +PythonHist = "x hisztográmiája" +PythonImportCmath = "cmath modul importálása" +PythonImportIon = "Ion modul importálása" +PythonImportKandinsky = "Kandinsky modul importálása" +PythonImportRandom = "Véletlenszerü modul importálása" +PythonImportMath = "math modul importálása" +PythonImportMatplotlibPyplot = "matplotlib.pyplot modul importálása" +PythonImportNumpy = "ulab.numpy modul importálása" +PythonImportScipy = "ulab.scipy modul importálása" +PythonImportTurtle = "turtle modul importálása" +PythonImportTime = "time modul importálása" +PythonIndex = "Az elsö x esemény indexe" +PythonInput = "Irjon egy értéket (számot)" +PythonInsert = "x-et i. pozícióra helyezze a listában" +PythonInt = "egész számra konvertálás" +PythonIonFunction = "ion modul funkció elötag" +PythonIsFinite = "x véges-e" +PythonIsInfinite = "x végtelen-e" +PythonIsKeyDown = "True-t válaszol ha a k gomb le van nyomva" +PythonBattery = "Az akkumulátor feszültségének visszaadása" +PythonBatteryLevel = "Az akkumulátor töltöttségi szintjének visszaadása" +PythonBatteryIscharging = "Visszaadja, ha az akkumulátor töltődik" +PythonSetBrightness = "Fényerőszint beállítása" +PythonGetBrightness = "Get brightness level" +PythonIsNaN = "Ellenörizze hogy x nem NaN" +PythonKandinskyFunction = "kandinsky modul funkció elötag" +PythonLdexp = "frexp ellentéte : x*(2**i)" +PythonLength = "Egy targy hossza" +PythonLgamma = "Gamma funkció logaritmusa" +PythonLog = "a alapú logaritmus" +PythonLog10 = "Decimális logaritmus" +PythonLog2 = "Bináris logaritmus" +PythonMathFunction = "math modul funkció elötag" +PythonMatplotlibPyplotFunction = "matplotlib.pyplot elötag" +PythonMax = "Maximum" +PythonMin = "Minimum" +PythonModf = "x-nek tört és egész részei" +PythonMonotonic = "Az óra értékét adja vissza" +PythonNumpyFunction = "numpy elötag" +PythonNumpyFftFunction = "numpy.fft elötag" +PythonNumpyLinalgFunction = "numpy.linalg elötag" +PythonScipyFunction = "scipy elötag" +PythonScipyLinalgFunction = "scipy.linalg elötag" +PythonScipyOptimizeFunction = "scipy.optimize elötag" +PythonScipySignalFunction = "scipy.signal elötag" +PythonScipySpecialFunction = "scipy.special elötag" +PythonOct = "Decimális szám konvertálása octális számra" +PythonPhase = "z fázisa" +PythonPlot = "y-t jelöli x függvényében" +PythonPolar = "Verctorizálni" +PythonPop = "Az utolsó elemet el törölni" +PythonPower = "x y. kitevö" +PythonPrint = "Ki irni a elemeket" +PythonRadians = "Fokról radiánra konvertálni" +PythonRandint = "Véletlen egész szám [a;b] -ban" +PythonRandom = "Decimális szám [0;1] -ban" +PythonRandomFunction = "random modul funkció elötag" +PythonRandrange = "Véletlen szám range(start,stop)-ban" +PythonRangeStartStop = "start-tol stop-ig listája" +PythonRangeStop = "0 tol stop-ig lista" +PythonRect = "Algebrai számra konvertálni" +PythonRemove = "Elsö x elöfordulását törolni" +PythonReverse = "A lista elemeit megfordítani (másik irány)" +PythonRound = "N számjegyre kerekítni" +PythonScatter = "(x,y) halmaza" +PythonSeed = "Inicializálni a véletlenszám-választót" +PythonSetPixel = "Az (x,y) pixel-t ki szinezni" +PythonShow = "Mutassa az ábrát" +PythonSin = "Szinusz" +PythonSinh = "Hiperbolikus szinusz" +PythonSleep = "t másodpercre meg állitani a programmot" +PythonLocalTime = "Idő konvertálása csomóvá" +PythonMktime = "A tuple konvertálása az időben" +PythonTime = "Az aktuális időbélyeg letöltése" +PythonSetLocaltime = "Idő beállítása egy csomóból" +PythonRTCmode = "Aktuális RTC mód" +PythonSetRTCmode = "RTC mód beállítása" +PythonSort = "A listát rendezni" +PythonSqrt = "Négyzetgyök" +PythonSum = "Összeadni a lista elemeit" +PythonTan = "Érintö (tan)" +PythonTanh = "Hiperbolikus érintö (tan)" +PythonText = "(x,y) nél egy szöveget irni" +PythonTimeFunction = "time funkció elötag" +PythonTrunc = "Egész csonka (?)" +PythonTurtleBackward = "x pixelt hátra" +PythonTurtleCircle = "r pixel sugarú kört rajzolni" +PythonTurtleColor = "Toll szinét beállitani" +PythonTurtleColorMode = "Szin módot 1.0-ra vagy 255-ra állitani" +PythonTurtleForward = "x pixelt elölre" +PythonTurtleFunction = "turtle modul funkció elötag" +PythonTurtleGoto = "Menjen a (x,y) koordinátákra" +PythonTurtleHeading = "Visszaadja az aktuális irányt" +PythonTurtleHideturtle = "A teknös elrejtése" +PythonTurtleIsdown = "True-t válaszol ha a toll irás pozícióban van" +PythonTurtleLeft = "a fokkot forduljon balra" +PythonTurtlePendown = "Húzza le a tollat" +PythonTurtlePensize = "Állítsa a vonalvastagságot x pixelre" +PythonTurtlePenup = "Húzza fel a tollat" +PythonTurtlePosition = "Az aktuális (x,y) pozíciót visszaadása" +PythonTurtleReset = "Visszaállitani a rajzot (torléssel)" +PythonTurtleRight = "a fokkot forduljon jobbra" +PythonTurtleSetheading = "a fokokra állítja be az irányt" +PythonTurtleShowturtle = "A teknöst meg mutatni" +PythonTurtleSpeed = "Rajzolási sebesség 0 és 10 között" +PythonTurtleWrite = "Szöveg irás" +PythonUniform = "Lebegöpontos szám [a,b] -ban" +PythonImportTime = "time modul importálása" +PythonMonotonic = "Meg fordítani a monoton idö" +PythonFileOpen = "Fájl megnyitása" +PythonFileSeekable = "Seek-et lehete használni" +PythonFileSeek = "A kurzort áthelyezni" +PythonFileTell = "Visszaadja a kurzor helye" +PythonFileClose = "Bezárni egy fájlt" +PythonFileClosed = "True ha a fájl bezárva" +PythonFileRead = "Olvas 16 bájtig" +PythonFileWrite = "b-t irjon a fájlba" +PythonFileReadline = "Olvas egy sort vagy 16 bájtig" +PythonFileReadlines = "Olvas több sort" +PythonFileTruncate = "A fájl átméretezése" +PythonFileWritelines = "Irjon több sort" +PythonFileName = "A fájl neve" +PythonFileMode = "A fájl nyitott módja" +PythonFileReadable = "read-et lehete használni" +PythonFileWritable = "write-ot lehete használni" +PythonImportOs = "os modul importálása" +PythonOsUname = "Rendszer informaciók" +PythonOsGetlogin = "Get username" +PythonOsRemove = "Fájl törlése" +PythonOsRename = "Fájl átnevezése" +PythonOsListdir = "Fájlok listája" +PythonImportSys = "sys modul importálása" +PythonSysExit = "Terminate current program" +PythonSysPrintexception = "Print exception" +PythonSysByteorder = "The byte order of the system" +PythonSysImplementation = "Information about Python" +PythonSysModules = "Dictionary of loaded modules" +PythonSysVersion = "Python language version (string)" +PythonSysVersioninfo = "Python language version (tuple)" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index 8aa01572959..9a1bae232d5 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -186,14 +186,11 @@ PythonTurtlePosition = "Fornisce posizione corrente (x,y)" PythonTurtleReset = "Azzera il disegno" PythonTurtleRight = "Ruota di a gradi a destra" PythonTurtleSetheading = "Imposta l'orientamento per a gradi" -PythonTurtleSetposition = "Posiziona la tartaruga" PythonTurtleShowturtle = "Mostra la tartaruga" PythonTurtleSpeed = "Velocità di disegno (x tra 0 e 10)" PythonTurtleWrite = "Mostra un testo" PythonUniform = "Numero decimale tra [a,b]" PythonImportTime = "Import time module" -PythonTimePrefix = "time module function prefix" -PythonTimeSleep = "Wait for n second" PythonMonotonic = "Return monotonic time" PythonFileOpen = "Opens a file" PythonFileSeekable = "Tells if seek can be used on a file" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index 17c9f9eb7dd..8fe6b81e0e7 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -187,14 +187,11 @@ PythonTurtlePosition = "Zet huidige (x,y) locatie terug" PythonTurtleReset = "Reset de tekening" PythonTurtleRight = "Ga rechtsaf met a graden" PythonTurtleSetheading = "Zet de oriëntatie op a graden" -PythonTurtleSetposition = "Plaats de schildpad" PythonTurtleShowturtle = "Laat de schildpad zien" PythonTurtleSpeed = "Tekensnelheid tussen 0 and 10" PythonTurtleWrite = "Display a text" PythonUniform = "Decimaal getal in [a,b]" PythonImportTime = "Import time module" -PythonTimePrefix = "time module function prefix" -PythonTimeSleep = "Wait for n second" PythonMonotonic = "Return monotonic time" PythonFileOpen = "Opens a file" PythonFileSeekable = "Tells if seek can be used on a file" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index 3243a1e0c01..1a894c32273 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -172,7 +172,6 @@ PythonTurtlePosition = "Devolve a posição atual (x,y)" PythonTurtleReset = "Reiniciar o desenho" PythonTurtleRight = "Virar à esquerda por a graus" PythonTurtleSetheading = "Definir a orientação por a graus" -PythonTurtleSetposition = "Posicionamento da tartaruga" PythonTurtleShowturtle = "Mostrar o turtle" PythonTurtleSpeed = "Velocidade do desenho entre 0 e 10" PythonTurtleWrite = "Mostrar um texto" @@ -192,8 +191,6 @@ PythonSysImplementation = "Information about Python" PythonSysModules = "Dictionary of loaded modules" PythonSysVersion = "Python language version (string)" PythonSysVersioninfo = "Python language version (tuple)" -PythonTimePrefix = "time module function prefix" -PythonTimeSleep = "Wait for n second" PythonMonotonic = "Return monotonic time" PythonFileOpen = "Opens a file" PythonFileSeekable = "Tells if seek can be used on a file" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index 8159d966ba0..7da352806d6 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -114,52 +114,6 @@ PythonCommandIsInfinite = "isinf(x)" PythonCommandIsNaN = "isnan(x)" PythonCommandKandinskyFunction = "kandinsky.function" PythonCommandKandinskyFunctionWithoutArg = "kandinsky.\x11" -PythonCommandKeyLeft = "KEY_LEFT" -PythonCommandKeyUp = "KEY_UP" -PythonCommandKeyDown = "KEY_DOWN" -PythonCommandKeyRight = "KEY_RIGHT" -PythonCommandKeyOk = "KEY_OK" -PythonCommandKeyBack = "KEY_BACK" -PythonCommandKeyHome = "KEY_HOME" -PythonCommandKeyOnOff = "KEY_ONOFF" -PythonCommandKeyShift = "KEY_SHIFT" -PythonCommandKeyAlpha = "KEY_ALPHA" -PythonCommandKeyXnt = "KEY_XNT" -PythonCommandKeyVar = "KEY_VAR" -PythonCommandKeyToolbox = "KEY_TOOLBOX" -PythonCommandKeyBackspace = "KEY_BACKSPACE" -PythonCommandKeyExp = "KEY_EXP" -PythonCommandKeyLn = "KEY_LN" -PythonCommandKeyLog = "KEY_LOG" -PythonCommandKeyImaginary = "KEY_IMAGINARY" -PythonCommandKeyComma = "KEY_COMMA" -PythonCommandKeyPower = "KEY_POWER" -PythonCommandKeySine = "KEY_SINE" -PythonCommandKeyCosine = "KEY_COSINE" -PythonCommandKeyTangent = "KEY_TANGENT" -PythonCommandKeyPi = "KEY_PI" -PythonCommandKeySqrt = "KEY_SQRT" -PythonCommandKeySquare = "KEY_SQUARE" -PythonCommandKeySeven = "KEY_SEVEN" -PythonCommandKeyEight = "KEY_EIGHT" -PythonCommandKeyNine = "KEY_NINE" -PythonCommandKeyLeftParenthesis = "KEY_LEFTPARENTHESIS" -PythonCommandKeyRightParenthesis = "KEY_RIGHTPARENTHESIS" -PythonCommandKeyFour = "KEY_FOUR" -PythonCommandKeyFive = "KEY_FIVE" -PythonCommandKeySix = "KEY_SIX" -PythonCommandKeyMultiplication = "KEY_MULTIPLICATION" -PythonCommandKeyDivision = "KEY_DIVISION" -PythonCommandKeyOne = "KEY_ONE" -PythonCommandKeyTwo = "KEY_TWO" -PythonCommandKeyThree = "KEY_THREE" -PythonCommandKeyPlus = "KEY_PLUS" -PythonCommandKeyMinus = "KEY_MINUS" -PythonCommandKeyZero = "KEY_ZERO" -PythonCommandKeyDot = "KEY_DOT" -PythonCommandKeyEe = "KEY_EE" -PythonCommandKeyAns = "KEY_ANS" -PythonCommandKeyExe = "KEY_EXE" PythonCommandIsKeyDown = "keydown(k)" PythonCommandBattery = "battery()" PythonCommandBatteryLevel = "battery_level()" @@ -403,16 +357,8 @@ PythonTurtleCommandPosition = "position()" PythonTurtleCommandReset = "reset()" PythonTurtleCommandRight = "right(a)" PythonTurtleCommandSetheading = "setheading(a)" -PythonTurtleCommandSetposition = "setposition(x,[y])" PythonTurtleCommandShowturtle = "showturtle()" PythonTurtleCommandSpeed = "speed(x)" -PythonTurtleCommandWhite = "'white'" -PythonTurtleCommandYellow = "'yellow'" -PythonTimeModule = "time" -PythonTimeCommandImportFrom = "from time import *" -PythonTimeCommandSleep = "sleep()" -PythonTimeCommandSleepDemo = "sleep(n)" -PythonTimeCommandMonotonic = "monotonic()" PythonCommandFileOpen = "open(name, [mode])" PythonCommandFileOpenWithoutArg = "open(\x11)" PythonCommandFileSeek = "file.seek(offset, [whence])" diff --git a/apps/sequence/base.de.i18n b/apps/sequence/base.de.i18n index 88f518ca92b..21da2aeabd6 100644 --- a/apps/sequence/base.de.i18n +++ b/apps/sequence/base.de.i18n @@ -17,6 +17,5 @@ NEnd = "Endwert" TermSum = "Summe der Terme" SelectFirstTerm = "Erster Term " SelectLastTerm = "Letzter Term " -ValueNotReachedBySequence = "Wert wird von Folge nicht erreicht" NColumn = "n-te Spalte" FirstTermIndex = "Anfangsindex" diff --git a/apps/sequence/base.en.i18n b/apps/sequence/base.en.i18n index e9917698c8d..8776d4b7910 100644 --- a/apps/sequence/base.en.i18n +++ b/apps/sequence/base.en.i18n @@ -17,6 +17,5 @@ NEnd = "N end" TermSum = "Sum of terms" SelectFirstTerm = "Select First Term " SelectLastTerm = "Select last term " -ValueNotReachedBySequence = "Value not reached by sequence" NColumn = "n column" FirstTermIndex = "First term index" diff --git a/apps/sequence/base.es.i18n b/apps/sequence/base.es.i18n index 4bf2fe4929b..691b286b249 100644 --- a/apps/sequence/base.es.i18n +++ b/apps/sequence/base.es.i18n @@ -17,6 +17,5 @@ NEnd = "N fin" TermSum = "Suma de términos" SelectFirstTerm = "Seleccionar el primer término " SelectLastTerm = "Seleccionar el último término " -ValueNotReachedBySequence = "No se alcanza este valor" NColumn = "Columna n" FirstTermIndex = "Índice del primer término" diff --git a/apps/sequence/base.fr.i18n b/apps/sequence/base.fr.i18n index 3772efb4713..09d8bfa9da9 100644 --- a/apps/sequence/base.fr.i18n +++ b/apps/sequence/base.fr.i18n @@ -17,6 +17,5 @@ NEnd = "N fin" TermSum = "Somme des termes" SelectFirstTerm = "Sélectionner le premier terme " SelectLastTerm = "Sélectionner le dernier terme " -ValueNotReachedBySequence = "Valeur non atteinte par la suite" NColumn = "Colonne n" FirstTermIndex = "Indice premier terme" diff --git a/apps/sequence/base.hu.i18n b/apps/sequence/base.hu.i18n index f98ed2366d8..b52a13cfaf0 100644 --- a/apps/sequence/base.hu.i18n +++ b/apps/sequence/base.hu.i18n @@ -1,22 +1,21 @@ -SequenceApp = "Szekvenciák" -SequenceAppCapital = "SZEKVENCIÁK" -SequenceTab = "Szekvenciák" -AddSequence = "Szekvencia hozzáadása" -ChooseSequenceType = "Válassza ki a sorozat típusát" -SequenceType = "Szekvencia típusa" -Explicit = "Explicit kifejezés" -SingleRecurrence = "Rekurzív elsö sorrend" -DoubleRecurrence = "Rekurzív második sorrend" -SequenceOptions = "Szekvencia opciók" -SequenceColor = "Szekvencia színe" -DeleteSequence = "Sorozat törlése" -NoSequence = "Nincs sorrend" -NoActivatedSequence = "Nincs szekvencia bekapcsolva" -NStart = "N start" -NEnd = "N vég" -TermSum = "A kifejezés összege" -SelectFirstTerm = "Elsö kifejezés kiválasztása " -SelectLastTerm = "Utolsó kifejezés kiválasztása " -ValueNotReachedBySequence = "Az értéket nem érte el a sorozat" -NColumn = "n oszlop" -FirstTermIndex = "Elsö kifejezés index" +SequenceApp = "Szekvenciák" +SequenceAppCapital = "SZEKVENCIÁK" +SequenceTab = "Szekvenciák" +AddSequence = "Szekvencia hozzáadása" +ChooseSequenceType = "Válassza ki a sorozat típusát" +SequenceType = "Szekvencia típusa" +Explicit = "Explicit kifejezés" +SingleRecurrence = "Rekurzív elsö sorrend" +DoubleRecurrence = "Rekurzív második sorrend" +SequenceOptions = "Szekvencia opciók" +SequenceColor = "Szekvencia színe" +DeleteSequence = "Sorozat törlése" +NoSequence = "Nincs sorrend" +NoActivatedSequence = "Nincs szekvencia bekapcsolva" +NStart = "N start" +NEnd = "N vég" +TermSum = "A kifejezés összege" +SelectFirstTerm = "Elsö kifejezés kiválasztása " +SelectLastTerm = "Utolsó kifejezés kiválasztása " +NColumn = "n oszlop" +FirstTermIndex = "Elsö kifejezés index" diff --git a/apps/sequence/base.it.i18n b/apps/sequence/base.it.i18n index d9ddac4a1bf..3d714acfeac 100644 --- a/apps/sequence/base.it.i18n +++ b/apps/sequence/base.it.i18n @@ -17,6 +17,5 @@ NEnd = "N finale" TermSum = "Somma dei termini" SelectFirstTerm = "Selezionare il primo termine " SelectLastTerm = "Selezionare l'ultimo termine " -ValueNotReachedBySequence = "Valore non raggiunto dalla successione" NColumn = "Colonna n" FirstTermIndex = "Indice del primo termine" diff --git a/apps/sequence/base.nl.i18n b/apps/sequence/base.nl.i18n index 34a63d53d43..178761f1d40 100644 --- a/apps/sequence/base.nl.i18n +++ b/apps/sequence/base.nl.i18n @@ -17,6 +17,5 @@ NEnd = "N einde" TermSum = "Som van termen" SelectFirstTerm = "Selecteer eerste term " SelectLastTerm = "Selecteer laatste term " -ValueNotReachedBySequence = "Waarde niet bereikt door de rij" NColumn = "n-kolom" FirstTermIndex = "Eerste termindex" diff --git a/apps/sequence/base.pt.i18n b/apps/sequence/base.pt.i18n index 97f6580f8dd..3cfbb3338a7 100644 --- a/apps/sequence/base.pt.i18n +++ b/apps/sequence/base.pt.i18n @@ -17,6 +17,5 @@ NEnd = "N fim" TermSum = "Soma dos termos" SelectFirstTerm = "Selecionar primeiro termo " SelectLastTerm = "Selecionar último termo " -ValueNotReachedBySequence = "O valor não é alcançado pela sequência" NColumn = "Coluna n" FirstTermIndex = "Índice do primeiro termo" diff --git a/apps/settings/base.de.i18n b/apps/settings/base.de.i18n index 2f38be2bb73..d44631a8ca6 100644 --- a/apps/settings/base.de.i18n +++ b/apps/settings/base.de.i18n @@ -62,7 +62,6 @@ SymbolFunction = "Ausdrucksformat " SymbolDefaultFunction = "Standardl " SymbolArgFunction = "Leer " SymbolArgDefaultFunction = "Argument " -PythonFont = "Python Schriftart" MemUse = "Speicher" DateTime = "Datum/Uhrzeit" ActivateClock = "Uhr aktivieren" diff --git a/apps/settings/base.en.i18n b/apps/settings/base.en.i18n index ebdc55abe8b..50707610ffa 100644 --- a/apps/settings/base.en.i18n +++ b/apps/settings/base.en.i18n @@ -62,7 +62,6 @@ SymbolFunction = "Expression format " SymbolDefaultFunction = "Default " SymbolArgFunction = "Empty " SymbolArgDefaultFunction = "Argument " -PythonFont = "Python Font" MemUse = "Memory" DateTime = "Date/time" ActivateClock = "Activate clock" diff --git a/apps/settings/base.es.i18n b/apps/settings/base.es.i18n index b3b8ec6e6ed..4d47b15e02a 100644 --- a/apps/settings/base.es.i18n +++ b/apps/settings/base.es.i18n @@ -62,7 +62,6 @@ SymbolFunction = "Formato expresión " SymbolDefaultFunction = "Defecto " SymbolArgFunction = "Vacío " SymbolArgDefaultFunction = "Argumento " -PythonFont = "Fuente Python" MemUse = "Memoria" DateTime = "Fecha/Hora" ActivateClock = "Activar el reloj" diff --git a/apps/settings/base.fr.i18n b/apps/settings/base.fr.i18n index 3204ba62161..e200107613c 100644 --- a/apps/settings/base.fr.i18n +++ b/apps/settings/base.fr.i18n @@ -62,7 +62,6 @@ SymbolFunction = "Format expression " SymbolDefaultFunction = "Défaut " SymbolArgFunction = "Vide " SymbolArgDefaultFunction = "Arguments " -PythonFont = "Police Python" MemUse = "Mémoire" DateTime = "Date/heure" ActivateClock = "Activer horloge" diff --git a/apps/settings/base.hu.i18n b/apps/settings/base.hu.i18n index 080b7b5da40..3b4f083f7f6 100644 --- a/apps/settings/base.hu.i18n +++ b/apps/settings/base.hu.i18n @@ -1,82 +1,81 @@ -SettingsApp = "Beállítások" -SettingsAppCapital = "BEÁLLÍTÁSOK" -AngleUnit = "Szögmérö" -DisplayMode = "Eredmény formátuma" -EditionMode = "Írás formátuma" -EditionLinear = "Lineáris" -Edition2D = "Természetes" -ComplexFormat = "Komplex formátum" -ExamMode = "Vizsga mód" -ExamModeActive = "A vizsgamód újraaktiválása" -ToDeactivateExamMode1 = "a vizsga mód kikapcsoláshoz" -ToDeactivateExamMode2 = "csatlakoztassa a számológépet a számítógéphez" -ToDeactivateExamMode3 = "vagy egy konnektorhoz." -# --------------------- Please do not edit these messages --------------------- -ExamModeWarning1 = "Vigyázat: a használt szoftver nem" -ExamModeWarning2 = "hivatalos, Numworks nem garantálja" -ExamModeWarning3 = "a vizsgálati mód megfelelőségét." -AboutWarning1 = "Vigyázat: a használt szoftver" -AboutWarning2 = "nem hivatalos. A NumWorks nem" -AboutWarning3 = "vállal felelösséget az" -AboutWarning4 = "esetleges károkért." -# ----------------------------------------------------------------------------- -About = "Apropó" -Degrees = "Fokok " -Gradians = "Gradiens " -Radian = "Radián " -Decimal = "Tizedes " -Scientific = "Tudományos " -Engineering = "Mérnökség " -SignificantFigures = "Tizedes számok " -Real = "Valódi " -Cartesian = "Kartéziánus " -Polar = "Poláris " -Brightness = "Fényerö" -SoftwareVersion = "Epsilon verzió" -UpsilonVersion = "Upsilon verzió" -OmegaVersion = "Omega verzió" -Username = "Felhasználónév" -MicroPythonVersion = "µPython verzió" -FontSizes = "Python betü méret" -LargeFont = "Nagy " -SmallFont = "Kicsi " -SerialNumber = "Sorozatszám" -UpdatePopUp = "Frissítés figyelmeztetés" -BetaPopUp = "Béta figyelmeztetés" -Contributors = "Közremüködök" -Battery = "Akkumulátor" -Accessibility = "Több vizuális beállitások" -AccessibilityInvertColors = "Invertált színek" -AccessibilityMagnify = "Nagyító" -AccessibilityGamma = "Gamma korrekció" -AccessibilityGammaRed = "Piros gamma" -AccessibilityGammaGreen = "Zöld gamma" -AccessibilityGammaBlue = "Kék gamma" -MathOptions = "Matematikai beállítások" -SymbolMultiplication = "Szorzás" -SymbolMultiplicationCross = "Kereszt " -SymbolMultiplicationMiddleDot = "Pont " -SymbolMultiplicationStar = "Csillag " -SymbolMultiplicationAutoSymbol = "Automatitus " -SymbolFunction = "Kifejezés " -SymbolDefaultFunction = "Alap " -SymbolArgFunction = "Üres " -SymbolArgDefaultFunction = "Argumentummal " -PythonFont = "Python Betütipus" -MemUse = "Memória" -DateTime = "Dátum/óra" -ActivateClock = "Óra bekapcsolása" -Date = "Datum" -Time = "Óra" -RTCWarning1 = "Amikor a számológép alvómódban van, az óra" -RTCWarning2 = "használása az elemet gyorsabban meríti ki." -SyntaxHighlighting = "Szintaxis kiemelés" -CursorSaving = "Kurzor mentése" -USBExplanation1 = "Az USB-védelem megvédi" -USBExplanation2 = "a számológépet a nem" -USBExplanation3 = "szándékos reteszeléstől" -USBProtection = "USB védelem" -USBProtectionLevel = "Elfogadott frissítések" -USBDefaultLevel = "Upsilon alapján" -USBLowLevel = "Omega alapján" -USBParanoidLevel = "Egyik sem" +SettingsApp = "Beállítások" +SettingsAppCapital = "BEÁLLÍTÁSOK" +AngleUnit = "Szögmérö" +DisplayMode = "Eredmény formátuma" +EditionMode = "Írás formátuma" +EditionLinear = "Lineáris" +Edition2D = "Természetes" +ComplexFormat = "Komplex formátum" +ExamMode = "Vizsga mód" +ExamModeActive = "A vizsgamód újraaktiválása" +ToDeactivateExamMode1 = "a vizsga mód kikapcsoláshoz" +ToDeactivateExamMode2 = "csatlakoztassa a számológépet a számítógéphez" +ToDeactivateExamMode3 = "vagy egy konnektorhoz." +# --------------------- Please do not edit these messages --------------------- +ExamModeWarning1 = "Vigyázat: a használt szoftver nem" +ExamModeWarning2 = "hivatalos, Numworks nem garantálja" +ExamModeWarning3 = "a vizsgálati mód megfelelőségét." +AboutWarning1 = "Vigyázat: a használt szoftver" +AboutWarning2 = "nem hivatalos. A NumWorks nem" +AboutWarning3 = "vállal felelösséget az" +AboutWarning4 = "esetleges károkért." +# ----------------------------------------------------------------------------- +About = "Apropó" +Degrees = "Fokok " +Gradians = "Gradiens " +Radian = "Radián " +Decimal = "Tizedes " +Scientific = "Tudományos " +Engineering = "Mérnökség " +SignificantFigures = "Tizedes számok " +Real = "Valódi " +Cartesian = "Kartéziánus " +Polar = "Poláris " +Brightness = "Fényerö" +SoftwareVersion = "Epsilon verzió" +UpsilonVersion = "Upsilon verzió" +OmegaVersion = "Omega verzió" +Username = "Felhasználónév" +MicroPythonVersion = "µPython verzió" +FontSizes = "Python betü méret" +LargeFont = "Nagy " +SmallFont = "Kicsi " +SerialNumber = "Sorozatszám" +UpdatePopUp = "Frissítés figyelmeztetés" +BetaPopUp = "Béta figyelmeztetés" +Contributors = "Közremüködök" +Battery = "Akkumulátor" +Accessibility = "Több vizuális beállitások" +AccessibilityInvertColors = "Invertált színek" +AccessibilityMagnify = "Nagyító" +AccessibilityGamma = "Gamma korrekció" +AccessibilityGammaRed = "Piros gamma" +AccessibilityGammaGreen = "Zöld gamma" +AccessibilityGammaBlue = "Kék gamma" +MathOptions = "Matematikai beállítások" +SymbolMultiplication = "Szorzás" +SymbolMultiplicationCross = "Kereszt " +SymbolMultiplicationMiddleDot = "Pont " +SymbolMultiplicationStar = "Csillag " +SymbolMultiplicationAutoSymbol = "Automatitus " +SymbolFunction = "Kifejezés " +SymbolDefaultFunction = "Alap " +SymbolArgFunction = "Üres " +SymbolArgDefaultFunction = "Argumentummal " +MemUse = "Memória" +DateTime = "Dátum/óra" +ActivateClock = "Óra bekapcsolása" +Date = "Datum" +Time = "Óra" +RTCWarning1 = "Amikor a számológép alvómódban van, az óra" +RTCWarning2 = "használása az elemet gyorsabban meríti ki." +SyntaxHighlighting = "Szintaxis kiemelés" +CursorSaving = "Kurzor mentése" +USBExplanation1 = "Az USB-védelem megvédi" +USBExplanation2 = "a számológépet a nem" +USBExplanation3 = "szándékos reteszeléstől" +USBProtection = "USB védelem" +USBProtectionLevel = "Elfogadott frissítések" +USBDefaultLevel = "Upsilon alapján" +USBLowLevel = "Omega alapján" +USBParanoidLevel = "Egyik sem" diff --git a/apps/settings/base.it.i18n b/apps/settings/base.it.i18n index ae64c22ddc7..a292dd0d2fc 100644 --- a/apps/settings/base.it.i18n +++ b/apps/settings/base.it.i18n @@ -62,7 +62,6 @@ SymbolFunction = "Expression format " SymbolDefaultFunction = "Default " SymbolArgFunction = "Empty " SymbolArgDefaultFunction = "Argument " -PythonFont = "Python Font" MemUse = "Memory" DateTime = "Date/time" ActivateClock = "Activate clock" diff --git a/apps/settings/base.nl.i18n b/apps/settings/base.nl.i18n index 6d72e340249..9baf4be1704 100644 --- a/apps/settings/base.nl.i18n +++ b/apps/settings/base.nl.i18n @@ -62,7 +62,6 @@ SymbolFunction = "Expression format " SymbolDefaultFunction = "Default " SymbolArgFunction = "Empty " SymbolArgDefaultFunction = "Argument " -PythonFont = "Python Font" MemUse = "Memory" DateTime = "Date/time" ActivateClock = "Activate clock" diff --git a/apps/settings/base.pt.i18n b/apps/settings/base.pt.i18n index d1f42789c1a..66a43282070 100644 --- a/apps/settings/base.pt.i18n +++ b/apps/settings/base.pt.i18n @@ -62,7 +62,6 @@ SymbolFunction = "Formato expressão " SymbolDefaultFunction = "Padrão " SymbolArgFunction = "Vazio " SymbolArgDefaultFunction = "Argumento " -PythonFont = "Fonte Python" MemUse = "Memória" DateTime = "Date/time" ActivateClock = "Activate clock" diff --git a/apps/shared.de.i18n b/apps/shared.de.i18n index 7219416911b..b8926850148 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -1,12 +1,8 @@ ActivateDeactivate = "Aktivieren/Deaktivieren" -ActivateDutchExamMode = "Prüfungsmodus starten NL" ActivateExamMode = "Prüfungsmodus starten" ActiveExamModeMessage1 = "Alle Ihre Daten werden " ActiveExamModeMessage2 = "gelöscht, wenn Sie den " ActiveExamModeMessage3 = "Prüfungsmodus einschalten." -ActiveDutchExamModeMessage1 = "Alle Daten werden gelöscht, wenn" -ActiveDutchExamModeMessage2 = "Sie den Prüfungsmodus einschalten. " -ActiveDutchExamModeMessage3 = "Python wird nicht verfügbar sein." Axis = "Achse" Cancel = "Abbrechen" ClearColumn = "Spalte löschen" @@ -39,7 +35,6 @@ ExitExamMode2 = "Prüfungsmodus verlassen?" Exponential = "Exponentielle" FillWithFormula = "Mit einer Formel füllen" ForbiddenValue = "Verbotener Wert" -FunctionColumn = "0(0) Spalte" FunctionOptions = "Funktionsoptionen" Goto = "Gehe zu" GraphTab = "Graph" @@ -59,7 +54,6 @@ Navigate = "Navigieren" NEnd = "N Endwert" Next = "Nächste" NoDataToPlot = "Keine Daten zum Zeichnen" -NoFunctionToDelete = "Keine Funktion zum Löschen" NoValueToCompute = "Keine Größe zum Berechnen" NStart = "N Startwert" Ok = "Bestätigen" @@ -78,7 +72,6 @@ StandardDeviation = "Standardabweichung" Step = "Schrittwert" StorageMemoryFull1 = "Der Speicher ist voll. Löschen Sie" StorageMemoryFull2 = "einige Daten, dann erneut versuchen." -StoreExpressionNotAllowed = "'store' ist verboten" SyntaxError = "Syntaxfehler" Sym = "sym" TEnd = "T Endwert" @@ -91,9 +84,6 @@ ValuesTab = "Tabelle" Warning = "Achtung" XEnd = "X Endwert" XStart = "X Startwert" -Zoom = "Zoom" -Developers = "Entwickler" -BetaTesters = "Beta-Tester" ExamModeMode = "Modus" ExamModeModeStandard = "Standard " ExamModeModeNoSym = "Kein Symbol " diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index d61df44e108..c18e948aac3 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -1,12 +1,8 @@ ActivateDeactivate = "Turn on/off" ActivateExamMode = "Activate exam mode" -ActivateDutchExamMode = "Activate Dutch exam mode" ActiveExamModeMessage1 = "All your data will be " ActiveExamModeMessage2 = "deleted when you activate " ActiveExamModeMessage3 = "the exam mode." -ActiveDutchExamModeMessage1 = "All your data will be deleted when" -ActiveDutchExamModeMessage2 = "you activate the exam mode. Python" -ActiveDutchExamModeMessage3 = "application will be unavailable." Axis = "Axes" Cancel = "Cancel" ClearColumn = "Clear column" @@ -39,7 +35,6 @@ ExitExamMode2 = "mode?" Exponential = "Exponential" FillWithFormula = "Fill with a formula" ForbiddenValue = "Forbidden value" -FunctionColumn = "0(0) column" FunctionOptions = "Function options" Goto = "Go to" GraphTab = "Graph" @@ -58,7 +53,6 @@ NameTooLong = "This name is too long" Navigate = "Navigate" Next = "Next" NoDataToPlot = "No data to draw" -NoFunctionToDelete = "No function to delete" NoValueToCompute = "No values to calculate" NEnd = "N end" NStart = "N start" @@ -74,7 +68,6 @@ SortValues = "Sort by increasing values" SortSizes = "Sort by increasing frequencies" SquareSum = "Sum of squares" StandardDeviation = "Standard deviation" -StoreExpressionNotAllowed = "'store' is not allowed" StatTab = "Stats" Step = "Step" StorageMemoryFull1 = "The memory is full." @@ -91,9 +84,6 @@ ValuesTab = "Table" Warning = "Warning" XEnd = "X end" XStart = "X start" -Zoom = "Zoom" -Developers = "Developers" -BetaTesters = "Beta testers" ExamModeMode = "Mode" ExamModeModeStandard = "Standard " ExamModeModeNoSym = "No sym " diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index e542571edab..800b303552a 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -1,12 +1,8 @@ ActivateDeactivate = "Activar/Desactivar" ActivateExamMode = "Activar el modo examen" -ActivateDutchExamMode = "Activar el modo examen NL" ActiveExamModeMessage1 = "Todos sus datos se " ActiveExamModeMessage2 = "eliminaran al activar " ActiveExamModeMessage3 = "el modo examen." -ActiveDutchExamModeMessage1 = "Todos sus datos se eliminaran al" -ActiveDutchExamModeMessage2 = "activar el modo examen. La aplicación" -ActiveDutchExamModeMessage3 = "Python ya no estará disponible." Axis = "Ejes" Cancel = "Cancelar" ClearColumn = "Borrar la columna" @@ -39,7 +35,6 @@ ExitExamMode2 = "examen ?" Exponential = "Exponencial" FillWithFormula = "Rellenar con una fórmula" ForbiddenValue = "Valor prohibido" -FunctionColumn = "Columna 0(0)" FunctionOptions = "Opciones de la función" Goto = "Ir a" GraphTab = "Gráfico" @@ -59,7 +54,6 @@ Navigate = "Navegar" NEnd = "N fin" Next = "Siguiente" NoDataToPlot = "Ningunos datos que dibujar" -NoFunctionToDelete = "Ninguna función que eliminar" NoValueToCompute = "Ninguna medida que calcular" NStart = "N inicio" Ok = "Confirmar" @@ -78,7 +72,6 @@ StatTab = "Medidas" Step = "Incremento" StorageMemoryFull1 = "La memoria está llena." StorageMemoryFull2 = "Borre datos e intente de nuevo." -StoreExpressionNotAllowed = "'store' no está permitido" SyntaxError = "Error sintáctico" Sym = "sim" TEnd = "T fin" @@ -91,9 +84,6 @@ ValuesTab = "Tabla" Warning = "Cuidado" XEnd = "X fin" XStart = "X inicio" -Zoom = "Zoom" -Developers = "Desarrolladores" -BetaTesters = "Probadores beta" ExamModeMode = "Modo" ExamModeModeStandard = "Estándar " ExamModeModeNoSym = "Sin simbólico " diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index 46b76202d59..6bcacc18e1e 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -1,12 +1,8 @@ ActivateDeactivate = "Activer/Désactiver" ActivateExamMode = "Activer le mode examen" -ActivateDutchExamMode = "Activer le mode examen NL" ActiveExamModeMessage1 = "Toutes vos données seront " ActiveExamModeMessage2 = "supprimées si vous activez " ActiveExamModeMessage3 = "le mode examen." -ActiveDutchExamModeMessage1 = "Toutes vos données seront supprimées " -ActiveDutchExamModeMessage2 = "si vous activez le mode examen." -ActiveDutchExamModeMessage3 = "Python sera inaccessible." Axis = "Axes" Cancel = "Annuler" ClearColumn = "Effacer la colonne" @@ -39,7 +35,6 @@ ExitExamMode2 = "du mode examen ?" Exponential = "Exponentielle" FillWithFormula = "Remplir avec une formule" ForbiddenValue = "Valeur interdite" -FunctionColumn = "Colonne 0(0)" FunctionOptions = "Options de la fonction" Goto = "Aller à" GraphTab = "Graphique" @@ -59,7 +54,6 @@ Navigate = "Naviguer" Next = "Suivant" NEnd = "N fin" NoDataToPlot = "Aucune donnée à tracer" -NoFunctionToDelete = "Pas de fonction à supprimer" NoValueToCompute = "Aucune grandeur à calculer" NStart = "N début" Ok = "Valider" @@ -78,7 +72,6 @@ StatTab = "Stats" Step = "Pas" StorageMemoryFull1 = "La mémoire est pleine." StorageMemoryFull2 = "Effacez des données et réessayez." -StoreExpressionNotAllowed = "'store' n'est pas autorisé" SyntaxError = "Attention à la syntaxe" Sym = "sym" TEnd = "T fin" @@ -91,9 +84,6 @@ ValuesTab = "Tableau" Warning = "Attention" XEnd = "X fin" XStart = "X début" -Zoom = "Zoom" -Developers = "Développeurs" -BetaTesters = "Beta testeurs" ExamModeMode = "Mode" ExamModeModeStandard = "Standard " ExamModeModeNoSym = "Sans symbolique " diff --git a/apps/shared.hu.i18n b/apps/shared.hu.i18n index 60ba0754b34..b9be6807bd2 100644 --- a/apps/shared.hu.i18n +++ b/apps/shared.hu.i18n @@ -1,101 +1,91 @@ -ActivateDeactivate = "Ki/Be kapcsolás" -ActivateExamMode = "A vizsgálati mód aktiválása" -ActivateDutchExamMode = "A holland vizsga mód aktiválása" -ActiveExamModeMessage1 = "Az összes adatod" -ActiveExamModeMessage2 = "törölve lesz ha" -ActiveExamModeMessage3 = "a vizsga módot aktiválja." -ActiveDutchExamModeMessage1 = "Az összes adatod törölve lesz" -ActiveDutchExamModeMessage2 = "ha a vizsga módot aktiválja. A" -ActiveDutchExamModeMessage3 = "Python alkalmazás használhatatlan lesz." -Axis = "Tengelyek" -Cancel = "Mégse" -ClearColumn = "Oszlop törlése" -ColumnOptions = "Oszlop opciók" -ConfirmDiscard1 = "Minden változtatást elvetünk" -ConfirmDiscard2 = "" -CopyColumnInList = "Az oszlopot egy listába másolni" -Country = "Ország" -CountryCA = "Kanada " -CountryDE = "Németország " -CountryES = "Spanyolország " -CountryFR = "Franciaország " -CountryGB = "Egyesült Királyság " -CountryIT = "Olaszország " -CountryNL = "Hollandia " -CountryPT = "Portugália " -CountryUS = "Egyesült Államok " -CountryWW = "Nemzetközi " -CountryWarning1 = "Ez a beállítás meghatározza az" -CountryWarning2 = "alkalmazott tematikus konvenciókat." -DataNotSuitable = "Az adatok nem felelnek meg" -DataTab = "Adatok" -Deg = "deg" -Deviation = "Varianca" -DisplayValues = "Értékek mutatása" -Empty = "Üres" -Eng = "eng" -ExitExamMode1 = "Kilépni a vizsga " -ExitExamMode2 = "módból?" -Exponential = "Exponenciális" -FillWithFormula = "Töltse ki egy képlettel" -ForbiddenValue = "Tiltott érték" -FunctionColumn = "0(0) oszlop" -FunctionOptions = "Funkció opciók" -Goto = "Menj ..." -GraphTab = "Grafikon" -HardwareTestLaunch1 = "A hardverteszt indítása :" -HardwareTestLaunch2 = "Nyomjon a reset gombra a" -HardwareTestLaunch3 = "teszt megállításához (ez" -HardwareTestLaunch4 = "az adatokat törölni fogja)" -IntervalSet = "Állítsa be az intervallumot" -Language = "Nyelv" -LowBattery = "Majdnem kimerült az elem" -Mean = "középérték" -Move = " Odébb rakni: " -NameCannotStartWithNumber = "Egy név nem kezdöthet számmal" -NameTaken = "Ez a név foglalt" -NameTooLong = "Ez a név túl hosszú" -Navigate = "Hajózik" -Next = "következö" -NEnd = "N vége" -NoDataToPlot = "Nincs rajzolható adat" -NoFunctionToDelete = "Nincs törölhetö függvény" -NoValueToCompute = "Nincs számítható érték" -NStart = "N kezdete" -Ok = "Érvényesítés" -Or = " vagy " -Orthonormal = "Ortonormált" -Plot = "Grafikon rajzolása" -PoolMemoryFull1 = "A memória megtelt." -PoolMemoryFull2 = "Kérem próbálja újra." -Rename = "Átnevezés" -Sci = "sci" -SortValues = "Rendezés értékek növelésével" -SortSizes = "Rendezés növekvő frekvenciák szerint" -SquareSum = "Négyzetek összege" -StandardDeviation = "Alap eltérés" -StatTab = "Statisztikák" -Step = "Lépés" -StorageMemoryFull1 = "A memória megtelt." -StorageMemoryFull2 = "Törlöljön adatokat és próbálkozzon újra." -StoreExpressionNotAllowed = "'szore' nem engedélyezett" -SyntaxError = "Szintaxis hiba" -Sym = "sym" -TEnd = "T vég" -ThetaEnd = "θ vége" -ThetaStart = "θ kezdete" -TStart = "T kezdete" -ToZoom = "Nagyítani : " -UndefinedValue = "Meghatározatlan adat" -ValuesTab = "Táblázat" -Warning = "Figyelem" -XEnd = "X vége" -XStart = "X kezdete" -Zoom = "Nagyítás" -Developers = "Kifejlesztök" -BetaTesters = "Béta tesztelök" -ExamModeMode = "Üzemmód" -ExamModeModeStandard = "Normál " -ExamModeModeNoSym = "Szimbólikus nélkül " -ExamModeModeNoSymNoText = "Szimbólikus és szöveg nélkül " -ExamModeModeDutch = "Holland " +ActivateDeactivate = "Ki/Be kapcsolás" +ActivateExamMode = "A vizsgálati mód aktiválása" +ActiveExamModeMessage1 = "Az összes adatod" +ActiveExamModeMessage2 = "törölve lesz ha" +ActiveExamModeMessage3 = "a vizsga módot aktiválja." +Axis = "Tengelyek" +Cancel = "Mégse" +ClearColumn = "Oszlop törlése" +ColumnOptions = "Oszlop opciók" +ConfirmDiscard1 = "Minden változtatást elvetünk" +ConfirmDiscard2 = "" +CopyColumnInList = "Az oszlopot egy listába másolni" +Country = "Ország" +CountryCA = "Kanada " +CountryDE = "Németország " +CountryES = "Spanyolország " +CountryFR = "Franciaország " +CountryGB = "Egyesült Királyság " +CountryIT = "Olaszország " +CountryNL = "Hollandia " +CountryPT = "Portugália " +CountryUS = "Egyesült Államok " +CountryWW = "Nemzetközi " +CountryWarning1 = "Ez a beállítás meghatározza az" +CountryWarning2 = "alkalmazott tematikus konvenciókat." +DataNotSuitable = "Az adatok nem felelnek meg" +DataTab = "Adatok" +Deg = "deg" +Deviation = "Varianca" +DisplayValues = "Értékek mutatása" +Empty = "Üres" +Eng = "eng" +ExitExamMode1 = "Kilépni a vizsga " +ExitExamMode2 = "módból?" +Exponential = "Exponenciális" +FillWithFormula = "Töltse ki egy képlettel" +ForbiddenValue = "Tiltott érték" +FunctionOptions = "Funkció opciók" +Goto = "Menj ..." +GraphTab = "Grafikon" +HardwareTestLaunch1 = "A hardverteszt indítása :" +HardwareTestLaunch2 = "Nyomjon a reset gombra a" +HardwareTestLaunch3 = "teszt megállításához (ez" +HardwareTestLaunch4 = "az adatokat törölni fogja)" +IntervalSet = "Állítsa be az intervallumot" +Language = "Nyelv" +LowBattery = "Majdnem kimerült az elem" +Mean = "középérték" +Move = " Odébb rakni: " +NameCannotStartWithNumber = "Egy név nem kezdöthet számmal" +NameTaken = "Ez a név foglalt" +NameTooLong = "Ez a név túl hosszú" +Navigate = "Hajózik" +Next = "következö" +NEnd = "N vége" +NoDataToPlot = "Nincs rajzolható adat" +NoValueToCompute = "Nincs számítható érték" +NStart = "N kezdete" +Ok = "Érvényesítés" +Or = " vagy " +Orthonormal = "Ortonormált" +Plot = "Grafikon rajzolása" +PoolMemoryFull1 = "A memória megtelt." +PoolMemoryFull2 = "Kérem próbálja újra." +Rename = "Átnevezés" +Sci = "sci" +SortValues = "Rendezés értékek növelésével" +SortSizes = "Rendezés növekvő frekvenciák szerint" +SquareSum = "Négyzetek összege" +StandardDeviation = "Alap eltérés" +StatTab = "Statisztikák" +Step = "Lépés" +StorageMemoryFull1 = "A memória megtelt." +StorageMemoryFull2 = "Törlöljön adatokat és próbálkozzon újra." +SyntaxError = "Szintaxis hiba" +Sym = "sym" +TEnd = "T vég" +ThetaEnd = "θ vége" +ThetaStart = "θ kezdete" +TStart = "T kezdete" +ToZoom = "Nagyítani : " +UndefinedValue = "Meghatározatlan adat" +ValuesTab = "Táblázat" +Warning = "Figyelem" +XEnd = "X vége" +XStart = "X kezdete" +ExamModeMode = "Üzemmód" +ExamModeModeStandard = "Normál " +ExamModeModeNoSym = "Szimbólikus nélkül " +ExamModeModeNoSymNoText = "Szimbólikus és szöveg nélkül " +ExamModeModeDutch = "Holland " diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n index 3322800c8c5..4476ed58c78 100644 --- a/apps/shared.it.i18n +++ b/apps/shared.it.i18n @@ -1,12 +1,8 @@ ActivateDeactivate = "Attivare/Disattivare" ActivateExamMode = "Attiva modalità d'esame" -ActivateDutchExamMode = "Attiva modalità d'esame NL" ActiveExamModeMessage1 = "Tutti i tuoi dati saranno " ActiveExamModeMessage2 = "cancellati se attivi " ActiveExamModeMessage3 = "la modalità d'esame." -ActiveDutchExamModeMessage1 = "Tutti i tuoi dati saranno cancellati" -ActiveDutchExamModeMessage2 = "se attivi la modalità d'esame." -ActiveDutchExamModeMessage3 = "L'app Python sarà inaccessibile." Axis = "Assi" Cancel = "Annullare" ClearColumn = "Cancella la colonna" @@ -39,7 +35,6 @@ ExitExamMode2 = "dalla modalità d'esame ?" Exponential = "Esponenziale" FillWithFormula = "Compilare con una formula" ForbiddenValue = "Valore non consentito" -FunctionColumn = "Colonna 0(0)" FunctionOptions = "Opzioni della funzione" Goto = "Andare a" GraphTab = "Grafico" @@ -59,7 +54,6 @@ Navigate = "Navigare" Next = "Successivo" NEnd = "N finale" NoDataToPlot = "Nessun dato da tracciare" -NoFunctionToDelete = "Nessuna funzione da cancellare" NoValueToCompute = "Nessun valore da calcolare" NStart = "N iniziale" Ok = "Conferma" @@ -78,7 +72,6 @@ StatTab = "Stats" Step = "Passo" StorageMemoryFull1 = "La memoria è piena." StorageMemoryFull2 = "Cancellate i dati e riprovate." -StoreExpressionNotAllowed = "'store' non è consentito" SyntaxError = "Sintassi errata" Sym = "sym" TEnd = "T finale" @@ -91,9 +84,6 @@ ValuesTab = "Tabella" Warning = "Attenzione" XEnd = "X finale" XStart = "X iniziale" -Zoom = "Zoom" -Developers = "Developers" -BetaTesters = "Beta testers" ExamModeMode = "Modalità" ExamModeModeStandard = "Standard " ExamModeModeNoSym = "Nessun simbolo " diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index b3971aa6cfe..cd091b0dcef 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -1,12 +1,8 @@ ActivateDeactivate = "Zet aan/uit" ActivateExamMode = "Internationale examenst." -ActivateDutchExamMode = "Nederlandse examenstand" ActiveExamModeMessage1 = "Al je gegevens worden " ActiveExamModeMessage2 = "gewist wanneer je de " ActiveExamModeMessage3 = "examenstand activeert." -ActiveDutchExamModeMessage1 = "Al je gegevens worden gewist wanneer" -ActiveDutchExamModeMessage2 = "je de examenstand activeert. De Python" -ActiveDutchExamModeMessage3 = "applicatie wordt uitgeschakeld." Axis = "Assen" Cancel = "Annuleer" ClearColumn = "Wis kolom" @@ -39,7 +35,6 @@ ExitExamMode2 = "examenstand?" Exponential = "Exponentieel" FillWithFormula = "Vul met een formule" ForbiddenValue = "Verboden waarde" -FunctionColumn = "0(0) kolom" FunctionOptions = "Functie-opties" Goto = "Ga naar" GraphTab = "Grafiek" @@ -58,7 +53,6 @@ NameTooLong = "Deze naam is te lang" Navigate = "Navigeren" Next = "Volgende" NoDataToPlot = "Geen gegevens om te plotten" -NoFunctionToDelete = "Geen functie om te verwijderen" NoValueToCompute = "Geen waarden om te berekenen" NEnd = "N einde" NStart = "N begin" @@ -74,7 +68,6 @@ SortValues = "Sorteer waarden oplopend" SortSizes = "Sorteer frequenties oplopend" SquareSum = "Som van kwadraten" StandardDeviation = "Standaardafwijking" -StoreExpressionNotAllowed = "'opslaan' is niet toegestaan" StatTab = "Stats" Step = "Stap" StorageMemoryFull1 = "Het geheugen is vol." @@ -91,9 +84,6 @@ ValuesTab = "Tabel" Warning = "Waarschuwing" XEnd = "X einde" XStart = "X begin" -Zoom = "Zoom" -Developers = "Developers" -BetaTesters = "Beta testers" ExamModeMode = "Mode" ExamModeModeStandard = "Standaard " ExamModeModeNoSym = "Geen sym " diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index 39fa24c1261..b68443df121 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -1,12 +1,8 @@ ActivateDeactivate = "Ativar/Desativar" ActivateExamMode = "Ativar o modo de exame" -ActivateDutchExamMode = "Ativar o modo de exame NL" ActiveExamModeMessage1 = "Todos os seus dados serão " ActiveExamModeMessage2 = "apagados se ativar " ActiveExamModeMessage3 = "o modo de exame." -ActiveDutchExamModeMessage1 = "Todos os seus dados serão apagados " -ActiveDutchExamModeMessage2 = "se ativar o modo de exame. A" -ActiveDutchExamModeMessage3 = "aplicação Python estará indisponível." Axis = "Eixos" Cancel = "Cancelar" ClearColumn = "Excluir coluna" @@ -39,7 +35,6 @@ ExitExamMode2 = "exame ?" Exponential = "Exponencial" FillWithFormula = "Preencher com uma fórmula" ForbiddenValue = "Valor proibido" -FunctionColumn = "Coluna 0(0)" FunctionOptions = "Opções de função" Goto = "Ir para" GraphTab = "Gráfico" @@ -59,7 +54,6 @@ Navigate = "Navegar" NEnd = "N fim" Next = "Seguinte" NoDataToPlot = "Não há dados para desenhar" -NoFunctionToDelete = "Sem função para eliminar" NoValueToCompute = "Não há dados para calcular" NStart = "N início" Ok = "Confirmar" @@ -78,7 +72,6 @@ StatTab = "Estat" Step = "Passo" StorageMemoryFull1 = "A memória esta cheia." StorageMemoryFull2 = "Apage dados e tente novamente." -StoreExpressionNotAllowed = "'store' não está permitido" SyntaxError = "Erro de sintaxe" Sym = "sim" TEnd = "T fim" @@ -91,9 +84,6 @@ ValuesTab = "Tabela" Warning = "Atenção" XEnd = "X fim" XStart = "X início" -Zoom = "Zoom" -Developers = "Desenvolvedores" -BetaTesters = "Testadores beta" ExamModeMode = "Modo" ExamModeModeStandard = "Padrão " ExamModeModeNoSym = "Sem sym " diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index d1d3fc9b5b9..cad61ea9b97 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -25,7 +25,6 @@ UnitMassGramKiloSymbol = "_kg" UnitMassGramSymbol = "_g" UnitMassGramMilliSymbol = "_mg" UnitMassGramMicroSymbol = "_μg" -UnitMassGramNanoSymbol = "_ng" UnitMassTonneSymbol = "_t" UnitMassOunceSymbol = "_oz" UnitMassPoundSymbol = "_lb" @@ -122,7 +121,6 @@ DeterminantCommandWithArg = "det(M)" DiffCommandWithArg = "diff(f(x),x,a)" DiffCommand = "diff(\x11,x,\x11)" DimensionCommandWithArg = "dim(M)" -DiscriminantFormulaDegree2 = "Δ=b^2-4ac" DotCommandWithArg = "dot(u,v)" E = "e" Equal = "=" @@ -352,7 +350,6 @@ PVenceslasDuet = "@Citorva" CyprienMejat = "Cyprien Méjat" PCyprienMejat = "@A2drien" SpeedOfLight = "2.99792458·10^8_m_s^-1" -YearLight = "9.461·10^15_m" Boltzmann = "1.380649·10^-23_J_K^-1" StefanBoltzmann = "5.670374419·10^-8_W_m^-2_K^-4" VacuumImpedance = "376.730313668_Ω" diff --git a/apps/statistics/base.de.i18n b/apps/statistics/base.de.i18n index 8a84b549edd..256fae301a4 100644 --- a/apps/statistics/base.de.i18n +++ b/apps/statistics/base.de.i18n @@ -8,7 +8,6 @@ Values3 = "Werte V3" Frequencies1 = "Häufigkeiten N1" Frequencies2 = "Häufigkeiten N2" Frequencies3 = "Häufigkeiten N3" -ImportList = "Laden einer Liste" Interval = " Intervall" Frequency = " Häufigkeit:" RelativeFrequency = "Relative:" diff --git a/apps/statistics/base.en.i18n b/apps/statistics/base.en.i18n index 4bb66972991..645eeb47461 100644 --- a/apps/statistics/base.en.i18n +++ b/apps/statistics/base.en.i18n @@ -8,7 +8,6 @@ Values3 = "Value V3" Frequencies1 = "Frequency N1" Frequencies2 = "Frequency N2" Frequencies3 = "Frequency N3" -ImportList = "Import from a list" Interval = " Interval " Frequency = " Frequency:" RelativeFrequency = "Relative:" diff --git a/apps/statistics/base.es.i18n b/apps/statistics/base.es.i18n index 648c851693d..2eb0c5f5b94 100644 --- a/apps/statistics/base.es.i18n +++ b/apps/statistics/base.es.i18n @@ -8,7 +8,6 @@ Values3 = "Valores V3" Frequencies1 = "Frecuencias N1" Frequencies2 = "Frecuencias N2" Frequencies3 = "Frecuencias N3" -ImportList = "Importar una lista" Interval = " Intervalo" Frequency = " Frecuencia:" RelativeFrequency = "Relativa:" @@ -24,4 +23,4 @@ StandardDeviationSigma = "Desviación típica σ" SampleStandardDeviationS = "Desviación típica s" SumValues = "Suma" SumSquareValues = "Suma cuadrados" -InterquartileRange = "Rango intercuartilo" \ No newline at end of file +InterquartileRange = "Rango intercuartilo" diff --git a/apps/statistics/base.fr.i18n b/apps/statistics/base.fr.i18n index c6fbf2966be..c3459bbdc71 100644 --- a/apps/statistics/base.fr.i18n +++ b/apps/statistics/base.fr.i18n @@ -8,7 +8,6 @@ Values3 = "Valeurs V3" Frequencies1 = "Effectifs N1" Frequencies2 = "Effectifs N2" Frequencies3 = "Effectifs N3" -ImportList = "Importer une liste" Interval = " Intervalle " Frequency = " Effectif:" RelativeFrequency = "Fréquence:" diff --git a/apps/statistics/base.hu.i18n b/apps/statistics/base.hu.i18n index 097a28b2224..82da786c60c 100644 --- a/apps/statistics/base.hu.i18n +++ b/apps/statistics/base.hu.i18n @@ -1,27 +1,26 @@ -StatsApp = "Statisztika" -StatsAppCapital = "STATISZTIKA" -HistogramTab = "Hisztogram" -BoxTab = "Doboz" -Values1 = "V1 értékek" -Values2 = "V2 értékek" -Values3 = "V3 értékek" -Frequencies1 = "N1 Frekvencia" -Frequencies2 = "N2 Frekvencia" -Frequencies3 = "N3 Frekvencia" -ImportList = "Importálás egy listáról" -Interval = "Intervallum" -Frequency = "Frekvencia:" -RelativeFrequency = "Relatív:" -HistogramSet = "Hisztogram beállítások" -RectangleWidth = "Tálca szélessége" -BarStart = "X kezdet" -FirstQuartile = "Elsö kvartilis" -Median = "Medián" -ThirdQuartile = "Harmadik kvartilis" -TotalFrequency = "Adatpontok száma " -Range = "Intervallum" -StandardDeviationSigma = "σ szórás" -SampleStandardDeviationS = "Minta std eltérés σ" -SumValues = "Értékek összege" -SumSquareValues = "Négyzetértékek összege" -InterquartileRange = "Interkvartilis tartomány" +StatsApp = "Statisztika" +StatsAppCapital = "STATISZTIKA" +HistogramTab = "Hisztogram" +BoxTab = "Doboz" +Values1 = "V1 értékek" +Values2 = "V2 értékek" +Values3 = "V3 értékek" +Frequencies1 = "N1 Frekvencia" +Frequencies2 = "N2 Frekvencia" +Frequencies3 = "N3 Frekvencia" +Interval = "Intervallum" +Frequency = "Frekvencia:" +RelativeFrequency = "Relatív:" +HistogramSet = "Hisztogram beállítások" +RectangleWidth = "Tálca szélessége" +BarStart = "X kezdet" +FirstQuartile = "Elsö kvartilis" +Median = "Medián" +ThirdQuartile = "Harmadik kvartilis" +TotalFrequency = "Adatpontok száma " +Range = "Intervallum" +StandardDeviationSigma = "σ szórás" +SampleStandardDeviationS = "Minta std eltérés σ" +SumValues = "Értékek összege" +SumSquareValues = "Négyzetértékek összege" +InterquartileRange = "Interkvartilis tartomány" diff --git a/apps/statistics/base.it.i18n b/apps/statistics/base.it.i18n index 2879e7e637a..043fd75dfca 100644 --- a/apps/statistics/base.it.i18n +++ b/apps/statistics/base.it.i18n @@ -8,7 +8,6 @@ Values3 = "Valori V3" Frequencies1 = "Frequenze N1" Frequencies2 = "Frequenze N2" Frequencies3 = "Frequenze N3" -ImportList = "Importare una lista" Interval = " Intervallo " Frequency = " Frequenza:" RelativeFrequency = "Relativa:" @@ -24,4 +23,4 @@ StandardDeviationSigma = "Deviazione standard σ" SampleStandardDeviationS = "Dev. std campionaria s" SumValues = "Somma" SumSquareValues = "Somma dei quadrati" -InterquartileRange = "Scarto interquartile" \ No newline at end of file +InterquartileRange = "Scarto interquartile" diff --git a/apps/statistics/base.nl.i18n b/apps/statistics/base.nl.i18n index 2e43cbc204c..beee307b850 100644 --- a/apps/statistics/base.nl.i18n +++ b/apps/statistics/base.nl.i18n @@ -8,7 +8,6 @@ Values3 = "Waarden V3" Frequencies1 = "Frequenties N1" Frequencies2 = "Frequenties N2" Frequencies3 = "Frequenties N3" -ImportList = "Importeren uit een lijst" Interval = " Interval " Frequency = " Frequentie:" RelativeFrequency = "Relatieve:" @@ -24,4 +23,4 @@ StandardDeviationSigma = "Standaardafwijking σ" SampleStandardDeviationS = "Standaardafwijking s" SumValues = "Som" SumSquareValues = "Som van kwadraten" -InterquartileRange = "Interkwartielafstand" \ No newline at end of file +InterquartileRange = "Interkwartielafstand" diff --git a/apps/statistics/base.pt.i18n b/apps/statistics/base.pt.i18n index 29ac75e6e57..11f5df9f15c 100644 --- a/apps/statistics/base.pt.i18n +++ b/apps/statistics/base.pt.i18n @@ -8,7 +8,6 @@ Values3 = "Valores V3" Frequencies1 = "Frequências N1" Frequencies2 = "Frequências N2" Frequencies3 = "Frequências N3" -ImportList = "Importar de uma lista" Interval = " Intervalo" Frequency = " Frequência:" RelativeFrequency = "Relativa:" @@ -24,4 +23,4 @@ StandardDeviationSigma = "Desvio padrão σ" SampleStandardDeviationS = "Desvio padrão amostral s" SumValues = "Somatório" SumSquareValues = "Soma dos quadrados" -InterquartileRange = "Amplitude interquartil" \ No newline at end of file +InterquartileRange = "Amplitude interquartil" diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index e4a7c49e41f..40c021a7065 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -1,6 +1,5 @@ Unit = "Einheiten" UnitTimeMenu = "Zeit" -UnitTimeSecondMenu = "Sekunde" UnitTimeSecond = "Sekunde" UnitTimeSecondMilli = "Millisekunde" UnitTimeSecondMicro = "Mikrosekunde" @@ -12,7 +11,6 @@ UnitTimeWeek = "Woche" UnitTimeMonth = "Monat" UnitTimeYear = "Jahr" UnitDistanceMenu = "Entfernung" -UnitDistanceMeterMenu = "Meter" UnitDistanceMeterKilo = "Kilometer" UnitDistanceMeter = "Meter" UnitDistanceMeterMilli = "Millimeter" @@ -31,9 +29,6 @@ UnitMassGramKilo = "Kilogramm" UnitMassGram = "Gramm" UnitMassGramMilli = "Milligramm" UnitMassGramMicro = "Mikrogramm" -UnitMassGramNano = "Nanogramm" -UnitDistanceImperialMenu = "Angloamerikanisch" -UnitMassImperialMenu = "Angloamerikanisch" UnitMassTonne = "Tonne" UnitMassOunce = "Unze" UnitMassPound = "Pfund" @@ -423,7 +418,6 @@ EscapeVelocity = "Fluchtgeschwindigkeit" EscapeVelocityFromEarth = "Von der Erde" EscapeVelocityFromMoon = "Vom Mond" EscapeVelocityFromSun = "Von der Sonne" -YearLightTag = "Lichtjahr" Thermodynamics = "Thermodynamik" BoltzmannTag = "Boltzmann Konstante" AvogadroTag = "Avogadro-Konstante" @@ -466,7 +460,6 @@ MoonMassTag = "Mond" EarthMassTag = "Erde" SunMassTag = "Sonne" ParticleMass = "Partikel Masse" -AstronomicalMass = "Astronomische" Radiuses = "Radien" Length = "Länge" Distances = "Entfernungen" diff --git a/apps/toolbox.en.i18n b/apps/toolbox.en.i18n index e2d7dd94ad3..b3ff61e2961 100644 --- a/apps/toolbox.en.i18n +++ b/apps/toolbox.en.i18n @@ -1,6 +1,5 @@ Unit = "Units" UnitTimeMenu = "Time" -UnitTimeSecondMenu = "Second" UnitTimeSecond = "Second" UnitTimeSecondMilli = "Millisecond" UnitTimeSecondMicro = "Microsecond" @@ -12,14 +11,12 @@ UnitTimeWeek = "Week" UnitTimeMonth = "Month" UnitTimeYear = "Year" UnitDistanceMenu = "Distance" -UnitDistanceMeterMenu = "Meter" UnitDistanceMeterKilo = "Kilometer" UnitDistanceMeter = "Meter" UnitDistanceMeterMilli = "Millimeter" UnitDistanceMeterMicro = "Micrometer" UnitDistanceMeterNano = "Nanometer" UnitDistanceMeterPico = "Picometer" -UnitDistanceImperialMenu = "US Customary" UnitDistanceInch = "Inch" UnitDistanceFoot = "Foot" UnitDistanceYard = "Yard" @@ -27,13 +24,11 @@ UnitDistanceMile = "Mile" UnitDistanceAstronomicalUnit = "Astronomical unit" UnitDistanceLightYear = "Light year" UnitDistanceParsec = "Parsec" -UnitMassImperialMenu = "US Customary" UnitMassMenu = "Mass" UnitMassGramKilo = "Kilogram" UnitMassGram = "Gram" UnitMassGramMilli = "Milligram" UnitMassGramMicro = "Microgram" -UnitMassGramNano = "Nanogram" UnitMassTonne = "Metric Ton" UnitMassOunce = "Ounce" UnitMassPound = "Pound" @@ -423,7 +418,6 @@ EscapeVelocityFromEarth = "Of Earth" EscapeVelocityFromMoon = "Of Moon" EscapeVelocityFromSun = "Of Sun" SpeedOfLightTag = "Speed of light" -YearLightTag = "One year light" Thermodynamics = "Thermodynamics" BoltzmannTag = "Boltzmann Constant" AvogadroTag = "Avogadro Constant" @@ -466,7 +460,6 @@ MoonMassTag = "Moon" EarthMassTag = "Earth" SunMassTag = "Sun" ParticleMass = "Particles Mass" -AstronomicalMass = "Astronomical" Radiuses = "Radiuses" Length = "Length" Distances = "Distances" diff --git a/apps/toolbox.es.i18n b/apps/toolbox.es.i18n index 21247a2711a..f4f9a212c56 100644 --- a/apps/toolbox.es.i18n +++ b/apps/toolbox.es.i18n @@ -1,6 +1,5 @@ Unit = "Units" UnitTimeMenu = "Time" -UnitTimeSecondMenu = "Second" UnitTimeSecond = "Second" UnitTimeSecondMilli = "Millisecond" UnitTimeSecondMicro = "Microsecond" @@ -12,18 +11,15 @@ UnitTimeWeek = "Week" UnitTimeMonth = "Month" UnitTimeYear = "Year" UnitDistanceMenu = "Distance" -UnitDistanceMeterMenu = "Meter" UnitDistanceMeterKilo = "Kilometer" UnitDistanceMeter = "Meter" UnitDistanceMeterMilli = "Millimeter" UnitDistanceMeterMicro = "Micrometer" UnitDistanceMeterNano = "Nanometer" UnitDistanceMeterPico = "Picometer" -UnitDistanceImperialMenu = "US Customary" UnitDistanceAstronomicalUnit = "Astronomical unit" UnitDistanceLightYear = "Light year" UnitDistanceParsec = "Parsec" -UnitMassImperialMenu = "US Customary" UnitDistanceMile = "Milla" UnitDistanceYard = "Yardas" UnitDistanceFoot = "Pie" @@ -33,7 +29,6 @@ UnitMassGramKilo = "Kilogram" UnitMassGram = "Gram" UnitMassGramMilli = "Milligram" UnitMassGramMicro = "Microgram" -UnitMassGramNano = "Nanogram" UnitMassTonne = "Tonne" UnitMassOunce = "Onza" UnitMassPound = "Libra" @@ -415,7 +410,6 @@ AlphaElementUbn = "Ubn - Unbinilio (120)" Speed = "Velocidad" SpeedOfLightTag = "Velocidad de la luz" SpeedOfSound = "La velocidad del sonido" -YearLightTag = "Un año de luz" Thermodynamics = "Termodinámica" SpeedOfSound0Tag = "Nivel del mar, 20 ° C" SpeedOfSoundWaterTag = "En el agua" @@ -466,7 +460,6 @@ MoonMassTag = "Luna" EarthMassTag = "Tierra" SunMassTag = "Sol" ParticleMass = "Misa de las partículas" -AstronomicalMass = "Astronómica" Radiuses = "Radios" Length = "Lenght" Distances = "Distancias" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index 4cd0db6fa0f..41164cd0863 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -1,6 +1,5 @@ Unit = "Unités" UnitTimeMenu = "Temps" -UnitTimeSecondMenu = "Seconde" UnitTimeSecond = "Seconde" UnitTimeSecondMilli = "Milliseconde" UnitTimeSecondMicro = "Microseconde" @@ -12,14 +11,12 @@ UnitTimeWeek = "Semaine" UnitTimeMonth = "Mois" UnitTimeYear = "Année" UnitDistanceMenu = "Distance" -UnitDistanceMeterMenu = "Mètre" UnitDistanceMeterKilo = "Kilomètre" UnitDistanceMeter = "Mètre" UnitDistanceMeterMilli = "Millimètre" UnitDistanceMeterMicro = "Micromètre" UnitDistanceMeterNano = "Nanomètre" UnitDistanceMeterPico = "Picomètre" -UnitDistanceImperialMenu = "US Customary" UnitDistanceInch = "Inch" UnitDistanceFoot = "Foot" UnitDistanceYard = "Yard" @@ -27,7 +24,6 @@ UnitDistanceMile = "Mile" UnitDistanceAstronomicalUnit = "Unité astronomique" UnitDistanceLightYear = "Année-lumière" UnitDistanceParsec = "Parsec" -UnitMassImperialMenu = "US Customary" UnitDistanceMile = "Mile" UnitDistanceYard = "Yard" UnitDistanceFoot = "Pied" @@ -37,7 +33,6 @@ UnitMassGramKilo = "Kilogramme" UnitMassGram = "Gramme" UnitMassGramMilli = "Milligramme" UnitMassGramMicro = "Microgramme" -UnitMassGramNano = "Nanogramme" UnitMassTonne = "Tonne" UnitMassOunce = "Once" UnitMassPound = "Livre" @@ -423,7 +418,6 @@ SpeedOfSoundWaterTag = "Dans l'eau" SpeedOfSoundSteelTag = "Dans l'acier" SpeedOfSoundGlassTag = "Dans le verre" SpeedOfLightTag = "Vitesse de la lumière" -YearLightTag = "Année lumière" Thermodynamics = "Thermodynamique" BoltzmannTag = "Constante de Boltzmann" AvogadroTag = "Constante d'Avogadro" @@ -470,7 +464,6 @@ MoonMassTag = "Lune" EarthMassTag = "Terre" SunMassTag = "Soleil" ParticleMass = "Masses des Particules" -AstronomicalMass = "Astronomiques" Radiuses = "Rayons" Length = "Longueur" Distances = "Distances" diff --git a/apps/toolbox.hu.i18n b/apps/toolbox.hu.i18n index 137c3791733..79d22308ab3 100644 --- a/apps/toolbox.hu.i18n +++ b/apps/toolbox.hu.i18n @@ -1,519 +1,512 @@ -Unit = "Mértékegység" -UnitTimeMenu = "Idö" -UnitTimeSecondMenu = "Másodperc" -UnitTimeSecond = "Másodperc" -UnitTimeSecondMilli = "Miliszekundum" -UnitTimeSecondMicro = "Mikroszekundum" -UnitTimeSecondNano = "Nanoszekundum" -UnitTimeMinute = "Perc" -UnitTimeHour = "Óra" -UnitTimeDay = "Nap" -UnitTimeWeek = "hét" -UnitTimeMonth = "Hónap" -UnitTimeYear = "Év" -UnitDistanceMenu = "Távolság" -UnitDistanceMeterMenu = "Méter" -UnitDistanceMeterKilo = "Kilométer" -UnitDistanceMeter = "Méter" -UnitDistanceMeterMilli = "Milliméter" -UnitDistanceMeterMicro = "Mikrométer" -UnitDistanceMeterNano = "Nanométer" -UnitDistanceMeterPico = "Pikométer" -UnitDistanceImperialMenu = "Angolszász mértékegységek" -UnitDistanceInch = "Hüvelyk" -UnitDistanceFoot = "Láb" -UnitDistanceYard = "Yard" -UnitDistanceMile = "Mérföld" -UnitDistanceAstronomicalUnit = "Csillagászati egység" -UnitDistanceLightYear = "Fény év" -UnitDistanceParsec = "Parsec" -UnitMassShortTon = "Rövid tonna" -UnitMassLongTon = "Hosszú tonna" -UnitMassImperialMenu = "Angolszász mértékegységek" -UnitMassPound = "Font" -UnitMassOunce = "Uncia" -UnitMassMenu = "Tömeg" -UnitMassGramKilo = "Kilogramm" -UnitMassGram = "Gramm" -UnitMassGramMilli = "Milligramm" -UnitMassGramMicro = "Mikrogramm" -UnitMassGramNano = "Nanogramm" -UnitMassTonne = "Tonna" -UnitCurrentMenu = "Áram" -UnitCurrentAmpere = "Amper" -UnitCurrentAmpereMilli = "Milliamper" -UnitCurrentAmpereMicro = "Mikroamper" -UnitTemperatureMenu = "Hömérséklet" -UnitAmountMenu = "Az anyag mennyisége" -UnitAmountMole = "Mól" -UnitAmountMoleMilli = "Millimól" -UnitAmountMoleMicro = "Mikromól" -UnitLuminousIntensityMenu = "Fényerö" -UnitFrequencyMenu = "Frekvencia" -UnitFrequencyHertzMega = "Megahertz" -UnitForceMenu = "Erö" -UnitForceNewtonMilli = "Millinewton" -UnitPressureMenu = "Nyomás" -UnitPressurePascalHecto = "Hectopascal" -UnitPressureAtm = "Légkör" -UnitEnergyMenu = "Energia" -UnitEnergyJouleMilli = "Millijoule" -UnitEnergyElectronVoltMenu = "Electronvolt" -UnitEnergyElectronVoltMega = "Megaelectronvolt" -UnitEnergyElectronVoltKilo = "Kiloelectronvolt" -UnitEnergyElectronVolt = "Electronvolt" -UnitEnergyElectronVoltMilli = "Millielectronvolt" -UnitPowerMenu = "Teljesítmény" -UnitPowerWattMega = "Megawatt" -UnitPowerWattMilli = "Milliwatt" -UnitPowerWattMicro = "Microwatt" -UnitElectricChargeMenu = "Elektromos töltés" -UnitPotentialMenu = "Elektromos potenciál" -UnitPotentialVoltMilli = "Millivolt" -UnitPotentialVoltMicro = "Microvolt" -UnitCapacitanceMenu = "Elektromos kapacitás" -UnitCapacitanceFaradMilli = "Millifarad" -UnitCapacitanceFaradMicro = "Microfarad" -UnitResistanceMenu = "Elektromos ellenállás" -UnitConductanceMenu = "Elektromos vezetöképesség" -UnitConductanceSiemensMilli = "Millisiemens" -UnitMagneticFieldMenu = "Mágneses mezö" -InductanceMenu = "Elektromos induktivitás" -UnitSurfaceMenu = "Terület" -UnitSurfaceAcre = "Acre" -UnitSurfaceHectar = "Hektár" -UnitVolumeMenu = "Kötet" -UnitVolumeLiter = "Liter" -UnitVolumeLiterDeci = "Deciliter" -UnitVolumeLiterCenti = "Centiliter" -UnitVolumeLiterMilli = "Milliliter" -UnitVolumeTeaspoon = "Teáskanál" -UnitVolumeTablespoon = "evőkanál" -UnitVolumeFluidOunce = "Folyadék uncia" -UnitVolumeCup = "Kupa" -UnitVolumePint = "Pint" -UnitVolumeQuart = "Quart" -UnitVolumeGallon = "Gallon" -UnitMetricMenu = "Metrikus" -UnitImperialMenu = "Birodalmi" -Toolbox = "Eszköztár" -AbsoluteValue = "Abszolút érték" -NthRoot = "n-gyökér" -BasedLogarithm = "Logaritmus az alap" -Calculation = "Számítás" -ComplexNumber = "Komplex számok" -Combinatorics = "Kombinatorika" -Arithmetic = "számtani" -Matrices = "Mátrix" -NewMatrix = "Új mátrix" -Identity = "n méretü azonosító mátrix" -Lists = "Lista" -HyperbolicTrigonometry = "Hiperbolikus trigonometria" -Fluctuation = "Jósolt intervallum" -InfinityMessage = "Végtelen" -DerivateNumber = "Származékos" -Integral = "Integral" -Sum = "Összeg" -Product = "Termék" -ComplexAbsoluteValue = "Abszolút érték" -Argument = "érv" -RealPart = "Igazi rész" -ImaginaryPart = "Képzeletbeli rész" -Conjugate = "Konjugátum" -Combination = "Kombináció" -Permutation = "Permutáció" -GreatCommonDivisor = "GCD" -LeastCommonMultiple = "LCM" -Remainder = "P maradékos osztás q-val" -Quotient = "P hányados osztás q-val" -Inverse = "Inverz" -Determinant = "determináns" -Transpose = "Átültetés" -Trace = "Trace" -Dimension = "Méret" -RowEchelonForm = "Sor echelon forma" -ReducedRowEchelonForm = "Csökkentett sorú echelon forma" -Vectors = "Vektorok" -Dot = "Pont termék" -Cross = "Kereszt termék" -NormVector = "Norm" -Sort = "Növekvö sorrend" -InvSort = "Csökkenö rendezés" -Maximum = "Maximum" -Minimum = "Minimum" -Floor = "Emelet" -FracPart = "Frakciós rész" -Ceiling = "Plafon" -Rounding = "N számjegyre kerekítés" -HyperbolicCosine = "Hiperbolikus koszinusz" -HyperbolicSine = "Hiperbolikus szinusz" -HyperbolicTangent = "Hiperbolikus érintö" -InverseHyperbolicCosine = "Inverz hiperbolikus koszinusz" -InverseHyperbolicSine = "Inverz hiperbolikus szinusz" -InverseHyperbolicTangent = "Inverz hiperbolikus érintö" -Prediction95 = "Jóslási intervallum 95%" -Prediction = "Egyszerü elörejelzési intervallum" -Confidence = "Bizalmi intervallum" -RandomAndApproximation = "Véletlen és közelítés" -RandomFloat = "Lebegöpontos szám [0,1-ben [" -RandomInteger = "Véletlen egész szám [a, b] -ben" -PrimeFactorDecomposition = "Egész szám tényezö" -NormCDF = "P (X list[str]: + """Get all files in the given directory recursively. + + Args: + directory (str): The directory + + Returns: + List[str]: The list of files in the directory + + """ + # Initialize the list of files + files = [] + # Iterate over all files in the directory + for i in os.listdir(directory): + # Add the current directory to the file name + i = f'{directory}/{i}' + # If the file is a directory + if FIND_RECURSIVE and os.path.isdir(i): + # Iter the directory and add the files to the list + files.extend(get_all_files_in_directory(i)) + # Else the file is a file + else: + # Add the file to the list + files.append(i) + # Return the list of files + return files + + +def get_files(directory: str) -> list[str]: + """Get all files in the directory recursively with ignoring list. + + Args: + directory (str): The start directory + + Returns: + List[str]: The list of files + """ + # Initialize the list of files + files = [] + # Iterate over the directory + for filename in os.listdir(directory): + # Add the full path to the directory/file + filename = f'{directory}/{filename}' + # Exclude path in IGNORE_PATHS + if filename in IGNORE_PATHS: + continue + # Exclude paths in IGNORE_PATHS_CONTENTS + if any(extension in filename for extension in IGNORE_PATHS_CONTENTS): + continue + # If it is a directory, find recursively the files into it + if os.path.isdir(filename): + files.extend(get_files(filename)) + else: + files.append(filename) + # Return the file list + return files + + +def get_keys_in_i18n_file(filename: str) -> list[list[str]]: + """Return a list of keys in the file. + + Args: + filename (str): The name of the file to read + + Returns: + List[list[str]]: The keys in the file + + """ + # Initialize the list of keys in the file + keys: list[list[str]] = [] + # Open the file read only + with open(filename, 'r', encoding='utf-8') as file_read: + # Read the content of the file line by line + for line in file_read.readlines(): + # Ignore commented lines + if re.match(r"^#(.*)$", line): + continue + # Ignore lines without = + if '=' not in line: + continue + # Get the key by spliting the line by = + key = line.split("=")[0] + # Remove spaces from the start of the key + while key[0] == " ": + key = key[1:] + # Remove spaces from the end of the key + while key[-1] == " ": + key = key[:-1] + # Get generic filename into a list separated by dots + generic_filename_list = filename.split(".")[:-2] + # Get if the locale is universal + locale = filename.split(".")[-2] + # Get the filename as string with a trailing dot at the end + generic_filename = "".join(f'{i}.' for i in generic_filename_list) + # Remove trailing dot from the end of the generic filename + generic_filename = generic_filename[:-1] + # Add the key and the generic filename to the list of keys + keys.append([key, generic_filename, locale]) + return keys + + +def list_keys_in_i18n_file_list(i18n_files: list[str]) -> list[list[str]]: + """List all keys in the i18n files. + + Args: + i18n_files (list[str]): I18n files list + + Returns: + List[list[str]]: The dictionnary of keys in the i18n files by + locale. + + """ + # Initialize the list of keys in the i18n files + keys_dict: list[list[str]] = [] + # Iterate on the file list + for actual_file in i18n_files: + # Get the keys in the file and add them to the list + keys_dict.extend(get_keys_in_i18n_file(actual_file)) + # Return the dictionary of keys in the i18n files sorted by locale + return keys_dict + + +def extract_keys_from_line(line: str) -> list[str]: + """Extract keys from a line. + + Args: + line (str): The line to extract keys from + + Returns: + list[str]: The extracted keys + """ + # Initialize the list of separator to separate the key from the part + keys_separator: list[str] = ["}", ":", ";", ",", " ", ")"] + # Initialize the list of keys + keys: list[str] = [] + # Split the line by :: + line_splitted: list[str] = line.split("::") + # Initialize loop variables + # The last part (used inside the loop) + last_part: str = "" + # Mark the next line contain the key + save_next_line: bool = False + # The key part + key_parts: list[str] = [] + # Iterate over the splitted line + for part in line_splitted: + # print(part) + if save_next_line: + key_parts.append(part) + save_next_line = False + # If the actual part is Message and the last part contain I18n + # (I18n::Message), the next part will contain the key name + # TODO: Improve catching + if part == "Message" and "I18n" in last_part: + save_next_line = True + # Save the current part into the last part + # (loop iteration is finished) + last_part = part + # Get the key from the key part + # TODO: Improve catching + # Iterate over all the keys in the line + for actual_key in key_parts: + # Initialize real key variable + key_real: str = actual_key + for separator_to_test in keys_separator: + key_separated: list[str] = key_real.split(separator_to_test) + # print(key_real, separator_to_test, key_separated) + # If the key was splitted, save the separated key + if len(key_separated) > 1: + key_real = key_separated[0] + if key_real: + keys.append(key_real) + return keys + + +def keys_from_file_list(files: list[str]) -> list[str]: + """Get an array of keys from files. + + Args: + files (list[str]): The list of files to read + + Returns: + list[str]: The keys + """ + # Initialize the list of keys from the files + keys: list[str] = [] + # Iterate over the file list + for filename in files: + # Read the file contents into "content" variable + with open(filename, 'r', encoding="utf-8") as file_obj: + # Store the contents of the file + content = file_obj.read() + # Iterate over the file contents + for line in content.split("\n"): + # Get if the line contains an I18n key + if "I18n::Message" in line: + # Extract the keys from the line + keys.extend(extract_keys_from_line(line)) + return keys + + +def get_i18n_files(directory: str = '.') -> list[str]: + """Get the list of i18n files in the given directory. + + Args: + directory (str, optional): The directory to find the i18n files. + Defaults to '.'. + + Returns: + list[str]: The list of i18n files in a dictionary of languages. + + """ + # Get all files in the directory recursively + files = get_all_files_in_directory(directory) + # Return only i18n files + return [i for i in files if ".i18n" in i] + + +def get_unused_keys(file_keys: list[str], i18n_keys: list[list[str]]) -> list[list[str]]: + """Get unused keys. + + Args: + file_keys (list[str]): The keys in the source files + i18n_keys (list[list[str]]): The keys in the i18n files + + Returns: + list[list[str]]: The unused keys + """ + # Initialize the list of unused keys + unused_keys: list[list[str]] = [] + # Iterate over the i18n key definitions + for key_i18n_actual in i18n_keys: + # Get if the key is used, and mark it as used if it is in the kepping + # list + key_used = next( + ( + True + for string_to_test in KEEP_KEY_IF_CONTAIN + if string_to_test in key_i18n_actual[0] + ), + any(key_i18n_actual[0] == file_key for file_key in file_keys), + ) + + # If the key is not used, add it to the list + if not key_used: + if key_i18n_actual not in unused_keys: + print(f"{key_i18n_actual[0]} unused") + unused_keys.append(key_i18n_actual) + return unused_keys + + +def remove_keys_from_i18n_files(unused_keys: list[list[str]]): + """Remove unused keys from i18n files. + + Args: + unused_keys (list[list[str]]): The list of keys to remove + """ + # Initialize the dictionary of files + # (to prevent intensive writing to disk) + files_to_write: dict[str, str] = {} + # Iterate over the keys to remove + for key in unused_keys: + key_name_to_remove = key[0] + filename_generic = key[1] + locale = key[2] + # Get the filename of from the generic filename + filename = f'{filename_generic}.{locale}.i18n' + # If the file is not in the dictionary, add it + if filename not in files_to_write: + # Save the file contents + with open(filename, 'r', encoding='utf8') as file_read: + files_to_write[filename] = file_read.read() + # Split the file by new lines + file_splitted = files_to_write[filename].split("\n") + # Iterate over the file contents + for line, value in enumerate(file_splitted): + # Ignore lines without = + if '=' not in value: + continue + # Get the key from the line + key_to_check: str = value.split("=")[0] + # Remove spaces from the start of the key + while key_to_check[0] == " ": + key_to_check = key_to_check[1:] + # Remove spaces from the end of the key + while key_to_check[-1] == " ": + key_to_check = key_to_check[:-1] + # If the key is the key to remove, remove it + if key_to_check == key_name_to_remove: + del file_splitted[line] + break + file_str = "".join(line + "\n" for line in file_splitted) + # Remove double line return + while file_str[-2:] == '\n\n': + file_str = file_str[:-1] + files_to_write[filename] = file_str + # When the loop is end, write the files + for actual_file, content_to_write in files_to_write.items(): + with open(actual_file, 'w', encoding='utf-8') as file_to_write: + file_to_write.write(content_to_write) + # print(actual_file, content_to_write) + + +def main(): + """Execute the program.""" + # Get the file list + file_list = get_files(BASE_PATH) + # Get the keys in the file list + files_keys = keys_from_file_list(file_list) + # Get i18n files list + i18n_files = get_i18n_files(BASE_PATH) + # Get keys from i18n files + i18n_files_keys = list_keys_in_i18n_file_list(i18n_files) + # Get unused keys + unused_keys = get_unused_keys(files_keys, i18n_files_keys) + # If dryrun is disabled, remove the keys definitions from the i18n files + if not DRYRUN: + remove_keys_from_i18n_files(unused_keys) + + +main() From 8ac969d772fc6cad691d0a04438a25c84843634b Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Tue, 22 Mar 2022 17:59:52 +0100 Subject: [PATCH 187/355] [Feature] Backlight settings (#137) * Added backlight settings * Changed location of the brightness setting * Fix row size of brightness settings * [apps/settings/brightness] Update translations * Fix dimmer * Update translations * [apps/settings] Add dimmer duration setting * [apps/settings] Ensure of the brightness level is greater than the dimmed brightness level * Make transition smooth * Removed transition time setting I personally think that this setting is completely useless except if you absolutely want a transition that is not smooth, which is weird. * Moved everything related to brightness in one submenu * Some refactoring * Update defaults * Removed unnecessary translation * [apps/settings] Fix Shift + Minus/Plus in settings * Apply suggestions from code review * [apps/shared] Remove a think that I don't know what it is * Apply review suggestions Co-authored-by: Joachim LF Co-authored-by: lolocomotive --- apps/apps_container.cpp | 5 +- apps/backlight_dimming_timer.cpp | 43 ++---- apps/backlight_dimming_timer.h | 11 -- apps/global_preferences.cpp | 21 ++- apps/global_preferences.h | 12 ++ apps/settings/Makefile | 1 + apps/settings/base.de.i18n | 5 + apps/settings/base.en.i18n | 5 + apps/settings/base.es.i18n | 5 + apps/settings/base.fr.i18n | 5 + apps/settings/base.hu.i18n | 5 + apps/settings/base.it.i18n | 5 + apps/settings/base.nl.i18n | 5 + apps/settings/base.pt.i18n | 5 + apps/settings/main_controller.cpp | 35 +---- apps/settings/main_controller.h | 6 +- apps/settings/main_controller_prompt_beta.cpp | 2 +- apps/settings/main_controller_prompt_none.cpp | 2 +- .../main_controller_prompt_update.cpp | 4 +- .../sub_menu/brightness_controller.cpp | 142 ++++++++++++++++++ .../settings/sub_menu/brightness_controller.h | 33 ++++ apps/suspend_timer.cpp | 2 +- apps/suspend_timer.h | 1 - escher/include/escher/timer.h | 2 +- escher/src/timer.cpp | 5 +- ion/include/ion/backlight.h | 1 - 26 files changed, 288 insertions(+), 80 deletions(-) create mode 100644 apps/settings/sub_menu/brightness_controller.cpp create mode 100644 apps/settings/sub_menu/brightness_controller.h diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index 9423684e46e..1bb917ddc28 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -266,7 +266,8 @@ bool AppsContainer::processEvent(Ion::Events::Event event) { } if (event == Ion::Events::BrightnessPlus || event == Ion::Events::BrightnessMinus) { int delta = Ion::Backlight::MaxBrightness/GlobalPreferences::NumberOfBrightnessStates; - int direction = (event == Ion::Events::BrightnessPlus) ? Ion::Backlight::NumberOfStepsPerShortcut*delta : -delta*Ion::Backlight::NumberOfStepsPerShortcut; + int NumberOfStepsPerShortcut = GlobalPreferences::sharedGlobalPreferences()->brightnessShortcut(); + int direction = (event == Ion::Events::BrightnessPlus) ? NumberOfStepsPerShortcut*delta : -delta*NumberOfStepsPerShortcut; GlobalPreferences::sharedGlobalPreferences()->setBrightnessLevel(GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()+direction); } return false; @@ -353,6 +354,8 @@ bool AppsContainer::updateBatteryState() { } void AppsContainer::refreshPreferences() { + m_suspendTimer.reset(GlobalPreferences::sharedGlobalPreferences()->idleBeforeSuspendSeconds()*1000/Timer::TickDuration); + m_backlightDimmingTimer.reset(GlobalPreferences::sharedGlobalPreferences()->idleBeforeDimmingSeconds()*1000/Timer::TickDuration); m_window.refreshPreferences(); } diff --git a/apps/backlight_dimming_timer.cpp b/apps/backlight_dimming_timer.cpp index 0d98d2749e8..a86c29c0080 100644 --- a/apps/backlight_dimming_timer.cpp +++ b/apps/backlight_dimming_timer.cpp @@ -1,39 +1,26 @@ #include "backlight_dimming_timer.h" #include "global_preferences.h" +#include +#include +#include BacklightDimmingTimer::BacklightDimmingTimer() : - Timer(k_idleBeforeDimmingDuration/Timer::TickDuration) + Timer(GlobalPreferences::sharedGlobalPreferences()->idleBeforeDimmingSeconds()*1000/Timer::TickDuration) { } -bool BacklightDimmingTimer::fire() { - if (m_dimerExecutions == 0) { - m_brightnessLevel = GlobalPreferences::sharedGlobalPreferences()->brightnessLevel(); - m_dimerSteps = m_brightnessLevel / decreaseBy; - m_timeToSleep = decreasetime / m_dimerSteps; - m_period = m_timeToSleep / Timer::TickDuration; - if (m_period == 0) { - m_period = 1; +bool BacklightDimmingTimer::fire(){ + int i = Ion::Backlight::brightness(); + while (i > 0){ + int t = 20; + Ion::Events::Event e = Ion::Events::getEvent(&t); + AppsContainer::sharedAppsContainer()->dispatchEvent(e); + if (e.isKeyboardEvent()){ + return false; } - resetTimer(); - } - if (m_dimerExecutions < m_dimerSteps) { - m_nextbrightness = (m_brightnessLevel-k_dimBacklightBrightness)/m_dimerSteps * (m_dimerSteps-m_dimerExecutions); - Ion::Backlight::setBrightness(m_nextbrightness); - resetTimer(); - } else if (m_dimerExecutions == m_dimerSteps) { - Ion::Backlight::setBrightness(k_dimBacklightBrightness); + + Ion::Backlight::setBrightness(i); + i -= 15; } - m_dimerExecutions++; return false; } - -void BacklightDimmingTimer::reset() { - m_dimerExecutions = 0; - m_period = k_idleBeforeDimmingDuration / Timer::TickDuration; - resetTimer(); -} - -void BacklightDimmingTimer::resetTimer() { - BacklightDimmingTimer::m_numberOfTicksBeforeFire = BacklightDimmingTimer::m_period; -} diff --git a/apps/backlight_dimming_timer.h b/apps/backlight_dimming_timer.h index 1502ea2dcc4..1925913705d 100644 --- a/apps/backlight_dimming_timer.h +++ b/apps/backlight_dimming_timer.h @@ -6,19 +6,8 @@ class BacklightDimmingTimer : public Timer { public: BacklightDimmingTimer(); - void reset(); private: - constexpr static int k_idleBeforeDimmingDuration = 30*1000; // In miliseconds - constexpr static int k_dimBacklightBrightness = 0; - constexpr static int decreaseBy = 15; - constexpr static int decreasetime = 1*1000; // In miliseconds - int m_dimerExecutions = 0; - int m_brightnessLevel; - int m_dimerSteps; - int m_nextbrightness; - float m_timeToSleep; // In miliseconds bool fire() override; - void resetTimer(); }; #endif diff --git a/apps/global_preferences.cpp b/apps/global_preferences.cpp index 938cff8616a..c0eac31c866 100644 --- a/apps/global_preferences.cpp +++ b/apps/global_preferences.cpp @@ -41,6 +41,25 @@ void GlobalPreferences::setBrightnessLevel(int brightnessLevel) { brightnessLevel = brightnessLevel < 0 ? 0 : brightnessLevel; brightnessLevel = brightnessLevel > Ion::Backlight::MaxBrightness ? Ion::Backlight::MaxBrightness : brightnessLevel; m_brightnessLevel = brightnessLevel; - Ion::Backlight::setBrightness(m_brightnessLevel); } } + +void GlobalPreferences::setIdleBeforeSuspendSeconds(int idleBeforeSuspendSeconds) { + if (m_idleBeforeSuspendSeconds != idleBeforeSuspendSeconds) { + idleBeforeSuspendSeconds = idleBeforeSuspendSeconds < 5 ? 5 : idleBeforeSuspendSeconds; + idleBeforeSuspendSeconds = idleBeforeSuspendSeconds > 7200 ? 7200 : idleBeforeSuspendSeconds; + m_idleBeforeSuspendSeconds = idleBeforeSuspendSeconds; + } +} + +void GlobalPreferences::setIdleBeforeDimmingSeconds(int idleBeforeDimmingSeconds) { + if (m_idleBeforeDimmingSeconds != idleBeforeDimmingSeconds) { + idleBeforeDimmingSeconds = idleBeforeDimmingSeconds < 3 ? 3 : idleBeforeDimmingSeconds; + idleBeforeDimmingSeconds = idleBeforeDimmingSeconds > 1200 ? 1200 : idleBeforeDimmingSeconds; + m_idleBeforeDimmingSeconds = idleBeforeDimmingSeconds; + } +} + +void GlobalPreferences::setBrightnessShortcut(int brightnessShortcut){ + m_brightnessShortcut = brightnessShortcut; +} diff --git a/apps/global_preferences.h b/apps/global_preferences.h index bcfa324ea21..f092fdaa08b 100644 --- a/apps/global_preferences.h +++ b/apps/global_preferences.h @@ -45,6 +45,12 @@ class GlobalPreferences { const KDFont * font() const { return m_font; } void setFont(const KDFont * font) { m_font = font; } constexpr static int NumberOfBrightnessStates = 15; + int idleBeforeSuspendSeconds() const { return m_idleBeforeSuspendSeconds; } + void setIdleBeforeSuspendSeconds(int m_idleBeforeSuspendSeconds); + int idleBeforeDimmingSeconds() const { return m_idleBeforeDimmingSeconds; } + void setIdleBeforeDimmingSeconds(int m_idleBeforeDimmingSeconds); + int brightnessShortcut() const { return m_brightnessShortcut; } + void setBrightnessShortcut(int m_BrightnessShortcut); private: static_assert(I18n::NumberOfLanguages > 0, "I18n::NumberOfLanguages is not superior to 0"); // There should already have been an error when processing an empty EPSILON_I18N flag static_assert(I18n::NumberOfCountries > 0, "I18n::NumberOfCountries is not superior to 0"); // There should already have been an error when processing an empty EPSILON_COUNTRIES flag @@ -60,6 +66,9 @@ class GlobalPreferences { m_syntaxhighlighting(true), m_cursorSaving(true), m_brightnessLevel(Ion::Backlight::MaxBrightness), + m_idleBeforeSuspendSeconds(55), + m_idleBeforeDimmingSeconds(45), + m_brightnessShortcut(4), m_font(KDFont::LargeFont) {} I18n::Language m_language; I18n::Country m_country; @@ -74,6 +83,9 @@ class GlobalPreferences { bool m_syntaxhighlighting; bool m_cursorSaving; int m_brightnessLevel; + int m_idleBeforeSuspendSeconds; + int m_idleBeforeDimmingSeconds; + int m_brightnessShortcut; const KDFont * m_font; }; diff --git a/apps/settings/Makefile b/apps/settings/Makefile index 9823f80dd08..c806527cbc0 100644 --- a/apps/settings/Makefile +++ b/apps/settings/Makefile @@ -26,6 +26,7 @@ app_settings_src = $(addprefix apps/settings/,\ sub_menu/math_options_controller.cpp \ sub_menu/selectable_view_with_messages.cpp \ sub_menu/usb_protection_controller.cpp \ + sub_menu/brightness_controller.cpp\ ) SFLAGS += -DOMEGA_STATE="$(OMEGA_STATE)" diff --git a/apps/settings/base.de.i18n b/apps/settings/base.de.i18n index d44631a8ca6..225fb46bfb2 100644 --- a/apps/settings/base.de.i18n +++ b/apps/settings/base.de.i18n @@ -32,6 +32,7 @@ Real = "Reell " Cartesian = "Kartesisch " Polar = "Polar " Brightness = "Helligkeit" +BrightnessSettings = "Helligkeitseinstellungen" SoftwareVersion = "Epsilon version" UpsilonVersion = "Upsilon version" OmegaVersion = "Omega version" @@ -79,3 +80,7 @@ USBProtectionLevel = "Akzeptierte Updates" USBDefaultLevel = "Basierend auf Upsilon" USBLowLevel = "Basierend auf Omega" USBParanoidLevel = "Nichts" +Normal = "Normal" +IdleTimeBeforeDimming = "Abdunkeln nach (s)" +IdleTimeBeforeSuspend = "Anhalten nach (s)" +BrightnessShortcut = "Tastenkombinationsschritte" diff --git a/apps/settings/base.en.i18n b/apps/settings/base.en.i18n index 50707610ffa..427eed03cf9 100644 --- a/apps/settings/base.en.i18n +++ b/apps/settings/base.en.i18n @@ -32,6 +32,7 @@ Real = "Real " Cartesian = "Cartesian " Polar = "Polar " Brightness = "Brightness" +BrightnessSettings = "Brightness settings" SoftwareVersion = "Epsilon version" UpsilonVersion = "Upsilon version" OmegaVersion = "Omega version" @@ -79,3 +80,7 @@ USBProtectionLevel = "Updates accepted" USBDefaultLevel = "Based on Upsilon" USBLowLevel = "Based on Omega" USBParanoidLevel = "None" +Normal = "Normal" +IdleTimeBeforeDimming = "Dim after (s)" +IdleTimeBeforeSuspend = "Suspend after (s)" +BrightnessShortcut = "Shortcut steps" diff --git a/apps/settings/base.es.i18n b/apps/settings/base.es.i18n index 4d47b15e02a..b5723e5a755 100644 --- a/apps/settings/base.es.i18n +++ b/apps/settings/base.es.i18n @@ -32,6 +32,7 @@ Real = "Real " Cartesian = "Binómica " Polar = "Polar " Brightness = "Brillo" +BrightnessSettings = "Configuración de brillo" SoftwareVersion = "Versión de Epsilon" UpsilonVersion = "Versión de Upsilon" OmegaVersion = "Versión de Omega" @@ -79,3 +80,7 @@ USBProtectionLevel = "Actualizaciones aceptadas" USBDefaultLevel = "Basado en Upsilon" USBLowLevel = "Basado en Omega" USBParanoidLevel = "Ninguno" +Normal = "Normal" +IdleTimeBeforeDimming = "Oscurecer después de (s)" +IdleTimeBeforeSuspend = "Suspender después de (s)" +BrightnessShortcut = "Pasos de acceso directo" diff --git a/apps/settings/base.fr.i18n b/apps/settings/base.fr.i18n index e200107613c..c8131e9753f 100644 --- a/apps/settings/base.fr.i18n +++ b/apps/settings/base.fr.i18n @@ -32,6 +32,7 @@ Real = "Réel " Cartesian = "Algébrique " Polar = "Exponentielle " Brightness = "Luminosité" +BrightnessSettings = "Paramètres de luminosité" SoftwareVersion = "Version d'Epsilon" UpsilonVersion = "Version d'Upsilon" OmegaVersion = "Version d'Omega" @@ -79,3 +80,7 @@ USBProtectionLevel = "Mise à jour acceptées" USBDefaultLevel = "Basées sur Upsilon" USBLowLevel = "Basées sur Omega" USBParanoidLevel = "Aucune" +Normal = "Normale" +IdleTimeBeforeDimming = "Assombrir après (s)" +IdleTimeBeforeSuspend = "Éteindre après (s)" +BrightnessShortcut = "Étapes du raccourci" diff --git a/apps/settings/base.hu.i18n b/apps/settings/base.hu.i18n index 3b4f083f7f6..4d5485cdadd 100644 --- a/apps/settings/base.hu.i18n +++ b/apps/settings/base.hu.i18n @@ -32,6 +32,7 @@ Real = "Valódi " Cartesian = "Kartéziánus " Polar = "Poláris " Brightness = "Fényerö" +BrightnessSettings = "Fényerő beállításai" SoftwareVersion = "Epsilon verzió" UpsilonVersion = "Upsilon verzió" OmegaVersion = "Omega verzió" @@ -79,3 +80,7 @@ USBProtectionLevel = "Elfogadott frissítések" USBDefaultLevel = "Upsilon alapján" USBLowLevel = "Omega alapján" USBParanoidLevel = "Egyik sem" +Normal = "Normale" +IdleTimeBeforeDimming = "Assombrir après (s)" +IdleTimeBeforeSuspend = "Éteindre après (s)" +BrightnessShortcut = "Parancsikon lépések" diff --git a/apps/settings/base.it.i18n b/apps/settings/base.it.i18n index a292dd0d2fc..74701f626f1 100644 --- a/apps/settings/base.it.i18n +++ b/apps/settings/base.it.i18n @@ -32,6 +32,7 @@ Real = "Reale " Cartesian = "Algebrico " Polar = "Esponenziale " Brightness = "Luminosità" +BrightnessSettings = "Impostazioni di luminosità" SoftwareVersion = "Epsilon version" UpsilonVersion = "Upsilon version" OmegaVersion = "Omega version" @@ -79,3 +80,7 @@ USBProtectionLevel = "Aggiornamenti accettati" USBDefaultLevel = "Basato su Upsilon" USBLowLevel = "A base di Omega" USBParanoidLevel = "Nessuno" +Normal = "Normale" +IdleTimeBeforeDimming = "Scurisci dopo (s)" +IdleTimeBeforeSuspend = "Sospendi dopo (s)" +BrightnessShortcut = "Passaggi di scelta rapida" diff --git a/apps/settings/base.nl.i18n b/apps/settings/base.nl.i18n index 9baf4be1704..b393b653b35 100644 --- a/apps/settings/base.nl.i18n +++ b/apps/settings/base.nl.i18n @@ -32,6 +32,7 @@ Real = "Reëel " Cartesian = "Cartesisch " Polar = "Polair " Brightness = "Helderheid" +BrightnessSettings = "Helderheidsinstellingen" SoftwareVersion = "Epsilon version" UpsilonVersion = "Upsilon version" OmegaVersion = "Omega version" @@ -79,3 +80,7 @@ USBProtectionLevel = "Updates geaccepteerd" USBDefaultLevel = "Gebaseerd op Upsilon" USBLowLevel = "Op basis van Omega" USBParanoidLevel = "Geen" +Normal = "Normaal" +IdleTimeBeforeDimming = "Donkerder maken na (s)" +IdleTimeBeforeSuspend = "Suspend after (s)" +BrightnessShortcut = "Snelkoppelingsstappen" diff --git a/apps/settings/base.pt.i18n b/apps/settings/base.pt.i18n index 66a43282070..c11a54a1692 100644 --- a/apps/settings/base.pt.i18n +++ b/apps/settings/base.pt.i18n @@ -32,6 +32,7 @@ Real = "Real " Cartesian = "Cartesiana " Polar = "Polar " Brightness = "Brilho" +BrightnessSettings = "Configurações de brilho" SoftwareVersion = "Versão do Epsilon" UpsilonVersion = "Versão do Upsilon" OmegaVersion = "Versão do Omega" @@ -79,3 +80,7 @@ USBProtectionLevel = "Atualizações aceitas" USBDefaultLevel = "Baseado em Upsilon" USBLowLevel = "Baseado em Ômega" USBParanoidLevel = "Nenhum" +Normal = "Normal" +IdleTimeBeforeDimming = "Diminuir depois (s)" +IdleTimeBeforeSuspend = "Suspender depois (s)" +BrightnessShortcut = "Passos de atalho" diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index 1b2e484689f..71f7a5a34aa 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -19,6 +19,7 @@ constexpr SettingsMessageTree s_usbProtectionChildren[2] = {SettingsMessageTree( constexpr SettingsMessageTree s_usbProtectionLevelChildren[3] = {SettingsMessageTree(I18n::Message::USBDefaultLevel), SettingsMessageTree(I18n::Message::USBLowLevel), SettingsMessageTree(I18n::Message::USBParanoidLevel)}; constexpr SettingsMessageTree s_symbolFunctionChildren[3] = {SettingsMessageTree(I18n::Message::SymbolDefaultFunction), SettingsMessageTree(I18n::Message::SymbolArgDefaultFunction), SettingsMessageTree(I18n::Message::SymbolArgFunction)}; constexpr SettingsMessageTree s_modelMathOptionsChildren[6] = {SettingsMessageTree(I18n::Message::AngleUnit, s_modelAngleChildren), SettingsMessageTree(I18n::Message::DisplayMode, s_modelFloatDisplayModeChildren), SettingsMessageTree(I18n::Message::EditionMode, s_modelEditionModeChildren), SettingsMessageTree(I18n::Message::SymbolFunction, s_symbolFunctionChildren), SettingsMessageTree(I18n::Message::ComplexFormat, s_modelComplexFormatChildren), SettingsMessageTree(I18n::Message::SymbolMultiplication, s_symbolChildren)}; +constexpr SettingsMessageTree s_brightnessChildren[4] = {SettingsMessageTree(I18n::Message::Brightness), SettingsMessageTree(I18n::Message::IdleTimeBeforeDimming), SettingsMessageTree(I18n::Message::IdleTimeBeforeSuspend), SettingsMessageTree(I18n::Message::BrightnessShortcut)}; constexpr SettingsMessageTree s_accessibilityChildren[6] = {SettingsMessageTree(I18n::Message::AccessibilityInvertColors), SettingsMessageTree(I18n::Message::AccessibilityMagnify),SettingsMessageTree(I18n::Message::AccessibilityGamma),SettingsMessageTree(I18n::Message::AccessibilityGammaRed),SettingsMessageTree(I18n::Message::AccessibilityGammaGreen),SettingsMessageTree(I18n::Message::AccessibilityGammaBlue)}; constexpr SettingsMessageTree s_contributorsChildren[18] = {SettingsMessageTree(I18n::Message::LaurianFournier), SettingsMessageTree(I18n::Message::YannCouturier), SettingsMessageTree(I18n::Message::DavidLuca), SettingsMessageTree(I18n::Message::LoicE), SettingsMessageTree(I18n::Message::VictorKretz), SettingsMessageTree(I18n::Message::QuentinGuidee), SettingsMessageTree(I18n::Message::JoachimLeFournis), SettingsMessageTree(I18n::Message::MaximeFriess), SettingsMessageTree(I18n::Message::JeanBaptisteBoric), SettingsMessageTree(I18n::Message::SandraSimmons), SettingsMessageTree(I18n::Message::David), SettingsMessageTree(I18n::Message::DamienNicolet), SettingsMessageTree(I18n::Message::EvannDreumont), SettingsMessageTree(I18n::Message::SzaboLevente), SettingsMessageTree(I18n::Message::VenceslasDuet), SettingsMessageTree(I18n::Message::CharlotteThomas), SettingsMessageTree(I18n::Message::AntoninLoubiere), SettingsMessageTree(I18n::Message::CyprienMejat)}; @@ -32,10 +33,10 @@ constexpr SettingsMessageTree s_modelAboutChildren[10] = {SettingsMessageTree(I1 MainController::MainController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate) : ViewController(parentResponder), - m_brightnessCell(I18n::Message::Default, KDFont::LargeFont), m_popUpCell(I18n::Message::Default, KDFont::LargeFont), m_selectableTableView(this), m_mathOptionsController(this, inputEventHandlerDelegate), + m_brightnessController(this, inputEventHandlerDelegate), m_localizationController(this, Metric::CommonTopMargin, LocalizationController::Mode::Language), m_accessibilityController(this), m_dateTimeController(this), @@ -63,13 +64,6 @@ void MainController::didBecomeFirstResponder() { bool MainController::handleEvent(Ion::Events::Event event) { GlobalPreferences * globalPreferences = GlobalPreferences::sharedGlobalPreferences(); - if (event == Ion::Events::BrightnessPlus || event == Ion::Events::BrightnessMinus){ - int delta = Ion::Backlight::MaxBrightness/GlobalPreferences::NumberOfBrightnessStates; - int direction = (event == Ion::Events::BrightnessPlus) ? Ion::Backlight::NumberOfStepsPerShortcut*delta : -delta*Ion::Backlight::NumberOfStepsPerShortcut; - GlobalPreferences::sharedGlobalPreferences()->setBrightnessLevel(GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()+direction); - m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), 1); - return true; - } if (model()->childAtIndex(selectedRow())->numberOfChildren() == 0) { if (model()->childAtIndex(selectedRow())->label() == promptMessage()) { if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { @@ -79,16 +73,6 @@ bool MainController::handleEvent(Ion::Events::Event event) { } return false; } - if (model()->childAtIndex(selectedRow())->label() == I18n::Message::Brightness) { - if (event == Ion::Events::Right || event == Ion::Events::Left || event == Ion::Events::Plus || event == Ion::Events::Minus) { - int delta = Ion::Backlight::MaxBrightness/GlobalPreferences::NumberOfBrightnessStates; - int direction = (event == Ion::Events::Right || event == Ion::Events::Plus) ? delta : -delta; - globalPreferences->setBrightnessLevel(globalPreferences->brightnessLevel()+direction); - m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); - return true; - } - return false; - } if (model()->childAtIndex(selectedRow())->label() == I18n::Message::Language || model()->childAtIndex(selectedRow())->label() == I18n::Message::Country) { if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { m_localizationController.setMode(model()->childAtIndex(selectedRow())->label() == I18n::Message::Language ? LocalizationController::Mode::Language : LocalizationController::Mode::Country); @@ -107,6 +91,8 @@ bool MainController::handleEvent(Ion::Events::Event event) { subController = &m_examModeController; } else if (title == I18n::Message::About) { subController = &m_aboutController; + } else if (title == I18n::Message::BrightnessSettings) { + subController = &m_brightnessController; } else if (title == I18n::Message::Accessibility) { subController = &m_accessibilityController; } else if (title == I18n::Message::DateTime) { @@ -140,11 +126,7 @@ KDCoordinate MainController::rowHeight(int j) { } KDCoordinate MainController::cumulatedHeightFromIndex(int j) { - KDCoordinate height = j * rowHeight(0); - if (j > k_indexOfBrightnessCell) { - height += CellWithSeparator::k_margin; - } - return height; + return j * rowHeight(0); } int MainController::indexFromCumulatedHeight(KDCoordinate offsetY) { @@ -161,11 +143,8 @@ HighlightCell * MainController::reusableCell(int index, int type) { return &m_cells[index]; } assert(index == 0); - if (type == 2) { - return &m_popUpCell; - } - assert(type == 1); - return &m_brightnessCell; + assert(type == 2); + return &m_popUpCell; } int MainController::reusableCellCount(int type) { diff --git a/apps/settings/main_controller.h b/apps/settings/main_controller.h index 28c5b07d779..5a721e0c4a2 100644 --- a/apps/settings/main_controller.h +++ b/apps/settings/main_controller.h @@ -13,6 +13,7 @@ #include "sub_menu/math_options_controller.h" #include "sub_menu/preferences_controller.h" #include "sub_menu/usb_protection_controller.h" +#include "sub_menu/brightness_controller.h" namespace Settings { @@ -31,6 +32,7 @@ extern const Shared::SettingsMessageTree s_contributorsChildren[18]; extern const Shared::SettingsMessageTree s_modelAboutChildren[10]; extern const Shared::SettingsMessageTree s_usbProtectionChildren[2]; extern const Shared::SettingsMessageTree s_usbProtectionLevelChildren[3]; +extern const Shared::SettingsMessageTree s_brightnessChildren[4]; extern const Shared::SettingsMessageTree s_model; class MainController : public ViewController, public ListViewDataSource, public SelectableTableViewDataSource { @@ -66,12 +68,12 @@ class MainController : public ViewController, public ListViewDataSource, public StackViewController * stackController() const; I18n::Message promptMessage() const; bool hasPrompt() const { return promptMessage() != I18n::Message::Default; } - constexpr static int k_numberOfSimpleChevronCells = 9; + constexpr static int k_numberOfSimpleChevronCells = 10; MessageTableCellWithChevronAndMessage m_cells[k_numberOfSimpleChevronCells]; - MessageTableCellWithGaugeWithSeparator m_brightnessCell; MessageTableCellWithSwitch m_popUpCell; SelectableTableView m_selectableTableView; MathOptionsController m_mathOptionsController; + BrightnessController m_brightnessController; LocalizationController m_localizationController; AccessibilityController m_accessibilityController; DateTimeController m_dateTimeController; diff --git a/apps/settings/main_controller_prompt_beta.cpp b/apps/settings/main_controller_prompt_beta.cpp index 80b3e1459a8..90df4b89791 100644 --- a/apps/settings/main_controller_prompt_beta.cpp +++ b/apps/settings/main_controller_prompt_beta.cpp @@ -8,7 +8,7 @@ namespace Settings { constexpr SettingsMessageTree s_modelMenu[] = {SettingsMessageTree(I18n::Message::MathOptions, s_modelMathOptionsChildren), - SettingsMessageTree(I18n::Message::Brightness), + SettingsMessageTree(I18n::Message::BrightnessSettings, s_brightnessChildren), SettingsMessageTree(I18n::Message::DateTime, s_modelDateTimeChildren), SettingsMessageTree(I18n::Message::Language), SettingsMessageTree(I18n::Message::Country), diff --git a/apps/settings/main_controller_prompt_none.cpp b/apps/settings/main_controller_prompt_none.cpp index d0b2727c562..9dc5d45a6fc 100644 --- a/apps/settings/main_controller_prompt_none.cpp +++ b/apps/settings/main_controller_prompt_none.cpp @@ -9,7 +9,7 @@ namespace Settings { constexpr SettingsMessageTree s_modelMenu[] = {SettingsMessageTree(I18n::Message::MathOptions, s_modelMathOptionsChildren), - SettingsMessageTree(I18n::Message::Brightness), + SettingsMessageTree(I18n::Message::BrightnessSettings, s_brightnessChildren), SettingsMessageTree(I18n::Message::DateTime, s_modelDateTimeChildren), SettingsMessageTree(I18n::Message::Language), SettingsMessageTree(I18n::Message::Country), diff --git a/apps/settings/main_controller_prompt_update.cpp b/apps/settings/main_controller_prompt_update.cpp index e028803ef17..48c5b5012a6 100644 --- a/apps/settings/main_controller_prompt_update.cpp +++ b/apps/settings/main_controller_prompt_update.cpp @@ -8,14 +8,14 @@ using namespace Shared; constexpr SettingsMessageTree s_modelMenu[] = {SettingsMessageTree(I18n::Message::MathOptions, s_modelMathOptionsChildren), - SettingsMessageTree(I18n::Message::Brightness), + SettingsMessageTree(I18n::Message::BrightnessSettings, s_brightnessChildren), SettingsMessageTree(I18n::Message::DateTime, s_modelDateTimeChildren), SettingsMessageTree(I18n::Message::Language), SettingsMessageTree(I18n::Message::Country), SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), #ifdef HAS_CODE SettingsMessageTree(I18n::Message::CodeApp, s_codeChildren), -#endif +#endif SettingsMessageTree(I18n::Message::UpdatePopUp), SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), SettingsMessageTree(I18n::Message::UsbSetting, s_usbProtectionChildren), diff --git a/apps/settings/sub_menu/brightness_controller.cpp b/apps/settings/sub_menu/brightness_controller.cpp new file mode 100644 index 00000000000..9af1f42155a --- /dev/null +++ b/apps/settings/sub_menu/brightness_controller.cpp @@ -0,0 +1,142 @@ +#include "brightness_controller.h" +#include "../../global_preferences.h" +#include "../../apps_container.h" +#include "../../shared/poincare_helpers.h" +#include +#include +#include "../app.h" +#include +#include + +using namespace Poincare; +using namespace Shared; + +namespace Settings { + +BrightnessController::BrightnessController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate) : + GenericSubController(parentResponder), + m_brightnessCell(I18n::Message::Brightness, KDFont::LargeFont), + m_editableCellIdleBeforeDimmingSeconds(&m_selectableTableView, inputEventHandlerDelegate, this), + m_editableCellIdleBeforeSuspendSeconds(&m_selectableTableView, inputEventHandlerDelegate, this), + m_BrightnessShortcutCell(&m_selectableTableView, inputEventHandlerDelegate, this) +{ + m_editableCellIdleBeforeDimmingSeconds.setMessage(I18n::Message::IdleTimeBeforeDimming); + m_editableCellIdleBeforeDimmingSeconds.setMessageFont(KDFont::LargeFont); + m_editableCellIdleBeforeSuspendSeconds.setMessage(I18n::Message::IdleTimeBeforeSuspend); + m_editableCellIdleBeforeSuspendSeconds.setMessageFont(KDFont::LargeFont); + m_BrightnessShortcutCell.setMessage(I18n::Message::BrightnessShortcut); + m_BrightnessShortcutCell.setMessageFont(KDFont::LargeFont); +} + +HighlightCell * BrightnessController::reusableCell(int index, int type) { + HighlightCell * editableCell[] = { + &m_brightnessCell, + &m_editableCellIdleBeforeDimmingSeconds, + &m_editableCellIdleBeforeSuspendSeconds, + &m_BrightnessShortcutCell + }; + return editableCell[index]; +} + +int BrightnessController::reusableCellCount(int type) { + switch(type) { + case 0: + case 1: + return k_totalNumberOfCell; + default: + assert(false); + return 0; + } +} + +void BrightnessController::willDisplayCellForIndex(HighlightCell * cell, int index) { + if(index == 0){ + MessageTableCellWithGauge * myGaugeCell = (MessageTableCellWithGauge *)cell; + GaugeView * myGauge = (GaugeView *)myGaugeCell->accessoryView(); + myGauge->setLevel((float)GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()/(float)Ion::Backlight::MaxBrightness); + return; + } else { + MessageTableCellWithEditableText * myCell = (MessageTableCellWithEditableText *)cell; + GenericSubController::willDisplayCellForIndex(myCell, index); + char buffer[5]; + int val; + switch(index){ + case 1:{ + val = GlobalPreferences::sharedGlobalPreferences()->idleBeforeDimmingSeconds(); + break; + } + case 2:{ + val = GlobalPreferences::sharedGlobalPreferences()->idleBeforeSuspendSeconds(); + break; + } + case 3:{ + val = GlobalPreferences::sharedGlobalPreferences()->brightnessShortcut(); + break; + } + default: + assert(false); + } + Poincare::Integer(val).serialize(buffer, 5); + myCell->setAccessoryText(buffer); + } +} + +bool BrightnessController::handleEvent(Ion::Events::Event event) { + if ((selectedRow() == 0) && (event == Ion::Events::Right || event == Ion::Events::Left || event == Ion::Events::Plus || event == Ion::Events::Minus)) { + int delta = Ion::Backlight::MaxBrightness/GlobalPreferences::NumberOfBrightnessStates; + int direction = (event == Ion::Events::Right || event == Ion::Events::Plus) ? delta : -delta; + GlobalPreferences::sharedGlobalPreferences()->setBrightnessLevel(GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()+direction); + m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); + return true; + } + if (event == Ion::Events::BrightnessPlus || event == Ion::Events::BrightnessMinus){ + int delta = Ion::Backlight::MaxBrightness/GlobalPreferences::NumberOfBrightnessStates; + int NumberOfStepsPerShortcut = GlobalPreferences::sharedGlobalPreferences()->brightnessShortcut(); + int direction = (event == Ion::Events::BrightnessPlus) ? NumberOfStepsPerShortcut*delta : -delta*NumberOfStepsPerShortcut; + GlobalPreferences::sharedGlobalPreferences()->setBrightnessLevel(GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()+direction); + m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), 0); + return true; + } + return false; +} + +bool BrightnessController::textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) { + return ((event == Ion::Events::Up && selectedRow() > 0) || (event == Ion::Events::Down && selectedRow() < k_totalNumberOfCell - 1)) + || TextFieldDelegate::textFieldShouldFinishEditing(textField, event); +} + +bool BrightnessController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { + double floatBody; + if (textFieldDelegateApp()->hasUndefinedValue(text, floatBody)) { + return false; + } + int val = std::round(floatBody); + switch (selectedRow()) { + case 1: + GlobalPreferences::sharedGlobalPreferences()->setIdleBeforeDimmingSeconds(val); + m_editableCellIdleBeforeDimmingSeconds.setAccessoryText(text); + break; + case 2: + GlobalPreferences::sharedGlobalPreferences()->setIdleBeforeSuspendSeconds(val); + m_editableCellIdleBeforeSuspendSeconds.setAccessoryText(text); + break; + case 3:{ + if(val > GlobalPreferences::NumberOfBrightnessStates){ val = GlobalPreferences::NumberOfBrightnessStates; + } else if (val < 0){val = 0;} + GlobalPreferences::sharedGlobalPreferences()->setBrightnessShortcut(val); + m_BrightnessShortcutCell.setAccessoryText(text); + break; + } + default: + assert(false); + } + AppsContainer * myContainer = AppsContainer::sharedAppsContainer(); + myContainer->refreshPreferences(); + m_selectableTableView.reloadCellAtLocation(0, selectedRow()); + if (event == Ion::Events::Up || event == Ion::Events::Down || event == Ion::Events::OK) { + m_selectableTableView.handleEvent(event); + } + return true; +} + +} diff --git a/apps/settings/sub_menu/brightness_controller.h b/apps/settings/sub_menu/brightness_controller.h new file mode 100644 index 00000000000..a36a39a3246 --- /dev/null +++ b/apps/settings/sub_menu/brightness_controller.h @@ -0,0 +1,33 @@ +#ifndef SETTINGS_BRIGHTNESS_CONTROLLER_H +#define SETTINGS_BRIGHTNESS_CONTROLLER_H + +#include "generic_sub_controller.h" +#include "preferences_controller.h" +#include "../message_table_cell_with_editable_text_with_separator.h" +#include "../../shared/parameter_text_field_delegate.h" +#include "../message_table_cell_with_gauge_with_separator.h" + +namespace Settings { + +class BrightnessController : public GenericSubController, public Shared::ParameterTextFieldDelegate { +public: + BrightnessController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate); + TELEMETRY_ID("BrightnessSettings"); + HighlightCell * reusableCell(int index, int type) override; + int reusableCellCount(int type) override; + void willDisplayCellForIndex(HighlightCell * cell, int index) override; + bool handleEvent(Ion::Events::Event event) override; + bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; + bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; +protected: + constexpr static int k_totalNumberOfCell = 4; +private: + MessageTableCellWithGauge m_brightnessCell; + MessageTableCellWithEditableText m_editableCellIdleBeforeDimmingSeconds; + MessageTableCellWithEditableText m_editableCellIdleBeforeSuspendSeconds; + MessageTableCellWithEditableText m_BrightnessShortcutCell; +}; + +} + +#endif diff --git a/apps/suspend_timer.cpp b/apps/suspend_timer.cpp index 34562d8190c..b21c1d6b39b 100644 --- a/apps/suspend_timer.cpp +++ b/apps/suspend_timer.cpp @@ -2,7 +2,7 @@ #include "apps_container.h" SuspendTimer::SuspendTimer() : - Timer(k_idleBeforeSuspendDuration/Timer::TickDuration) + Timer(GlobalPreferences::sharedGlobalPreferences()->idleBeforeSuspendSeconds()*1000/Timer::TickDuration) { } diff --git a/apps/suspend_timer.h b/apps/suspend_timer.h index 0a7b735d674..8125ca85513 100644 --- a/apps/suspend_timer.h +++ b/apps/suspend_timer.h @@ -7,7 +7,6 @@ class SuspendTimer : public Timer { public: SuspendTimer(); private: - constexpr static int k_idleBeforeSuspendDuration = 5*60*1000; // In miliseconds bool fire() override; }; diff --git a/escher/include/escher/timer.h b/escher/include/escher/timer.h index 3a7e2a7ec5b..94ed476c4c4 100644 --- a/escher/include/escher/timer.h +++ b/escher/include/escher/timer.h @@ -17,7 +17,7 @@ class Timer { static constexpr int TickDuration = 300; // In Miliseconds Timer(uint32_t period); // Period is in ticks bool tick(); - void reset(); + void reset(uint32_t NewPeriod = -1); protected: virtual bool fire() = 0; uint32_t m_period; diff --git a/escher/src/timer.cpp b/escher/src/timer.cpp index 0e9f5a5eac8..742634fbf1a 100644 --- a/escher/src/timer.cpp +++ b/escher/src/timer.cpp @@ -16,6 +16,9 @@ bool Timer::tick() { return false; } -void Timer::reset() { +void Timer::reset(uint32_t NewPeriod) { + if(NewPeriod != -1){ + m_period = NewPeriod; + } m_numberOfTicksBeforeFire = m_period; } diff --git a/ion/include/ion/backlight.h b/ion/include/ion/backlight.h index 89a19687c08..cd69ee88822 100644 --- a/ion/include/ion/backlight.h +++ b/ion/include/ion/backlight.h @@ -12,7 +12,6 @@ bool isInitialized(); void shutdown(); void setBrightness(uint8_t b); uint8_t brightness(); -const int NumberOfStepsPerShortcut = 4; } } From 3b293c822a14a6e82951d469de6bcb6ea37cb958 Mon Sep 17 00:00:00 2001 From: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> Date: Wed, 23 Mar 2022 08:36:23 +0100 Subject: [PATCH 188/355] [apps/graph] Added a color menu in graph and list (#189) * Revert "[github/workflows] Update Metrics to remove NumWorksBot" This reverts commit 110f33312264861427280414798d8170a4468488. * Added a color menu in graph and list * Fixed color select display issue and build issue * Changed color_cell to a circle * Revert "Changed color_cell to a circle" This reverts commit 28dddb42af18ace8599615b33e14b5dd23bdeff2. * Color_cell with mask * Fixed build issue * Color selection : Added right handle and color name display in menu * Fixed constexpr static colorMask * Changed font in color_parameter_controller * Fix building without debug * Re-Fix building without debug * Update colors Co-authored-by: Hugo Saint-Vignes Co-authored-by: Joachim LF --- apps/graph/list/list_parameter_controller.cpp | 2 +- .../list/list_parameter_controller.cpp | 22 +---- .../sequence/list/list_parameter_controller.h | 4 - apps/shared.de.i18n | 7 ++ apps/shared.en.i18n | 7 ++ apps/shared.es.i18n | 7 ++ apps/shared.fr.i18n | 7 ++ apps/shared.hu.i18n | 7 ++ apps/shared.it.i18n | 7 ++ apps/shared.nl.i18n | 7 ++ apps/shared.pt.i18n | 7 ++ apps/shared/Makefile | 2 + apps/shared/color_cell.cpp | 51 +++++++++++ apps/shared/color_cell.h | 42 +++++++++ apps/shared/color_parameter_controller.cpp | 86 +++++++++++++++++++ apps/shared/color_parameter_controller.h | 44 ++++++++++ apps/shared/function.cpp | 4 + apps/shared/function.h | 2 + apps/shared/list_parameter_controller.cpp | 39 +++++---- apps/shared/list_parameter_controller.h | 10 +-- 20 files changed, 312 insertions(+), 52 deletions(-) create mode 100644 apps/shared/color_cell.cpp create mode 100644 apps/shared/color_cell.h create mode 100644 apps/shared/color_parameter_controller.cpp create mode 100644 apps/shared/color_parameter_controller.h diff --git a/apps/graph/list/list_parameter_controller.cpp b/apps/graph/list/list_parameter_controller.cpp index 29b7bbdef70..01d266db2f2 100644 --- a/apps/graph/list/list_parameter_controller.cpp +++ b/apps/graph/list/list_parameter_controller.cpp @@ -41,7 +41,7 @@ bool ListParameterController::handleEvent(Ion::Events::Event event) { } if (event == Ion::Events::Right) { int selectedR = selectedRow(); - if (selectedR == 0 || selectedR == 1) { + if (selectedR == 0 || selectedR == 1 || selectedR == 3) { // Go in the submenu return handleEnterOnRow(selectedR); } diff --git a/apps/sequence/list/list_parameter_controller.cpp b/apps/sequence/list/list_parameter_controller.cpp index 9058083e3c9..2ac1397cd62 100644 --- a/apps/sequence/list/list_parameter_controller.cpp +++ b/apps/sequence/list/list_parameter_controller.cpp @@ -24,38 +24,18 @@ const char * ListParameterController::title() { bool ListParameterController::handleEvent(Ion::Events::Event event) { bool hasAdditionalRow = hasInitialRankRow(); -#if FUNCTION_COLOR_CHOICE - if (event == Ion::Events::OK || event == Ion::Events::EXE || (event == Ion::Events::Right && selectedRow() == 1)) { -#else if (event == Ion::Events::OK || event == Ion::Events::EXE || (event == Ion::Events::Right && selectedRow() == 0)) { -#endif int selectedRowIndex = selectedRow(); -#if FUNCTION_COLOR_CHOICE if (selectedRowIndex == 0) { - return handleEnterOnRow(selectedRowIndex); - } - if (selectedRowIndex == 1) { -#else - if (selectedRowIndex == 0) { -#endif StackViewController * stack = (StackViewController *)(parentResponder()); m_typeParameterController.setRecord(m_record); stack->push(&m_typeParameterController); return true; } -#if FUNCTION_COLOR_CHOICE - if (selectedRowIndex == 2+hasAdditionalRow) { - -#else - if (selectedRowIndex == 1+hasAdditionalRow) { -#endif + if (selectedRowIndex == 1+hasAdditionalRow || selectedRowIndex == 2+hasAdditionalRow) { return handleEnterOnRow(selectedRowIndex-hasAdditionalRow-1); } -#if FUNCTION_COLOR_CHOICE if (selectedRowIndex == 3+hasAdditionalRow) { -#else - if (selectedRowIndex == 2+hasAdditionalRow) { -#endif App::app()->localContext()->resetCache(); return handleEnterOnRow(selectedRowIndex-hasAdditionalRow-1); } diff --git a/apps/sequence/list/list_parameter_controller.h b/apps/sequence/list/list_parameter_controller.h index 911630a0c8e..06166e8b0b7 100644 --- a/apps/sequence/list/list_parameter_controller.h +++ b/apps/sequence/list/list_parameter_controller.h @@ -25,11 +25,7 @@ class ListParameterController : public Shared::ListParameterController, public S HighlightCell * reusableCell(int index, int type) override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; private: -#if FUNCTION_COLOR_CHOICE constexpr static int k_totalNumberOfCell = 5; -#else - constexpr static int k_totalNumberOfCell = 4; -#endif int totalNumberOfCells() const override; Shared::Sequence * sequence() { return static_cast(function().pointer()); } bool hasInitialRankRow() const; diff --git a/apps/shared.de.i18n b/apps/shared.de.i18n index b8926850148..1e61ff9f594 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -89,3 +89,10 @@ ExamModeModeStandard = "Standard " ExamModeModeNoSym = "Kein Symbol " ExamModeModeNoSymNoText = "Kein Symbol kein Text " ExamModeModeDutch = "Niederländisch " +ColorRed = "Rot " +ColorBlue = "Blau " +ColorGreen = "Grün " +ColorYellow = "Gelb " +ColorPurple = "Violett " +ColorPink = "Rosa " +ColorOrange = "Orange " diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index c18e948aac3..2da4bfabc1e 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -89,3 +89,10 @@ ExamModeModeStandard = "Standard " ExamModeModeNoSym = "No sym " ExamModeModeNoSymNoText = "No sym no text " ExamModeModeDutch = "Dutch " +ColorRed = "Red " +ColorBlue = "Blue " +ColorGreen = "Green " +ColorYellow = "Yellow " +ColorPurple = "Purple " +ColorPink = "Pink " +ColorOrange = "Orange " diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index 800b303552a..df5b55fdf17 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -89,3 +89,10 @@ ExamModeModeStandard = "Estándar " ExamModeModeNoSym = "Sin simbólico " ExamModeModeNoSymNoText = "Sin simbólico sin texto " ExamModeModeDutch = "Holandés " +ColorRed = "Rojo " +ColorBlue = "Azul " +ColorGreen = "Verde " +ColorYellow = "Amarillo " +ColorPurple = "Púrpura " +ColorPink = "Rosa " +ColorOrange = "Naranja " diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index 6bcacc18e1e..058514d0c29 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -89,3 +89,10 @@ ExamModeModeStandard = "Standard " ExamModeModeNoSym = "Sans symbolique " ExamModeModeNoSymNoText = "Sans symbolique ni texte " ExamModeModeDutch = "Dutch " +ColorRed = "Rouge " +ColorBlue = "Bleu " +ColorGreen = "Vert " +ColorYellow = "Jaune " +ColorPurple = "Violet " +ColorPink = "Rose " +ColorOrange = "Orange " diff --git a/apps/shared.hu.i18n b/apps/shared.hu.i18n index b9be6807bd2..2a2dde2b6a6 100644 --- a/apps/shared.hu.i18n +++ b/apps/shared.hu.i18n @@ -89,3 +89,10 @@ ExamModeModeStandard = "Normál " ExamModeModeNoSym = "Szimbólikus nélkül " ExamModeModeNoSymNoText = "Szimbólikus és szöveg nélkül " ExamModeModeDutch = "Holland " +ColorRed = "Piros " +ColorBlue = "Kék " +ColorGreen = "Zöld " +ColorYellow = "Sárga " +ColorPurple = "Lila " +ColorPink = "Rózsaszín " +ColorOrange = "Narancssárga " diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n index 4476ed58c78..04472b55396 100644 --- a/apps/shared.it.i18n +++ b/apps/shared.it.i18n @@ -89,3 +89,10 @@ ExamModeModeStandard = "Standard " ExamModeModeNoSym = "Nessun simbolo " ExamModeModeNoSymNoText = "Nessun simbolo nessun testo " ExamModeModeDutch = "Olandese " +ColorRed = "Rosso " +ColorBlue = "Blu " +ColorGreen = "Verde " +ColorYellow = "Giallo " +ColorPurple = "Viola " +ColorPink = "Rosa " +ColorOrange = "Arancia " diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index cd091b0dcef..92bbbd66b52 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -89,3 +89,10 @@ ExamModeModeStandard = "Standaard " ExamModeModeNoSym = "Geen sym " ExamModeModeNoSymNoText = "Geen sym geen tekst " ExamModeModeDutch = "Nederlands " +ColorRed = "rood" +ColorBlue = "Blauw" +ColorGreen = "Groente" +ColorYellow = "Geel" +ColorPurple = "Purper" +ColorPink = "Roze" +ColorOrange = "Oranje" diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index b68443df121..297cfeba759 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -89,3 +89,10 @@ ExamModeModeStandard = "Padrão " ExamModeModeNoSym = "Sem sym " ExamModeModeNoSymNoText = "Sem sym sem texto " ExamModeModeDutch = "holandês " +ColorRed = "Vermelho " +ColorBlue = "Azul " +ColorGreen = "Verde " +ColorYellow = "Amarelo " +ColorPurple = "Roxa " +ColorPink = "Cor de rosa " +ColorOrange = "Laranja " diff --git a/apps/shared/Makefile b/apps/shared/Makefile index bd421f508a6..1d0530e7383 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -29,6 +29,8 @@ app_shared_src = $(addprefix apps/shared/,\ buffer_function_title_cell.cpp \ buffer_text_view_with_text_field.cpp \ button_with_separator.cpp \ + color_cell.cpp \ + color_parameter_controller.cpp \ cursor_view.cpp \ editable_cell_table_view_controller.cpp \ expression_field_delegate_app.cpp \ diff --git a/apps/shared/color_cell.cpp b/apps/shared/color_cell.cpp new file mode 100644 index 00000000000..a43e3f3ea5b --- /dev/null +++ b/apps/shared/color_cell.cpp @@ -0,0 +1,51 @@ +#include "color_cell.h" + +namespace Shared { + +constexpr const I18n::Message MessageTableCellWithColor::k_textForIndex[Palette::numberOfDataColors()]; + +constexpr const uint8_t colorMask[MessageTableCellWithColor::ColorView::k_colorSize][MessageTableCellWithColor::ColorView::k_colorSize] = { // FIXME Can't link with constexpr static + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xE1, 0x0C, 0x00, 0x00, 0x0C, 0xE1, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xFF, 0xFF}, + {0xFF, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE1, 0xFF}, + {0xFF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF}, + {0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, + {0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, + {0xFF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF}, + {0xFF, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE1, 0xFF}, + {0xFF, 0xFF, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xE1, 0x0C, 0x00, 0x00, 0x0C, 0xE1, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, +}; + +MessageTableCellWithColor::MessageTableCellWithColor() : + MessageTableCell(), + m_accessoryView() + {} + +View * MessageTableCellWithColor::accessoryView() const { + return (View *)&m_accessoryView; +} + +void MessageTableCellWithColor::setColor(int i) { + m_accessoryView.setColor(i); + MessageTextView * label = (MessageTextView*)(labelView()); + return label->setMessage(k_textForIndex[i]); +} + +MessageTableCellWithColor::ColorView::ColorView() : + m_index(0) + {} + +void MessageTableCellWithColor::ColorView::drawRect(KDContext * ctx, KDRect rect) const { + KDColor Buffer[MessageTableCellWithColor::ColorView::k_colorSize*MessageTableCellWithColor::ColorView::k_colorSize]; + KDRect Frame(bounds().x(), bounds().y() + bounds().height()/2 - k_colorSize/2, k_colorSize, k_colorSize); + ctx->blendRectWithMask(Frame, Palette::DataColor[m_index], (const uint8_t *)colorMask, Buffer); +} + +KDSize MessageTableCellWithColor::ColorView::minimalSizeForOptimalDisplay() const { + return KDSize(k_colorSize, k_colorSize); +} + +} diff --git a/apps/shared/color_cell.h b/apps/shared/color_cell.h new file mode 100644 index 00000000000..16967bdcbc9 --- /dev/null +++ b/apps/shared/color_cell.h @@ -0,0 +1,42 @@ +#ifndef SHARED_COLOR_CELL_CONTROLLER_H +#define SHARED_COLOR_CELL_CONTROLLER_H + +#include +#include + +namespace Shared { + +class MessageTableCellWithColor : public MessageTableCell { +public: + MessageTableCellWithColor(); + View * accessoryView() const override; + void setColor(int i); + int color() { return m_accessoryView.color(); } + constexpr static I18n::Message k_textForIndex[Palette::numberOfDataColors()] = { + I18n::Message::ColorRed, + I18n::Message::ColorBlue, + I18n::Message::ColorGreen, + I18n::Message::ColorYellow, + I18n::Message::ColorPurple, + I18n::Message::ColorBlue, + I18n::Message::ColorPink, + I18n::Message::ColorOrange + }; + class ColorView : public TransparentView { + public: + ColorView(); + void setColor(int i) { m_index = i; } + int color() { return m_index; } + void drawRect(KDContext * ctx, KDRect rect) const override; + KDSize minimalSizeForOptimalDisplay() const override; + constexpr static KDCoordinate k_colorSize = 12; + private: + int m_index; + }; +private: + ColorView m_accessoryView; +}; + +} + +#endif diff --git a/apps/shared/color_parameter_controller.cpp b/apps/shared/color_parameter_controller.cpp new file mode 100644 index 00000000000..2b2e4ef8d81 --- /dev/null +++ b/apps/shared/color_parameter_controller.cpp @@ -0,0 +1,86 @@ +#include "color_parameter_controller.h" + +#include "function_app.h" +#include "../apps_container.h" +#include + +namespace Shared { + +ColorParameterController::ColorParameterController(Responder * parentResponder, I18n::Message title) : + ViewController(parentResponder), + m_selectableTableView(this), + m_record(), + m_title(title) +{} + +void ColorParameterController::viewWillAppear() { + ViewController::viewWillAppear(); + // Restore the selected color + KDColor FunctionColor = function()->color(); + uint8_t cellXPosition = 0; + // TODO: Improve this if possible + for (uint8_t i = 0; i < sizeof(Palette::DataColor)/sizeof(Palette::DataColor[0]); i++) { + if (Palette::DataColor[i] == FunctionColor) { + cellXPosition = i; + break; + } + } + assert(Palette::DataColor[cellXPosition] == FunctionColor); + selectCellAtLocation(0, cellXPosition); + m_selectableTableView.reloadData(); +} + +void ColorParameterController::didBecomeFirstResponder() { + Container::activeApp()->setFirstResponder(&m_selectableTableView); +} + +bool ColorParameterController::handleEvent(Ion::Events::Event event) { + StackViewController * stack = (StackViewController *)(parentResponder()); + if (event == Ion::Events::Left) { + stack->pop(); + return true; + } + if (event == Ion::Events::OK || event == Ion::Events::EXE) { + handleEnterOnRow(selectedRow()); + stack->pop(); + return true; + } + return false; +} + +KDCoordinate ColorParameterController::cellHeight() { + return Metric::ParameterCellHeight; +} + +HighlightCell * ColorParameterController::reusableCell(int index) { + assert(index < numberOfRows()); + return &m_cells[index]; +} + +void ColorParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { + MessageTableCellWithColor * myCell = (MessageTableCellWithColor *)cell; + myCell->setColor(index); + myCell->setMessageFont(KDFont::LargeFont); + cell->reloadCell(); +} + +bool ColorParameterController::handleEnterOnRow(int rowIndex) { + function()->setColor(Palette::DataColor[rowIndex]); + return true; +} + +void ColorParameterController::setRecord(Ion::Storage::Record record) { + m_record = record; + selectCellAtLocation(0, 0); +} + +ExpiringPointer ColorParameterController::function() { + return functionStore()->modelForRecord(m_record); +} + +FunctionStore * ColorParameterController::functionStore() { + return FunctionApp::app()->functionStore(); +} + + +} diff --git a/apps/shared/color_parameter_controller.h b/apps/shared/color_parameter_controller.h new file mode 100644 index 00000000000..416e9401924 --- /dev/null +++ b/apps/shared/color_parameter_controller.h @@ -0,0 +1,44 @@ +#ifndef SHARED_COLOR_PARAM_CONTROLLER_H +#define SHARED_COLOR_PARAM_CONTROLLER_H + +#include +#include "function_store.h" +#include "color_cell.h" +#include + +namespace Shared { + +class ColorParameterController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource { +public: + ColorParameterController(Responder * parentResponder, I18n::Message title); + + View * view() override { return &m_selectableTableView; } + void viewWillAppear() override; + void didBecomeFirstResponder() override; + + const char * title() override { return I18n::translate(m_title); } + + bool handleEvent(Ion::Events::Event event) override; + + TELEMETRY_ID("ColorParameter"); + + void setRecord(Ion::Storage::Record record); + + int numberOfRows() const override { return Palette::numberOfDataColors(); } + KDCoordinate cellHeight() override; + HighlightCell * reusableCell(int index) override; + int reusableCellCount() const override { return Palette::numberOfDataColors(); } // FIXME Display issue + void willDisplayCellForIndex(HighlightCell * cell, int index); +private: + bool handleEnterOnRow(int rowIndex); + FunctionStore * functionStore(); + ExpiringPointer function(); + SelectableTableView m_selectableTableView; + Ion::Storage::Record m_record; + I18n::Message m_title; + MessageTableCellWithColor m_cells[Palette::numberOfDataColors()]; +}; + +} + +#endif diff --git a/apps/shared/function.cpp b/apps/shared/function.cpp index 3f3150d6f14..7248b6b6a71 100644 --- a/apps/shared/function.cpp +++ b/apps/shared/function.cpp @@ -55,6 +55,10 @@ void Function::setActive(bool active) { } } +void Function::setColor(KDColor color) { + recordData()->setColor(color); +} + int Function::printValue(double cursorT, double cursorX, double cursorY, char * buffer, int bufferSize, int precision, Poincare::Context * context) { return PoincareHelpers::ConvertFloatToText(cursorY, buffer, bufferSize, precision); } diff --git a/apps/shared/function.h b/apps/shared/function.h index a5daa9c5917..6fa039bac0e 100644 --- a/apps/shared/function.h +++ b/apps/shared/function.h @@ -36,6 +36,7 @@ class Function : public ExpressionModelHandle { bool isActive() const; KDColor color() const; void setActive(bool active); + void setColor(KDColor color); // Definition Interval virtual bool shouldClipTRangeToXRange() const { return true; } // Returns true if the function will not be displayed if t is outside x range. @@ -76,6 +77,7 @@ class Function : public ExpressionModelHandle { KDColor color() const { return KDColor::RGB16(m_color); } + void setColor(KDColor color) { m_color = color; } bool isActive() const { return m_active; } void setActive(bool active) { m_active = active; } private: diff --git a/apps/shared/list_parameter_controller.cpp b/apps/shared/list_parameter_controller.cpp index 27c11745f71..e55e3bf2a8d 100644 --- a/apps/shared/list_parameter_controller.cpp +++ b/apps/shared/list_parameter_controller.cpp @@ -8,12 +8,12 @@ ListParameterController::ListParameterController(Responder * parentResponder, I1 ViewController(parentResponder), m_selectableTableView(this, this, this, tableDelegate), m_record(), -#if FUNCTION_COLOR_CHOICE - m_colorCell(functionColorMessage), -#endif + m_colorCell(), m_enableCell(I18n::Message::ActivateDeactivate), - m_deleteCell(deleteFunctionMessage) + m_deleteCell(deleteFunctionMessage), + m_colorParameterController(parentResponder, functionColorMessage) { + m_colorCell.setMessage(functionColorMessage); } const char * ListParameterController::title() { @@ -38,6 +38,16 @@ void ListParameterController::willDisplayCellForIndex(HighlightCell * cell, int if (cell == &m_enableCell) { SwitchView * switchView = (SwitchView *)m_enableCell.accessoryView(); switchView->setState(function()->isActive()); + } else if(cell == &m_colorCell) { + int index = -1; + KDColor color = function()->color(); + for(int i = 0; i < Palette::numberOfDataColors(); i++) { + if(color == Palette::DataColor[i]) { + index = i; + } + } + assert(index >= 0); + m_colorCell.setSubtitle(MessageTableCellWithColor::k_textForIndex[index]); } } @@ -64,11 +74,7 @@ int ListParameterController::indexFromCumulatedHeight(KDCoordinate offsetY) { HighlightCell * ListParameterController::reusableCell(int index, int type) { assert(index == 0); assert(index < totalNumberOfCells()); -#if FUNCTION_COLOR_CHOICE HighlightCell * cells[] = {&m_colorCell, &m_enableCell, &m_deleteCell}; -#else - HighlightCell * cells[] = {&m_enableCell, &m_deleteCell}; -#endif return cells[type]; } @@ -78,22 +84,17 @@ int ListParameterController::typeAtLocation(int i, int j) { bool ListParameterController::handleEnterOnRow(int rowIndex) { switch (rowIndex) { -#if FUNCTION_COLOR_CHOICE - case 0: - /* TODO: implement function color choice */ + case 0: { + StackViewController * stack = (StackViewController *)(parentResponder()); + m_colorParameterController.setRecord(m_record); + stack->push(&m_colorParameterController); return true; + } case 1: -#else - case 0: -#endif function()->setActive(!function()->isActive()); m_selectableTableView.reloadData(); return true; -#if FUNCTION_COLOR_CHOICE - case 2: -#else - case 1: -#endif + case 2: { assert(functionStore()->numberOfModels() > 0); functionStore()->removeModel(m_record); diff --git a/apps/shared/list_parameter_controller.h b/apps/shared/list_parameter_controller.h index 4ef2b616ec4..e7ffb0fc6b8 100644 --- a/apps/shared/list_parameter_controller.h +++ b/apps/shared/list_parameter_controller.h @@ -3,6 +3,7 @@ #include #include "function_store.h" +#include "color_parameter_controller.h" #include namespace Shared { @@ -31,22 +32,17 @@ class ListParameterController : public ViewController, public ListViewDataSource protected: virtual bool handleEnterOnRow(int rowIndex); virtual int totalNumberOfCells() const { -#if FUNCTION_COLOR_CHOICE return 3; -#else - return 2; -#endif } FunctionStore * functionStore(); ExpiringPointer function(); SelectableTableView m_selectableTableView; Ion::Storage::Record m_record; private: -#if FUNCTION_COLOR_CHOICE - MessageTableCellWithChevron m_colorCell; -#endif + MessageTableCellWithChevronAndMessage m_colorCell; MessageTableCellWithSwitch m_enableCell; MessageTableCell m_deleteCell; + ColorParameterController m_colorParameterController; }; } From 15f0833f3b436833df93f9ad6138f005e26c3ddb Mon Sep 17 00:00:00 2001 From: Laury Date: Mon, 28 Mar 2022 18:23:22 +0200 Subject: [PATCH 189/355] [ion] Fix compilation --- ion/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/ion/Makefile b/ion/Makefile index 4182a922492..96797fba26f 100644 --- a/ion/Makefile +++ b/ion/Makefile @@ -34,6 +34,7 @@ ion_src += $(addprefix ion/src/shared/, \ rtc.cpp \ stack_position.cpp \ storage.cpp \ + internal_storage.cpp \ unicode/utf8_decoder.cpp\ unicode/utf8_helper.cpp\ ) From b675a78a875d936b055bf4bd481f7f1b48b4f29f Mon Sep 17 00:00:00 2001 From: Laury Date: Mon, 28 Mar 2022 19:25:29 +0200 Subject: [PATCH 190/355] [ion] Part of the fix for CI --- ion/src/device/n0100/boot/rt0.cpp | 185 ++++++++++++++++++--- ion/src/shared/dummy/usb.cpp | 4 - ion/src/simulator/shared/platform_info.cpp | 4 - 3 files changed, 166 insertions(+), 27 deletions(-) diff --git a/ion/src/device/n0100/boot/rt0.cpp b/ion/src/device/n0100/boot/rt0.cpp index 454d41c7e19..4e2f22a5641 100644 --- a/ion/src/device/n0100/boot/rt0.cpp +++ b/ion/src/device/n0100/boot/rt0.cpp @@ -1,31 +1,31 @@ #include #include -#include #include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include typedef void (*cxx_constructor)(); extern "C" { - extern char _data_section_start_flash; - extern char _data_section_start_ram; - extern char _data_section_end_ram; - extern char _bss_section_start_ram; - extern char _bss_section_end_ram; - extern cxx_constructor _init_array_start; - extern cxx_constructor _init_array_end; -} - -void __attribute__((noinline)) abort() { -#ifdef NDEBUG - Ion::Device::Reset::core(); -#else - while (1) { - } -#endif +extern char _data_section_start_flash; +extern char _data_section_start_ram; +extern char _data_section_end_ram; +extern char _bss_section_start_ram; +extern char _bss_section_end_ram; +extern cxx_constructor _init_array_start; +extern cxx_constructor _init_array_end; } /* In order to ensure that this method is execute from the external flash, we @@ -38,7 +38,6 @@ static void __attribute__((noinline)) external_flash_start() { * after the Power-On Self-Test if there is one or before switching to the * home app otherwise. */ Ion::Device::Board::initPeripherals(false); - return ion_main(0, nullptr); } @@ -64,6 +63,154 @@ static void __attribute__((noinline)) jump_to_external_flash() { external_flash_start(); } +void __attribute__((noinline)) abort_init() { + Ion::Device::Board::shutdownPeripherals(true); + Ion::Device::Board::initPeripherals(false); + Ion::Timing::msleep(100); + Ion::Backlight::init(); + Ion::LED::setColor(KDColorRed); + Ion::Backlight::setBrightness(180); +} + +void __attribute__((noinline)) abort_economy() { + int brightness = Ion::Backlight::brightness(); + bool plugged = Ion::USB::isPlugged(); + while (brightness > 0) { + brightness--; + Ion::Backlight::setBrightness(brightness); + Ion::Timing::msleep(50); + if(plugged || (!plugged && Ion::USB::isPlugged())){ + Ion::Backlight::setBrightness(180); + return; + } + } + Ion::Backlight::shutdown(); + while (1) { + Ion::Device::Power::sleepConfiguration(); + Ion::Device::WakeUp::onUSBPlugging(); + Ion::Device::WakeUp::onChargingEvent(); + Ion::Device::Power::internalFlashSuspend(true); + if (!plugged && Ion::USB::isPlugged()) { + break; + } + plugged = Ion::USB::isPlugged(); + }; + Ion::Device::Board::setStandardFrequency(Ion::Device::Board::Frequency::High); + Ion::Backlight::init(); + Ion::Backlight::setBrightness(180); +} + +void __attribute__((noinline)) abort_sleeping() { + if (Ion::Battery::level() != Ion::Battery::Charge::EMPTY) { + return; + } + // we don't use Ion::Power::suspend because we don't want to move the exam buffer into the internal + Ion::Device::Board::shutdownPeripherals(true); + bool plugged = Ion::USB::isPlugged(); + while (1) { + Ion::Device::Battery::initGPIO(); + Ion::Device::USB::initGPIO(); + Ion::Device::LED::init(); + Ion::Device::Power::sleepConfiguration(); + Ion::Device::Board::shutdownPeripherals(true); + Ion::Device::WakeUp::onUSBPlugging(); + Ion::Device::WakeUp::onChargingEvent(); + Ion::Device::Power::internalFlashSuspend(true); + Ion::Device::USB::initGPIO(); + if (!plugged && Ion::USB::isPlugged()) { + break; + } + plugged = Ion::USB::isPlugged(); + } + Ion::Device::Board::setStandardFrequency(Ion::Device::Board::Frequency::High); + abort_init(); +} + +void __attribute__((noinline)) abort_core(const char * text) { + Ion::Timing::msleep(100); + int counting; + while (true) { + counting = 0; + if (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) { + abort_sleeping(); + abort_screen(text); + } + + Ion::USB::enable(); + Ion::Battery::Charge previous_state = Ion::Battery::level(); + while (!Ion::USB::isEnumerated()) { + if (Ion::Battery::level() == Ion::Battery::Charge::LOW) { + if (previous_state != Ion::Battery::Charge::LOW) { + previous_state = Ion::Battery::Charge::LOW; + counting = 0; + } + Ion::Timing::msleep(500); + if (counting >= 20) { + abort_sleeping(); + abort_screen(text); + counting = -1; + } + counting++; + + } else { + if (previous_state == Ion::Battery::Charge::LOW) { + previous_state = Ion::Battery::level(); + counting = 0; + } + Ion::Timing::msleep(100); + if (counting >= 300) { + abort_economy(); + counting = -1; + } + counting++; + } + } + Ion::USB::DFU(false, false, 0); + } +} + +void __attribute__((noinline)) abort_screen(const char * text){ + KDRect screen = KDRect(0, 0, Ion::Display::Width, Ion::Display::Height); + Ion::Display::pushRectUniform(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height), KDColor::RGB24(0xffffff)); + KDContext* ctx = KDIonContext::sharedContext(); + ctx->setOrigin(KDPointZero); + ctx->setClippingRect(screen); + ctx->drawString("UPSILON CRASH", KDPoint(90, 10), KDFont::LargeFont, KDColorRed, KDColor::RGB24(0xffffff)); + ctx->drawString("An error occurred", KDPoint(10, 30), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("If you have some important data, please", KDPoint(10, 45), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("use bit.ly/upsiBackup to backup them.", KDPoint(10, 60), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("YOU WILL LOSE ALL YOUR DATA", KDPoint(10, 85), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("→ You can try to reboot by presssing the", KDPoint(10, 110), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("reset button at the back of the calculator", KDPoint(10, 125), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("→ If Upsilon keeps crashing, you can connect", KDPoint(10, 140), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("the calculator to a computer or a phone", KDPoint(10, 160), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("and try to reinstall Upsilon", KDPoint(10, 175), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString(text, KDPoint(220, 200), KDFont::SmallFont, KDColorRed, KDColor::RGB24(0xffffff)); +} + +void __attribute__((noinline)) abort() { + abort_init(); + abort_screen("HARDFAULT"); + abort_core("HARDFAULT"); +} + +void __attribute__((noinline)) nmi_abort() { + abort_init(); + abort_screen("NMIFAULT"); + abort_core("NMIFAULT"); +} + +void __attribute__((noinline)) bf_abort() { + abort_init(); + abort_screen("BUSFAULT"); + abort_core("BUSFAULT"); +} +void __attribute__((noinline)) uf_abort() { + abort_init(); + abort_screen("USAGEFAULT"); + abort_core("USAGEFAULT"); +} + /* When 'start' is executed, the external flash is supposed to be shutdown. We * thus forbid inlining to prevent executing this code from external flash * (just in case 'start' was to be called from the external flash). */ @@ -97,7 +244,7 @@ void __attribute__((noinline)) start() { * call the pointed function. */ #define SUPPORT_CPP_GLOBAL_CONSTRUCTORS 0 #if SUPPORT_CPP_GLOBAL_CONSTRUCTORS - for (cxx_constructor * c = &_init_array_start; c<&_init_array_end; c++) { + for (cxx_constructor* c = &_init_array_start; c < &_init_array_end; c++) { (*c)(); } #else diff --git a/ion/src/shared/dummy/usb.cpp b/ion/src/shared/dummy/usb.cpp index 4f7af5bf4f1..6a7fc8e1953 100644 --- a/ion/src/shared/dummy/usb.cpp +++ b/ion/src/shared/dummy/usb.cpp @@ -26,7 +26,3 @@ void disable() { } } - -void Ion::updateSlotInfo() { - -} diff --git a/ion/src/simulator/shared/platform_info.cpp b/ion/src/simulator/shared/platform_info.cpp index 28a9b04dc66..23160c6b61e 100644 --- a/ion/src/simulator/shared/platform_info.cpp +++ b/ion/src/simulator/shared/platform_info.cpp @@ -137,7 +137,3 @@ const volatile char * Ion::username() { const char * Ion::patchLevel() { return platform_infos.patchLevel(); } - -void Ion::updateSlotInfo() { - -} From 45229b80cd22375b262a017f3d63b06061a4c345 Mon Sep 17 00:00:00 2001 From: Mino1289 <68814671+Mino1289@users.noreply.github.com> Date: Mon, 28 Mar 2022 20:59:21 +0200 Subject: [PATCH 191/355] [python] Support more operators overload (classes) (#188) --- python/port/genhdr/qstrdefs.in.h | 16 ++++++++++++++++ python/port/mpconfigport.h | 3 +++ 2 files changed, 19 insertions(+) diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index 53c2f0b85c3..b3d03ab3a2f 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -65,16 +65,20 @@ Q(UnicodeError) Q(ValueError) Q(ZeroDivisionError) Q(_0x0a_) +Q(__abs__) Q(__add__) +Q(__and__) Q(__bool__) Q(__build_class__) Q(__call__) Q(__class__) Q(__contains__) Q(__delitem__) +Q(__divmod__) Q(__enter__) Q(__eq__) Q(__exit__) +Q(__floordiv__) Q(__ge__) Q(__getattr__) Q(__getitem__) @@ -84,28 +88,40 @@ Q(__iadd__) Q(__import__) Q(__init__) Q(__int__) +Q(__invert__) Q(__isub__) Q(__iter__) Q(__le__) Q(__len__) +Q(__lshift__) Q(__lt__) Q(__main__) +Q(__matmul__) +Q(__mod__) Q(__module__) +Q(__mul__) Q(__name__) #if __EMSCRIPTEN__ Q(__ne__) #endif +Q(__neg__) Q(__new__) Q(__next__) +Q(__or__) Q(__path__) +Q(__pos__) +Q(__pow__) Q(__qualname__) Q(__repl_print__) Q(__repr__) Q(__reversed__) +Q(__rshift__) Q(__setitem__) Q(__str__) Q(__sub__) Q(__traceback__) +Q(__truediv__) +Q(__xor__) Q(_brace_open__colon__hash_b_brace_close_) Q(_lt_dictcomp_gt_) Q(_lt_genexpr_gt_) diff --git a/python/port/mpconfigport.h b/python/port/mpconfigport.h index 7f5fe20e29a..26f813ba9e4 100644 --- a/python/port/mpconfigport.h +++ b/python/port/mpconfigport.h @@ -115,6 +115,9 @@ // Whether to support rounding of integers (incl bignum); eg round(123,-1)=120 #define MICROPY_PY_BUILTINS_ROUND_INT (1) +// Wheter to support all the special methods for custom classes +#define MICROPY_PY_ALL_SPECIAL_METHODS (1) + // Function to seed URANDOM with on init #define MICROPY_PY_URANDOM_SEED_INIT_FUNC micropython_port_random() From 202c273c67af32f7bdd37b1c792e5537da33333f Mon Sep 17 00:00:00 2001 From: "Yaya.Cout" <67095734+Yaya-Cout@users.noreply.github.com> Date: Wed, 30 Mar 2022 08:18:18 +0200 Subject: [PATCH 192/355] [apps/graph] Separate Cyan from Blue (fix #192) This separate the Cyan colour's name from the blue, before this commit, it was the same text --- apps/shared.de.i18n | 1 + apps/shared.en.i18n | 1 + apps/shared.es.i18n | 1 + apps/shared.fr.i18n | 1 + apps/shared.hu.i18n | 1 + apps/shared.it.i18n | 1 + apps/shared.nl.i18n | 1 + apps/shared.pt.i18n | 1 + apps/shared/color_cell.h | 2 +- 9 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/shared.de.i18n b/apps/shared.de.i18n index 1e61ff9f594..b72d2803a15 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -94,5 +94,6 @@ ColorBlue = "Blau " ColorGreen = "Grün " ColorYellow = "Gelb " ColorPurple = "Violett " +ColorCyan = "Cyan " ColorPink = "Rosa " ColorOrange = "Orange " diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index 2da4bfabc1e..257e39c9429 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -94,5 +94,6 @@ ColorBlue = "Blue " ColorGreen = "Green " ColorYellow = "Yellow " ColorPurple = "Purple " +ColorCyan = "Cyan " ColorPink = "Pink " ColorOrange = "Orange " diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index df5b55fdf17..f1f6ca4ade0 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -94,5 +94,6 @@ ColorBlue = "Azul " ColorGreen = "Verde " ColorYellow = "Amarillo " ColorPurple = "Púrpura " +ColorCyan = "Cian " ColorPink = "Rosa " ColorOrange = "Naranja " diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index 058514d0c29..e7c89c7a00e 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -94,5 +94,6 @@ ColorBlue = "Bleu " ColorGreen = "Vert " ColorYellow = "Jaune " ColorPurple = "Violet " +ColorCyan = "Cyan " ColorPink = "Rose " ColorOrange = "Orange " diff --git a/apps/shared.hu.i18n b/apps/shared.hu.i18n index 2a2dde2b6a6..7931f0c7585 100644 --- a/apps/shared.hu.i18n +++ b/apps/shared.hu.i18n @@ -94,5 +94,6 @@ ColorBlue = "Kék " ColorGreen = "Zöld " ColorYellow = "Sárga " ColorPurple = "Lila " +ColorCyan = "Cián " ColorPink = "Rózsaszín " ColorOrange = "Narancssárga " diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n index 04472b55396..91bef493b21 100644 --- a/apps/shared.it.i18n +++ b/apps/shared.it.i18n @@ -94,5 +94,6 @@ ColorBlue = "Blu " ColorGreen = "Verde " ColorYellow = "Giallo " ColorPurple = "Viola " +ColorCyan = "Ciano" ColorPink = "Rosa " ColorOrange = "Arancia " diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index 92bbbd66b52..62060e011c9 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -94,5 +94,6 @@ ColorBlue = "Blauw" ColorGreen = "Groente" ColorYellow = "Geel" ColorPurple = "Purper" +ColorCyan = "Cyaan " ColorPink = "Roze" ColorOrange = "Oranje" diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index 297cfeba759..4edea43ba44 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -94,5 +94,6 @@ ColorBlue = "Azul " ColorGreen = "Verde " ColorYellow = "Amarelo " ColorPurple = "Roxa " +ColorCyan = "Ciano" ColorPink = "Cor de rosa " ColorOrange = "Laranja " diff --git a/apps/shared/color_cell.h b/apps/shared/color_cell.h index 16967bdcbc9..e9d5e4b66de 100644 --- a/apps/shared/color_cell.h +++ b/apps/shared/color_cell.h @@ -18,7 +18,7 @@ class MessageTableCellWithColor : public MessageTableCell { I18n::Message::ColorGreen, I18n::Message::ColorYellow, I18n::Message::ColorPurple, - I18n::Message::ColorBlue, + I18n::Message::ColorCyan, I18n::Message::ColorPink, I18n::Message::ColorOrange }; From 2ebc1bc22f1e358b448d8dc209412b6114ce4b4e Mon Sep 17 00:00:00 2001 From: "Yaya.Cout" <67095734+Yaya-Cout@users.noreply.github.com> Date: Wed, 30 Mar 2022 19:21:44 +0200 Subject: [PATCH 193/355] [apps] Add Shift + EE to go to the settings Before, EE go to the settings, because ShiftEE event wasn't correctly bound : It was bound to a simple press of the key, and not to a Shift press. This commit keep this behavior, but add the correct binding (Shift + EE). This fix #194. --- apps/apps_container.cpp | 9 +++++++++ ion/include/ion/events.h | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index 1bb917ddc28..abb0c0a8825 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -243,15 +243,18 @@ bool AppsContainer::processEvent(Ion::Events::Event event) { } return true; } + // If key home or key back is pressed, we switch to the home app. if (event == Ion::Events::Home || event == Ion::Events::Back) { switchTo(appSnapshotAtIndex(0)); return true; } + // If shift + Home are pressed, we switch to the first app. if (event == Ion::Events::ShiftHome) { switchTo(appSnapshotAtIndex(1)); return true; } + // Iterate through the switch events to find the one that matches the event, if one match, switch to the app at the index of the switch event. for(int i = 0; i < std::min((int) (sizeof(switch_events) / sizeof(Ion::Events::Event)), APPS_CONTAINER_SNAPSHOT_COUNT); i++) { if (event == switch_events[i]) { m_window.redraw(true); @@ -260,6 +263,12 @@ bool AppsContainer::processEvent(Ion::Events::Event event) { } } + // Add EE shortcut to go to the settings (app number 12) + if (event == Ion::Events::EE) { + switchTo(appSnapshotAtIndex(12)); + return true; + } + if (event == Ion::Events::OnOff) { suspend(true); return true; diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index 394271cf299..51c8da397b7 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -171,7 +171,7 @@ constexpr Event DoubleParenthesis = Event::ShiftKey(Keyboard::Key::LeftParenthes constexpr Event ShiftZero = Event::ShiftKey(Keyboard::Key::Zero); constexpr Event ShiftDot = Event::ShiftKey(Keyboard::Key::Dot); -constexpr Event ShiftEE = Event::PlainKey(Keyboard::Key::EE); +constexpr Event ShiftEE = Event::ShiftKey(Keyboard::Key::EE); constexpr Event ShiftOne = Event::ShiftKey(Keyboard::Key::One); constexpr Event ShiftTwo = Event::ShiftKey(Keyboard::Key::Two); From e2598031c83c368c1fd7528ad620932924182d56 Mon Sep 17 00:00:00 2001 From: Andrej Date: Thu, 31 Mar 2022 10:10:05 -0400 Subject: [PATCH 194/355] [escher] Changed font in Toolbox expressions to LargeFont for better readability and aesthetics (#190) --- escher/include/escher/message_table_cell_with_expression.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/escher/include/escher/message_table_cell_with_expression.h b/escher/include/escher/message_table_cell_with_expression.h index c01d8af76e3..f9704eb2248 100644 --- a/escher/include/escher/message_table_cell_with_expression.h +++ b/escher/include/escher/message_table_cell_with_expression.h @@ -6,7 +6,7 @@ class MessageTableCellWithExpression : public MessageTableCell { public: - MessageTableCellWithExpression(I18n::Message message = (I18n::Message)0, const KDFont * font = KDFont::SmallFont); + MessageTableCellWithExpression(I18n::Message message = (I18n::Message)0, const KDFont * font = KDFont::LargeFont); View * accessoryView() const override; void setHighlighted(bool highlight) override; void setLayout(Poincare::Layout layout); From 7e64124ddc79c302fa113f76e6435f9f70fd7acb Mon Sep 17 00:00:00 2001 From: Laury Date: Wed, 23 Mar 2022 22:07:20 +0100 Subject: [PATCH 195/355] Revert #113 because of incompatibility with the workshop --- apps/code/editor_controller.cpp | 3 +-- apps/code/script.cpp | 17 +---------------- apps/code/script.h | 14 ++++---------- apps/code/script_store.cpp | 2 +- apps/code/script_store.h | 2 +- apps/code/script_template.cpp | 2 +- apps/code/script_template.h | 2 +- 7 files changed, 10 insertions(+), 32 deletions(-) diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index bb02d1ff66b..6de0a862fbb 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -77,7 +77,6 @@ void EditorController::viewDidDisappear() { } bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) { - m_script.setCursorPosition(textArea->cursorLocation() - m_script.content()); if (App::app()->textInputDidReceiveEvent(textArea, event)) { return true; } @@ -163,7 +162,7 @@ void EditorController::cleanStorageEmptySpace() { Ion::Storage::Record::Data scriptValue = m_script.value(); Ion::Storage::sharedStorage()->getAvailableSpaceFromEndOfRecord( m_script, - scriptValue.size - Script::StatusSize() - Script::CursorPositionSize() - (strlen(m_script.content()) + 1)); // TODO optimize number of script fetches + scriptValue.size - Script::StatusSize() - (strlen(m_script.content()) + 1)); // TODO optimize number of script fetches } diff --git a/apps/code/script.cpp b/apps/code/script.cpp index ed666d36c48..4b39b345281 100644 --- a/apps/code/script.cpp +++ b/apps/code/script.cpp @@ -1,9 +1,6 @@ #include "script.h" #include "script_store.h" -#if APP_SCRIPT_LOG -#include -#endif namespace Code { static inline void intToText(int i, char * buffer, int bufferSize) { @@ -72,18 +69,6 @@ uint8_t * StatusFromData(Script::Data d) { return const_cast(static_cast(d.buffer)); } -uint16_t * Script::CursorPosition() { - assert(!isNull()); - Data d = value(); - return (uint16_t *)(StatusFromData(d) + StatusSize()); -} -void Script::setCursorPosition(uint16_t position) { - assert(!isNull()); - Data d = value(); - *CursorPosition() = position; - setValue(d); -} - bool Script::autoImportationStatus() const { return getStatutBit(k_autoImportationStatusMask); } @@ -97,7 +82,7 @@ void Script::toggleAutoimportationStatus() { const char * Script::content() const { Data d = value(); - return ((const char *)d.buffer) + StatusSize() + CursorPositionSize(); + return ((const char *)d.buffer) + StatusSize(); } bool Script::fetchedFromConsole() const { diff --git a/apps/code/script.h b/apps/code/script.h index 1c6ff1d15aa..6f7df9cdae7 100644 --- a/apps/code/script.h +++ b/apps/code/script.h @@ -5,8 +5,8 @@ namespace Code { -/* Record: | Size | Name | Body | - * Script: | | | Status | CursorPosition | Content | +/* Record: | Size | Name | Body | + * Script: | | | Status | Content | * * * |FetchedForVariableBoxBit @@ -23,9 +23,7 @@ namespace Code { * FetchedForVariableBoxBit is used to prevent circular importation problems, * such as scriptA importing scriptB, which imports scriptA. Once we get the * variables from a script to put them in the variable box, we switch the bit to - * 1 and won't reload it afterwards. - * - * Cursor Position is two bytes long and has the cursor position value */ + * 1 and won't reload it afterwards. */ class Script : public Ion::Storage::Record { private: @@ -35,7 +33,6 @@ class Script : public Ion::Storage::Record { // See the comment at the beginning of the file static constexpr size_t k_statusSize = 1; - static constexpr size_t k_cursorPositionSize = 2; public: static constexpr int k_defaultScriptNameMaxSize = 6 + k_defaultScriptNameNumberMaxSize + 1; @@ -46,16 +43,13 @@ class Script : public Ion::Storage::Record { static bool DefaultName(char buffer[], size_t bufferSize); static bool nameCompliant(const char * name); static constexpr size_t StatusSize() { return k_statusSize; } - static constexpr size_t CursorPositionSize() { return k_cursorPositionSize; } Script(Ion::Storage::Record r = Ion::Storage::Record()) : Record(r) {} bool autoImportationStatus() const; void toggleAutoimportationStatus(); const char * content() const; - size_t contentSize() { return value().size - k_statusSize - k_cursorPositionSize; } - void setCursorPosition(uint16_t position); - uint16_t * CursorPosition(); + size_t contentSize() { return value().size - k_statusSize; } /* Fetched status */ bool fetchedFromConsole() const; diff --git a/apps/code/script_store.cpp b/apps/code/script_store.cpp index 03632b04b3f..ad790811b53 100644 --- a/apps/code/script_store.cpp +++ b/apps/code/script_store.cpp @@ -51,7 +51,7 @@ void ScriptStore::clearConsoleFetchInformation() { } Script::ErrorStatus ScriptStore::addScriptFromTemplate(const ScriptTemplate * scriptTemplate) { - size_t valueSize = Script::StatusSize() + Script::CursorPositionSize() + strlen(scriptTemplate->content()) + 1; // (auto importation status + cursor position + content fetched status) + scriptcontent size + null-terminating char + size_t valueSize = Script::StatusSize() + strlen(scriptTemplate->content()) + 1; // (auto importation status + content fetched status) + scriptcontent size + null-terminating char assert(Script::nameCompliant(scriptTemplate->name())); Script::ErrorStatus err = Ion::Storage::sharedStorage()->createRecordWithFullName(scriptTemplate->name(), scriptTemplate->value(), valueSize); assert(err != Script::ErrorStatus::NonCompliantName); diff --git a/apps/code/script_store.h b/apps/code/script_store.h index 9d232d00210..00beec5d3c7 100644 --- a/apps/code/script_store.h +++ b/apps/code/script_store.h @@ -51,7 +51,7 @@ class ScriptStore : public MicroPython::ScriptProvider { * be able to store a Script with default name and its extension, the * importation status (1 char), the cursor (2 char), the default content "from math import *\n" * (20 char) and 10 char of free space. */ - static constexpr int k_fullFreeSpaceSizeLimit = sizeof(Ion::Storage::record_size_t)+Script::k_defaultScriptNameMaxSize+k_scriptExtensionLength+1+20+10+2; + static constexpr int k_fullFreeSpaceSizeLimit = sizeof(Ion::Storage::record_size_t)+Script::k_defaultScriptNameMaxSize+k_scriptExtensionLength+1+20+10; }; } diff --git a/apps/code/script_template.cpp b/apps/code/script_template.cpp index eabdc427da8..c5bde929ea4 100644 --- a/apps/code/script_template.cpp +++ b/apps/code/script_template.cpp @@ -2,7 +2,7 @@ namespace Code { -constexpr ScriptTemplate emptyScriptTemplate(".py", "\x01" "\x00\x00" R"(from math import * +constexpr ScriptTemplate emptyScriptTemplate(".py", "\x01" R"(from math import * )"); diff --git a/apps/code/script_template.h b/apps/code/script_template.h index fad0ea0de55..6ca117f4874 100644 --- a/apps/code/script_template.h +++ b/apps/code/script_template.h @@ -10,7 +10,7 @@ class ScriptTemplate { constexpr ScriptTemplate(const char * name, const char * value) : m_name(name), m_value(value) {} static const ScriptTemplate * Empty(); const char * name() const { return m_name; } - const char * content() const { return m_value + Script::StatusSize() + Script::CursorPositionSize(); } + const char * content() const { return m_value; + Script::StatusSize();} const char * value() const { return m_value; } private: const char * m_name; From a770ee919ba99308c76da0a25473c84fb8816964 Mon Sep 17 00:00:00 2001 From: Laury Date: Thu, 31 Mar 2022 22:23:21 +0200 Subject: [PATCH 196/355] [code] End of revert #113 --- apps/code/editor_controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index 6de0a862fbb..be09c9662b8 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -65,7 +65,7 @@ void EditorController::viewWillAppear() { ViewController::viewWillAppear(); m_editorView.loadSyntaxHighlighter(); if(GlobalPreferences::sharedGlobalPreferences()->cursorSaving()) { - m_editorView.setCursorLocation(m_script.content() + *m_script.CursorPosition()); + m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); } else { m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); } From c0c1615f1472e5b088568297e19f1a97fb6e6fcd Mon Sep 17 00:00:00 2001 From: "Yaya.Cout" <67095734+Yaya-Cout@users.noreply.github.com> Date: Sun, 3 Apr 2022 09:56:07 +0200 Subject: [PATCH 197/355] [apps] Swap graph and rpn apps Close #195 --- apps/home/apps_layout.csv | 4 ++-- build/config.mak | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/home/apps_layout.csv b/apps/home/apps_layout.csv index b17d33f7d24..423aa17ce23 100644 --- a/apps/home/apps_layout.csv +++ b/apps/home/apps_layout.csv @@ -1,2 +1,2 @@ -Default,calculation,rpn,graph,code,statistics,probability,solver,atomic,sequence,regression,reader,settings -HidePython,calculation,rpn,graph,code,statistics,probability,solver,atomic,sequence,regression,reader,settings +Default,calculation,graph,rpn,code,statistics,probability,solver,atomic,sequence,regression,reader,settings +HidePython,calculation,graph,rpn,code,statistics,probability,solver,atomic,sequence,regression,reader,settings diff --git a/build/config.mak b/build/config.mak index 53d37a8c1d3..9d6c999a99a 100644 --- a/build/config.mak +++ b/build/config.mak @@ -9,7 +9,7 @@ OMEGA_VERSION ?= 2.0.0 UPSILON_VERSION ?= 1.0.0-dev # OMEGA_USERNAME ?= N/A OMEGA_STATE ?= public -EPSILON_APPS ?= calculation rpn graph code statistics probability solver atomic sequence regression reader settings external +EPSILON_APPS ?= calculation graph rpn code statistics probability solver atomic sequence regression reader settings external SUBMODULES_APPS = atomic rpn EPSILON_I18N ?= en fr nl pt it de es hu EPSILON_COUNTRIES ?= WW CA DE ES FR GB IT NL PT US From 5365718761013db0c9fe707e9224f6f02f99f16e Mon Sep 17 00:00:00 2001 From: "Yaya.Cout" <67095734+Yaya-Cout@users.noreply.github.com> Date: Tue, 5 Apr 2022 18:23:02 +0200 Subject: [PATCH 198/355] [bootloader] Fix building with no computer and cable icons --- bootloader/cable.png | Bin 0 -> 179 bytes bootloader/computer.png | Bin 0 -> 357 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 bootloader/cable.png create mode 100644 bootloader/computer.png diff --git a/bootloader/cable.png b/bootloader/cable.png new file mode 100644 index 0000000000000000000000000000000000000000..ac0996ca74685d68c185ee0bbc4cc3a05cdb6aec GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^Y(VV7!2~3A?JidUsVYww$B>FS$tej5D^{!s2nb9_ zNT?`y?&$8$F3il#+`^x3V3@$52{5#SsAJA*S1~lk!zb;$B{EN4o7dwGqi2xG= YtES2ht{+eQfv#ZiboFyt=akR{0N5@#iU0rr literal 0 HcmV?d00001 diff --git a/bootloader/computer.png b/bootloader/computer.png new file mode 100644 index 0000000000000000000000000000000000000000..d30a99af2af41be5ed19fed9df7ea01d38eb3acb GIT binary patch literal 357 zcmeAS@N?(olHy`uVBq!ia0vp^!9c9f!2~1)3=-cmFfi(Px;TbZ%z1lDvFNaYK7}4lH07&=AltcwxxJ!ozZ6At1t`aiZjyXlCbA z&vN@|r8b}I^4L0=aSo2B8^(o^QSwf_z?GwA>L^kc)t8E*qE z?Lzwar#^Om`XhiyW<9qeLqm|(|0z@NMCHYLyMC&+;D45|Yv1WjTJwYJz4UTqj=%aE zU;F*L_~++mPA$J=slN11l}}h{tTDrbIj`ql%a5}2R%Chp=eqg(ke-W{>CvzE{%0x` XaGfW8;hGOH_!vB0{an^LB{Ts5Syh Date: Wed, 6 Apr 2022 21:43:46 +0200 Subject: [PATCH 199/355] Run upload to firebase only when on UpsilonNumworks/Upsilon on upsilon-dev (#197) --- .github/workflows/ci-workflow.yml | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 2dc7f1be618..43e3d699353 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,6 @@ on: description: 'Run 3DS tests' required: true default: 'no' - jobs: nintendo_3ds: if: github.event.inputs.trigger3DS == 'yes' @@ -51,12 +50,12 @@ jobs: submodules: 'recursive' - run: make -j2 PLATFORM=simulator TARGET=android - id: 'auth' - if: ${{ github.event_name == 'push' }} + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} uses: 'google-github-actions/auth@v0' with: credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' - id: 'upload-file' - if: ${{ github.event_name == 'push' }} + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} uses: 'google-github-actions/upload-cloud-storage@v0' with: path: 'output/release/simulator/android/epsilon.apk' @@ -104,12 +103,12 @@ jobs: - run: find final-output/ -type f -exec bash -c "shasum -a 256 -b {} > {}.sha256" \; - run: tar cvfz binpack-n0100.tgz final-output/* - id: 'auth' - if: ${{ github.event_name == 'push'}} + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon'}} uses: 'google-github-actions/auth@v0' with: credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' - id: 'upload-directory' - if: ${{ github.event_name == 'push'}} + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon'}} uses: 'google-github-actions/upload-cloud-storage@v0' with: path: 'final-output/' @@ -138,12 +137,12 @@ jobs: - run: make -j2 binpack - run: cp output/release/device/n0110/binpack-n0110-`git rev-parse HEAD | head -c 7`.tgz output/release/device/n0110/binpack-n0110.tgz - id: 'auth' - if: ${{ github.event_name == 'push' }} + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} uses: 'google-github-actions/auth@v0' with: credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' - id: 'upload-directory' - if: ${{ github.event_name == 'push' }} + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} uses: 'google-github-actions/upload-cloud-storage@v0' with: path: 'output/release/device/n0110/binpack/' @@ -187,12 +186,12 @@ jobs: - run: make -j2 PLATFORM=simulator test.exe - run: cmd /c output\release\simulator\windows\test.exe --headless - id: 'auth' - if: ${{ github.event_name == 'push' }} + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} uses: 'google-github-actions/auth@v0' with: credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' - id: 'upload-file' - if: ${{ github.event_name == 'push' }} + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} uses: 'google-github-actions/upload-cloud-storage@v0' with: path: 'output/release/simulator/windows/epsilon.exe' @@ -215,12 +214,12 @@ jobs: - run: make -j2 PLATFORM=simulator TARGET=web test.js - run: node output/release/simulator/web/test.js --headless - id: 'auth' - if: ${{ github.event_name == 'push' }} + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} uses: 'google-github-actions/auth@v0' with: credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' - id: 'upload-file' - if: ${{ github.event_name == 'push' }} + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} uses: 'google-github-actions/upload-cloud-storage@v0' with: path: 'output/release/simulator/web/epsilon.js' @@ -241,12 +240,12 @@ jobs: - run: make -j2 PLATFORM=simulator test.bin - run: output/release/simulator/linux/test.bin --headless - id: 'auth' - if: ${{ github.event_name == 'push' }} + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} uses: 'google-github-actions/auth@v0' with: credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' - id: 'upload-file' - if: ${{ github.event_name == 'push' }} + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} uses: 'google-github-actions/upload-cloud-storage@v0' with: path: 'output/release/simulator/linux/epsilon.bin' From 3dfc8d749c501de6e56dce070b946acad4cc6e1c Mon Sep 17 00:00:00 2001 From: devdl11 Date: Thu, 7 Apr 2022 19:56:53 +0200 Subject: [PATCH 200/355] [bootloader/storage] new bootloader and fix python issue --- apps/apps_container.cpp | 3 +- apps/settings/main_controller_prompt_beta.cpp | 2 +- apps/settings/main_controller_prompt_none.cpp | 2 +- .../main_controller_prompt_update.cpp | 2 +- bootloader/Makefile | 8 +- bootloader/boot.cpp | 182 +++++-- bootloader/boot.h | 8 +- bootloader/boot/isr.c | 129 +++++ bootloader/boot/isr.h | 23 + bootloader/boot/rt0.cpp | 146 ++++++ bootloader/drivers/board.cpp | 444 ++++++++++++++++++ bootloader/interface.cpp | 295 ++++++++++-- bootloader/interface.h | 13 +- bootloader/itoa.cpp | 64 +++ bootloader/itoa.h | 11 + bootloader/kernel_header.cpp | 14 + bootloader/kernel_header.h | 1 + bootloader/main.cpp | 51 +- bootloader/messages.h | 60 +++ bootloader/recovery.cpp | 130 +++++ bootloader/recovery.h | 32 ++ bootloader/slot.cpp | 17 + bootloader/slot.h | 5 +- bootloader/slot_exam_mode.cpp | 166 +++++++ bootloader/slot_exam_mode.h | 47 ++ bootloader/trampoline.cpp | 1 + bootloader/usb_data.cpp | 39 ++ bootloader/usb_data.h | 56 +++ bootloader/userland_header.cpp | 12 +- bootloader/userland_header.h | 10 +- build/device/secure_ext.py | 2 + build/targets.device.n0110.mak | 5 +- ion/include/ion/internal_storage.h | 5 +- ion/include/ion/usb.h | 2 +- ion/src/device/Makefile | 2 +- ion/src/device/bootloader/boot/rt0.cpp | 167 +------ .../bootloader/drivers/external_flash.cpp | 45 ++ .../drivers/external_flash_tramp.cpp | 7 + .../device/bootloader/drivers/usb_desc.cpp | 12 + ion/src/device/bootloader/internal_flash.ld | 123 +++++ ion/src/device/bootloader/usb/Makefile | 99 ++++ ion/src/device/bootloader/usb/boot.cpp | 2 + ion/src/device/bootloader/usb/calculator.cpp | 94 ++++ ion/src/device/bootloader/usb/calculator.h | 176 +++++++ ion/src/device/bootloader/usb/dfu.ld | 57 +++ .../device/bootloader/usb/dfu_interface.cpp | 314 +++++++++++++ ion/src/device/bootloader/usb/dfu_interface.h | 192 ++++++++ .../device/bootloader/usb/dfu_relocated.cpp | 89 ++++ ion/src/device/bootloader/usb/dfu_xip.cpp | 13 + .../usb/stack/descriptor/bos_descriptor.cpp | 22 + .../usb/stack/descriptor/bos_descriptor.h | 36 ++ .../descriptor/configuration_descriptor.cpp | 26 + .../descriptor/configuration_descriptor.h | 48 ++ .../usb/stack/descriptor/descriptor.cpp | 15 + .../usb/stack/descriptor/descriptor.h | 32 ++ .../device_capability_descriptor.cpp | 18 + .../descriptor/device_capability_descriptor.h | 31 ++ .../stack/descriptor/device_descriptor.cpp | 29 ++ .../usb/stack/descriptor/device_descriptor.h | 62 +++ .../descriptor/dfu_functional_descriptor.cpp | 21 + .../descriptor/dfu_functional_descriptor.h | 38 ++ .../extended_compat_id_descriptor.cpp | 61 +++ .../extended_compat_id_descriptor.h | 43 ++ .../stack/descriptor/interface_descriptor.cpp | 27 ++ .../stack/descriptor/interface_descriptor.h | 55 +++ .../language_id_string_descriptor.cpp | 19 + .../language_id_string_descriptor.h | 25 + .../microsoft_os_string_descriptor.cpp | 19 + .../microsoft_os_string_descriptor.h | 30 ++ .../platform_device_capability_descriptor.cpp | 21 + .../platform_device_capability_descriptor.h | 47 ++ .../stack/descriptor/string_descriptor.cpp | 25 + .../usb/stack/descriptor/string_descriptor.h | 28 ++ .../usb/stack/descriptor/url_descriptor.cpp | 25 + .../usb/stack/descriptor/url_descriptor.h | 36 ++ .../descriptor/webusb_platform_descriptor.cpp | 22 + .../descriptor/webusb_platform_descriptor.h | 37 ++ .../device/bootloader/usb/stack/device.cpp | 168 +++++++ ion/src/device/bootloader/usb/stack/device.h | 67 +++ .../device/bootloader/usb/stack/endpoint0.cpp | 348 ++++++++++++++ .../device/bootloader/usb/stack/endpoint0.h | 79 ++++ .../device/bootloader/usb/stack/interface.cpp | 66 +++ .../device/bootloader/usb/stack/interface.h | 42 ++ .../usb/stack/request_recipient.cpp | 32 ++ .../bootloader/usb/stack/request_recipient.h | 29 ++ .../bootloader/usb/stack/setup_packet.cpp | 38 ++ .../bootloader/usb/stack/setup_packet.h | 65 +++ .../bootloader/usb/stack/streamable.cpp | 15 + .../device/bootloader/usb/stack/streamable.h | 44 ++ ion/src/device/flasher/display_light.cpp | 8 +- ion/src/device/flasher/display_verbose.cpp | 90 ++-- ion/src/device/flasher/main.cpp | 2 +- ion/src/device/n0100/boot/rt0.cpp | 185 +------- ion/src/device/n0100/flash.ld | 2 +- ion/src/device/n0110/Makefile | 13 +- ion/src/device/n0110/boot/rt0.cpp | 185 +------- ion/src/device/n0110/drivers/board.cpp | 10 +- ion/src/device/n0110/drivers/config/clocks.h | 2 +- .../device/n0110/drivers/external_flash.cpp | 45 ++ ion/src/device/n0110/flash.ld | 182 +------ ion/src/device/n0110/internal_flash.ld | 2 +- ion/src/device/shared/Makefile | 4 + ion/src/device/shared/boot/Makefile | 6 + ion/src/device/shared/boot/isr.c | 6 +- ion/src/device/shared/boot/isr.h | 8 - ion/src/device/shared/drivers/Makefile | 1 - ion/src/device/shared/drivers/battery.cpp | 4 +- ion/src/device/shared/drivers/display.cpp | 2 +- ion/src/device/shared/drivers/exam_mode.cpp | 24 +- .../device/shared/drivers/external_flash.h | 3 + ion/src/device/shared/drivers/flash.cpp | 20 + ion/src/device/shared/drivers/flash.h | 6 + .../device/shared/drivers/internal_flash.cpp | 97 ++++ .../device/shared/drivers/internal_flash.h | 4 + ion/src/device/shared/drivers/reset.cpp | 2 +- ion/src/device/shared/drivers/wakeup.h | 2 +- ion/src/device/shared/ram.ld | 6 +- ion/src/device/shared/regs/flash.h | 18 + ion/src/device/shared/regs/tim.h | 2 +- ion/src/device/shared/usb/Makefile | 4 + ion/src/device/shared/usb/calculator.cpp | 8 +- ion/src/device/shared/usb/calculator.h | 6 +- ion/src/device/shared/usb/dfu.ld | 2 +- ion/src/device/shared/usb/dfu_interface.cpp | 189 ++------ ion/src/device/shared/usb/dfu_interface.h | 136 ++---- ion/src/device/shared/usb/dfu_relocated.cpp | 8 +- ion/src/device/shared/usb/dfu_xip.cpp | 4 +- .../descriptor/device_capability_descriptor.h | 4 +- .../platform_device_capability_descriptor.h | 4 +- ion/src/device/shared/usb/stack/device.cpp | 2 +- themes/icons.json | 1 - .../local/upsilon_light/bootloader/cable.png | Bin 179 -> 0 bytes .../upsilon_light/bootloader/computer.png | Bin 357 -> 449 bytes 133 files changed, 5447 insertions(+), 1132 deletions(-) create mode 100644 bootloader/boot/isr.c create mode 100644 bootloader/boot/isr.h create mode 100644 bootloader/boot/rt0.cpp create mode 100644 bootloader/drivers/board.cpp create mode 100644 bootloader/itoa.cpp create mode 100644 bootloader/itoa.h create mode 100644 bootloader/messages.h create mode 100644 bootloader/recovery.cpp create mode 100644 bootloader/recovery.h create mode 100644 bootloader/slot_exam_mode.cpp create mode 100644 bootloader/slot_exam_mode.h create mode 100644 bootloader/usb_data.cpp create mode 100644 bootloader/usb_data.h create mode 100644 ion/src/device/bootloader/drivers/usb_desc.cpp create mode 100644 ion/src/device/bootloader/internal_flash.ld create mode 100644 ion/src/device/bootloader/usb/Makefile create mode 100644 ion/src/device/bootloader/usb/boot.cpp create mode 100644 ion/src/device/bootloader/usb/calculator.cpp create mode 100644 ion/src/device/bootloader/usb/calculator.h create mode 100644 ion/src/device/bootloader/usb/dfu.ld create mode 100644 ion/src/device/bootloader/usb/dfu_interface.cpp create mode 100644 ion/src/device/bootloader/usb/dfu_interface.h create mode 100644 ion/src/device/bootloader/usb/dfu_relocated.cpp create mode 100644 ion/src/device/bootloader/usb/dfu_xip.cpp create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/bos_descriptor.cpp create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/bos_descriptor.h create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/configuration_descriptor.cpp create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/configuration_descriptor.h create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/descriptor.cpp create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/descriptor.h create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.cpp create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.h create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/device_descriptor.cpp create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/device_descriptor.h create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/dfu_functional_descriptor.cpp create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/dfu_functional_descriptor.h create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/extended_compat_id_descriptor.cpp create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/extended_compat_id_descriptor.h create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/interface_descriptor.cpp create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/interface_descriptor.h create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/language_id_string_descriptor.cpp create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/language_id_string_descriptor.h create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/microsoft_os_string_descriptor.cpp create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/microsoft_os_string_descriptor.h create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.cpp create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.h create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/string_descriptor.cpp create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/string_descriptor.h create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/url_descriptor.cpp create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/url_descriptor.h create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/webusb_platform_descriptor.cpp create mode 100644 ion/src/device/bootloader/usb/stack/descriptor/webusb_platform_descriptor.h create mode 100644 ion/src/device/bootloader/usb/stack/device.cpp create mode 100644 ion/src/device/bootloader/usb/stack/device.h create mode 100644 ion/src/device/bootloader/usb/stack/endpoint0.cpp create mode 100644 ion/src/device/bootloader/usb/stack/endpoint0.h create mode 100644 ion/src/device/bootloader/usb/stack/interface.cpp create mode 100644 ion/src/device/bootloader/usb/stack/interface.h create mode 100644 ion/src/device/bootloader/usb/stack/request_recipient.cpp create mode 100644 ion/src/device/bootloader/usb/stack/request_recipient.h create mode 100644 ion/src/device/bootloader/usb/stack/setup_packet.cpp create mode 100644 ion/src/device/bootloader/usb/stack/setup_packet.h create mode 100644 ion/src/device/bootloader/usb/stack/streamable.cpp create mode 100644 ion/src/device/bootloader/usb/stack/streamable.h delete mode 100644 themes/themes/local/upsilon_light/bootloader/cable.png diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index abb0c0a8825..0b41800599d 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -147,7 +147,8 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { * pictogram. */ updateBatteryState(); if (switchTo(usbConnectedAppSnapshot())) { - Ion::USB::DFU(true, GlobalPreferences::sharedGlobalPreferences()->dfuUnlocked(), GlobalPreferences::sharedGlobalPreferences()->dfuLevel()); + Ion::USB::DFU(true + ); // Update LED when exiting DFU mode Ion::LED::updateColorWithPlugAndCharge(); bool switched = switchTo(activeSnapshot); diff --git a/apps/settings/main_controller_prompt_beta.cpp b/apps/settings/main_controller_prompt_beta.cpp index 90df4b89791..33337d606cc 100644 --- a/apps/settings/main_controller_prompt_beta.cpp +++ b/apps/settings/main_controller_prompt_beta.cpp @@ -18,7 +18,7 @@ constexpr SettingsMessageTree s_modelMenu[] = #endif SettingsMessageTree(I18n::Message::BetaPopUp), SettingsMessageTree(I18n::Message::About, s_modelAboutChildren), - SettingsMessageTree(I18n::Message::UsbSetting, s_usbProtectionChildren), + // SettingsMessageTree(I18n::Message::UsbSetting, s_usbProtectionChildren), SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren)}; constexpr SettingsMessageTree s_model = SettingsMessageTree(I18n::Message::SettingsApp, s_modelMenu); diff --git a/apps/settings/main_controller_prompt_none.cpp b/apps/settings/main_controller_prompt_none.cpp index 9dc5d45a6fc..9f15d0c07ef 100644 --- a/apps/settings/main_controller_prompt_none.cpp +++ b/apps/settings/main_controller_prompt_none.cpp @@ -17,7 +17,7 @@ constexpr SettingsMessageTree s_modelMenu[] = #ifdef HAS_CODE SettingsMessageTree(I18n::Message::CodeApp, s_codeChildren), #endif - SettingsMessageTree(I18n::Message::UsbSetting, s_usbProtectionChildren), + // SettingsMessageTree(I18n::Message::UsbSetting, s_usbProtectionChildren), SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; diff --git a/apps/settings/main_controller_prompt_update.cpp b/apps/settings/main_controller_prompt_update.cpp index 48c5b5012a6..161fc7d5d5e 100644 --- a/apps/settings/main_controller_prompt_update.cpp +++ b/apps/settings/main_controller_prompt_update.cpp @@ -18,7 +18,7 @@ constexpr SettingsMessageTree s_modelMenu[] = #endif SettingsMessageTree(I18n::Message::UpdatePopUp), SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), - SettingsMessageTree(I18n::Message::UsbSetting, s_usbProtectionChildren), + // SettingsMessageTree(I18n::Message::UsbSetting, s_usbProtectionChildren), SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; constexpr SettingsMessageTree s_model = SettingsMessageTree(I18n::Message::SettingsApp, s_modelMenu); diff --git a/bootloader/Makefile b/bootloader/Makefile index 83f835a36ac..41648a0e382 100644 --- a/bootloader/Makefile +++ b/bootloader/Makefile @@ -1,5 +1,6 @@ bootloader_src += $(addprefix bootloader/,\ + slot_exam_mode.cpp \ boot.cpp \ main.cpp \ kernel_header.cpp \ @@ -8,14 +9,15 @@ bootloader_src += $(addprefix bootloader/,\ interface.cpp \ jump_to_firmware.s \ trampoline.cpp \ - usb_desc.cpp \ + recovery.cpp \ + usb_data.cpp \ + itoa.cpp \ ) bootloader_images = $(addprefix bootloader/, \ - cable.png \ computer.png \ ) -bootloader_src += $(filter-out ion/src/device/shared/drivers/usb_desc.cpp,$(ion_src)) $(simple_kandinsky_src) $(liba_src) $(libaxx_src) $(bootloader_images) +bootloader_src += $(ion_src) $(simple_kandinsky_src) $(liba_src) $(libaxx_src) $(bootloader_images) $(eval $(call depends_on_image,bootloader/interface.cpp,$(bootloader_images))) diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index fa8d8282e8e..c46dc8cf6e7 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -3,64 +3,156 @@ #include #include #include +#include +#include +#include +#include #include namespace Bootloader { BootMode Boot::mode() { - // We use the exam mode driver as storage for the boot mode - uint8_t mode = Ion::ExamMode::FetchExamMode(); - - if (mode > 3) - return Unknown; - - return (BootMode) mode; + return BootMode::SlotA; } void Boot::setMode(BootMode mode) { - BootMode currentMode = Boot::mode(); - if (currentMode == mode) - return; - - assert(mode != BootMode::Unknown); - int8_t deltaMode = (int8_t)mode - (int8_t)currentMode; - deltaMode = deltaMode < 0 ? deltaMode + 4 : deltaMode; - assert(deltaMode > 0); - Ion::ExamMode::IncrementExamMode(deltaMode); + // We dont use the exam mode driver as storage for the boot mode because we need the 16k of storage x) +} + +void Boot::bootSlot(Bootloader::Slot s) { + if (!s.userlandHeader()->isOmega() && !s.userlandHeader()->isUpsilon()) { + // We are trying to boot epsilon, so we check the version and show an advertisement if needed + const char * version = s.userlandHeader()->version(); + const char * min = "18.2.4"; + int vsum = 0; + for (int i = 0; i < strlen(version); i++) { + vsum += version[i] * (100-i*15); + } + int minsum = 0; + for (int i = 0; i < strlen(min); i++) { + minsum += min[i] * (100-i*15); + } + if (vsum >= minsum) { + Interface::drawEpsilonAdvertisement(); + uint64_t scan = 0; + while (scan != Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { + scan = Ion::Keyboard::scan(); + if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::EXE) || scan == Ion::Keyboard::State(Ion::Keyboard::Key::OK)) { + scan = Ion::Keyboard::State(Ion::Keyboard::Key::Back); + s.boot(); + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { + Ion::Power::standby(); // Force a core reset to exit + } + } + Interface::drawMenu(); + return; + } + } + s.boot(); + Interface::drawMenu(); } __attribute__((noreturn)) void Boot::boot() { assert(mode() != BootMode::Unknown); - if (!Slot::A().kernelHeader()->isValid() && !Slot::B().kernelHeader()->isValid()) { - // Bootloader if both invalid - bootloader(); - } else if (!Slot::A().kernelHeader()->isValid()) { - // If slot A is invalid and B valid, boot B - setMode(BootMode::SlotB); - Slot::B().boot(); - } else if (!Slot::B().kernelHeader()->isValid()) { - // If slot B is invalid and A valid, boot A - setMode(BootMode::SlotA); - Slot::A().boot(); - } else { - // Both valid, boot the selected one - if (mode() == BootMode::SlotA) { - Slot::A().boot(); - } else if (mode() == BootMode::SlotB) { - Slot::B().boot(); + bool isSlotA = Slot::A().kernelHeader()->isValid(); + bool isSlotB = Slot::B().kernelHeader()->isValid(); + bool isSlotKhi = Slot::Khi().kernelHeader()->isValid(); + + Interface::drawMenu(); + + while (true) { + uint64_t scan = Ion::Keyboard::scan(); + if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::One) && isSlotA) { + Boot::bootSlot(Slot::A()); + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Two) && isSlotKhi) { + Boot::bootSlot(Slot::Khi()); + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Three) && isSlotB) { + Boot::bootSlot(Slot::B()); + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Four)) { + installerMenu(); + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Five)) { + aboutMenu(); + } + // else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Six)) { + // Ion::Device::Reset::core(); + // } + else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { + Ion::Power::standby(); // Force a core reset to exit } } - // Achivement unlocked: How Did We Get Here? + // Achievement unlocked: How Did We Get Here? bootloader(); } -__attribute__ ((noreturn)) void Boot::bootloader() { +void Boot::installerMenu() { + Interface::drawInstallerSelection(); + uint64_t scan = 0; + while (scan != Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { + scan = Ion::Keyboard::scan(); + if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::One)) { + scan = Ion::Keyboard::State(Ion::Keyboard::Key::Back); + bootloader(); + continue; + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Two)) { + scan = Ion::Keyboard::State(Ion::Keyboard::Key::Back); + blupdate(); + continue; + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { + Ion::Power::standby(); // Force a core reset to exit + } + } + + Interface::drawMenu(); +} + +void Boot::aboutMenu() { + // Draw the about menu + Interface::drawAbout(); + // Wait for the user to press OK, EXE or Back button + while (true) { + uint64_t scan = Ion::Keyboard::scan(); + if ((scan == Ion::Keyboard::State(Ion::Keyboard::Key::OK)) || + (scan == Ion::Keyboard::State(Ion::Keyboard::Key::EXE)) || + (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Back))) { + // Redraw the menu and return + Interface::drawMenu(); + return; + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { + Ion::Power::standby(); // Force a core reset to exit + } + } + +} + +void Boot::blupdate() { + USBData data = USBData::BLUPDATE(); + + for (;;) { + Bootloader::Interface::drawBLUpdate(); + Ion::USB::enable(); + do { + uint64_t scan = Ion::Keyboard::scan(); + if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { + Ion::USB::disable(); + Interface::drawMenu(); + return; + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { + Ion::Power::standby(); + } + } while (!Ion::USB::isEnumerated()); + + Ion::USB::DFU(true, &data); + } +} + +void Boot::bootloader() { + USBData data = USBData::DEFAULT(); for(;;) { // Draw the interfaces and infos - Bootloader::Interface::draw(); + Bootloader::Interface::drawFlasher(); // Enable USB Ion::USB::enable(); @@ -70,13 +162,27 @@ __attribute__ ((noreturn)) void Boot::bootloader() { // If we pressed back while waiting, reset. uint64_t scan = Ion::Keyboard::scan(); if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { - Ion::Device::Reset::core(); + // Disable USB, redraw the menu and return + Ion::USB::disable(); + Interface::drawMenu(); + return; + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { + Ion::Power::standby(); // Force a core reset to exit } } while (!Ion::USB::isEnumerated()); // Launch the DFU stack, allowing to press Back to quit and reset - Ion::USB::DFU(true); + Ion::USB::DFU(true, &data); } } +void Boot::lockInternal() { + Ion::Device::Flash::DisableInternalProtection(); + Ion::Device::Flash::SetInternalSectorProtection(0, true); + Ion::Device::Flash::SetInternalSectorProtection(1, true); + Ion::Device::Flash::SetInternalSectorProtection(2, true); + Ion::Device::Flash::SetInternalSectorProtection(3, true); + Ion::Device::Flash::EnableInternalProtection(); +} + } diff --git a/bootloader/boot.h b/bootloader/boot.h index bb5df17c1b1..361d881441b 100644 --- a/bootloader/boot.h +++ b/bootloader/boot.h @@ -2,6 +2,7 @@ #define BOOTLOADER_BOOT_H #include +#include namespace Bootloader { @@ -20,7 +21,12 @@ class Boot { static BootMode mode(); static void setMode(BootMode mode); __attribute__ ((noreturn)) static void boot(); - __attribute__ ((noreturn)) static void bootloader(); + static void bootloader(); + static void aboutMenu(); + static void installerMenu(); + static void blupdate(); + static void bootSlot(Bootloader::Slot slot); + static void lockInternal(); }; } diff --git a/bootloader/boot/isr.c b/bootloader/boot/isr.c new file mode 100644 index 00000000000..77b1121938e --- /dev/null +++ b/bootloader/boot/isr.c @@ -0,0 +1,129 @@ +#include "isr.h" +extern const void * _stack_start; + +/* Interrupt Service Routines are void->void functions */ +typedef void(*ISR)(void); + +/* Notice: The Cortex-M4 expects all jumps to be made at an odd address when + * jumping to Thumb code. For example, if you want to execute Thumb code at + * address 0x100, you'll have to jump to 0x101. Luckily, this idiosyncrasy is + * properly handled by the C compiler that will generate proper addresses when + * using function pointers. */ + +#define INITIALISATION_VECTOR_SIZE 0x71 + +ISR InitialisationVector[INITIALISATION_VECTOR_SIZE] + __attribute__((section(".isr_vector_table"))) + __attribute__((used)) + = { + (ISR)&_stack_start, // Stack start + start, // Reset service routine, + 0, // NMI service routine, + hard_fault_handler, // HardFault service routine, + mem_fault_handler, // MemManage service routine, + bus_fault_handler, // BusFault service routine, + usage_fault_handler, // UsageFault service routine, + 0, 0, 0, 0, // Reserved + 0, // SVCall service routine, + 0, // DebugMonitor service routine, + 0, // Reserved + 0, // PendSV service routine, + isr_systick, // SysTick service routine + 0, // WWDG service routine + 0, // PVD service routine + 0, // TampStamp service routine + 0, // RtcWakeup service routine + 0, // Flash service routine + 0, // RCC service routine + 0, // EXTI0 service routine + 0, // EXTI1 service routine + 0, // EXTI2 service routine + 0, // EXTI3 service routine + 0, // EXTI4 service routine + 0, // DMA1Stream0 service routine + 0, // DMA1Stream1 service routine + 0, // DMA1Stream2 service routine + 0, // DMA1Stream3 service routine + 0, // DMA1Stream4 service routine + 0, // DMA1Stream5 service routine + 0, // DMA1Stream6 service routine + 0, // ADC1 global interrupt + 0, // CAN1 TX interrupt + 0, // CAN1 RX0 interrupt + 0, // CAN1 RX1 interrupt + 0, // CAN1 SCE interrupt + 0, // EXTI Line[9:5] interrupts + 0, // TIM1 Break interrupt and TIM9 global interrupt + 0, // TIM1 update interrupt and TIM10 global interrupt + 0, // TIM1 Trigger & Commutation interrupts and TIM11 global interrupt + 0, // TIM1 Capture Compare interrupt + 0, // TIM2 global interrupt + 0, // TIM3 global interrupt + 0, // TIM4 global interrupt + 0, // I2C1 global event interrupt + 0, // I2C1 global error interrupt + 0, // I2C2 global event interrupt + 0, // I2C2 global error interrupt + 0, // SPI1 global interrupt + 0, // SPI2 global interrupt + 0, // USART1 global interrupt + 0, // USART2 global interrupt + 0, // USART3 global interrupt + 0, // EXTI Line[15:10] interrupts + 0, // EXTI Line 17 interrupt RTC Alarms (A and B) through EXTI line interrupt + 0, // EXTI Line 18 interrupt / USB On-The-Go FS Wakeup through EXTI line interrupt + 0, // TIM8 Break interrupt TIM12 global interrupt + 0, // TIM8 Update interrupt TIM13 global interrupt + 0, // TIM8 Trigger & Commutation interrupt TIM14 global interrupt + 0, // TIM8 Cap/Com interrupt + 0, // DMA1 global interrupt Channel 7 + 0, // FSMC global interrupt + 0, // SDIO global interrupt + 0, // TIM5 global interrupt + 0, // SPI3 global interrupt + 0, // ? + 0, // ? + 0, // TIM6 global interrupt + 0, // TIM7 global interrupt + 0, // DMA2 Stream0 global interrupt + 0, // DMA2 Stream1 global interrupt + 0, // DMA2 Stream2 global interrupt + 0, // DMA2 Stream3 global interrupt + 0, // DMA2 Stream4 global interrupt + 0, // SD filter0 global interrupt + 0, // SD filter1 global interrupt + 0, // CAN2 TX interrupt + 0, // BXCAN2 RX0 interrupt + 0, // BXCAN2 RX1 interrupt + 0, // CAN2 SCE interrupt + 0, // USB On The Go FS global interrupt + 0, // DMA2 Stream5 global interrupts + 0, // DMA2 Stream6 global interrupt + 0, // DMA2 Stream7 global interrupt + 0, // USART6 global interrupt + 0, // I2C3 event interrupt + 0, // I2C3 error interrupt + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // RNG global interrupt + 0, // FPU global interrupt + 0, // ? + 0, // ? + 0, // SPI4 global interrupt + 0, // SPI5 global interrupt + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // Quad-SPI global interrupt + 0, // ? + 0, // ? + 0, // I2CFMP1 event interrupt + 0 // I2CFMP1 error interrupt +}; diff --git a/bootloader/boot/isr.h b/bootloader/boot/isr.h new file mode 100644 index 00000000000..aa980065e25 --- /dev/null +++ b/bootloader/boot/isr.h @@ -0,0 +1,23 @@ +#ifndef ION_DEVICE_BOOT_ISR_H +#define ION_DEVICE_BOOT_ISR_H + +#ifdef __cplusplus +extern "C" { +#endif + +void start(); +void abort(); +void isr_systick(); + +// Fault handlers + +void hard_fault_handler(); +void mem_fault_handler(); +void usage_fault_handler(); +void bus_fault_handler(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/bootloader/boot/rt0.cpp b/bootloader/boot/rt0.cpp new file mode 100644 index 00000000000..59751ca5fcd --- /dev/null +++ b/bootloader/boot/rt0.cpp @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef void (*cxx_constructor)(); + +extern "C" { + extern char _data_section_start_flash; + extern char _data_section_start_ram; + extern char _data_section_end_ram; + extern char _bss_section_start_ram; + extern char _bss_section_end_ram; + extern cxx_constructor _init_array_start; + extern cxx_constructor _init_array_end; +} + +void __attribute__((noinline)) abort() { +#ifdef NDEBUG + Ion::Device::Reset::core(); +#else + while (1) { + } +#endif +} + + +void __attribute__((interrupt, noinline)) isr_systick() { + auto t = Ion::Device::Timing::MillisElapsed; + t++; + Ion::Device::Timing::MillisElapsed = t; +} + +void __attribute__((noinline)) hard_fault_handler() { + Bootloader::Recovery::crash_handler("HardFault"); +} + +void __attribute__((noinline)) mem_fault_handler() { + Bootloader::Recovery::crash_handler("MemoryFault"); +} + +void __attribute__((noinline)) usage_fault_handler() { + Bootloader::Recovery::crash_handler("UsageFault"); +} + +void __attribute__((noinline)) bus_fault_handler() { + Bootloader::Recovery::crash_handler("BusFault"); +} + +/* In order to ensure that this method is execute from the external flash, we + * forbid inlining it.*/ + +static void __attribute__((noinline)) external_flash_start() { + /* Init the peripherals. We do not initialize the backlight in case there is + * an on boarding app: indeed, we don't want the user to see the LCD tests + * happening during the on boarding app. The backlight will be initialized + * after the Power-On Self-Test if there is one or before switching to the + * home app otherwise. */ + Ion::Device::Board::initPeripherals(false); + + return ion_main(0, nullptr); +} + +/* This additional function call 'jump_to_external_flash' serves two purposes: + * - By default, the compiler is free to inline any function call he wants. If + * the compiler decides to inline some functions that make use of VFP + * registers, it will need to push VFP them onto the stack in calling + * function's prologue. + * Problem: in start()'s prologue, we would never had a chance to enable the + * FPU since this function is the first thing called after reset. + * We can safely assume that neither memcpy, memset, nor any Ion::Device::init* + * method will use floating-point numbers, but ion_main very well can. + * To make sure ion_main's potential usage of VFP registers doesn't bubble-up to + * start(), we isolate it in its very own non-inlined function call. + * - To avoid jumping on the external flash when it is shut down, we ensure + * there is no symbol references from the internal flash to the external + * flash except this jump. In order to do that, we isolate this + * jump in a symbol that we link in a special section separated from the + * internal flash section. We can than forbid cross references from the + * internal flash to the external flash. */ + +static void __attribute__((noinline)) jump_to_external_flash() { + external_flash_start(); +} + +/* When 'start' is executed, the external flash is supposed to be shutdown. We + * thus forbid inlining to prevent executing this code from external flash + * (just in case 'start' was to be called from the external flash). */ + +void __attribute__((noinline)) start() { + /* This is where execution starts after reset. + * Many things are not initialized yet so the code here has to pay attention. */ + + /* Initialize the FPU as early as possible. + * For example, static C++ objects are very likely to manipulate float values */ + Ion::Device::Board::initFPU(); + + + + /* Copy data section to RAM + * The data section is R/W but its initialization value matters. It's stored + * in Flash, but linked as if it were in RAM. Now's our opportunity to copy + * it. Note that until then the data section (e.g. global variables) contains + * garbage values and should not be used. */ + size_t dataSectionLength = (&_data_section_end_ram - &_data_section_start_ram); + memcpy(&_data_section_start_ram, &_data_section_start_flash, dataSectionLength); + + /* Zero-out the bss section in RAM + * Until we do, any uninitialized global variable will be unusable. */ + size_t bssSectionLength = (&_bss_section_end_ram - &_bss_section_start_ram); + memset(&_bss_section_start_ram, 0, bssSectionLength); + + + /* Call static C++ object constructors + * The C++ compiler creates an initialization function for each static object. + * The linker then stores the address of each of those functions consecutively + * between _init_array_start and _init_array_end. So to initialize all C++ + * static objects we just have to iterate between theses two addresses and + * call the pointed function. */ +#define SUPPORT_CPP_GLOBAL_CONSTRUCTORS 0 +#if SUPPORT_CPP_GLOBAL_CONSTRUCTORS + for (cxx_constructor * c = &_init_array_start; c<&_init_array_end; c++) { + (*c)(); + } +#else + /* In practice, static initialized objects are a terrible idea. Since the init + * order is not specified, most often than not this yields the dreaded static + * init order fiasco. How about bypassing the issue altogether? */ + if (&_init_array_start != &_init_array_end) { + abort(); + } +#endif + + /* At this point, we initialized clocks and the external flash but no other + * peripherals. */ + Ion::Device::Board::init(); + + jump_to_external_flash(); + + abort(); +} diff --git a/bootloader/drivers/board.cpp b/bootloader/drivers/board.cpp new file mode 100644 index 00000000000..2ee8fe666d9 --- /dev/null +++ b/bootloader/drivers/board.cpp @@ -0,0 +1,444 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +typedef void(*ISR)(void); +extern ISR InitialisationVector[]; + +// Public Ion methods + +const char * Ion::fccId() { + return "2ALWP-N0110"; +} + +// Private Ion::Device methods + +namespace Ion { +namespace Device { +namespace Board { + +using namespace Regs; + +void bootloaderMPU() { + // 1. Disable the MPU + // 1.1 Memory barrier + Cache::dmb(); + + // 1.3 Disable the MPU and clear the control register + MPU.CTRL()->setENABLE(false); + + MPU.RNR()->setREGION(7); + MPU.RBAR()->setADDR(0x90000000); + MPU.RASR()->setXN(false); + MPU.RASR()->setENABLE(true); + + // 2.3 Enable MPU + MPU.CTRL()->setENABLE(true); + + // 3. Data/instruction synchronisation barriers to ensure that the new MPU configuration is used by subsequent instructions. + Cache::disable(); + Cache::dsb(); + Cache::isb(); +} + +void initMPU() { + // 1. Disable the MPU + // 1.1 Memory barrier + Cache::dmb(); + + // 1.2 Disable fault exceptions + CORTEX.SHCRS()->setMEMFAULTENA(false); + + // 1.3 Disable the MPU and clear the control register + MPU.CTRL()->setENABLE(false); + + // 2. MPU settings + // 2.1 Configure a MPU region for the FMC memory area + /* This is needed for interfacing with the LCD + * We define the whole FMC memory bank 1 as strongly ordered, non-executable + * and not accessible. We define the FMC command and data addresses as + * writeable non-cachable, non-buffereable and non shareable. */ + int sector = 0; + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x60000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_256MB); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::NoAccess); + MPU.RASR()->setXN(true); + MPU.RASR()->setTEX(2); + MPU.RASR()->setS(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x60000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_32B); + MPU.RASR()->setXN(true); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW); + MPU.RASR()->setTEX(2); + MPU.RASR()->setS(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x60000000+0x20000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_32B); + MPU.RASR()->setXN(true); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW); + MPU.RASR()->setTEX(2); + MPU.RASR()->setS(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + // 2.2 Configure MPU regions for the QUADSPI peripheral + /* L1 Cache can issue speculative reads to any memory address. But, when the + * Quad-SPI is in memory-mapped mode, if an access is made to an address + * outside of the range defined by FSIZE but still within the 256Mbytes range, + * then an AHB error is given (AN4760). To prevent this to happen, we + * configure the MPU to define the whole Quad-SPI addressable space as + * strongly ordered, non-executable and not accessible. Plus, we define the + * Quad-SPI region corresponding to the Expternal Chip as executable and + * fully accessible (AN4861). */ + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x90000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_256MB); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::NoAccess); + MPU.RASR()->setXN(true); + MPU.RASR()->setTEX(0); + MPU.RASR()->setS(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x90000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_8MB); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW); + MPU.RASR()->setXN(false); + MPU.RASR()->setTEX(0); + MPU.RASR()->setS(0); + MPU.RASR()->setC(1); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + // 2.3 Enable MPU + MPU.CTRL()->setPRIVDEFENA(true); + MPU.CTRL()->setENABLE(true); + + //2.4 Enable fault exceptions + CORTEX.SHCRS()->setMEMFAULTENA(true); + CORTEX.SHCRS()->setBUSFAULTENA(true); + CORTEX.SHCRS()->setUSGFAULTENA(true); + + // 3. Data/instruction synchronisation barriers to ensure that the new MPU configuration is used by subsequent instructions. + Cache::dsb(); + Cache::isb(); +} + +void init() { + initFPU(); + initMPU(); + initClocks(); + + // The bootloader leaves its own after flashing + //SYSCFG.MEMRMP()->setMEM_MODE(SYSCFG::MEMRMP::MemMode::MainFlashmemory); + // Ensure right location of interrupt vectors + CORTEX.VTOR()->setVTOR((void*)&InitialisationVector); + + // Put all inputs as Analog Input, No pull-up nor pull-down + // Except for the SWD port (PB3, PA13, PA14) + GPIOA.MODER()->set(0xEBFFFFFF); + GPIOA.PUPDR()->set(0x24000000); + GPIOB.MODER()->set(0xFFFFFFBF); + GPIOB.PUPDR()->set(0x00000000); + for (int g=2; g<5; g++) { + GPIO(g).MODER()->set(0xFFFFFFFF); // All to "Analog" + GPIO(g).PUPDR()->set(0x00000000); // All to "None" + } + + ExternalFlash::init(); + // Initiate L1 cache after initiating the external flash + Cache::enable(); +} + +void initClocks() { + /* System clock + * Configure the CPU at 192 MHz and USB at 48 MHz. */ + + /* After reset, the device is using the high-speed internal oscillator (HSI) + * as a clock source, which runs at a fixed 16 MHz frequency. The HSI is not + * accurate enough for reliable USB operation, so we need to use the external + * high-speed oscillator (HSE). */ + + // Enable the HSI and wait for it to be ready + RCC.CR()->setHSION(true); + while(!RCC.CR()->getHSIRDY()) { + } + + // Enable the HSE and wait for it to be ready + RCC.CR()->setHSEON(true); + while(!RCC.CR()->getHSERDY()) { + } + + // Enable PWR peripheral clock + RCC.APB1ENR()->setPWREN(true); + + /* To pass electromagnetic compatibility tests, we activate the Spread + * Spectrum clock generation, which adds jitter to the PLL clock in order to + * "lower peak-energy on the central frequency" and its harmonics. + * It must be done before enabling the PLL. */ + class RCC::SSCGR sscgr(0); // Reset value + sscgr.setMODPER(Clocks::Config::SSCG_MODPER); + sscgr.setINCSTEP(Clocks::Config::SSCG_INCSTEP); + sscgr.setSPREADSEL(RCC::SSCGR::SPREADSEL::CenterSpread); + sscgr.setSSCGEN(true); + RCC.SSCGR()->set(sscgr); + + /* Given the crystal used on our device, the HSE will oscillate at 8 MHz. By + * piping it through a phase-locked loop (PLL) we can derive other frequencies + * for use in different parts of the system. */ + + // Configure the PLL ratios and use HSE as a PLL input + RCC.PLLCFGR()->setPLLM(Clocks::Config::PLL_M); + RCC.PLLCFGR()->setPLLN(Clocks::Config::PLL_N); + RCC.PLLCFGR()->setPLLQ(Clocks::Config::PLL_Q); + RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); + + // Enable the PLL and wait for it to be ready + RCC.CR()->setPLLON(true); + + // Enable Over-drive + PWR.CR()->setODEN(true); + while(!PWR.CSR()->getODRDY()) { + } + + PWR.CR()->setODSWEN(true); + while(!PWR.CSR()->getODSWRDY()) { + } + + // Choose Voltage scale 1 + PWR.CR()->setVOS(PWR::CR::Voltage::Scale1); + while (!PWR.CSR()->getVOSRDY()) {} + + /* After reset the Flash runs as fast as the CPU. When we clock the CPU faster + * the flash memory cannot follow and therefore flash memory accesses need to + * wait a little bit. + * The spec tells us that at 2.8V and over 210MHz the flash expects 7 WS. */ + FLASH.ACR()->setLATENCY(7); + + /* Enable prefetching flash instructions */ + /* Fetching instructions increases slightly the power consumption but the + * increase is negligible compared to the screen consumption. */ + FLASH.ACR()->setPRFTEN(true); + + /* Enable the ART */ + FLASH.ACR()->setARTEN(true); + + // 192 MHz is too fast for APB1. Divide it by four to reach 48 MHz + RCC.CFGR()->setPPRE1(Clocks::Config::APB1PrescalerReg); + // 192 MHz is too fast for APB2. Divide it by two to reach 96 MHz + RCC.CFGR()->setPPRE2(Clocks::Config::APB2PrescalerReg); + + while(!RCC.CR()->getPLLRDY()) { + } + + // Use the PLL output as a SYSCLK source + RCC.CFGR()->setSW(RCC::CFGR::SW::PLL); + while (RCC.CFGR()->getSWS() != RCC::CFGR::SW::PLL) { + } + + // Now that we don't need use it anymore, turn the HSI off + RCC.CR()->setHSION(false); + + // Peripheral clocks + + // AHB1 bus + // Our peripherals are using GPIO A, B, C, D and E. + // We're not using the CRC nor DMA engines. + class RCC::AHB1ENR ahb1enr(0x00100000); // Reset value + ahb1enr.setGPIOAEN(true); + ahb1enr.setGPIOBEN(true); + ahb1enr.setGPIOCEN(true); + ahb1enr.setGPIODEN(true); + ahb1enr.setGPIOEEN(true); + ahb1enr.setDMA2EN(true); + RCC.AHB1ENR()->set(ahb1enr); + + // AHB2 bus + RCC.AHB2ENR()->setOTGFSEN(true); + + // AHB3 bus + RCC.AHB3ENR()->setFSMCEN(true); + + // APB1 bus + // We're using TIM3 for the LEDs + RCC.APB1ENR()->setTIM3EN(true); + RCC.APB1ENR()->setPWREN(true); + RCC.APB1ENR()->setRTCAPB(true); + + // APB2 bus + class RCC::APB2ENR apb2enr(0); // Reset value + apb2enr.setADC1EN(true); + apb2enr.setSYSCFGEN(true); + apb2enr.setUSART6EN(true); // TODO required if building bench target only? + RCC.APB2ENR()->set(apb2enr); + + // Configure clocks in sleep mode + // AHB1 peripheral clock enable in low-power mode register + class RCC::AHB1LPENR ahb1lpenr(0x7EF7B7FF); // Reset value + ahb1lpenr.setGPIOALPEN(true); // Enable IO port A for Charging/USB plug/Keyboard pins + ahb1lpenr.setGPIOBLPEN(true); // Enable IO port B for LED pins + ahb1lpenr.setGPIOCLPEN(true); // Enable IO port C for LED/Keyboard pins + ahb1lpenr.setGPIODLPEN(false); // Disable IO port D (LCD...) + ahb1lpenr.setGPIOELPEN(true); // Enable IO port E for Keyboard/Battery pins + ahb1lpenr.setGPIOFLPEN(false); // Disable IO port F + ahb1lpenr.setGPIOGLPEN(false); // Disable IO port G + ahb1lpenr.setGPIOHLPEN(false); // Disable IO port H + ahb1lpenr.setGPIOILPEN(false); // Disable IO port I + ahb1lpenr.setCRCLPEN(false); + ahb1lpenr.setFLITFLPEN(false); + ahb1lpenr.setSRAM1LPEN(false); + ahb1lpenr.setDMA1LPEN(false); + ahb1lpenr.setDMA2LPEN(false); + ahb1lpenr.setAXILPEN(false); + ahb1lpenr.setSRAM2LPEN(false); + ahb1lpenr.setBKPSRAMLPEN(false); + ahb1lpenr.setDTCMLPEN(false); + ahb1lpenr.setOTGHSLPEN(false); + ahb1lpenr.setOTGHSULPILPEN(false); + RCC.AHB1LPENR()->set(ahb1lpenr); + + // AHB2 peripheral clock enable in low-power mode register + class RCC::AHB2LPENR ahb2lpenr(0x000000F1); // Reset value + ahb2lpenr.setOTGFSLPEN(false); + ahb2lpenr.setRNGLPEN(false); + ahb2lpenr.setAESLPEN(false); + RCC.AHB2LPENR()->set(ahb2lpenr); + + // AHB3 peripheral clock enable in low-power mode register + class RCC::AHB3LPENR ahb3lpenr(0x00000003); // Reset value + ahb3lpenr.setFMCLPEN(false); + ahb3lpenr.setQSPILPEN(false); + RCC.AHB3LPENR()->set(ahb3lpenr); + + // APB1 peripheral clock enable in low-power mode register + class RCC::APB1LPENR apb1lpenr(0xFFFFCBFF); // Reset value + apb1lpenr.setTIM2LPEN(false); + apb1lpenr.setTIM3LPEN(true); // Enable TIM3 in sleep mode for LEDs + apb1lpenr.setTIM4LPEN(false); + apb1lpenr.setTIM5LPEN(false); + apb1lpenr.setTIM6LPEN(false); + apb1lpenr.setTIM7LPEN(false); + apb1lpenr.setTIM12LPEN(false); + apb1lpenr.setTIM13LPEN(false); + apb1lpenr.setTIM14LPEN(false); + apb1lpenr.setRTCAPBLPEN(false); + apb1lpenr.setWWDGLPEN(false); + apb1lpenr.setSPI2LPEN(false); + apb1lpenr.setSPI3LPEN(false); + apb1lpenr.setUSART2LPEN(false); + apb1lpenr.setUSART3LPEN(false); + apb1lpenr.setI2C1LPEN(false); + apb1lpenr.setI2C2LPEN(false); + apb1lpenr.setI2C3LPEN(false); + apb1lpenr.setCAN1LPEN(false); + apb1lpenr.setPWRLPEN(false); + apb1lpenr.setLPTIM1LPEN(false); + apb1lpenr.setUSART4LPEN(false); + apb1lpenr.setUSART5LPEN(false); + apb1lpenr.setOTGHSLPEN(false); + apb1lpenr.setOTGHSULPILPEN(false); + RCC.APB1LPENR()->set(apb1lpenr); + + // APB2 peripheral clock enable in low-power mode register + class RCC::APB2LPENR apb2lpenr(0x04F77F33); // Reset value + apb2lpenr.setTIM1LPEN(false); + apb2lpenr.setTIM8LPEN(false); + apb2lpenr.setUSART1LPEN(false); + apb2lpenr.setUSART6LPEN(false); + apb2lpenr.setADC1LPEN(false); + apb2lpenr.setSPI1LPEN(false); + apb2lpenr.setSPI4LPEN(false); + apb2lpenr.setSYSCFGLPEN(false); + apb2lpenr.setTIM9LPEN(false); + apb2lpenr.setTIM10LPEN(false); + apb2lpenr.setTIM11LPEN(false); + apb2lpenr.setSPI5LPEN(false); + apb2lpenr.setSDMMC2LPEN(false); + apb2lpenr.setADC2LPEN(false); + apb2lpenr.setADC3LPEN(false); + apb2lpenr.setSAI1LPEN(false); + apb2lpenr.setSAI2LPEN(false); + RCC.APB2LPENR()->set(apb2lpenr); +} + +void shutdownClocks(bool keepLEDAwake) { + // APB2 bus + RCC.APB2ENR()->set(0); // Reset value + + // AHB2 bus + RCC.AHB2ENR()->set(0); // Reset value + + // AHB3 bus + RCC.AHB3ENR()->set(0); // Reset value + + // APB1 + class RCC::APB1ENR apb1enr(0); // Reset value + // AHB1 bus + class RCC::AHB1ENR ahb1enr(0x00100000); // Reset value + if (keepLEDAwake) { + apb1enr.setTIM3EN(true); + ahb1enr.setGPIOBEN(true); + } + RCC.APB1ENR()->set(apb1enr); + RCC.AHB1ENR()->set(ahb1enr); +} + +constexpr int k_pcbVersionOTPIndex = 0; + +/* As we want the PCB versions to be in ascending order chronologically, and + * because the OTP are initialized with 1s, we store the bitwise-not of the + * version number. This way, devices with blank OTP are considered version 0. */ + +PCBVersion pcbVersion() { +#if IN_FACTORY + /* When flashing for the first time, we want all systems that depend on the + * PCB version to function correctly before flashing the PCB version. This + * way, flashing the PCB version can be done last. */ + return PCB_LATEST; +#else + PCBVersion version = readPCBVersionInMemory(); + return (version == k_alternateBlankVersion ? 0 : version); +#endif +} + +PCBVersion readPCBVersionInMemory() { + return ~(*reinterpret_cast(InternalFlash::Config::OTPAddress(k_pcbVersionOTPIndex))); +} + +void writePCBVersion(PCBVersion version) { + uint8_t * destination = reinterpret_cast(InternalFlash::Config::OTPAddress(k_pcbVersionOTPIndex)); + PCBVersion formattedVersion = ~version; + InternalFlash::WriteMemory(destination, reinterpret_cast(&formattedVersion), sizeof(formattedVersion)); +} + +void lockPCBVersion() { + uint8_t * destination = reinterpret_cast(InternalFlash::Config::OTPLockAddress(k_pcbVersionOTPIndex)); + uint8_t zero = 0; + InternalFlash::WriteMemory(destination, &zero, sizeof(zero)); +} + +bool pcbVersionIsLocked() { + return *reinterpret_cast(InternalFlash::Config::OTPLockAddress(k_pcbVersionOTPIndex)) == 0; +} + +} +} +} diff --git a/bootloader/interface.cpp b/bootloader/interface.cpp index e1cd6e334e2..42b81fa9866 100644 --- a/bootloader/interface.cpp +++ b/bootloader/interface.cpp @@ -1,20 +1,22 @@ #include #include +#include #include +#include #include #include #include "computer.h" -#include "cable.h" namespace Bootloader { -void Interface::drawImage(KDContext* ctx, const Image* image, int offset) { - const uint8_t* data; +void Interface::drawImage(KDContext * ctx, const Image * image, int offset) { + const uint8_t * data; size_t size; size_t pixelBufferSize; + if (image != nullptr) { data = image->compressedPixelData(); size = image->compressedPixelDataSize(); @@ -39,44 +41,281 @@ void Interface::drawImage(KDContext* ctx, const Image* image, int offset) { ctx->fillRectWithPixels(bounds, pixelBuffer, nullptr); } -void Interface::draw() { +void Interface::drawHeader() { KDContext * ctx = KDIonContext::sharedContext(); - ctx->fillRect(KDRect(0,0,320,240), KDColorBlack); - drawImage(ctx, ImageStore::Computer, 70); - drawImage(ctx, ImageStore::Cable, 172); - - ctx->drawString("Slot A:", KDPoint(0, 0), KDFont::SmallFont, KDColorWhite, KDColorBlack); - ctx->drawString("Slot B:", KDPoint(0, 13), KDFont::SmallFont, KDColorWhite, KDColorBlack); - ctx->drawString("Current:", KDPoint(0, 26), KDFont::SmallFont, KDColorWhite, KDColorBlack); - - if (Boot::mode() == BootMode::SlotA) { - ctx->drawString("Slot A", KDPoint(63, 26), KDFont::SmallFont, KDColorWhite, KDColorBlack); - } else if (Boot::mode() == BootMode::SlotB) { - ctx->drawString("Slot B", KDPoint(63, 26), KDFont::SmallFont, KDColorWhite, KDColorBlack); - } + ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); + drawImage(ctx, ImageStore::Computer, 25); + KDSize fontSize = KDFont::LargeFont->glyphSize(); + int initPos = (320 - fontSize.width() * strlen(Messages::mainTitle)) / 2; + ctx->drawString(Messages::mainTitle, KDPoint(initPos, ImageStore::Computer->height() + fontSize.height() + 10), KDFont::LargeFont, KDColorBlack, KDColorWhite); +} - Slot slots[2] = {Slot::A(), Slot::B()}; +void Interface::drawMenu() { + // Get the context + KDContext * ctx = KDIonContext::sharedContext(); + // Clear the screen + ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); + // Draw the image + drawImage(ctx, ImageStore::Computer, 25); + // Get the font size + KDSize largeSize = KDFont::LargeFont->glyphSize(); + KDSize smallSize = KDFont::SmallFont->glyphSize(); + // Draw the title + int initPos = (320 - largeSize.width() * strlen(Messages::mainMenuTitle)) / 2; + ctx->drawString(Messages::mainMenuTitle, KDPoint(initPos, ImageStore::Computer->height() + largeSize.height() + 10), KDFont::LargeFont, KDColorBlack, KDColorWhite); - for(uint8_t i = 0; i < 2; i++) { - Slot slot = slots[i]; + // Initialize the slot list + Slot slots[3] = {Slot::A(), Slot::Khi(), Slot::B()}; + char indexes[3] = {'1', '2', '3'}; + // Set the khi slot index, improve this when Khi will have a dedicated header + const uint8_t khiIndex = 2 - 1; + + // Get the start y position + int y = ImageStore::Computer->height() + (largeSize.height() + 10) + (smallSize.height() + 10); + + // Iterate over the slot list + for (uint8_t i = 0; i < sizeof(indexes) / sizeof(indexes[0]); i++) { + // Get the slot from the list + Slot slot = slots[i]; + // Get the "X - " string + char converted[] = {indexes[i], ' ', '-', ' ', '\0'}; + // Setup the margin + int x = 10; + // If the slot is valid, draw the slot if (slot.kernelHeader()->isValid() && slot.userlandHeader()->isValid()) { + // Draw the slot number + ctx->drawString(converted, KDPoint(x, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); + // Increment the x position + x += strlen(converted) * smallSize.width(); + // Draw the slot version + ctx->drawString(slot.userlandHeader()->version(), KDPoint(x, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); + // Increment the x position + x += strlen(slot.userlandHeader()->version()) * smallSize.width() + smallSize.width() * 2; + // Draw the slot commit + ctx->drawString(slot.kernelHeader()->patchLevel(), KDPoint(x, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); + // Increment the x position + x += strlen(slot.kernelHeader()->patchLevel()) * smallSize.width() + smallSize.width(); + + const char * OSName = ""; + const char * OSVersion = ""; + // If the slot is Upsilon, draw the slot name if (slot.userlandHeader()->isOmega() && slot.userlandHeader()->isUpsilon()) { - ctx->drawString("Upsilon", KDPoint(56, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); - ctx->drawString(slot.userlandHeader()->upsilonVersion(), KDPoint(112, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + // Set the OS name + OSName = Messages::upsilon; + // Set the OS version + OSVersion = slot.userlandHeader()->upsilonVersion(); } else if (slot.userlandHeader()->isOmega()) { - ctx->drawString("Omega", KDPoint(56, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); - ctx->drawString(slot.userlandHeader()->omegaVersion(), KDPoint(112, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + // Get if slot is Khi + bool isKhi = (i == khiIndex); + // If the slot is Khi, draw the slot name (Khi) + if (isKhi) { + // Set the OS name + OSName = Messages::khi; + } else { + // Set the OS name + OSName = Messages::omega; + } + // Set the OS version + OSVersion = slot.userlandHeader()->omegaVersion(); } else { - ctx->drawString("Epsilon", KDPoint(56, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); - ctx->drawString(slot.userlandHeader()->version(), KDPoint(112, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + // Set the OS name + OSName = Messages::epsilon; } - ctx->drawString(slot.kernelHeader()->patchLevel(), KDPoint(168, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + // Draw the OS name + ctx->drawString(OSName, KDPoint(x, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); + // Increment the x position + x += strlen(OSName) * smallSize.width() + smallSize.width(); + // Draw the OS version + ctx->drawString(OSVersion, KDPoint(x, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); + // Else, the slot is invalid } else { - ctx->drawString("Invalid", KDPoint(56, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + ctx->drawString(Messages::invalidSlot, KDPoint(10, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); } + // Increment the y position + y += smallSize.height() + 10; + } + + // Draw the DFU message + ctx->drawString(Messages::dfuText, KDPoint(10, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); + + // Increment the y position + y += smallSize.height() + 10; + // Draw the about message + ctx->drawString(Messages::aboutText, KDPoint(10, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); + + // Reboot is disabled for now + // y += smallSize.height() + 10; + // ctx->drawString(Messages::rebootText, KDPoint(10, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); + + // Draw the footer + initPos = (320 - smallSize.width() * strlen(Messages::mainTitle)) / 2; + ctx->drawString(Messages::mainTitle, KDPoint(initPos, 240 - smallSize.height() - 10), KDFont::SmallFont, KDColorBlack, KDColorWhite); +} + +void Interface::drawFlasher() { + Interface::drawHeader(); + KDContext * ctx = KDIonContext::sharedContext(); + int y = ImageStore::Computer->height() + (KDFont::LargeFont->glyphSize().height() + 10) + (KDFont::SmallFont->glyphSize().height() + 10); + int initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::dfuSubtitle)) / 2; + ctx->drawString(Messages::dfuSubtitle, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); +} + +void Interface::drawLoading() { + KDContext * ctx = KDIonContext::sharedContext(); + ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); + drawImage(ctx, ImageStore::Computer, 25); + Ion::Timing::msleep(250); + KDSize fontSize = KDFont::LargeFont->glyphSize(); + int initPos = (320 - fontSize.width() * strlen(Messages::mainTitle)) / 2; + for (uint8_t i = 0; i < strlen(Messages::mainTitle); i++) { + char tmp[2] = {Messages::mainTitle[i], '\0'}; + ctx->drawString(tmp, KDPoint(initPos + i * (fontSize.width()), ImageStore::Computer->height() + fontSize.height() + 10), KDFont::LargeFont, KDColorBlack, KDColorWhite); + Ion::Timing::msleep(50); } + Ion::Timing::msleep(500); +} + +void Interface::drawAbout() { + drawHeader(); + // Create the list of about messages + // TODO: Move it to messages.h + char aboutMessages[][38] = { + "This is the bootloader of", + "the Upsilon Calculator.", + "It is used to install", + "and select the OS", + "to boot." + }; + // Get the context + KDContext * ctx = KDIonContext::sharedContext(); + // Get the start Y position + KDSize largeSize = KDFont::LargeFont->glyphSize(); + KDSize smallSize = KDFont::SmallFont->glyphSize(); + int y = ImageStore::Computer->height() + (largeSize.height() + 10) + (smallSize.height() + 10); + // Iterate over the list and draw each message + for (uint8_t i = 0; i < sizeof(aboutMessages) / sizeof(aboutMessages[0]); i++) { + // Get the message + char * actualMessage = aboutMessages[i]; + // Get the start X position + int initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(actualMessage)) / 2; + // Draw the message + ctx->drawString(actualMessage, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); + // Increment the Y position + y += smallSize.height() + 10; + } + + ctx->drawString(Messages::bootloaderVersion, KDPoint((320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::bootloaderVersion)) / 2, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); + +} + +void Interface::drawCrash(const char * error) { + KDContext * ctx = KDIonContext::sharedContext(); + ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); + drawImage(ctx, ImageStore::Computer, 25); + KDSize fontSize = KDFont::LargeFont->glyphSize(); + int initPos = (320 - fontSize.width() * strlen(Messages::crashTitle)) / 2; + ctx->drawString(Messages::crashTitle, KDPoint(initPos, ImageStore::Computer->height() + fontSize.height() + 10), KDFont::LargeFont, KDColorBlack, KDColorWhite); + initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(error)) / 2; + ctx->drawString(error, KDPoint(initPos, ImageStore::Computer->height() + fontSize.height() + 10 + KDFont::SmallFont->glyphSize().height() + 10), KDFont::SmallFont, KDColorBlack, KDColorWhite); + initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::crashText1)) / 2; + ctx->drawString(Messages::crashText1, KDPoint(initPos, ImageStore::Computer->height() + fontSize.height() + 10 + KDFont::SmallFont->glyphSize().height() + 10 + KDFont::SmallFont->glyphSize().height() + 10 + 20), KDFont::SmallFont, KDColorBlack, KDColorWhite); + initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::crashText2)) / 2; + ctx->drawString(Messages::crashText2, KDPoint(initPos, ImageStore::Computer->height() + fontSize.height() + 10 + KDFont::SmallFont->glyphSize().height() + 10 + KDFont::SmallFont->glyphSize().height() + 10 + 20 + KDFont::SmallFont->glyphSize().height() + 10), KDFont::SmallFont, KDColorBlack, KDColorWhite); +} +void Interface::drawRecovery() { + KDContext * ctx = KDIonContext::sharedContext(); + ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); + drawImage(ctx, ImageStore::Computer, 25); + KDSize fontSize = KDFont::LargeFont->glyphSize(); + int initPos = (320 - fontSize.width() * strlen(Messages::recoveryTitle)) / 2; + int y = ImageStore::Computer->height() + fontSize.height() + 5; + ctx->drawString(Messages::recoveryTitle, KDPoint(initPos, y), KDFont::LargeFont, KDColorBlack, KDColorWhite); + initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::recoveryText1)) / 2; + y += fontSize.height() + 5; + ctx->drawString(Messages::recoveryText1, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); + initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::recoveryText2)) / 2; + y += fontSize.height() + 5; + ctx->drawString(Messages::recoveryText2, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); + initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::recoveryText3)) / 2; + y += fontSize.height() + 5; + ctx->drawString(Messages::recoveryText3, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); + initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::recoveryText4)) / 2; + y += fontSize.height() + 5; + ctx->drawString(Messages::recoveryText4, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); + initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::recoveryText5)) / 2; + y += fontSize.height() + 5; + ctx->drawString(Messages::recoveryText5, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); +} + +void Interface::drawInstallerSelection() { + // Get the context + KDContext * ctx = KDIonContext::sharedContext(); + // Clear the screen + ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); + // Draw the image + drawImage(ctx, ImageStore::Computer, 25); + // Get the font size + KDSize largeSize = KDFont::LargeFont->glyphSize(); + KDSize smallSize = KDFont::SmallFont->glyphSize(); + // Get the start x position + int initPos = (320 - largeSize.width() * strlen(Messages::installerSelectionTitle)) / 2; + // Get the start y position + int y = ImageStore::Computer->height() + largeSize.height() + 10; + // Draw the title + ctx->drawString(Messages::installerSelectionTitle, KDPoint(initPos, y), KDFont::LargeFont, KDColorBlack, KDColorWhite); + // Increment the y position + y += largeSize.height() + 5; + // Get the y position of the subtitle + initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::installerText1)) / 2; + // Draw the subtitle + ctx->drawString(Messages::installerText1, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); + // Increment the y position + y += smallSize.height() + 10; + // Set the start x position + initPos = 10; + // Draw the first button (slot flash) + ctx->drawString(Messages::installerText2, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); + // Increment the y position + y += smallSize.height() + 10; + // Draw the second button (bootloader flash) + ctx->drawString(Messages::installerText3, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); +} + +void Interface::drawBLUpdate() { + Interface::drawHeader(); + KDContext * ctx = KDIonContext::sharedContext(); + int y = ImageStore::Computer->height() + (KDFont::LargeFont->glyphSize().height() + 10) + (KDFont::SmallFont->glyphSize().height() + 10); + int initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::bootloaderSubtitle)) / 2; + ctx->drawString(Messages::bootloaderSubtitle, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); +} + +void Interface::drawEpsilonAdvertisement() { + KDContext * ctx = KDIonContext::sharedContext(); + ctx->fillRect(KDRect(0, 0, 320, 240), KDColorRed); + drawImage(ctx, ImageStore::Computer, 25); + KDSize fontSize = KDFont::LargeFont->glyphSize(); + int initPos = (320 - fontSize.width() * strlen(Messages::epsilonWarningTitle)) / 2; + int y = ImageStore::Computer->height() + fontSize.height() + 15; + ctx->drawString(Messages::epsilonWarningTitle, KDPoint(initPos, y), KDFont::LargeFont, KDColorWhite, KDColorRed); + initPos = (320 - fontSize.width() * strlen(Messages::epsilonWarningText1)) / 2; + y += fontSize.height() + 5; + ctx->drawString(Messages::epsilonWarningText1, KDPoint(initPos, y), KDFont::LargeFont, KDColorWhite, KDColorRed); + initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::epsilonWarningText2)) / 2; + y += fontSize.height() + 2; + ctx->drawString(Messages::epsilonWarningText2, KDPoint(initPos, y), KDFont::SmallFont, KDColorWhite, KDColorRed); + initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::epsilonWarningText3)) / 2; + y += KDFont::SmallFont->glyphSize().height() + 5; + ctx->drawString(Messages::epsilonWarningText3, KDPoint(initPos, y), KDFont::SmallFont, KDColorWhite, KDColorRed); + initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::epsilonWarningText4)) / 2; + y += KDFont::SmallFont->glyphSize().height() + 10; + ctx->drawString(Messages::epsilonWarningText4, KDPoint(initPos, y), KDFont::SmallFont, KDColorWhite, KDColorRed); + y += KDFont::SmallFont->glyphSize().height() + 10; + initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::epsilonWarningText5)) / 2; + ctx->drawString(Messages::epsilonWarningText5, KDPoint(initPos, y), KDFont::SmallFont, KDColorWhite, KDColorRed); + y += KDFont::SmallFont->glyphSize().height() + 5; + ctx->drawString(Messages::epsilonWarningText6, KDPoint(initPos, y), KDFont::SmallFont, KDColorWhite, KDColorRed); } } diff --git a/bootloader/interface.h b/bootloader/interface.h index c163ddc4c02..dcecdcda11b 100644 --- a/bootloader/interface.h +++ b/bootloader/interface.h @@ -10,10 +10,19 @@ namespace Bootloader { class Interface { private: - static void drawImage(KDContext* ctx, const Image* image, int offset); + static void drawImage(KDContext * ctx, const Image * image, int offset); public: - static void draw(); + static void drawLoading(); + static void drawHeader(); + static void drawMenu(); + static void drawFlasher(); + static void drawAbout(); + static void drawCrash(const char * error); + static void drawRecovery(); + static void drawInstallerSelection(); + static void drawBLUpdate(); + static void drawEpsilonAdvertisement(); }; diff --git a/bootloader/itoa.cpp b/bootloader/itoa.cpp new file mode 100644 index 00000000000..34aa6069281 --- /dev/null +++ b/bootloader/itoa.cpp @@ -0,0 +1,64 @@ +#include +#include +#include + +// https://www.techiedelight.com/implement-itoa-function-in-c/ + +// Function to swap two numbers +void swap(char *x, char *y) { + char t = *x; *x = *y; *y = t; +} + +// Function to reverse `buffer[i…j]` +char* reverse(char *buffer, int i, int j) +{ + while (i < j) { + swap(&buffer[i++], &buffer[j--]); + } + + return buffer; +} + +// Iterative function to implement `itoa()` function in C +char* Bootloader::Utility::itoa(int value, char* buffer, int base) +{ + // invalid input + if (base < 2 || base > 32) { + return buffer; + } + + // consider the absolute value of the number + int n = abs(value); + + int i = 0; + while (n) + { + int r = n % base; + + if (r >= 10) { + buffer[i++] = 65 + (r - 10); + } + else { + buffer[i++] = 48 + r; + } + + n = n / base; + } + + // if the number is 0 + if (i == 0) { + buffer[i++] = '0'; + } + + // If the base is 10 and the value is negative, the resulting string + // is preceded with a minus sign (-) + // With any other base, value is always considered unsigned + if (value < 0 && base == 10) { + buffer[i++] = '-'; + } + + buffer[i] = '\0'; // null terminate string + + // reverse the string and return it + return reverse(buffer, 0, i - 1); +} \ No newline at end of file diff --git a/bootloader/itoa.h b/bootloader/itoa.h new file mode 100644 index 00000000000..1a8602528c5 --- /dev/null +++ b/bootloader/itoa.h @@ -0,0 +1,11 @@ +#ifndef _BOOTLOADER_ITOA_H_ +#define _BOOTLOADER_ITOA_H_ + +namespace Bootloader { + class Utility { + public: + static char * itoa(int value, char * result, int base); + }; +} + +#endif // _BOOTLOADER_ITOA_H_ \ No newline at end of file diff --git a/bootloader/kernel_header.cpp b/bootloader/kernel_header.cpp index 7dac97a06b5..5b12d46f694 100644 --- a/bootloader/kernel_header.cpp +++ b/bootloader/kernel_header.cpp @@ -22,4 +22,18 @@ const void(*KernelHeader::startPointer() const)() { return m_startPointer; } +const bool KernelHeader::isNewVersion() const { + int sum = 0; + for (int i = 0; i < 2; i++) { + sum += m_version[i] * (5 - i); + } + char newVersion[] = "16"; + int min = 0; + for (int i = 0; i < 2; i++) { + min += newVersion[i] * (5 - i); + } + return sum >= min; +} + + } diff --git a/bootloader/kernel_header.h b/bootloader/kernel_header.h index 017036b62a8..c62c1590c6d 100644 --- a/bootloader/kernel_header.h +++ b/bootloader/kernel_header.h @@ -10,6 +10,7 @@ class KernelHeader { const char * version() const; const char * patchLevel() const; const bool isValid() const; + const bool isNewVersion() const; const uint32_t* stackPointer() const; const void(*startPointer() const)(); diff --git a/bootloader/main.cpp b/bootloader/main.cpp index 70c63213698..3b052bc518c 100644 --- a/bootloader/main.cpp +++ b/bootloader/main.cpp @@ -3,40 +3,47 @@ #include #include +#include +#include +#include +#include +#include __attribute__ ((noreturn)) void ion_main(int argc, const char * const argv[]) { // Clear the screen - Ion::Display::pushRectUniform(KDRect(0,0,320,240), KDColorBlack); + Ion::Display::pushRectUniform(KDRect(0,0,320,240), KDColorWhite); // Initialize the backlight Ion::Backlight::init(); - // Set the mode to slot A if undefined - if (Bootloader::Boot::mode() == Bootloader::BootMode::Unknown) { - Bootloader::Boot::setMode(Bootloader::BootMode::SlotA); + // We check if there is a slot in exam_mode + + bool isSlotA = Bootloader::Slot::A().kernelHeader()->isValid(); + + if (isSlotA) { + Bootloader::ExamMode::ExamMode SlotAExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotAExamMode(Bootloader::Slot::A().kernelHeader()->isNewVersion()); + if (SlotAExamMode != Bootloader::ExamMode::ExamMode::Off && SlotAExamMode != Bootloader::ExamMode::ExamMode::Unknown) { + // We boot the slot in exam_mode + Bootloader::Slot::A().boot(); + } } - // Handle rebooting to bootloader - if (Bootloader::Boot::mode() == Bootloader::BootMode::SlotABootloader) { - Bootloader::Boot::setMode(Bootloader::BootMode::SlotA); - Bootloader::Boot::bootloader(); - } else if (Bootloader::Boot::mode() == Bootloader::BootMode::SlotBBootloader) { - Bootloader::Boot::setMode(Bootloader::BootMode::SlotB); - Bootloader::Boot::bootloader(); + bool isSlotB = Bootloader::Slot::B().kernelHeader()->isValid(); + + if (isSlotB) { + Bootloader::ExamMode::ExamMode SlotBExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotBExamMode(Bootloader::Slot::B().kernelHeader()->isNewVersion()); + if (SlotBExamMode != Bootloader::ExamMode::ExamMode::Off && SlotBExamMode != Bootloader::ExamMode::ExamMode::Unknown && isSlotB) { + // We boot the slot in exam_mode + Bootloader::Slot::B().boot(); + } + } - uint64_t scan = Ion::Keyboard::scan(); - - // Reset+4 => Launch bootloader - if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Four)) { - Bootloader::Boot::bootloader(); - // Reset+1 => Launch slot A - } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::One)) { - Bootloader::Boot::setMode(Bootloader::BootMode::SlotA); - // Reset+2 => Launch slot B - } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Two)) { - Bootloader::Boot::setMode(Bootloader::BootMode::SlotB); + if (Bootloader::Recovery::has_crashed()) { + Bootloader::Recovery::recover_data(); } + Bootloader::Interface::drawLoading(); + // Boot the firmware Bootloader::Boot::boot(); } diff --git a/bootloader/messages.h b/bootloader/messages.h new file mode 100644 index 00000000000..1b18dcae325 --- /dev/null +++ b/bootloader/messages.h @@ -0,0 +1,60 @@ +#ifndef BOOTLOADER_INTERFACE_MESSAGES_H +#define BOOTLOADER_INTERFACE_MESSAGES_H + +namespace Bootloader { + +class Messages { +public: + // TODO: Remove it when this fork will be updated + #define UPSILON_VERSION "1.0.0-dev" + #ifdef UPSILON_VERSION + constexpr static const char * mainTitle = "Upsilon Calculator"; + #elif defined OMEGA_VERSION + constexpr static const char * mainTitle = "Omega Calculator"; + #else + constexpr static const char * mainTitle = "NumWorks Calculator"; + #endif + constexpr static const char * mainMenuTitle = "Select a slot"; + constexpr static const char * upsilon = "-- Upsilon "; + constexpr static const char * khi = "-- Khi "; + constexpr static const char * omega = "-- Omega "; + constexpr static const char * epsilon = "-- Epsilon "; + constexpr static const char * invalidSlot = "X - Invalid slot"; + constexpr static const char * dfuText = "4 - Installer Mode"; + constexpr static const char * aboutText = "5 - About"; + constexpr static const char * rebootText = "6 - Reboot"; + constexpr static const char * dfuSubtitle = "Waiting for Slots..."; + constexpr static const char * crashTitle = "BOOTLOADER CRASH"; + constexpr static const char * crashText1 = "The bootloader has crashed."; + constexpr static const char * crashText2 = "Please restart the calculator."; + constexpr static const char * recoveryTitle = "Recovery mode"; + constexpr static const char * recoveryText1 = "The bootloader has detected a crash."; + constexpr static const char * recoveryText2 = "Plug the calculator to a device capable of"; + constexpr static const char * recoveryText3 = "accessing the internal storage."; + constexpr static const char * recoveryText4 = "Press Back to continue."; + constexpr static const char * recoveryText5 = "(you will not be able to recover your data !)"; + constexpr static const char * installerSelectionTitle = "Installer Mode"; + constexpr static const char * installerText1 = "Please select a mode:"; + constexpr static const char * installerText2 = "1. Flash slots"; + constexpr static const char * installerText3 = "2. Flash the bootloader"; + constexpr static const char * bootloaderSubtitle = "Waiting for the bootloader..."; + constexpr static const char * epsilonWarningTitle = "Epsilon Slot"; + constexpr static const char * epsilonWarningText1 = "!! WARNING !! "; + constexpr static const char * epsilonWarningText2 = "This version of epsilon"; + constexpr static const char * epsilonWarningText3 = "can lock the calculator."; + constexpr static const char * epsilonWarningText4 = "Proceed the boot ?"; + constexpr static const char * epsilonWarningText5 = "EXE - Yes"; + constexpr static const char * epsilonWarningText6 = "BACK - No"; + constexpr static const char * bootloaderVersion = "Version 1.0.0 - FREEDOM"; + + //USB NAMES + + constexpr static const char * upsilonBootloader = "Upsilon Bootloader"; + constexpr static const char * upsilonRecovery = "Upsilon Recovery"; + constexpr static const char * bootloaderUpdate = "Bootloader Update"; + +}; + +}; + +#endif \ No newline at end of file diff --git a/bootloader/recovery.cpp b/bootloader/recovery.cpp new file mode 100644 index 00000000000..eb99bfbbcb9 --- /dev/null +++ b/bootloader/recovery.cpp @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +constexpr static uint32_t MagicStorage = 0xEE0BDDBA; + +void Bootloader::Recovery::crash_handler(const char *error) { + Ion::Device::Board::shutdownPeripherals(true); + Ion::Device::Board::initPeripherals(false); + Ion::Timing::msleep(100); + Ion::Backlight::init(); + Ion::Backlight::setBrightness(180); + + Ion::Display::pushRectUniform(KDRect(0,0,320,240), KDColorWhite); + Interface::drawCrash(error); + + while (true) { + + } +} + +bool Bootloader::Recovery::has_crashed() { + bool isA = Bootloader::Slot::A().kernelHeader()->isValid() && Bootloader::Slot::A().userlandHeader()->isValid(); + bool isB = Bootloader::Slot::B().kernelHeader()->isValid() && Bootloader::Slot::B().userlandHeader()->isValid(); + + bool isACrashed = false; + bool isBCrashed = false; + + if (isA) { + const uint32_t * storage = (uint32_t *)Bootloader::Slot::A().userlandHeader()->storageAddress(); + isACrashed = *storage == MagicStorage; + } + + if (isB) { + const uint32_t * storage = (uint32_t *)Bootloader::Slot::B().userlandHeader()->storageAddress(); + isBCrashed = *storage == MagicStorage; + } + + return (isACrashed || isBCrashed); +} + +Bootloader::Recovery::CrashedSlot Bootloader::Recovery::getSlotConcerned() { + bool isA = Bootloader::Slot::A().kernelHeader()->isValid() && Bootloader::Slot::A().userlandHeader()->isValid(); + bool isB = Bootloader::Slot::B().kernelHeader()->isValid() && Bootloader::Slot::B().userlandHeader()->isValid(); + + bool isACrashed = false; + bool isBCrashed = false; + + if (isA) { + const uint32_t * storage = (uint32_t *)Bootloader::Slot::A().userlandHeader()->storageAddress(); + isACrashed = *storage == MagicStorage; + } + + if (isB) { + const uint32_t * storage = (uint32_t *)Bootloader::Slot::B().userlandHeader()->storageAddress(); + isBCrashed = *storage == MagicStorage; + } + + assert(isACrashed || isBCrashed); + + if (isACrashed) { + return CrashedSlot(Bootloader::Slot::A().userlandHeader()->storageSize(), Bootloader::Slot::A().userlandHeader()->storageAddress()); + } else { + return CrashedSlot(Bootloader::Slot::B().userlandHeader()->storageSize(), Bootloader::Slot::B().userlandHeader()->storageAddress()); + } +} + +void Bootloader::Recovery::recover_data() { + Ion::Device::Board::initPeripherals(false); + Ion::Display::pushRectUniform(KDRect(0,0,320,240), KDColorWhite); + Ion::Backlight::init(); + Interface::drawRecovery(); + + uint64_t scan = 0; + + USBData udata = USBData::Recovery((uint32_t)getSlotConcerned().getStorageAddress(), (uint32_t)getSlotConcerned().getStorageSize()); + + while (scan != Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { + Ion::USB::enable(); + do { + scan = Ion::Keyboard::scan(); + if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { + Ion::USB::disable(); + } + } while (!Ion::USB::isEnumerated() && scan != Ion::Keyboard::State(Ion::Keyboard::Key::Back)); + + if (scan != Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { + Ion::USB::DFU(true, &udata); + } + } + + // Invalidate storage header + *(uint32_t *)(getSlotConcerned().getStorageAddress()) = (uint32_t)0x0; + +} + +void Bootloader::Recovery::debug() { + bool isA = Bootloader::Slot::A().kernelHeader()->isValid() && Bootloader::Slot::A().userlandHeader()->isValid(); + bool isB = Bootloader::Slot::B().kernelHeader()->isValid() && Bootloader::Slot::B().userlandHeader()->isValid(); + + if (isA) { + const uint32_t * storage = (uint32_t *)Bootloader::Slot::A().userlandHeader()->storageAddress(); + *(uint32_t *)(0x20000070) = *storage; + if (*storage == MagicStorage) { + *(uint32_t *)(0x20000070 + (sizeof(uint32_t))) = 0xDEADBEEF; + } + } + + if (isB) { + const uint32_t * storage = (uint32_t *)Bootloader::Slot::B().userlandHeader()->storageAddress(); + *(uint32_t *)(0x20000070 + 2*(sizeof(uint32_t))) = *storage; + if (*storage == MagicStorage) { + *(uint32_t *)(0x20000070 + 3*(sizeof(uint32_t))) = 0xDEADBEEF; + } + } + + if (has_crashed()) { + *(uint32_t *)(0x20000070 + 4*(sizeof(uint32_t))) = 0xDEADBEEF; + } else { + *(uint32_t *)(0x20000070 + 4*(sizeof(uint32_t))) = 0xBEEFDEAD; + } + +} diff --git a/bootloader/recovery.h b/bootloader/recovery.h new file mode 100644 index 00000000000..e9273bbfd1e --- /dev/null +++ b/bootloader/recovery.h @@ -0,0 +1,32 @@ +#ifndef BOOTLOADER_RECOVERY_H_ +#define BOOTLOADER_RECOVERY_H_ + +#include +#include + +namespace Bootloader { +class Recovery { + public: + class CrashedSlot { + public: + CrashedSlot(const size_t size, const void * address) : m_storageAddress(address), m_storageSize(size) {} + + const size_t getStorageSize() const { return m_storageSize; } + const void * getStorageAddress() const { return m_storageAddress; } + + private: + const void * m_storageAddress; + const size_t m_storageSize; + }; + + static CrashedSlot getSlotConcerned(); + + static void crash_handler(const char * error); + static void recover_data(); + static bool has_crashed(); + static void debug(); + +}; +}; + +#endif //BOOTLOADER_RECOVERY_H_ diff --git a/bootloader/slot.cpp b/bootloader/slot.cpp index b23655aedef..a6ffe0e1ddb 100644 --- a/bootloader/slot.cpp +++ b/bootloader/slot.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include extern "C" void jump_to_firmware(const uint32_t* stackPtr, const void(*startPtr)(void)); @@ -13,6 +15,10 @@ const Slot Slot::B() { return Slot(0x90400000); } +const Slot Slot::Khi() { + return Slot(0x90180000); +} + const KernelHeader* Slot::kernelHeader() const { return m_kernelHeader; } @@ -22,6 +28,17 @@ const UserlandHeader* Slot::userlandHeader() const { } [[ noreturn ]] void Slot::boot() const { + + if (m_address == 0x90000000) { + // If we are booting from slot A, we need to lock the slot B + Ion::Device::Flash::LockSlotB(); + } else { + // If we are booting from slot B, we need to lock the slot A (and Khi) + Ion::Device::Flash::LockSlotA(); + } + + Boot::lockInternal(); + // Configure the MPU for the booted firmware Ion::Device::Board::bootloaderMPU(); diff --git a/bootloader/slot.h b/bootloader/slot.h index 15a883f3948..f82b5b6d99e 100644 --- a/bootloader/slot.h +++ b/bootloader/slot.h @@ -13,7 +13,8 @@ class Slot { public: Slot(uint32_t address) : m_kernelHeader(reinterpret_cast(address)), - m_userlandHeader(reinterpret_cast(address + 64 * 1024)) { } + m_userlandHeader(reinterpret_cast(address + 64 * 1024)), + m_address(address) { } const KernelHeader* kernelHeader() const; const UserlandHeader* userlandHeader() const; @@ -21,10 +22,12 @@ class Slot { static const Slot A(); static const Slot B(); + static const Slot Khi(); private: const KernelHeader* m_kernelHeader; const UserlandHeader* m_userlandHeader; + const uint32_t m_address; }; diff --git a/bootloader/slot_exam_mode.cpp b/bootloader/slot_exam_mode.cpp new file mode 100644 index 00000000000..983a51fa847 --- /dev/null +++ b/bootloader/slot_exam_mode.cpp @@ -0,0 +1,166 @@ +#include +#include +#include +#include + +namespace Bootloader { +namespace ExamMode { + +/* The exam mode is written in flash so that it is resilient to resets. + * We erase the dedicated flash sector (all bits written to 1) and, upon + * deactivating or activating standard, nosym or Dutch exam mode we write one, two or tree + * bits to 0. To determine in which exam mode we are, we count the number of + * leading 0 bits. If it is equal to: + * - 0[3]: the exam mode is off; + * - 1[3]: the standard exam mode is activated; + * - 2[3]: the NoSym exam mode is activated; + * - 3[3]: the Dutch exam mode is activated; + * - 4[3]: the NoSymNoText exam mode is activated. */ + +/* significantExamModeAddress returns the first uint32_t * in the exam mode + * flash sector that does not point to 0. If this flash sector has only 0s or + * if it has only one 1, it is erased (to 1) and significantExamModeAddress + * returns the start of the sector. */ + +constexpr static size_t numberOfBitsInByte = 8; + +// if i = 0b000011101, firstOneBitInByte(i) returns 5 +size_t numberOfBitsAfterLeadingZeroes(int i) { + int minShift = 0; + int maxShift = numberOfBitsInByte; + while (maxShift > minShift+1) { + int shift = (minShift + maxShift)/2; + int shifted = i >> shift; + if (shifted == 0) { + maxShift = shift; + } else { + minShift = shift; + } + } + return maxShift; +} + +uint8_t * SignificantSlotAExamModeAddress(bool newVersion) { + uint32_t * persitence_start_32 = (uint32_t *)SlotsExamMode::getSlotAStartExamAddress(newVersion); + uint32_t * persitence_end_32 = (uint32_t *)SlotsExamMode::getSlotAEndExamAddress(newVersion); + if (!newVersion) { + assert((persitence_end_32 - persitence_start_32) % 4 == 0); + while (persitence_start_32 < persitence_end_32 && *persitence_start_32 == 0x0) { + // Scan by groups of 32 bits to reach first non-zero bit + persitence_start_32++; + } + uint8_t * persitence_start_8 = (uint8_t *)persitence_start_32; + uint8_t * persitence_end_8 = (uint8_t *)persitence_end_32; + while (persitence_start_8 < persitence_end_8 && *persitence_start_8 == 0x0) { + // Scan by groups of 8 bits to reach first non-zero bit + persitence_start_8++; + } + if (persitence_start_8 == persitence_end_8 + // we can't toggle from 0[3] to 2[3] when there is only one 1 bit in the whole sector + || (persitence_start_8 + 1 == persitence_end_8 && *persitence_start_8 == 1)) { + assert(Ion::Device::Flash::SectorAtAddress(SlotsExamMode::getSlotAStartExamAddress(newVersion)) >= 0); + Ion::Device::Flash::EraseSector(Ion::Device::Flash::SectorAtAddress(SlotsExamMode::getSlotAStartExamAddress(newVersion))); + return (uint8_t *)SlotsExamMode::getSlotAStartExamAddress(newVersion); + } + + return persitence_start_8; + } else { + persitence_end_32 = persitence_end_32 - 1; + while (persitence_end_32 - (uint32_t)(10 / 8) >= persitence_end_32 && *persitence_end_32 == 0xFFFFFFFF) { + persitence_end_32 -= 1; + } + uint8_t * start = reinterpret_cast(persitence_start_32); + uint8_t * end = reinterpret_cast(persitence_end_32 + 1) - 1; + while (end >= start + 2 && *end == 0xFF) { + end -= 1; + } + return end - 1; + } + +} + +uint8_t * SignificantSlotBExamModeAddress(bool newVersion) { + uint32_t * persitence_start_32 = (uint32_t *)SlotsExamMode::getSlotBStartExamAddress(newVersion); + uint32_t * persitence_end_32 = (uint32_t *)SlotsExamMode::getSlotBEndExamAddress(newVersion); + if (!newVersion) { + assert((persitence_end_32 - persitence_start_32) % 4 == 0); + while (persitence_start_32 < persitence_end_32 && *persitence_start_32 == 0x0) { + // Scan by groups of 32 bits to reach first non-zero bit + persitence_start_32++; + } + uint8_t * persitence_start_8 = (uint8_t *)persitence_start_32; + uint8_t * persitence_end_8 = (uint8_t *)persitence_end_32; + while (persitence_start_8 < persitence_end_8 && *persitence_start_8 == 0x0) { + // Scan by groups of 8 bits to reach first non-zero bit + persitence_start_8++; + } + if (persitence_start_8 == persitence_end_8 + // we can't toggle from 0[3] to 2[3] when there is only one 1 bit in the whole sector + || (persitence_start_8 + 1 == persitence_end_8 && *persitence_start_8 == 1)) { + assert(Ion::Device::Flash::SectorAtAddress(SlotsExamMode::getSlotBStartExamAddress(newVersion)) >= 0); + Ion::Device::Flash::EraseSector(Ion::Device::Flash::SectorAtAddress(SlotsExamMode::getSlotBStartExamAddress(newVersion))); + return (uint8_t *)SlotsExamMode::getSlotBStartExamAddress(newVersion); + } + + return persitence_start_8; + } else { + persitence_end_32 = persitence_end_32 - 1; + while (persitence_end_32 - (uint32_t)(10 / 8) >= persitence_end_32 && *persitence_end_32 == 0xFFFFFFFF) { + persitence_end_32 -= 1; + } + uint8_t * start = reinterpret_cast(persitence_start_32); + uint8_t * end = reinterpret_cast(persitence_end_32 + 1) - 1; + while (end >= start + 2 && *end == 0xFF) { + end -= 1; + } + return end - 1; + } + +} + +uint8_t SlotsExamMode::FetchSlotAExamMode(bool newVersion) { + uint8_t * readingAddress = SignificantSlotAExamModeAddress(newVersion); + if (!newVersion) { + // Count the number of 0[3] before reading address + uint32_t nbOfZerosBefore = ((readingAddress - (uint8_t *)getSlotAStartExamAddress(newVersion)) * numberOfBitsInByte) % 4; + // Count the number of 0[3] at reading address + size_t numberOfLeading0 = (numberOfBitsInByte - numberOfBitsAfterLeadingZeroes(*readingAddress)) % 4; + return (nbOfZerosBefore + numberOfLeading0) % 4; + } else { + return *((uint8_t *)readingAddress); + } + +} + +uint8_t SlotsExamMode::FetchSlotBExamMode(bool newVersion) { + uint8_t * readingAddress = SignificantSlotBExamModeAddress(newVersion); + if (!newVersion) { + // Count the number of 0[3] before reading address + uint32_t nbOfZerosBefore = ((readingAddress - (uint8_t *)getSlotBStartExamAddress(newVersion)) * numberOfBitsInByte) % 4; + // Count the number of 0[3] at reading address + size_t numberOfLeading0 = (numberOfBitsInByte - numberOfBitsAfterLeadingZeroes(*readingAddress)) % 4; + return (nbOfZerosBefore + numberOfLeading0) % 4; + } else { + return *((uint8_t *)readingAddress); + } + +} + +uint32_t SlotsExamMode::getSlotAStartExamAddress(bool newVersion) { + return newVersion ? SlotAExamModeBufferStartNewVersions : SlotAExamModeBufferStartOldVersions; +} + +uint32_t SlotsExamMode::getSlotAEndExamAddress(bool newVersion) { + return newVersion ? SlotAExamModeBufferEndNewVersions : SlotAExamModeBufferEndOldVersions; +} + +uint32_t SlotsExamMode::getSlotBStartExamAddress(bool newVersion) { + return newVersion ? SlotBExamModeBufferStartNewVersions : SlotBExamModeBufferStartOldVersions; +} + +uint32_t SlotsExamMode::getSlotBEndExamAddress(bool newVersion) { + return newVersion ? SlotBExamModeBufferEndNewVersions : SlotBExamModeBufferEndOldVersions; +} + +} +} diff --git a/bootloader/slot_exam_mode.h b/bootloader/slot_exam_mode.h new file mode 100644 index 00000000000..7c93eed7215 --- /dev/null +++ b/bootloader/slot_exam_mode.h @@ -0,0 +1,47 @@ +#ifndef BOOTLOADER_EXAM_MODE_H +#define BOOTLOADER_EXAM_MODE_H + +extern "C" { + #include + } + +namespace Bootloader { +namespace ExamMode { + +static const uint32_t SlotAExamModeBufferStartOldVersions = 0x90001000; +static const uint32_t SlotAExamModeBufferEndOldVersions = 0x90003000; + +static const uint32_t SlotAExamModeBufferStartNewVersions = 0x903f0000; +static const uint32_t SlotAExamModeBufferEndNewVersions = 0x90400000; + +static const uint32_t SlotBExamModeBufferStartOldVersions = 0x90401000; +static const uint32_t SlotBExamModeBufferEndOldVersions = 0x90403000; + +static const uint32_t SlotBExamModeBufferStartNewVersions = 0x907f0000; +static const uint32_t SlotBExamModeBufferEndNewVersions = 0x90800000; + +class SlotsExamMode{ + public: + static uint8_t FetchSlotAExamMode(bool newVersion); + static uint8_t FetchSlotBExamMode(bool newVerion); + + static uint32_t getSlotAStartExamAddress(bool newVersion); + static uint32_t getSlotAEndExamAddress(bool newVersion); + static uint32_t getSlotBStartExamAddress(bool newVersion); + static uint32_t getSlotBEndExamAddress(bool newVersion); + +}; + +enum class ExamMode : int8_t { + Unknown = -1, + Off = 0, + Standard = 1, + NoSym = 2, + NoSymNoText = 3, + Dutch = 4, + }; + +} +} + +#endif diff --git a/bootloader/trampoline.cpp b/bootloader/trampoline.cpp index 4d1013f3491..876db049652 100644 --- a/bootloader/trampoline.cpp +++ b/bootloader/trampoline.cpp @@ -36,6 +36,7 @@ void* CustomTrampolines[CUSTOM_TRAMPOLINES_COUNT] __attribute__((used)) = { (void*) Bootloader::Boot::mode, + // This function doesn't do anything ... (void*) Bootloader::Boot::setMode }; diff --git a/bootloader/usb_data.cpp b/bootloader/usb_data.cpp new file mode 100644 index 00000000000..2570b80eacd --- /dev/null +++ b/bootloader/usb_data.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +} + +static char data[255]; + +const char * Bootloader::USBData::buildStringDescriptor(StringHeader header, uint32_t startAddress, uint32_t size) { + strlcpy(data, header.getString(), sizeof(data)); + Bootloader::Utility::itoa((int32_t)startAddress, &data[strlen(header.getString())], 16); + data[strlen(header.getString()) + 8] = '/'; + data[strlen(header.getString()) + 8 + 1] = '0'; + data[strlen(header.getString()) + 8 + 2] = '1'; + data[strlen(header.getString()) + 8 + 3] = '*'; + data[strlen(header.getString()) + 8 + 4] = '0'; + Bootloader::Utility::itoa((int32_t)size/1024, &data[strlen(header.getString()) + 8 + 5], 10); + data[strlen(header.getString()) + 8 + 5 + 2] = 'K'; + data[strlen(header.getString()) + 8 + 5 + 2 + 1] = 'g'; + data[strlen(header.getString()) + 8 + 5 + 2 + 2] = '\0'; + return &data[0]; +} + +const Bootloader::USBData Bootloader::USBData::DEFAULT() { + return USBData("@Flash/0x90000000/08*004Kg,01*032Kg,63*064Kg,64*064Kg", Messages::upsilonBootloader, DFUData()); +} + +const Bootloader::USBData Bootloader::USBData::BLUPDATE() { + return USBData("@Flash/0x08000000/04*016Kg", Messages::bootloaderUpdate, DFUData(true, false)); +} + +Bootloader::USBData Bootloader::USBData::Recovery(uint32_t startAddress, uint32_t size) { + return USBData(buildStringDescriptor(StringHeader::SRAM(), startAddress, size), Messages::upsilonRecovery, DFUData(false, false)); +} \ No newline at end of file diff --git a/bootloader/usb_data.h b/bootloader/usb_data.h new file mode 100644 index 00000000000..2fba6a27d92 --- /dev/null +++ b/bootloader/usb_data.h @@ -0,0 +1,56 @@ +#ifndef BOOTLOADER_USB_DATA_H_ +#define BOOTLOADER_USB_DATA_H_ + +#include +#include + +namespace Bootloader { + +class DFUData { + public: + DFUData(bool unlockInternal = false, bool unlockExternal = true) : m_protectInternal(!unlockInternal), m_protectExternal(!unlockExternal) {}; + + bool isProtectedInternal() const { return m_protectInternal; } + bool isProtectedExternal() const { return m_protectExternal; } + + private: + bool m_protectInternal; + bool m_protectExternal; +}; + +class USBData { + public: + class StringHeader{ + public: + StringHeader(const char * string) : m_string(string) {}; + + const char * getString() const { return m_string; } + + static const StringHeader Flash() { return StringHeader("@Flash/0x"); } + static const StringHeader SRAM() { return StringHeader("@SRAM/0x"); } + + private: + const char * m_string; + }; + + USBData(const char * desc, const char * name, DFUData data = DFUData()) : m_stringDescriptor(desc), m_name(name), m_data(&data) {}; + + const char * stringDescriptor() const { return m_stringDescriptor; } + const char * getName() const { return m_name; } + DFUData * getData() const { return m_data; } + + static const char * buildStringDescriptor(StringHeader header, uint32_t startAddress, uint32_t size); + + static const USBData DEFAULT(); + static const USBData BLUPDATE(); + static USBData Recovery(uint32_t startAddress, uint32_t size); + + private: + const char * m_stringDescriptor; + const char * m_name; + DFUData * m_data; + +}; +} + +#endif //BOOTLOADER_USB_DATA_H_ \ No newline at end of file diff --git a/bootloader/userland_header.cpp b/bootloader/userland_header.cpp index 349d7708a83..a86a62ce8ef 100644 --- a/bootloader/userland_header.cpp +++ b/bootloader/userland_header.cpp @@ -14,7 +14,7 @@ const bool UserlandHeader::isValid() const { } const bool UserlandHeader::isOmega() const { - return m_omegaMagicHeader == OmegaMagic && m_omegaMagicFooter == OmegaMagic; + return m_ohm_header == OmegaMagic && m_ohm_footer == OmegaMagic; } @@ -23,11 +23,19 @@ const char * UserlandHeader::omegaVersion() const { } const bool UserlandHeader::isUpsilon() const { - return m_upsilonMagicHeader == UpsilonMagic && m_upsilonMagicFooter == UpsilonMagic; + return m_ups_header == UpsilonMagic && m_ups_footer == UpsilonMagic; } const char * UserlandHeader::upsilonVersion() const { return m_UpsilonVersion; } +const void * UserlandHeader::storageAddress() const { + return m_storageAddressRAM; +} + +const size_t UserlandHeader::storageSize() const { + return m_storageSizeRAM; +} + } diff --git a/bootloader/userland_header.h b/bootloader/userland_header.h index dbcfb453934..27359b17606 100644 --- a/bootloader/userland_header.h +++ b/bootloader/userland_header.h @@ -15,6 +15,8 @@ class UserlandHeader { const char * omegaVersion() const; const bool isUpsilon() const; const char * upsilonVersion() const; + const void * storageAddress() const; + const size_t storageSize() const; private: UserlandHeader(); @@ -32,14 +34,14 @@ class UserlandHeader { uint32_t m_externalAppsRAMStart; uint32_t m_externalAppsRAMEnd; uint32_t m_footer; - uint32_t m_omegaMagicHeader; + uint32_t m_ohm_header; const char m_omegaVersion[16]; const volatile char m_username[16]; - uint32_t m_omegaMagicFooter; - uint32_t m_upsilonMagicHeader; + uint32_t m_ohm_footer; + uint32_t m_ups_header; const char m_UpsilonVersion[16]; uint32_t m_osType; - uint32_t m_upsilonMagicFooter; + uint32_t m_ups_footer; }; extern const UserlandHeader* s_userlandHeaderA; diff --git a/build/device/secure_ext.py b/build/device/secure_ext.py index 38c555a65de..4d052680051 100644 --- a/build/device/secure_ext.py +++ b/build/device/secure_ext.py @@ -3,6 +3,8 @@ MAGIK_CODE = [0x32, 0x30, 0x30, 0x36] MAGIK_POS = 0x44F +# Disable Script +sys.exit(0) if len(sys.argv) > 1: ext_path = os.path.join(os.getcwd(), sys.argv[1]) diff --git a/build/targets.device.n0110.mak b/build/targets.device.n0110.mak index 570ae0b31e5..c78249b4507 100644 --- a/build/targets.device.n0110.mak +++ b/build/targets.device.n0110.mak @@ -1,14 +1,14 @@ HANDY_TARGETS += test.external_flash.write test.external_flash.read bootloader $(BUILD_DIR)/test.external_flash.%.$(EXE): LDSCRIPT = ion/test/device/n0110/external_flash_tests.ld -test_external_flash_src = $(ion_src) $(liba_src) $(libaxx_src) $(default_kandinsky_src) $(poincare_src) $(ion_device_dfu_relegated_src) $(runner_src) +test_external_flash_src = $(ion_src) $(liba_src) $(libaxx_src) $(default_kandinsky_src) $(poincare_src) $(ion_device_dfu_relogated_src) $(runner_src) $(BUILD_DIR)/test.external_flash.read.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_external_flash_read_symbols.o $(call object_for,$(test_external_flash_src) $(test_ion_external_flash_read_src)) $(BUILD_DIR)/test.external_flash.write.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_external_flash_write_symbols.o $(call object_for,$(test_external_flash_src) $(test_ion_external_flash_write_src)) .PHONY: bootloader bootloader: $(BUILD_DIR)/bootloader.bin $(BUILD_DIR)/bootloader.$(EXE): $(call flavored_object_for,$(bootloader_src),usbxip) -$(BUILD_DIR)/bootloader.$(EXE): LDSCRIPT = ion/src/device/n0110/internal_flash.ld +$(BUILD_DIR)/bootloader.$(EXE): LDSCRIPT = ion/src/device/bootloader/internal_flash.ld .PHONY: %_flash %_flash: $(BUILD_DIR)/%.dfu @@ -36,4 +36,3 @@ binpack: $(BUILD_DIR)/flasher.light.bin $(BUILD_DIR)/epsilon.onboarding.two_bina cp $(BUILD_DIR)/epsilon.onboarding.internal.bin $(BUILD_DIR)/epsilon.onboarding.external.bin $(BUILD_DIR)/binpack cd $(BUILD_DIR) && for binary in flasher.light.bin epsilon.onboarding.internal.bin epsilon.onboarding.external.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done cd $(BUILD_DIR) && tar cvfz binpack-$(MODEL)-`git rev-parse HEAD | head -c 7`.tgz binpack/* - $(PYTHON) build/device/secure_ext.py $(BUILD_DIR)/epsilon.onboarding.external.bin diff --git a/ion/include/ion/internal_storage.h b/ion/include/ion/internal_storage.h index 07287cf4090..a437b4a2e8b 100644 --- a/ion/include/ion/internal_storage.h +++ b/ion/include/ion/internal_storage.h @@ -140,8 +140,6 @@ class InternalStorage { }; RecordIterator end() const { return RecordIterator(nullptr); } - mutable Record m_lastRecordRetrieved; - mutable char * m_lastRecordRetrievedPointer; private: constexpr static uint32_t Magic = 0xEE0BDDBA; constexpr static size_t k_maxRecordSize = (1 << sizeof(record_size_t)*8); @@ -174,6 +172,9 @@ class InternalStorage { char m_buffer[k_storageSize]; uint32_t m_magicFooter; StorageDelegate * m_delegate; +public: + mutable Record m_lastRecordRetrieved; + mutable char * m_lastRecordRetrievedPointer; }; /* Some apps memoize records and need to be notified when a record might have diff --git a/ion/include/ion/usb.h b/ion/include/ion/usb.h index d9ec8afd7f5..6650d79c377 100644 --- a/ion/include/ion/usb.h +++ b/ion/include/ion/usb.h @@ -8,7 +8,7 @@ bool isPlugged(); bool isEnumerated(); // Speed-enumerated, to be accurate void clearEnumerationInterrupt(); -void DFU(bool exitWithKeyboard = true, bool unlocked = false, int level = 0); +void DFU(bool exitWithKeyboard = true, void * data = nullptr); void enable(); void disable(); diff --git a/ion/src/device/Makefile b/ion/src/device/Makefile index 37943dbe213..cc5594242de 100644 --- a/ion/src/device/Makefile +++ b/ion/src/device/Makefile @@ -10,7 +10,7 @@ ifeq ($(EPSILON_TELEMETRY),1) ion_src += ion/src/shared/telemetry_console.cpp endif -ion_device_src += ion/src/shared/collect_registers.cpp +ion_src += ion/src/shared/collect_registers.cpp IN_FACTORY ?= 0 diff --git a/ion/src/device/bootloader/boot/rt0.cpp b/ion/src/device/bootloader/boot/rt0.cpp index cd4bee46f57..a631356bbab 100644 --- a/ion/src/device/bootloader/boot/rt0.cpp +++ b/ion/src/device/bootloader/boot/rt0.cpp @@ -6,15 +6,6 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include typedef void (*cxx_constructor)(); @@ -32,6 +23,15 @@ extern "C" { extern char _isr_vector_table_end_ram; } +void __attribute__((noinline)) abort() { +#ifdef NDEBUG + Ion::Device::Reset::core(); +#else + while (1) { + } +#endif +} + /* In order to ensure that this method is execute from the external flash, we * forbid inlining it.*/ @@ -68,153 +68,6 @@ static void __attribute__((noinline)) jump_to_external_flash() { external_flash_start(); } -void __attribute__((noinline)) abort_init() { - Ion::Device::Board::shutdownPeripherals(true); - Ion::Device::Board::initPeripherals(false); - Ion::Timing::msleep(100); - Ion::Backlight::init(); - Ion::LED::setColor(KDColorRed); - Ion::Backlight::setBrightness(180); -} - -void __attribute__((noinline)) abort_economy() { - int brightness = Ion::Backlight::brightness(); - bool plugged = Ion::USB::isPlugged(); - while (brightness > 0) { - brightness--; - Ion::Backlight::setBrightness(brightness); - Ion::Timing::msleep(50); - if(plugged || (!plugged && Ion::USB::isPlugged())){ - Ion::Backlight::setBrightness(180); - return; - } - } - Ion::Backlight::shutdown(); - while (1) { - Ion::Device::Power::sleepConfiguration(); - Ion::Device::WakeUp::onUSBPlugging(); - Ion::Device::WakeUp::onChargingEvent(); - Ion::Device::Power::internalFlashSuspend(true); - if (!plugged && Ion::USB::isPlugged()) { - break; - } - plugged = Ion::USB::isPlugged(); - }; - Ion::Device::Board::setStandardFrequency(Ion::Device::Board::Frequency::High); - Ion::Backlight::init(); - Ion::Backlight::setBrightness(180); -} - -void __attribute__((noinline)) abort_sleeping() { - if (Ion::Battery::level() != Ion::Battery::Charge::EMPTY) { - return; - } - // we don't use Ion::Power::suspend because we don't want to move the exam buffer into the internal - Ion::Device::Board::shutdownPeripherals(true); - bool plugged = Ion::USB::isPlugged(); - while (1) { - Ion::Device::Battery::initGPIO(); - Ion::Device::USB::initGPIO(); - Ion::Device::LED::init(); - Ion::Device::Power::sleepConfiguration(); - Ion::Device::Board::shutdownPeripherals(true); - Ion::Device::WakeUp::onUSBPlugging(); - Ion::Device::WakeUp::onChargingEvent(); - Ion::Device::Power::internalFlashSuspend(true); - Ion::Device::USB::initGPIO(); - if (!plugged && Ion::USB::isPlugged()) { - break; - } - plugged = Ion::USB::isPlugged(); - } - Ion::Device::Board::setStandardFrequency(Ion::Device::Board::Frequency::High); - abort_init(); -} - -void __attribute__((noinline)) abort_core(const char * text) { - Ion::Timing::msleep(100); - int counting; - while (true) { - counting = 0; - if (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) { - abort_sleeping(); - abort_screen(text); - } - Ion::USB::enable(); - Ion::Battery::Charge previous_state = Ion::Battery::level(); - while (!Ion::USB::isEnumerated()) { - if (Ion::Battery::level() == Ion::Battery::Charge::LOW) { - if (previous_state != Ion::Battery::Charge::LOW) { - previous_state = Ion::Battery::Charge::LOW; - counting = 0; - } - Ion::Timing::msleep(500); - if (counting >= 20) { - abort_sleeping(); - abort_screen(text); - counting = -1; - } - counting++; - } else { - if (previous_state == Ion::Battery::Charge::LOW) { - previous_state = Ion::Battery::level(); - counting = 0; - } - Ion::Timing::msleep(100); - if (counting >= 300) { - abort_economy(); - counting = -1; - } - counting++; - } - } - Ion::USB::DFU(false, false, 0); - } -} - -void __attribute__((noinline)) abort_screen(const char * text){ - KDRect screen = KDRect(0, 0, Ion::Display::Width, Ion::Display::Height); - Ion::Display::pushRectUniform(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height), KDColor::RGB24(0xffffff)); - KDContext* ctx = KDIonContext::sharedContext(); - ctx->setOrigin(KDPointZero); - ctx->setClippingRect(screen); - ctx->drawString("UPSILON CRASH", KDPoint(90, 10), KDFont::LargeFont, KDColorRed, KDColor::RGB24(0xffffff)); - ctx->drawString("An error occurred", KDPoint(10, 30), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("If you have some important data, please", KDPoint(10, 45), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("use bit.ly/upsiBackup to backup them.", KDPoint(10, 60), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("YOU WILL LOSE ALL YOUR DATA", KDPoint(10, 85), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("→ You can try to reboot by presssing the", KDPoint(10, 110), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("reset button at the back of the calculator", KDPoint(10, 125), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("→ If Upsilon keeps crashing, you can connect", KDPoint(10, 140), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("the calculator to a computer or a phone", KDPoint(10, 160), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("and try to reinstall Upsilon", KDPoint(10, 175), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString(text, KDPoint(220, 200), KDFont::SmallFont, KDColorRed, KDColor::RGB24(0xffffff)); -} - -void __attribute__((noinline)) abort() { - abort_init(); - abort_screen("HARDFAULT"); - abort_core("HARDFAULT"); -} - -void __attribute__((noinline)) nmi_abort() { - abort_init(); - abort_screen("NMIFAULT"); - abort_core("NMIFAULT"); -} - -void __attribute__((noinline)) bf_abort() { - abort_init(); - abort_screen("BUSFAULT"); - abort_core("BUSFAULT"); -} - -void __attribute__((noinline)) uf_abort() { - abort_init(); - abort_screen("USAGEFAULT"); - abort_core("USAGEFAULT"); -} - /* When 'start' is executed, the external flash is supposed to be shutdown. We * thus forbid inlining to prevent executing this code from external flash * (just in case 'start' was to be called from the external flash). */ @@ -225,6 +78,8 @@ void __attribute__((noinline)) start() { /* Initialize the FPU as early as possible. * For example, static C++ objects are very likely to manipulate float values */ + Ion::Device::Board::initFPU(); + /* Copy data section to RAM * The data section is R/W but its initialization value matters. It's stored * in Flash, but linked as if it were in RAM. Now's our opportunity to copy diff --git a/ion/src/device/bootloader/drivers/external_flash.cpp b/ion/src/device/bootloader/drivers/external_flash.cpp index 612efc01796..5e38449852e 100644 --- a/ion/src/device/bootloader/drivers/external_flash.cpp +++ b/ion/src/device/bootloader/drivers/external_flash.cpp @@ -89,10 +89,15 @@ class ExternalFlashStatusRegister { public: using Register8::Register8; REGS_BOOL_FIELD_R(BUSY, 0); + REGS_BOOL_FIELD(BP, 2); + REGS_BOOL_FIELD(BP1, 3); + REGS_BOOL_FIELD(BP2, 4); + REGS_BOOL_FIELD(TB, 5); }; class StatusRegister2 : public Register8 { public: using Register8::Register8; + REGS_BOOL_FIELD(SRP1, 0); REGS_BOOL_FIELD(QE, 1); }; }; @@ -428,6 +433,46 @@ void unlockFlash() { wait(); } +void LockSlotA() { + unset_memory_mapped_mode(); + unlockFlash(); + send_command(Command::WriteEnable); + wait(); + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + ExternalFlashStatusRegister::StatusRegister2 currentStatusRegister2(0); + send_read_command(Command::ReadStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(¤tStatusRegister2), sizeof(currentStatusRegister2)); + statusRegister2.setQE(currentStatusRegister2.getQE()); + statusRegister2.setSRP1(true); + statusRegister1.setTB(true); + statusRegister1.setBP2(true); + statusRegister1.setBP1(true); + uint8_t registers[] = {statusRegister1.get(), statusRegister2.get()}; + send_write_command(Command::WriteStatusRegister, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(registers), sizeof(registers), sOperatingModes101); + wait(); + set_as_memory_mapped(); +} + +void LockSlotB() { + unset_memory_mapped_mode(); + unlockFlash(); + send_command(Command::WriteEnable); + wait(); + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + ExternalFlashStatusRegister::StatusRegister2 currentStatusRegister2(0); + send_read_command(Command::ReadStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(¤tStatusRegister2), sizeof(currentStatusRegister2)); + statusRegister2.setQE(currentStatusRegister2.getQE()); + statusRegister2.setSRP1(true); + statusRegister1.setTB(false); + statusRegister1.setBP2(true); + statusRegister1.setBP1(true); + uint8_t registers[] = {statusRegister1.get(), statusRegister2.get()}; + send_write_command(Command::WriteStatusRegister, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(registers), sizeof(registers), sOperatingModes101); + wait(); + set_as_memory_mapped(); +} + void MassErase() { if (Config::NumberOfSectors == 0) { return; diff --git a/ion/src/device/bootloader/drivers/external_flash_tramp.cpp b/ion/src/device/bootloader/drivers/external_flash_tramp.cpp index 2a98b5bf6a6..6ead8bc270d 100644 --- a/ion/src/device/bootloader/drivers/external_flash_tramp.cpp +++ b/ion/src/device/bootloader/drivers/external_flash_tramp.cpp @@ -378,6 +378,13 @@ void WriteMemory(uint8_t * destination, const uint8_t * source, size_t length) { void EraseSector(int i) { asm("cpsid if"); (*reinterpret_cast(Ion::Device::Trampoline::address(Ion::Device::Trampoline::ExternalFlashEraseSector)))(i); + asm("cpsie if"); +} + +void LockSlotA() { +} + +void LockSlotB() { } } diff --git a/ion/src/device/bootloader/drivers/usb_desc.cpp b/ion/src/device/bootloader/drivers/usb_desc.cpp new file mode 100644 index 00000000000..be49306000b --- /dev/null +++ b/ion/src/device/bootloader/drivers/usb_desc.cpp @@ -0,0 +1,12 @@ + +namespace Ion { +namespace Device { +namespace USB { + +const char* stringDescriptor() { + return "@Flash/0x90000000/08*004Kg,01*032Kg,63*064Kg,64*064Kg"; +} + +} +} +} diff --git a/ion/src/device/bootloader/internal_flash.ld b/ion/src/device/bootloader/internal_flash.ld new file mode 100644 index 00000000000..ebbf98a5198 --- /dev/null +++ b/ion/src/device/bootloader/internal_flash.ld @@ -0,0 +1,123 @@ +/* Same as flash.ld but everything is linked in internal flash */ + +MEMORY { + INTERNAL_FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K + SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K +} + +STACK_SIZE = 32K; +TRAMPOLINES_OFFSET = 0xE000; +CUSTOM_TRAMPOLINES_OFFSET = 64K - 64; + +SECTIONS { + .isr_vector_table ORIGIN(INTERNAL_FLASH) : { + /* When booting, the STM32F412 fetches the content of address 0x0, and + * extracts from it various key infos: the initial value of the PC register + * (program counter), the initial value of the stack pointer, and various + * entry points to interrupt service routines. This data is called the ISR + * vector table. + * + * Note that address 0x0 is always an alias. It points to the beginning of + * Flash, SRAM, or integrated bootloader depending on the boot mode chosen. + * (This mode is chosen by setting the BOOTn pins on the chip). + * + * We're generating the ISR vector table in code because it's very + * convenient: using function pointers, we can easily point to the service + * routine for each interrupt. */ + KEEP(*(.isr_vector_table)) + } >INTERNAL_FLASH + + .header : { + KEEP(*(.header)) + } >INTERNAL_FLASH + + .rodata : { + . = ALIGN(4); + *(.rodata) + *(.rodata.*) + } >INTERNAL_FLASH + + .exam_mode_buffer : { + _exam_mode_buffer_start = .; + KEEP(*(.exam_mode_buffer)) + /* We don't set it because we will not use it */ + /* . = ORIGIN(INTERNAL_FLASH) + FLASH_SECOND_SECTOR_OFFSET + FLASH_SECOND_SECTOR_SIZE; */ + _exam_mode_buffer_end = .; + } >INTERNAL_FLASH + + .text : { + . = ALIGN(4); + *(.text) + *(.text.*) + } >INTERNAL_FLASH + + .init_array : { + . = ALIGN(4); + _init_array_start = .; + KEEP (*(.init_array*)) + _init_array_end = .; + } >INTERNAL_FLASH + + .data : { + /* The data section is written to Flash but linked as if it were in RAM. + * + * This is required because its initial value matters (so it has to be in + * persistant memory in the first place), but it is a R/W area of memory + * so it will have to live in RAM upon execution (in linker lingo, that + * translates to the data section having a LMA in Flash and a VMA in RAM). + * + * This means we'll have to copy it from Flash to RAM on initialization. + * To do this, we'll need to know the source location of the data section + * (in Flash), the target location (in RAM), and the size of the section. + * That's why we're defining three symbols that we'll use in the initial- + * -ization routine. */ + . = ALIGN(4); + _data_section_start_flash = LOADADDR(.data); + _data_section_start_ram = .; + *(.data) + *(.data.*) + _data_section_end_ram = .; + } >SRAM AT> INTERNAL_FLASH + + .trampolines_table : { + . = ORIGIN(INTERNAL_FLASH) + TRAMPOLINES_OFFSET; + KEEP(*(.trampolines_table)); + } > INTERNAL_FLASH + + .custom_trampolines_table : { + . = ORIGIN(INTERNAL_FLASH) + CUSTOM_TRAMPOLINES_OFFSET; + KEEP(*(.custom_trampolines_table)); + } > INTERNAL_FLASH + + .bss : { + /* The bss section contains data for all uninitialized variables + * So like the .data section, it will go in RAM, but unlike the data section + * we don't care at all about an initial value. + * + * Before execution, crt0 will erase that section of memory though, so we'll + * need pointers to the beginning and end of this section. */ + . = ALIGN(4); + _bss_section_start_ram = .; + *(.bss) + *(.bss.*) + /* The compiler may choose to allocate uninitialized global variables as + * COMMON blocks. This can be disabled with -fno-common if needed. */ + *(COMMON) + _bss_section_end_ram = .; + } >SRAM + + .heap : { + _heap_start = .; + /* Note: We don't increment "." here, we set it. */ + . = (ORIGIN(SRAM) + LENGTH(SRAM) - STACK_SIZE); + _heap_end = .; + } >SRAM + + .stack : { + . = ALIGN(8); + _stack_end = .; + . += (STACK_SIZE - 8); + . = ALIGN(8); + _stack_start = .; + } >SRAM +} diff --git a/ion/src/device/bootloader/usb/Makefile b/ion/src/device/bootloader/usb/Makefile new file mode 100644 index 00000000000..e2b51e2a212 --- /dev/null +++ b/ion/src/device/bootloader/usb/Makefile @@ -0,0 +1,99 @@ +# USB code + +ion_device_usb_src += $(addprefix ion/src/device/bootloader/usb/, \ + calculator.cpp \ + dfu_interface.cpp\ +) + +ion_device_usb_src += $(addprefix ion/src/device/bootloader/usb/stack/, \ + device.cpp\ + endpoint0.cpp \ + interface.cpp\ + request_recipient.cpp\ + setup_packet.cpp\ + streamable.cpp\ +) + +ion_device_usb_src += $(addprefix ion/src/device/bootloader/usb/stack/descriptor/, \ + bos_descriptor.cpp\ + configuration_descriptor.cpp \ + descriptor.cpp\ + device_descriptor.cpp\ + device_capability_descriptor.cpp\ + dfu_functional_descriptor.cpp\ + extended_compat_id_descriptor.cpp \ + interface_descriptor.cpp\ + language_id_string_descriptor.cpp \ + microsoft_os_string_descriptor.cpp\ + platform_device_capability_descriptor.cpp\ + string_descriptor.cpp\ + url_descriptor.cpp\ + webusb_platform_descriptor.cpp\ +) + +# DFU code + +ion_device_dfu_src += liba/src/abs.c +ion_device_dfu_src += liba/src/assert.c +ion_device_dfu_src += liba/src/strlen.c +ion_device_dfu_src += liba/src/strlcpy.c +ion_device_dfu_src += liba/src/memset.c +ion_device_dfu_src += liba/src/memcpy.c +ion_device_dfu_src += libaxx/src/cxxabi/pure_virtual.cpp +ion_device_dfu_src += ion/src/device/bootloader/usb/boot.cpp +ion_device_dfu_src += ion/src/device/n0110/drivers/board.cpp +ion_device_dfu_src += ion/src/device/n0110/drivers/cache.cpp +ion_device_dfu_src += ion/src/device/n0110/drivers/external_flash.cpp +ion_device_dfu_src += ion/src/device/n0110/drivers/reset.cpp +ion_device_dfu_src += ion/src/device/n0110/drivers/usb.cpp +ion_device_dfu_src += $(addprefix ion/src/device/shared/drivers/, \ + backlight.cpp \ + battery.cpp \ + base64.cpp \ + board.cpp \ + console_uart.cpp \ + crc32.cpp \ + display.cpp \ + events_keyboard_platform.cpp \ + flash.cpp \ + internal_flash.cpp \ + keyboard.cpp \ + led.cpp \ + power.cpp\ + random.cpp\ + reset.cpp \ + serial_number.cpp \ + swd.cpp \ + timing.cpp \ + usb.cpp \ + usb_desc.cpp \ + wakeup.cpp \ +) + +# Sources required to execute DFU in place +ion_device_src += ion/src/device/bootloader/usb/dfu_xip.cpp:+usbxip +ion_device_src += $(addsuffix :+usbxip,$(ion_device_usb_src)) + +# Sources required to execute DFU in RAM + +$(BUILD_DIR)/ion/src/device/bootloader/usb/dfu.elf: LDSCRIPT = ion/src/device/bootloader/usb/dfu.ld +$(BUILD_DIR)/ion/src/device/bootloader/usb/dfu.elf: $(call object_for,$(ion_device_usb_src) $(ion_device_dfu_src)) + +# In order to link the dfu bootloader inside the epsilon firmware, we need to +# turn the dfu binary (dfu.bin) into an elf object. +# By default, 'objcpy' generates a section 'data' and two symbols to the +# start and the end of the binary input respectively named: +# - '_binary_[file name]_[file extension]_start' +# - '_binary_[file name]_[file extension]_end'. +# For our purpose, dfu.o can go in rodata section and we rename the start and +# end of binary symbols: _dfu_bootloader_flash_[start/end] +$(BUILD_DIR)/ion/src/device/bootloader/usb/dfu.o: $(BUILD_DIR)/ion/src/device/bootloader/usb/dfu.bin + $(call rule_label,OBJCOPY) + $(Q) cd $(dir $<) ; $(OBJCOPY) -I binary -O elf32-littlearm -B arm --rename-section .data=.rodata.dfu_bootloader --redefine-sym _binary_dfu_bin_start=_dfu_bootloader_flash_start --redefine-sym _binary_dfu_bin_end=_dfu_bootloader_flash_end $(notdir $<) $(notdir $@) + +ion_device_src += ion/src/device/bootloader/usb/dfu.cpp:-usbxip +ion_device_src += ion/src/device/bootloader/usb/dfu_relocated.cpp:-usbxip + +ion_device_src += $(addprefix ion/src/device/bootloader/drivers/, \ + usb_desc.cpp \ +) \ No newline at end of file diff --git a/ion/src/device/bootloader/usb/boot.cpp b/ion/src/device/bootloader/usb/boot.cpp new file mode 100644 index 00000000000..0eb8d71d608 --- /dev/null +++ b/ion/src/device/bootloader/usb/boot.cpp @@ -0,0 +1,2 @@ +extern "C" void abort() { +} diff --git a/ion/src/device/bootloader/usb/calculator.cpp b/ion/src/device/bootloader/usb/calculator.cpp new file mode 100644 index 00000000000..db8d8df3dde --- /dev/null +++ b/ion/src/device/bootloader/usb/calculator.cpp @@ -0,0 +1,94 @@ +#include "calculator.h" +#include +#include +#include +#include + +namespace Ion { +namespace Device { +namespace USB { + +void Calculator::PollAndReset(bool exitWithKeyboard, void * data) { + char serialNumber[Ion::Device::SerialNumber::Length+1]; + Ion::Device::SerialNumber::copy(serialNumber); + + Calculator c(serialNumber, data == nullptr ? stringDescriptor() : static_cast(data)->stringDescriptor(), data == nullptr ? "Upsilon Bootloader" : static_cast(data)->getName()); + + if (data != nullptr) { + c.setConfigData(static_cast(data)); + } + + /* Leave DFU mode if the Back key is pressed, the calculator unplugged or the + * USB core soft-disconnected. */ + Ion::Keyboard::Key exitKey = Ion::Keyboard::Key::Back; + uint8_t exitKeyRow = Ion::Device::Keyboard::rowForKey(exitKey); + uint8_t exitKeyColumn = Ion::Device::Keyboard::columnForKey(exitKey); + + Ion::Device::Keyboard::activateRow(exitKeyRow); + + while (!(exitWithKeyboard && !c.isErasingAndWriting() && Ion::Device::Keyboard::columnIsActive(exitKeyColumn)) && + Ion::USB::isPlugged() && + !c.isSoftDisconnected()) { + c.poll(); + } + if (!c.isSoftDisconnected()) { + c.detach(); + } + if (c.resetOnDisconnect()) { + c.leave(c.addressPointer()); + } +} + +Descriptor * Calculator::descriptor(uint8_t type, uint8_t index) { + /* Special case: Microsoft OS String Descriptor should be returned when + * searching for string descriptor at index 0xEE. */ + if (type == m_microsoftOSStringDescriptor.type() && index == 0xEE) { + return &m_microsoftOSStringDescriptor; + } + int typeCount = 0; + for (size_t i=0; itype() != type) { + continue; + } + if (typeCount == index) { + return descriptor; + } else { + typeCount++; + } + } + return nullptr; +} + +bool Calculator::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (Device::processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { + return true; + } + if (request->requestType() == SetupPacket::RequestType::Vendor) { + if (request->bRequest() == k_webUSBVendorCode && request->wIndex() == 2) { + // This is a WebUSB, GET_URL request + assert(request->wValue() == k_webUSBLandingPageIndex); + return getURLCommand(transferBuffer, transferBufferLength, transferBufferMaxLength); + } + if (request->bRequest() == k_microsoftOSVendorCode && request->wIndex() == 0x0004) { + // This is a Microsoft OS descriptor, Extended Compat ID request + assert(request->wValue() == 0); + return getExtendedCompatIDCommand(transferBuffer, transferBufferLength, transferBufferMaxLength); + } + } + return false; +} + +bool Calculator::getURLCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = m_workshopURLDescriptor.copy(transferBuffer, transferBufferMaxLength); + return true; +} + +bool Calculator::getExtendedCompatIDCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = m_extendedCompatIdDescriptor.copy(transferBuffer, transferBufferMaxLength); + return true; +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/calculator.h b/ion/src/device/bootloader/usb/calculator.h new file mode 100644 index 00000000000..e4954edaea6 --- /dev/null +++ b/ion/src/device/bootloader/usb/calculator.h @@ -0,0 +1,176 @@ +#ifndef ION_DEVICE_SHARED_USB_CALCULATOR_H +#define ION_DEVICE_SHARED_USB_CALCULATOR_H + +#include +#include +#include +#include +#include "dfu_interface.h" +#include "stack/device.h" +#include "stack/descriptor/bos_descriptor.h" +#include "stack/descriptor/configuration_descriptor.h" +#include "stack/descriptor/descriptor.h" +#include "stack/descriptor/device_descriptor.h" +#include "stack/descriptor/dfu_functional_descriptor.h" +#include "stack/descriptor/extended_compat_id_descriptor.h" +#include "stack/descriptor/interface_descriptor.h" +#include "stack/descriptor/language_id_string_descriptor.h" +#include "stack/descriptor/microsoft_os_string_descriptor.h" +#include "stack/descriptor/string_descriptor.h" +#include "stack/descriptor/url_descriptor.h" +#include "stack/descriptor/webusb_platform_descriptor.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +class Calculator : public Device { +public: + static void PollAndReset(bool exitWithKeyboard, void * data) + __attribute__((section(".dfu_entry_point"))) // Needed to pinpoint this symbol in the linker script + __attribute__((used)) // Make sure this symbol is not discarded at link time + ; // Return true if reset is needed + Calculator(const char * serialNumber, const char * desc, const char * product) : + Device(&m_dfuInterface), + m_usbConfig(nullptr), + m_deviceDescriptor( + 0x0210, /* bcdUSB: USB Specification Number which the device complies + * to. Must be greater than 0x0200 to use the BOS. */ + 0, // bDeviceClass: The class is defined by the interface. + 0, // bDeviceSUBClass: The subclass is defined by the interface. + 0, // bDeviceProtocol: The protocol is defined by the interface. + 64, // bMaxPacketSize0: Maximum packet size for endpoint 0 + 0x0483, // idVendor + 0xA291, // idProduct + 0x0100, // bcdDevice: Device Release Number + 1, // iManufacturer: Index of the manufacturer name string, see m_descriptor + 2, // iProduct: Index of the product name string, see m_descriptor + 3, // iSerialNumber: Index of the SerialNumber string, see m_descriptor + 1), // bNumConfigurations + m_dfuFunctionalDescriptor( + 0b0011, /* bmAttributes: + * - bitWillDetach: If true, the device will perform a bus + * detach-attach sequence when it receives a DFU_DETACH + * request. The host must not issue a USB Reset. + * - bitManifestationTolerant: if true, the device is able to + * communicate via USB after Manifestation phase. The + * manifestation phase implies a reset in the calculator, so, + * even if the device is still plugged, it needs to be + * re-enumerated to communicate. + * - bitCanUpload + * - bitCanDnload */ + 0, /* wDetachTimeOut: Time, in milliseconds, that the device in APP + * mode will wait after receipt of the DFU_DETACH request before + * switching to DFU mode. It does not apply to the calculator.*/ + 2048, // wTransferSize: Maximum number of bytes that the device can accept per control-write transaction + 0x0100),// bcdDFUVersion + m_interfaceDescriptor( + 0, // bInterfaceNumber + k_dfuInterfaceAlternateSetting, // bAlternateSetting + 0, // bNumEndpoints: Other than endpoint 0 + 0xFE, // bInterfaceClass: DFU (https://www.usb.org/defined-class-codes) + 1, // bInterfaceSubClass: DFU + 2, // bInterfaceProtocol: DFU Mode (not DFU Runtime, which would be 1) + 4, // iInterface: Index of the Interface string, see m_descriptor + &m_dfuFunctionalDescriptor), + m_configurationDescriptor( + 9 + 9 + 9, // wTotalLength: configuration descriptor + interface descriptor + dfu functional descriptor lengths + 1, // bNumInterfaces + k_bConfigurationValue, // bConfigurationValue + 0, // iConfiguration: No string descriptor for the configuration + 0x80, /* bmAttributes: + * Bit 7: Reserved, set to 1 + * Bit 6: Self Powered + * Bit 5: Remote Wakeup (allows the device to wake up the host when the host is in suspend) + * Bit 4..0: Reserved, set to 0 */ + 0x32, // bMaxPower: half of the Maximum Power Consumption + &m_interfaceDescriptor), + m_webUSBPlatformDescriptor( + k_webUSBVendorCode, + k_webUSBLandingPageIndex), + m_bosDescriptor( + 5 + 24, // wTotalLength: BOS descriptor + webusb platform descriptor lengths + 1, // bNumDeviceCapabilities + &m_webUSBPlatformDescriptor), + m_languageStringDescriptor(), + m_manufacturerStringDescriptor("NumWorks"), + m_productStringDescriptor(product), + m_serialNumberStringDescriptor(serialNumber), + m_interfaceStringDescriptor(desc), + //m_interfaceStringDescriptor("@SRAM/0x20000000/01*256Ke"), + /* Switch to this descriptor to use dfu-util to write in the SRAM. + * FIXME Should be an alternate Interface. */ + m_microsoftOSStringDescriptor(k_microsoftOSVendorCode), + m_workshopURLDescriptor(URLDescriptor::Scheme::HTTPS, "getupsilon.web.app"), + m_extendedCompatIdDescriptor("WINUSB"), + m_descriptors{ + &m_deviceDescriptor, // Type = Device, Index = 0 + &m_configurationDescriptor, // Type = Configuration, Index = 0 + &m_languageStringDescriptor, // Type = String, Index = 0 + &m_manufacturerStringDescriptor, // Type = String, Index = 1 + &m_productStringDescriptor, // Type = String, Index = 2 + &m_serialNumberStringDescriptor, // Type = String, Index = 3 + &m_interfaceStringDescriptor, // Type = String, Index = 4 + &m_bosDescriptor // Type = BOS, Index = 0 + }, + m_dfuInterface(this, &m_ep0, k_dfuInterfaceAlternateSetting) + { + } + uint32_t addressPointer() const { return m_dfuInterface.addressPointer(); } + bool isErasingAndWriting() const { return m_dfuInterface.isErasingAndWriting(); } + + void setConfigData(Bootloader::USBData * data) { m_usbConfig = data; m_dfuInterface.setDfuConfig(data->getData()); } + Bootloader::USBData * getConfigData() const { return m_usbConfig; } +protected: + Descriptor * descriptor(uint8_t type, uint8_t index) override; + void setActiveConfiguration(uint8_t configurationIndex) override { + assert(configurationIndex == k_bConfigurationValue); + } + uint8_t getActiveConfiguration() override { + return k_bConfigurationValue; + } + bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; + +private: + static constexpr uint8_t k_bConfigurationValue = 1; + static constexpr uint8_t k_dfuInterfaceAlternateSetting = 0; + static constexpr uint8_t k_webUSBVendorCode = 1; + static constexpr uint8_t k_webUSBLandingPageIndex = 1; + static constexpr uint8_t k_microsoftOSVendorCode = 2; + + // WebUSB and MicrosoftOSDescriptor commands + bool getURLCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool getExtendedCompatIDCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + + // Descriptors + Bootloader::USBData * m_usbConfig; + DeviceDescriptor m_deviceDescriptor; + DFUFunctionalDescriptor m_dfuFunctionalDescriptor; + InterfaceDescriptor m_interfaceDescriptor; + ConfigurationDescriptor m_configurationDescriptor; + WebUSBPlatformDescriptor m_webUSBPlatformDescriptor; + BOSDescriptor m_bosDescriptor; + LanguageIDStringDescriptor m_languageStringDescriptor; + StringDescriptor m_manufacturerStringDescriptor; + StringDescriptor m_productStringDescriptor; + StringDescriptor m_serialNumberStringDescriptor; + StringDescriptor m_interfaceStringDescriptor; + MicrosoftOSStringDescriptor m_microsoftOSStringDescriptor; + URLDescriptor m_workshopURLDescriptor; + ExtendedCompatIDDescriptor m_extendedCompatIdDescriptor; + + Descriptor * m_descriptors[8]; + /* m_descriptors contains only descriptors that sould be returned via the + * method descriptor(uint8_t type, uint8_t index), so do not count descriptors + * included in other descriptors or returned by other functions. */ + + // Interface + DFUInterface m_dfuInterface; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/dfu.ld b/ion/src/device/bootloader/usb/dfu.ld new file mode 100644 index 00000000000..957bcfdd536 --- /dev/null +++ b/ion/src/device/bootloader/usb/dfu.ld @@ -0,0 +1,57 @@ +/* DFU transfers can serve two purposes: + * - Transfering RAM data between the machine and the host, e.g. Python scripts + * - Upgrading the flash memory to perform a software update + * + * The second case raises a huge issue: code cannot be executed from memory that + * is being modified. We're solving this issue by copying the DFU code in RAM. + * + * This linker script will generate some code that expects to be executed from a + * fixed address in RAM. The corresponding instructions will be embedded in the + * main Epsilon ELF file, and copied to that address before execution. + * + * This address needs to live in RAM, and needs to be temporarily overwriteable + * when the program is being run. Epsilon has a large stack to allow deeply + * recursive code to run. But when doing DFU transfers it is safe to assume we + * will need very little stack space. We're therefore using the topmost 8K of + * the stack reserved by Epsilon. + * + * Last but not least, we'll want to jump to a known entry point when running + * the DFU code (namely, Ion::USB::Device::Calculator::Poll). We're simply + * making sure this is the first symbol output. */ + +EPSILON_STACK_END = 0x20000000 + 256K - 32K; + +MEMORY { + RAM_BUFFER (rw) : ORIGIN = EPSILON_STACK_END, LENGTH = 9K +} + +SECTIONS { + .text : { + . = ALIGN(4); + KEEP(*(.dfu_entry_point)) + *(.text) + *(.text.*) + } >RAM_BUFFER + + .rodata : { + *(.rodata) + *(.rodata.*) + } >RAM_BUFFER + + .data : { + /* We need to keep these symbols. */ + *(.data._ZN3Ion6Device13ExternalFlashL14sOperatingModeE) + *(.data._ZN3Ion6Device5BoardL18sStandardFrequencyE) + } >RAM_BUFFER + + /DISCARD/ : { + /* For now, we do not need .bss and .data sections. This allows us to simply + * skip any rt0-style initialization and jump straight into the PollAndReset + * routine. */ + *(.bss) + *(.bss.*) + *(.data) + *(.data.*) + } +} + diff --git a/ion/src/device/bootloader/usb/dfu_interface.cpp b/ion/src/device/bootloader/usb/dfu_interface.cpp new file mode 100644 index 00000000000..18562aef30c --- /dev/null +++ b/ion/src/device/bootloader/usb/dfu_interface.cpp @@ -0,0 +1,314 @@ +#include "dfu_interface.h" +#include +#include +#include + +namespace Ion { +namespace Device { +namespace USB { + +static inline uint32_t minUint32T(uint32_t x, uint32_t y) { return x < y ? x : y; } + +void DFUInterface::StatusData::push(Channel * c) const { + c->push(m_bStatus); + c->push(m_bwPollTimeout[2]); + c->push(m_bwPollTimeout[1]); + c->push(m_bwPollTimeout[0]); + c->push(m_bState); + c->push(m_iString); +} + +void DFUInterface::StateData::push(Channel * c) const { + c->push(m_bState); +} + +void DFUInterface::wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) { + if (request->bRequest() == (uint8_t) DFURequest::Download) { + // Handle a download request + if (request->wValue() == 0) { + // The request is a special command + switch (transferBuffer[0]) { + case (uint8_t) DFUDownloadCommand::SetAddressPointer: + setAddressPointerCommand(request, transferBuffer, *transferBufferLength); + return; + case (uint8_t) DFUDownloadCommand::Erase: + eraseCommand(transferBuffer, *transferBufferLength); + return; + default: + m_state = State::dfuERROR; + m_status = Status::errSTALLEDPKT; + return; + } + } + if (request->wValue() == 1) { + m_ep0->stallTransaction(); + return; + } + if (request->wLength() > 0) { + // The request is a "real" download. Compute the writing address. + m_writeAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer; + // Store the received data until we copy it on the flash. + memcpy(m_largeBuffer, transferBuffer, *transferBufferLength); + m_largeBufferLength = *transferBufferLength; + m_state = State::dfuDNLOADSYNC; + } + } +} + +void DFUInterface::wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) { + if (request->bRequest() == (uint8_t) DFURequest::GetStatus) { + // Do any needed action after the GetStatus request. + if (m_state == State::dfuMANIFEST) { + /* If we leave the DFU and reset immediately, dfu-util outputs an error: + * "File downloaded successfully + * dfu-util: Error during download get_status" + * If we sleep 1us here, there is no error. We put 1ms for security. + * This error might be due to the USB connection being cut too soon after + * the last USB exchange, so the host does not have time to process the + * answer received for the last GetStatus request. */ + Ion::Timing::msleep(1); + // Leave DFU routine: Leave DFU, reset device, jump to application code + leaveDFUAndReset(); + } else if (m_state == State::dfuDNBUSY) { + if (m_largeBufferLength != 0) { + // Here, copy the data from the transfer buffer to the flash memory + writeOnMemory(); + } + changeAddressPointerIfNeeded(); + eraseMemoryIfNeeded(); + m_state = State::dfuDNLOADIDLE; + } + } +} + +bool DFUInterface::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (Interface::processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { + return true; + } + switch (request->bRequest()) { + case (uint8_t) DFURequest::Detach: + m_device->detach(); + return true; + case (uint8_t) DFURequest::Download: + return processDownloadRequest(request->wLength(), transferBufferLength); + case (uint8_t) DFURequest::Upload: + return processUploadRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::GetStatus: + return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::ClearStatus: + return clearStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::GetState: + return getState(transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::Abort: + return dfuAbort(transferBufferLength); + } + return false; +} + +bool DFUInterface::processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength) { + if (m_state != State::dfuIDLE && m_state != State::dfuDNLOADIDLE) { + m_state = State::dfuERROR; + m_status = Status::errNOTDONE; + m_ep0->stallTransaction(); + return false; + } + if (wLength == 0) { + // Leave DFU routine: Reset the device and jump to application code + m_state = State::dfuMANIFESTSYNC; + } else { + // Prepare to receive the download data + m_ep0->clearForOutTransactions(wLength); + m_state = State::dfuDNLOADSYNC; + } + return true; +} + +bool DFUInterface::processUploadRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (m_state != State::dfuIDLE && m_state != State::dfuUPLOADIDLE) { + m_ep0->stallTransaction(); + return false; + } + if (request->wValue() == 0) { + /* The host requests to read the commands supported by the bootloader. After + * receiving this command, the device should returns N bytes representing + * the command codes for : + * Get command / Set Address Pointer / Erase / Read Unprotect + * We no not need it for now. */ + return false; + } else if (request->wValue() == 1) { + m_ep0->stallTransaction(); + return false; + } else { + /* We decided to never protect Read operation. Else we would have to check + * here it is not protected before reading. */ + + // Compute the reading address + uint32_t readAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer; + // Copy the requested memory zone into the transfer buffer. + uint16_t copySize = minUint32T(transferBufferMaxLength, request->wLength()); + memcpy(transferBuffer, (void *)readAddress, copySize); + *transferBufferLength = copySize; + } + m_state = State::dfuUPLOADIDLE; + return true; +} + +void DFUInterface::setAddressPointerCommand(SetupPacket * request, uint8_t * transferBuffer, uint16_t transferBufferLength) { + assert(transferBufferLength == 5); + // Compute the new address but change it after the next getStatus request. + m_potentialNewAddressPointer = transferBuffer[1] + + (transferBuffer[2] << 8) + + (transferBuffer[3] << 16) + + (transferBuffer[4] << 24); + m_state = State::dfuDNLOADSYNC; +} + +void DFUInterface::changeAddressPointerIfNeeded() { + if (m_potentialNewAddressPointer == 0) { + // There was no address change waiting. + return; + } + // If there is a new address pointer waiting, change the pointer address. + m_addressPointer = m_potentialNewAddressPointer; + m_potentialNewAddressPointer = 0; + m_state = State::dfuDNLOADIDLE; + m_status = Status::OK; +} + +void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBufferLength) { + /* We determine whether the commands asks for a mass erase or which sector to + * erase. The erase must be done after the next getStatus request. */ + m_state = State::dfuDNLOADSYNC; + + if (transferBufferLength == 1) { + // Mass erase + m_erasePage = Flash::TotalNumberOfSectors(); + return; + } + + // Sector erase + assert(transferBufferLength == 5); + + m_eraseAddress = transferBuffer[1] + + (transferBuffer[2] << 8) + + (transferBuffer[3] << 16) + + (transferBuffer[4] << 24); + + m_erasePage = Flash::SectorAtAddress(m_eraseAddress); + if (m_erasePage < 0) { + // Unrecognized sector + m_state = State::dfuERROR; + m_status = Status::errTARGET; + } +} + + +void DFUInterface::eraseMemoryIfNeeded() { + if (m_erasePage < 0) { + // There was no erase waiting. + return; + } + + willErase(); + + Bootloader::DFUData * config = getDfuConfig(); + + if (config != nullptr) { + // More simple to read + if ((0x08000000 <= m_eraseAddress && m_eraseAddress <= 0x08010000)&& !m_dfuData.isProtectedInternal()) { + Flash::EraseSector(m_erasePage); + } else if ((0x90000000 <= m_eraseAddress && m_eraseAddress <= 0x90800000)&& !m_dfuData.isProtectedExternal()) { + Flash::EraseSector(m_erasePage); + } + } else { + if (m_erasePage == Flash::TotalNumberOfSectors()) { + Flash::MassErase(); + } else { + Flash::EraseSector(m_erasePage); + } + } + + /* Put an out of range value in m_erasePage to indicate that no erase is + * waiting. */ + m_erasePage = -1; + m_state = State::dfuDNLOADIDLE; + m_status = Status::OK; +} + +void DFUInterface::writeOnMemory() { + if (m_writeAddress >= k_sramStartAddress && m_writeAddress <= k_sramEndAddress) { + // Write on SRAM + // FIXME We should check that we are not overriding the current instructions. + memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); + } else if (Flash::SectorAtAddress(m_writeAddress) >= 0) { + + Bootloader::DFUData * config = getDfuConfig(); + + if (config != nullptr) { + if (m_writeAddress >= 0x08000000 && m_writeAddress <= 0x08010000 && !m_dfuData.isProtectedInternal()) { + Flash::WriteMemory(reinterpret_cast(m_writeAddress), m_largeBuffer, m_largeBufferLength); + } else if (m_writeAddress >= 0x90000000 && m_writeAddress <= 0x90800000 && !m_dfuData.isProtectedExternal()) { + Flash::WriteMemory(reinterpret_cast(m_writeAddress), m_largeBuffer, m_largeBufferLength); + } + } else { + Flash::WriteMemory(reinterpret_cast(m_writeAddress), m_largeBuffer, m_largeBufferLength); + } + + } else { + // Invalid write address + m_largeBufferLength = 0; + m_state = State::dfuERROR; + m_status = Status::errTARGET; + return; + } + + // Reset the buffer length + m_largeBufferLength = 0; + // Change the interface state and status + m_state = State::dfuDNLOADIDLE; + m_status = Status::OK; +} + + +bool DFUInterface::getStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + // Change the status if needed + if (m_state == State::dfuMANIFESTSYNC) { + m_state = State::dfuMANIFEST; + } else if (m_state == State::dfuDNLOADSYNC) { + m_state = State::dfuDNBUSY; + } + // Copy the status on the TxFifo + *transferBufferLength = StatusData(m_status, m_state).copy(transferBuffer, transferBufferMaxLength); + return true; +} + +bool DFUInterface::clearStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + m_status = Status::OK; + m_state = State::dfuIDLE; + return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); +} + +bool DFUInterface::getState(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t maxSize) { + *transferBufferLength = StateData(m_state).copy(transferBuffer, maxSize); + return true; +} + +bool DFUInterface::dfuAbort(uint16_t * transferBufferLength) { + m_status = Status::OK; + m_state = State::dfuIDLE; + *transferBufferLength = 0; + return true; +} + +void DFUInterface::leaveDFUAndReset() { + m_device->setResetOnDisconnect(true); + m_device->detach(); +} + +void DFUInterface::copyDfuData() { + m_dfuData = Bootloader::DFUData(!m_dfuConfig->isProtectedInternal(), !m_dfuConfig->isProtectedExternal()); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/dfu_interface.h b/ion/src/device/bootloader/usb/dfu_interface.h new file mode 100644 index 00000000000..5602002ca83 --- /dev/null +++ b/ion/src/device/bootloader/usb/dfu_interface.h @@ -0,0 +1,192 @@ +#ifndef ION_DEVICE_SHARED_USB_DFU_INTERFACE_H +#define ION_DEVICE_SHARED_USB_DFU_INTERFACE_H + +#include +#include +#include "stack/device.h" +#include "stack/interface.h" +#include "stack/endpoint0.h" +#include "stack/setup_packet.h" +#include "stack/streamable.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +class DFUInterface : public Interface { + +public: + DFUInterface(Device * device, Endpoint0 * ep0, uint8_t bInterfaceAlternateSetting) : + Interface(ep0), + m_device(device), + m_status(Status::OK), + m_state(State::dfuIDLE), + m_addressPointer(0), + m_potentialNewAddressPointer(0), + m_erasePage(-1), + m_largeBuffer{0}, + m_largeBufferLength(0), + m_writeAddress(0), + m_bInterfaceAlternateSetting(bInterfaceAlternateSetting), + m_isErasingAndWriting(false), + m_dfuConfig(nullptr), + m_eraseAddress(0), + m_dfuData() + { + } + uint32_t addressPointer() const { return m_addressPointer; } + void wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) override; + void wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) override; + bool isErasingAndWriting() const { return m_isErasingAndWriting; } + + void setDfuConfig(Bootloader::DFUData * data) { m_dfuConfig = data; copyDfuData(); } + Bootloader::DFUData * getDfuConfig() const { return m_dfuConfig; } + +protected: + void setActiveInterfaceAlternative(uint8_t interfaceAlternativeIndex) override { + assert(interfaceAlternativeIndex == m_bInterfaceAlternateSetting); + } + uint8_t getActiveInterfaceAlternative() override { + return m_bInterfaceAlternateSetting; + } + bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; + +private: + // DFU Request Codes + enum class DFURequest { + Detach = 0, + Download = 1, + Upload = 2, + GetStatus = 3, + ClearStatus = 4, + GetState = 5, + Abort = 6 + }; + + // DFU Download Commmand Codes + enum class DFUDownloadCommand { + GetCommand = 0x00, + SetAddressPointer = 0x21, + Erase = 0x41, + ReadUnprotect = 0x92 + }; + + enum class Status : uint8_t { + OK = 0x00, + errTARGET = 0x01, + errFILE = 0x02, + errWRITE = 0x03, + errERASE = 0x04, + errCHECK_ERASED = 0x05, + errPROG = 0x06, + errVERIFY = 0x07, + errADDRESS = 0x08, + errNOTDONE = 0x09, + errFIRMWARE = 0x0A, + errVENDOR = 0x0B, + errUSBR = 0x0C, + errPOR = 0x0D, + errUNKNOWN = 0x0E, + errSTALLEDPKT = 0x0F + }; + + enum class State : uint8_t { + appIDLE = 0, + appDETACH = 1, + dfuIDLE = 2, + dfuDNLOADSYNC = 3, + dfuDNBUSY = 4, + dfuDNLOADIDLE = 5, + dfuMANIFESTSYNC = 6, + dfuMANIFEST = 7, + dfuMANIFESTWAITRESET = 8, + dfuUPLOADIDLE = 9, + dfuERROR = 10 + }; + + class StatusData : public Streamable { + public: + StatusData(Status status, State state, uint32_t pollTimeout = 1) : + /* We put a default pollTimeout value of 1ms: if the device is busy, the + * host has to wait 1ms before sending a getStatus Request. */ + m_bStatus((uint8_t)status), + m_bwPollTimeout{uint8_t((pollTimeout>>16) & 0xFF), uint8_t((pollTimeout>>8) & 0xFF), uint8_t(pollTimeout & 0xFF)}, + m_bState((uint8_t)state), + m_iString(0) + { + } + protected: + void push(Channel * c) const override; + private: + uint8_t m_bStatus; // Status resulting from the execution of the most recent request + uint8_t m_bwPollTimeout[3]; // m_bwPollTimeout is 24 bits + uint8_t m_bState; // State of the device immediately following transmission of this response + uint8_t m_iString; + }; + + class StateData : public Streamable { + public: + StateData(State state) : m_bState((uint8_t)state) {} + protected: + void push(Channel * c) const override; + private: + uint8_t m_bState; // Current state of the device + }; + + /* The Flash and SRAM addresses are in flash.ld. However, dfu_interface is + * linked with dfu.ld, so we cannot access the values. */ + constexpr static uint32_t k_sramStartAddress = 0x20000000; + constexpr static uint32_t k_sramEndAddress = 0x20040000; + + // Download and upload + bool processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength); + bool processUploadRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + // Address pointer + void setAddressPointerCommand(SetupPacket * request, uint8_t * transferBuffer, uint16_t transferBufferLength); + void changeAddressPointerIfNeeded(); + // Access memory + void eraseCommand(uint8_t * transferBuffer, uint16_t transferBufferLength); + void eraseMemoryIfNeeded(); + void writeOnMemory(); + void unlockFlashMemory(); + void lockFlashMemoryAndPurgeCaches(); + // Status + bool getStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool clearStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + // State + bool getState(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t maxSize); + // Abort + bool dfuAbort(uint16_t * transferBufferLength); + // Leave DFU + void leaveDFUAndReset(); + /* Erase and Write state. After starting the erase of flash memory, the user + * can no longer leave DFU mode by pressing the Back key of the keyboard. This + * way, we prevent the user from interrupting a software download. After every + * software download, the calculator resets, which unlocks the "exit on + * pressing back". */ + void willErase() { m_isErasingAndWriting = true; } + + void copyDfuData(); + + Device * m_device; + Status m_status; + State m_state; + uint32_t m_addressPointer; + uint32_t m_potentialNewAddressPointer; + int32_t m_erasePage; + uint8_t m_largeBuffer[Endpoint0::MaxTransferSize]; + uint16_t m_largeBufferLength; + uint32_t m_writeAddress; + uint8_t m_bInterfaceAlternateSetting; + bool m_isErasingAndWriting; + Bootloader::DFUData * m_dfuConfig; + uint32_t m_eraseAddress; + Bootloader::DFUData m_dfuData; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/dfu_relocated.cpp b/ion/src/device/bootloader/usb/dfu_relocated.cpp new file mode 100644 index 00000000000..7669a31036d --- /dev/null +++ b/ion/src/device/bootloader/usb/dfu_relocated.cpp @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include +#include "../drivers/timing.h" + +extern const void * _stack_end; +extern char _dfu_bootloader_flash_start; +extern char _dfu_bootloader_flash_end; + +namespace Ion { +namespace USB { + +typedef void (*PollFunctionPointer)(bool exitWithKeyboard, void * data); + +void DFU(bool exitWithKeyboard, void * data) { + Ion::updateSlotInfo(); + + /* DFU transfers can serve two purposes: + * - Transfering RAM data between the machine and a host, e.g. Python scripts + * - Upgrading the flash memory to perform a software update + * + * The second case raises a huge issue: code cannot be executed from memory + * that is being modified. We're solving this issue by copying the DFU code in + * RAM. + * + * The new DFU address in RAM needs to be temporarily overwriteable when the + * program is being run. Epsilon has a large stack to allow deeply recursive + * code to run, but when doing DFU transfers it is safe to assume we will need + * very little stack space. We're therefore using the topmost 8K of the stack + * reserved by Epsilon. */ + + /* 1- The stack being in reverse order, the end of the stack will be the + * beginning of the DFU bootloader copied in RAM. */ + + size_t dfu_bootloader_size = &_dfu_bootloader_flash_end - &_dfu_bootloader_flash_start; + char * dfu_bootloader_ram_start = reinterpret_cast(&_stack_end); + assert(&_stack_end == (void *)(0x20000000 + 256*1024 - 32*1024)); + + /* 2- Verify there is enough free space on the stack to copy the DFU code. */ + + char foo; + char * stackPointer = &foo; + if (dfu_bootloader_ram_start + dfu_bootloader_size > stackPointer) { + // There is not enough room on the stack to copy the DFU bootloader. + return; + } + + /* 3- Copy the DFU bootloader from Flash to RAM. */ + + memcpy(dfu_bootloader_ram_start, &_dfu_bootloader_flash_start, dfu_bootloader_size); + /* The DFU bootloader might have been copied in the DCache. However, when we + * run the instructions from the DFU bootloader, the CPU looks for + * instructions in the ICache and then in the RAM. We thus need to flush the + * DCache to update the RAM. */ + // Flush data cache + Device::Cache::cleanDCache(); + + /* 4- Disable all interrupts + * The interrupt service routines live in the Flash and could be overwritten + * by garbage during a firmware upgrade opration, so we disable them. */ + Device::Timing::shutdown(); + + /* 5- Jump to DFU bootloader code. We made sure in the linker script that the + * first function we want to call is at the beginning of the DFU code. */ + + PollFunctionPointer dfu_bootloader_entry = reinterpret_cast(dfu_bootloader_ram_start); + + /* To have the right debug symbols for the reallocated code, break here and: + * - Get the address of the new .text section + * In a terminal: arm-none-eabi-readelf -a ion/src/device/usb/dfu.elf + * - Delete the current symbol table + * symbol-file + * - Add the new symbol table, with the address of the new .text section + * add-symbol-file ion/src/device/usb/dfu.elf 0x20038000 + */ + + dfu_bootloader_entry(exitWithKeyboard, data); + + /* 5- Restore interrupts */ + Device::Timing::init(); + + /* 6- That's all. The DFU bootloader on the stack is now dead code that will + * be overwritten when the stack grows. */ +} + +} +} diff --git a/ion/src/device/bootloader/usb/dfu_xip.cpp b/ion/src/device/bootloader/usb/dfu_xip.cpp new file mode 100644 index 00000000000..55500cf3cee --- /dev/null +++ b/ion/src/device/bootloader/usb/dfu_xip.cpp @@ -0,0 +1,13 @@ +#include +#include "calculator.h" + +namespace Ion { +namespace USB { + +void DFU(bool exitWithKeyboard, void * data) { + Ion::updateSlotInfo(); + Ion::Device::USB::Calculator::PollAndReset(exitWithKeyboard, data); +} + +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/bos_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/bos_descriptor.cpp new file mode 100644 index 00000000000..2099df6b439 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/bos_descriptor.cpp @@ -0,0 +1,22 @@ +#include "bos_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +void BOSDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_wTotalLength); + c->push(m_bNumDeviceCaps); + for (uint8_t i = 0; i < m_bNumDeviceCaps; i++) { + m_deviceCapabilities[i].push(c); + } +} + +uint8_t BOSDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint16_t) + sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/bos_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/bos_descriptor.h new file mode 100644 index 00000000000..25529c0e522 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/bos_descriptor.h @@ -0,0 +1,36 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_BOS_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_BOS_DESCRIPTOR_H + +#include "descriptor.h" +#include "device_capability_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class BOSDescriptor : public Descriptor { +public: + constexpr BOSDescriptor( + uint16_t wTotalLength, + uint8_t bNumDeviceCapabilities, + const DeviceCapabilityDescriptor * deviceCapabilities) : + Descriptor(0x0F), + m_wTotalLength(wTotalLength), + m_bNumDeviceCaps(bNumDeviceCapabilities), + m_deviceCapabilities(deviceCapabilities) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + uint16_t m_wTotalLength; + uint8_t m_bNumDeviceCaps; + const DeviceCapabilityDescriptor * m_deviceCapabilities; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/configuration_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/configuration_descriptor.cpp new file mode 100644 index 00000000000..c4d97a36811 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/configuration_descriptor.cpp @@ -0,0 +1,26 @@ +#include "configuration_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +void ConfigurationDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_wTotalLength); + c->push(m_bNumInterfaces); + c->push(m_bConfigurationValue); + c->push(m_iConfiguration); + c->push(m_bmAttributes); + c->push(m_bMaxPower); + for (uint8_t i = 0; i < m_bNumInterfaces; i++) { + m_interfaces[i].push(c); + } +} + +uint8_t ConfigurationDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint16_t) + 5*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/configuration_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/configuration_descriptor.h new file mode 100644 index 00000000000..9f994b6e3b7 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/configuration_descriptor.h @@ -0,0 +1,48 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_CONFIGURATION_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_CONFIGURATION_DESCRIPTOR_H + +#include "descriptor.h" +#include "interface_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class ConfigurationDescriptor : public Descriptor { +public: + constexpr ConfigurationDescriptor( + uint16_t wTotalLength, + uint8_t bNumInterfaces, + uint8_t bConfigurationValue, + uint8_t iConfiguration, + uint8_t bmAttributes, + uint8_t bMaxPower, + const InterfaceDescriptor * interfaces) : + Descriptor(0x02), + m_wTotalLength(wTotalLength), + m_bNumInterfaces(bNumInterfaces), + m_bConfigurationValue(bConfigurationValue), + m_iConfiguration(iConfiguration), + m_bmAttributes(bmAttributes), + m_bMaxPower(bMaxPower), + m_interfaces(interfaces) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + uint16_t m_wTotalLength; + uint8_t m_bNumInterfaces; + uint8_t m_bConfigurationValue; + uint8_t m_iConfiguration; + uint8_t m_bmAttributes; + uint8_t m_bMaxPower; + const InterfaceDescriptor * m_interfaces; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/descriptor.cpp new file mode 100644 index 00000000000..373388cb322 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/descriptor.cpp @@ -0,0 +1,15 @@ +#include "descriptor.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +void Descriptor::push(Channel * c) const { + c->push(bLength()); + c->push(m_bDescriptorType); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/descriptor.h new file mode 100644 index 00000000000..b413b3fe44c --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/descriptor.h @@ -0,0 +1,32 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_DESCRIPTOR_H + +#include "../streamable.h" + +namespace Ion { +namespace Device { +namespace USB { + +class InterfaceDescriptor; + +class Descriptor : public Streamable { + friend class InterfaceDescriptor; +public: + constexpr Descriptor(uint8_t bDescriptorType) : + m_bDescriptorType(bDescriptorType) + { + } + uint8_t type() const { return m_bDescriptorType; } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const { return 2*sizeof(uint8_t); } +private: + uint8_t m_bDescriptorType; +}; + + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.cpp new file mode 100644 index 00000000000..8f5d6abb88b --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.cpp @@ -0,0 +1,18 @@ +#include "device_capability_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +void DeviceCapabilityDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bDeviceCapabilityType); +} + +uint8_t DeviceCapabilityDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.h new file mode 100644 index 00000000000..f434352f24b --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.h @@ -0,0 +1,31 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class BOSDescriptor; + +class DeviceCapabilityDescriptor : public Descriptor { + friend class BOSDescriptor; +public: + constexpr DeviceCapabilityDescriptor(uint8_t bDeviceCapabilityType) : + Descriptor(0x10), + m_bDeviceCapabilityType(bDeviceCapabilityType) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + uint8_t m_bDeviceCapabilityType; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/device_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/device_descriptor.cpp new file mode 100644 index 00000000000..424e891e865 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/device_descriptor.cpp @@ -0,0 +1,29 @@ +#include "device_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +void DeviceDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bcdUSB); + c->push(m_bDeviceClass); + c->push(m_bDeviceSubClass); + c->push(m_bDeviceProtocol); + c->push(m_bMaxPacketSize0); + c->push(m_idVendor); + c->push(m_idProduct); + c->push(m_bcdDevice); + c->push(m_iManufacturer); + c->push(m_iProduct); + c->push(m_iSerialNumber); + c->push(m_bNumConfigurations); +} + +uint8_t DeviceDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint16_t) + 4*sizeof(uint8_t) + 3*sizeof(uint16_t) + 4*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/device_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/device_descriptor.h new file mode 100644 index 00000000000..3b526f43f7d --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/device_descriptor.h @@ -0,0 +1,62 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_DEVICE_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_DEVICE_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class DeviceDescriptor : public Descriptor { +public: + constexpr DeviceDescriptor( + uint16_t bcdUSB, + uint8_t bDeviceClass, + uint8_t bDeviceSubClass, + uint8_t bDeviceProtocol, + uint8_t bMaxPacketSize0, + uint16_t idVendor, + uint16_t idProduct, + uint16_t bcdDevice, + uint8_t iManufacturer, + uint8_t iProduct, + uint8_t iSerialNumber, + uint8_t bNumConfigurations) : + Descriptor(0x01), + m_bcdUSB(bcdUSB), + m_bDeviceClass(bDeviceClass), + m_bDeviceSubClass(bDeviceSubClass), + m_bDeviceProtocol(bDeviceProtocol), + m_bMaxPacketSize0(bMaxPacketSize0), + m_idVendor(idVendor), + m_idProduct(idProduct), + m_bcdDevice(bcdDevice), + m_iManufacturer(iManufacturer), + m_iProduct(iProduct), + m_iSerialNumber(iSerialNumber), + m_bNumConfigurations(bNumConfigurations) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + uint16_t m_bcdUSB; + uint8_t m_bDeviceClass; + uint8_t m_bDeviceSubClass; + uint8_t m_bDeviceProtocol; + uint8_t m_bMaxPacketSize0; + uint16_t m_idVendor; + uint16_t m_idProduct; + uint16_t m_bcdDevice; + uint8_t m_iManufacturer; + uint8_t m_iProduct; + uint8_t m_iSerialNumber; + uint8_t m_bNumConfigurations; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/dfu_functional_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/dfu_functional_descriptor.cpp new file mode 100644 index 00000000000..414421d9b87 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/dfu_functional_descriptor.cpp @@ -0,0 +1,21 @@ +#include "dfu_functional_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +void DFUFunctionalDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bmAttributes); + c->push(m_wDetachTimeOut); + c->push(m_wTransferSize); + c->push(m_bcdDFUVersion); +} + +uint8_t DFUFunctionalDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint8_t) + 3*sizeof(uint16_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/dfu_functional_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/dfu_functional_descriptor.h new file mode 100644 index 00000000000..60439289542 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/dfu_functional_descriptor.h @@ -0,0 +1,38 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_DFU_FUNCTIONAL_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_DFU_FUNCTIONAL_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class DFUFunctionalDescriptor : public Descriptor { +public: + constexpr DFUFunctionalDescriptor( + uint8_t bmAttributes, + uint16_t wDetachTimeOut, + uint16_t wTransferSize, + uint16_t bcdDFUVersion) : + Descriptor(0x21), + m_bmAttributes(bmAttributes), + m_wDetachTimeOut(wDetachTimeOut), + m_wTransferSize(wTransferSize), + m_bcdDFUVersion(bcdDFUVersion) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + uint8_t m_bmAttributes; + uint16_t m_wDetachTimeOut; + uint16_t m_wTransferSize; + uint16_t m_bcdDFUVersion; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/extended_compat_id_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/extended_compat_id_descriptor.cpp new file mode 100644 index 00000000000..024ac15526f --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/extended_compat_id_descriptor.cpp @@ -0,0 +1,61 @@ +#include "extended_compat_id_descriptor.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +ExtendedCompatIDDescriptor::ExtendedCompatIDDescriptor(const char * compatibleID) : + m_dwLength(sizeof(uint32_t) + + 2*sizeof(uint16_t) + + sizeof(uint8_t) + + k_reserved1Size * sizeof(uint8_t) + + 2*sizeof(uint8_t) + + k_compatibleIDSize * sizeof(uint8_t) + + k_compatibleIDSize * sizeof(uint8_t) + + k_reserved2Size * sizeof(uint8_t)), + m_bcdVersion(0x0100), // Microsoft OS Descriptors version 1 + m_wIndex(Index), + m_bCount(1), // We assume one function only. + m_reserved1{0, 0, 0, 0, 0, 0, 0}, + m_bFirstInterfaceNumber(0), + m_bReserved(1), + m_subCompatibleID{0, 0, 0, 0, 0, 0, 0, 0}, + m_reserved2{0, 0, 0, 0, 0, 0} +{ + /* Compatible ID has size k_compatibleIDSize, and any unused bytes should be + * filled with 0. */ + size_t compatibleIDSize = strlen(compatibleID); + size_t compatibleIDCopySize = k_compatibleIDSize < compatibleIDSize ? k_compatibleIDSize : compatibleIDSize; + for (size_t i = 0; i < compatibleIDCopySize; i++) { + m_compatibleID[i] = compatibleID[i]; + } + for (size_t i = compatibleIDCopySize; i < k_compatibleIDSize; i++) { + m_compatibleID[i] = 0; + } +} + +void ExtendedCompatIDDescriptor::push(Channel * c) const { + c->push(m_dwLength); + c->push(m_bcdVersion); + c->push(m_wIndex); + c->push(m_bCount); + for (uint8_t i = 0; i < k_reserved1Size; i++) { + c->push(m_reserved1[i]); + } + c->push(m_bFirstInterfaceNumber); + c->push(m_bReserved); + for (uint8_t i = 0; i < k_compatibleIDSize; i++) { + c->push(m_compatibleID[i]); + } + for (uint8_t i = 0; i < k_compatibleIDSize; i++) { + c->push(m_subCompatibleID[i]); + } + for (uint8_t i = 0; i < k_reserved2Size; i++) { + c->push(m_reserved2[i]); + } +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/extended_compat_id_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/extended_compat_id_descriptor.h new file mode 100644 index 00000000000..016beab978d --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/extended_compat_id_descriptor.h @@ -0,0 +1,43 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_EXTENDED_COMPAT_ID_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_EXTENDED_COMPAT_ID_DESCRIPTOR_H + +#include "../streamable.h" + +namespace Ion { +namespace Device { +namespace USB { + +/* We use this descriptor to tell the Windows OS that the device should be + * treated as a WinUSB device. The Extended Compat ID Descriptor can set + * differents compat IDs according to the interface and function of the device, + * but we assume there is only one. */ + +class ExtendedCompatIDDescriptor : public Streamable { +public: + static constexpr uint8_t Index = 0x0004; + ExtendedCompatIDDescriptor(const char * compatibleID); +protected: + void push(Channel * c) const override; +private: + constexpr static uint8_t k_reserved1Size = 7; + constexpr static uint8_t k_compatibleIDSize = 8; + constexpr static uint8_t k_reserved2Size = 6; + // Header + uint32_t m_dwLength; // The length, in bytes, of the complete extended compat ID descriptor + uint16_t m_bcdVersion; // The descriptor’s version number, in binary coded decimal format + uint16_t m_wIndex; // An index that identifies the particular OS feature descriptor + uint8_t m_bCount; // The number of function sections + uint8_t m_reserved1[k_reserved1Size]; + // Function + uint8_t m_bFirstInterfaceNumber; // The interface or function number + uint8_t m_bReserved; + uint8_t m_compatibleID[k_compatibleIDSize]; + uint8_t m_subCompatibleID[k_compatibleIDSize]; + uint8_t m_reserved2[k_reserved2Size]; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/interface_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/interface_descriptor.cpp new file mode 100644 index 00000000000..75f193aba06 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/interface_descriptor.cpp @@ -0,0 +1,27 @@ +#include "interface_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +void InterfaceDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bInterfaceNumber); + c->push(m_bAlternateSetting); + c->push(m_bNumEndpoints); + c->push(m_bInterfaceClass); + c->push(m_bInterfaceSubClass); + c->push(m_bInterfaceProtocol); + c->push(m_iInterface); + if (m_additionalDescriptor != nullptr) { + m_additionalDescriptor->push(c); + } +} + +uint8_t InterfaceDescriptor::bLength() const { + return Descriptor::bLength() + 7*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/interface_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/interface_descriptor.h new file mode 100644 index 00000000000..6a49bfb8a22 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/interface_descriptor.h @@ -0,0 +1,55 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_INTERFACE_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_INTERFACE_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class ConfigurationDescriptor; + +class InterfaceDescriptor : public Descriptor { + friend class ConfigurationDescriptor; +public: + constexpr InterfaceDescriptor( + uint8_t bInterfaceNumber, + uint8_t bAlternateSetting, + uint8_t bNumEndpoints, + uint8_t bInterfaceClass, + uint8_t bInterfaceSubClass, + uint8_t bInterfaceProtocol, + uint8_t iInterface, + Descriptor * additionalDescriptor) : + Descriptor(0x04), + m_bInterfaceNumber(bInterfaceNumber), + m_bAlternateSetting(bAlternateSetting), + m_bNumEndpoints(bNumEndpoints), + m_bInterfaceClass(bInterfaceClass), + m_bInterfaceSubClass(bInterfaceSubClass), + m_bInterfaceProtocol(bInterfaceProtocol), + m_iInterface(iInterface), + m_additionalDescriptor(additionalDescriptor) + /* There could be more than one additional descriptor, but we do not need + * this for now. */ + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + uint8_t m_bInterfaceNumber; + uint8_t m_bAlternateSetting; + uint8_t m_bNumEndpoints; + uint8_t m_bInterfaceClass; + uint8_t m_bInterfaceSubClass; + uint8_t m_bInterfaceProtocol; + uint8_t m_iInterface; + const Descriptor * m_additionalDescriptor; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/language_id_string_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/language_id_string_descriptor.cpp new file mode 100644 index 00000000000..8027fb75cbd --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/language_id_string_descriptor.cpp @@ -0,0 +1,19 @@ +#include "language_id_string_descriptor.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +void LanguageIDStringDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push((uint16_t)(0x0409)); +} + +uint8_t LanguageIDStringDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint16_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/language_id_string_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/language_id_string_descriptor.h new file mode 100644 index 00000000000..0f72abb91bb --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/language_id_string_descriptor.h @@ -0,0 +1,25 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_LANGUAGE_ID_STRING_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_LANGUAGE_ID_STRING_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +// For now this LanguageIDStringDescriptor only ever returns American English + +class LanguageIDStringDescriptor : public Descriptor { +public: + constexpr LanguageIDStringDescriptor() : + Descriptor(0x03) { } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/microsoft_os_string_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/microsoft_os_string_descriptor.cpp new file mode 100644 index 00000000000..b125b8efcee --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/microsoft_os_string_descriptor.cpp @@ -0,0 +1,19 @@ +#include "microsoft_os_string_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +void MicrosoftOSStringDescriptor::push(Channel * c) const { + StringDescriptor::push(c); + c->push(m_bMSVendorCode); + c->push(m_bPad); +} + +uint8_t MicrosoftOSStringDescriptor::bLength() const { + return StringDescriptor::bLength() + 2 * sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/microsoft_os_string_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/microsoft_os_string_descriptor.h new file mode 100644 index 00000000000..dfe3a7ab264 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/microsoft_os_string_descriptor.h @@ -0,0 +1,30 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_MICROSOFT_OS_STRING_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_MICROSOFT_OS_STRING_DESCRIPTOR_H + +#include "string_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class MicrosoftOSStringDescriptor : public StringDescriptor { +public: + constexpr MicrosoftOSStringDescriptor(uint8_t bMSVendorCode) : + StringDescriptor("MSFT100"), + m_bMSVendorCode(bMSVendorCode), + m_bPad(0) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + uint8_t m_bMSVendorCode; + uint8_t m_bPad; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.cpp new file mode 100644 index 00000000000..d5c7756bafc --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.cpp @@ -0,0 +1,21 @@ +#include "platform_device_capability_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +void PlatformDeviceCapabilityDescriptor::push(Channel * c) const { + DeviceCapabilityDescriptor::push(c); + c->push(m_bReserved); + for (int i = 0; i < k_platformCapabilityUUIDSize; i++) { + c->push(m_platformCapabilityUUID[i]); + } +} + +uint8_t PlatformDeviceCapabilityDescriptor::bLength() const { + return DeviceCapabilityDescriptor::bLength() + sizeof(uint8_t) + k_platformCapabilityUUIDSize*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.h new file mode 100644 index 00000000000..d2c425d4a40 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.h @@ -0,0 +1,47 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H + +#include "device_capability_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class PlatformDeviceCapabilityDescriptor : public DeviceCapabilityDescriptor { +public: + constexpr PlatformDeviceCapabilityDescriptor(const uint8_t platformCapabilityUUID[]) : + DeviceCapabilityDescriptor(0x05), + m_bReserved(0), + m_platformCapabilityUUID{ + platformCapabilityUUID[0], + platformCapabilityUUID[1], + platformCapabilityUUID[2], + platformCapabilityUUID[3], + platformCapabilityUUID[4], + platformCapabilityUUID[5], + platformCapabilityUUID[6], + platformCapabilityUUID[7], + platformCapabilityUUID[8], + platformCapabilityUUID[9], + platformCapabilityUUID[10], + platformCapabilityUUID[11], + platformCapabilityUUID[12], + platformCapabilityUUID[13], + platformCapabilityUUID[14], + platformCapabilityUUID[15]} + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + constexpr static uint8_t k_platformCapabilityUUIDSize = 16; + uint8_t m_bReserved; + uint8_t m_platformCapabilityUUID[k_platformCapabilityUUIDSize]; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/string_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/string_descriptor.cpp new file mode 100644 index 00000000000..044e54fb0e1 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/string_descriptor.cpp @@ -0,0 +1,25 @@ +#include "string_descriptor.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +void StringDescriptor::push(Channel * c) const { + Descriptor::push(c); + const char * stringPointer = m_string; + while (*stringPointer != 0) { + uint16_t stringAsUTF16CodePoint = *stringPointer; + c->push(stringAsUTF16CodePoint); + stringPointer++; + } +} + +uint8_t StringDescriptor::bLength() const { + // The script is returned in UTF-16, hence the multiplication. + return Descriptor::bLength() + 2*strlen(m_string); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/string_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/string_descriptor.h new file mode 100644 index 00000000000..1391c4aec07 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/string_descriptor.h @@ -0,0 +1,28 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_STRING_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_STRING_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class StringDescriptor : public Descriptor { +public: + constexpr StringDescriptor(const char * string) : + Descriptor(0x03), + m_string(string) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + const char * m_string; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/url_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/url_descriptor.cpp new file mode 100644 index 00000000000..2b4580b8a02 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/url_descriptor.cpp @@ -0,0 +1,25 @@ +#include "url_descriptor.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +void URLDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bScheme); + const char * stringPointer = m_string; + while (*stringPointer != 0) { + c->push(*stringPointer); + stringPointer++; + } +} + +uint8_t URLDescriptor::bLength() const { + // The script is returned in UTF-8. + return Descriptor::bLength() + sizeof(uint8_t) + strlen(m_string); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/url_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/url_descriptor.h new file mode 100644 index 00000000000..7dee33e7c33 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/url_descriptor.h @@ -0,0 +1,36 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_URL_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_URL_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class URLDescriptor : public Descriptor { +public: + enum class Scheme { + HTTP = 0, + HTTPS = 1, + IncludedInURL = 255 + }; + + constexpr URLDescriptor(Scheme scheme, const char * url) : + Descriptor(0x03), + m_bScheme((uint8_t)scheme), + m_string(url) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + uint8_t m_bScheme; + const char * m_string; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/webusb_platform_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/webusb_platform_descriptor.cpp new file mode 100644 index 00000000000..d859f5927a7 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/webusb_platform_descriptor.cpp @@ -0,0 +1,22 @@ +#include "webusb_platform_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +constexpr uint8_t WebUSBPlatformDescriptor::k_webUSBUUID[]; + +void WebUSBPlatformDescriptor::push(Channel * c) const { + PlatformDeviceCapabilityDescriptor::push(c); + c->push(m_bcdVersion); + c->push(m_bVendorCode); + c->push(m_iLandingPage); +} + +uint8_t WebUSBPlatformDescriptor::bLength() const { + return PlatformDeviceCapabilityDescriptor::bLength() + sizeof(uint16_t) + 2*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/webusb_platform_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/webusb_platform_descriptor.h new file mode 100644 index 00000000000..549bcdae2bb --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/webusb_platform_descriptor.h @@ -0,0 +1,37 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_WEBUSB_PLATFORM_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_WEBUSB_PLATFORM_DESCRIPTOR_H + +#include "platform_device_capability_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class WebUSBPlatformDescriptor : public PlatformDeviceCapabilityDescriptor { +public: + constexpr WebUSBPlatformDescriptor(uint8_t bVendorCode, uint8_t iLandingPage) : + PlatformDeviceCapabilityDescriptor(k_webUSBUUID), + m_bcdVersion(0x0100), + m_bVendorCode(bVendorCode), + m_iLandingPage(iLandingPage) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + /* Little-endian encoding of {3408B638-09A9-47A0-8BFD-A0768815B665}. + * See https://wicg.github.io/webusb/#webusb-platform-capability-descriptor */ + constexpr static uint8_t k_webUSBUUID[] = { + 0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, + 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65}; + uint16_t m_bcdVersion; + uint8_t m_bVendorCode; + uint8_t m_iLandingPage; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/device.cpp b/ion/src/device/bootloader/usb/stack/device.cpp new file mode 100644 index 00000000000..294b647e12c --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/device.cpp @@ -0,0 +1,168 @@ +#include "device.h" +#include +#include +#include + +namespace Ion { +namespace Device { +namespace USB { + +using namespace Regs; + +static inline uint16_t minUint16T(uint16_t x, uint16_t y) { return x < y ? x : y; } + +void Device::poll() { + // Read the interrupts + class OTG::GINTSTS intsts(OTG.GINTSTS()->get()); + + /* SETUP or OUT transaction + * If the Rx FIFO is not empty, there is a SETUP or OUT transaction. + * The interrupt is done AFTER THE HANSDHAKE of the transaction. */ + if (intsts.getRXFLVL()) { + class OTG::GRXSTSP grxstsp(OTG.GRXSTSP()->get()); + + // Store the packet status + OTG::GRXSTSP::PKTSTS pktsts = grxstsp.getPKTSTS(); + + // We only use endpoint 0 + assert(grxstsp.getEPNUM() == 0); + + if (pktsts == OTG::GRXSTSP::PKTSTS::OutTransferCompleted || pktsts == OTG::GRXSTSP::PKTSTS::SetupTransactionCompleted) { + // There is no data associated with this interrupt. + return; + } + + assert(pktsts != OTG::GRXSTSP::PKTSTS::GlobalOutNAK); + /* We did not enable the GONAKEFFM (Global OUT NAK effective mask) bit in + * GINTSTS, so we should never get this interrupt. */ + + assert(pktsts == OTG::GRXSTSP::PKTSTS::OutReceived || pktsts == OTG::GRXSTSP::PKTSTS::SetupReceived); + + TransactionType type = (pktsts == OTG::GRXSTSP::PKTSTS::OutReceived) ? TransactionType::Out : TransactionType::Setup; + + if (type == TransactionType::Setup && OTG.DIEPTSIZ0()->getPKTCNT()) { + // SETUP received but there is a packet in the Tx FIFO. Flush it. + m_ep0.flushTxFifo(); + } + + // Save the received packet byte count + m_ep0.setReceivedPacketSize(grxstsp.getBCNT()); + + if (type == TransactionType::Setup) { + m_ep0.readAndDispatchSetupPacket(); + } else { + assert(type == TransactionType::Out); + m_ep0.processOUTpacket(); + } + + m_ep0.discardUnreadData(); + } + + /* IN transactions. + * The interrupt is done AFTER THE HANSDHAKE of the transaction. */ + if (OTG.DIEPINT(0)->getXFRC()) { // We only check endpoint 0. + m_ep0.processINpacket(); + // Clear the Transfer Completed Interrupt + OTG.DIEPINT(0)->setXFRC(true); + } + + // Handle USB RESET. ENUMDNE = **SPEED** Enumeration Done + if (intsts.getENUMDNE()) { + // Clear the ENUMDNE bit + OTG.GINTSTS()->setENUMDNE(true); + /* After a USB reset, the host talks to the device by sending messages to + * address 0; */ + setAddress(0); + // Flush the FIFOs + m_ep0.reset(); + m_ep0.setup(); + /* In setup(), we should set the MPSIZ field in OTG_DIEPCTL0 to the maximum + * packet size depending on the enumeration speed (found in OTG_DSTS). We + * should always get FullSpeed, so we set the packet size accordingly. */ + } +} + +bool Device::isSoftDisconnected() const { + return OTG.DCTL()->getSDIS(); +} + +void Device::detach() { + // Get in soft-disconnected state + OTG.DCTL()->setSDIS(true); +} + +void Device::leave(uint32_t leaveAddress) { + if (leaveAddress == Ion::Device::InternalFlash::Config::StartAddress) { + Ion::Device::Reset::coreWhilePlugged(); + } else { + Ion::Device::Reset::jump(leaveAddress); + } +} + +bool Device::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + // Device only handles standard requests. + if (request->requestType() != SetupPacket::RequestType::Standard) { + return false; + } + switch (request->bRequest()) { + case (int) Request::GetStatus: + return getStatus(transferBuffer, transferBufferLength, transferBufferMaxLength); + case (int) Request::SetAddress: + // Make sure the request is adress is valid. + assert(request->wValue() < 128); + /* According to the reference manual, the address should be set after the + * Status stage of the current transaction, but this is not true. + * It should be set here, after the Data stage. */ + setAddress(request->wValue()); + *transferBufferLength = 0; + return true; + case (int) Request::GetDescriptor: + return getDescriptor(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (int) Request::SetConfiguration: + *transferBufferLength = 0; + return setConfiguration(request); + case (int) Request::GetConfiguration: + return getConfiguration(transferBuffer, transferBufferLength); + } + return false; +} + +bool Device::getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = minUint16T(2, transferBufferMaxLength); + for (int i = 0; i<*transferBufferLength; i++) { + transferBuffer[i] = 0; // No remote wakeup, not self-powered. + } + return true; +} + +void Device::setAddress(uint8_t address) { + OTG.DCFG()->setDAD(address); +} + +bool Device::getDescriptor(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + Descriptor * wantedDescriptor = descriptor(request->descriptorType(), request->descriptorIndex()); + if (wantedDescriptor == nullptr) { + return false; + } + *transferBufferLength = wantedDescriptor->copy(transferBuffer, transferBufferMaxLength); + return true; +} + +bool Device::getConfiguration(uint8_t * transferBuffer, uint16_t * transferBufferLength) { + *transferBufferLength = 1; + transferBuffer[0] = getActiveConfiguration(); + return true; +} + +bool Device::setConfiguration(SetupPacket * request) { + // We support one configuration only + setActiveConfiguration(request->wValue()); + /* There is one configuration only, we no need to set it again, just reset the + * endpoint. */ + m_ep0.reset(); + return true; +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/device.h b/ion/src/device/bootloader/usb/stack/device.h new file mode 100644 index 00000000000..04f90710805 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/device.h @@ -0,0 +1,67 @@ +#ifndef ION_DEVICE_SHARED_USB_DEVICE_H +#define ION_DEVICE_SHARED_USB_DEVICE_H + +#include "descriptor/descriptor.h" +#include "endpoint0.h" +#include "interface.h" +#include "request_recipient.h" +#include "setup_packet.h" + +namespace Ion { +namespace Device { +namespace USB { + +// We only handle control transfers, on EP0. +class Device : public RequestRecipient { +public: + Device(Interface * interface) : + RequestRecipient(&m_ep0), + m_ep0(this, interface), + m_resetOnDisconnect(false) + { + } + void poll(); + bool isSoftDisconnected() const; + void detach(); + void leave(uint32_t leaveAddress); + bool resetOnDisconnect() { return m_resetOnDisconnect; } + void setResetOnDisconnect(bool reset) { m_resetOnDisconnect = reset; } +protected: + virtual Descriptor * descriptor(uint8_t type, uint8_t index) = 0; + virtual void setActiveConfiguration(uint8_t configurationIndex) = 0; + virtual uint8_t getActiveConfiguration() = 0; + bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; + Endpoint0 m_ep0; +private: + // USB Standard Device Request Codes + enum class Request { + GetStatus = 0, + ClearFeature = 1, + SetFeature = 3, + SetAddress = 5, + GetDescriptor = 6, + SetDescriptor = 7, + GetConfiguration = 8, + SetConfiguration = 9, + }; + + enum class TransactionType { + Setup, + In, + Out + }; + + void setAddress(uint8_t address); + bool getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool getDescriptor(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool getConfiguration(uint8_t * transferBuffer, uint16_t * transferBufferLength); + bool setConfiguration(SetupPacket * request); + + bool m_resetOnDisconnect; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/endpoint0.cpp b/ion/src/device/bootloader/usb/stack/endpoint0.cpp new file mode 100644 index 00000000000..32121076ffd --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/endpoint0.cpp @@ -0,0 +1,348 @@ +#include "endpoint0.h" +#include +#include +#include "device.h" +#include "interface.h" +#include "request_recipient.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +using namespace Regs; + +constexpr int Endpoint0::k_maxPacketSize; +constexpr uint16_t Endpoint0::MaxTransferSize; + +void Endpoint0::setup() { + // Setup the IN direction + + // Reset the device IN endpoint 0 transfer size register + class OTG::DIEPTSIZ0 dieptsiz0(0); + /* Transfer size. The core interrupts the application only after it has + * exhausted the transfer size amount of data. The transfer size is set to the + * maximum packet size, to be interrupted at the end of each packet. */ + dieptsiz0.setXFRSIZ(k_maxPacketSize); + OTG.DIEPTSIZ0()->set(dieptsiz0); + + // Reset the device IN endpoint 0 control register + class OTG::DIEPCTL0 diepctl0(0); // Reset value + // Set the maximum packet size + diepctl0.setMPSIZ(OTG::DIEPCTL0::MPSIZ::Size64); + // Set the NAK bit: all IN transactions on endpoint 0 receive a NAK answer + diepctl0.setSNAK(true); + // Enable the endpoint + diepctl0.setEPENA(true); + OTG.DIEPCTL0()->set(diepctl0); + + // Setup the OUT direction + + setupOut(); + // Set the NAK bit + OTG.DOEPCTL0()->setSNAK(true); + // Enable the endpoint + enableOut(); + + // Setup the Tx FIFO + + /* Tx FIFO depth + * We process each packet as soon as it arrives, so we only need + * k_maxPacketSize bytes. TX0FD being in terms of 32-bit words, we divide + * k_maxPacketSize by 4. */ + OTG.DIEPTXF0()->setTX0FD(k_maxPacketSize/4); + /* Tx FIFO RAM start address. It starts just after the Rx FIFOso the value is + * Rx FIFO start address (0) + Rx FIFO depth. the Rx FIFO depth is set in + * usb.cpp, but because the code is linked separately, we cannot get it. */ + OTG.DIEPTXF0()->setTX0FSA(128); +} + +void Endpoint0::setupOut() { + class OTG::DOEPTSIZ0 doeptsiz0(0); + // Number of back-to-back SETUP data packets the endpoint can receive + doeptsiz0.setSTUPCNT(1); + // Packet count, false if a packet is written into the Rx FIFO + doeptsiz0.setPKTCNT(true); + /* Transfer size. The core interrupts the application only after it has + * exhausted the transfer size amount of data. The transfer size is set to the + * maximum packet size, to be interrupted at the end of each packet. */ + doeptsiz0.setXFRSIZ(64); + OTG.DOEPTSIZ0()->set(doeptsiz0); +} + +void Endpoint0::setOutNAK(bool nak) { + m_forceNAK = nak; + /* We need to keep track of the NAK state of the endpoint to use the value + * after a setupOut in poll() of device.cpp. */ + if (nak) { + OTG.DOEPCTL0()->setSNAK(true); + } else { + OTG.DOEPCTL0()->setCNAK(true); + } +} + +void Endpoint0::enableOut() { + OTG.DOEPCTL0()->setEPENA(true); +} + +void Endpoint0::reset() { + flushTxFifo(); + flushRxFifo(); +} + +void Endpoint0::readAndDispatchSetupPacket() { + setOutNAK(true); + + // Read the 8-bytes Setup packet + if (readPacket(m_largeBuffer, sizeof(SetupPacket)) != sizeof(SetupPacket)) { + stallTransaction(); + return; + }; + + m_request = SetupPacket(m_largeBuffer); + uint16_t maxBufferLength = std::min(m_request.wLength(), MaxTransferSize); + + // Forward the request to the request recipient + uint8_t type = static_cast(m_request.recipientType()); + if (type == 0) { + // Device recipient + m_requestRecipients[0]->processSetupRequest(&m_request, m_largeBuffer, &m_transferBufferLength, maxBufferLength); + } else { + // Interface recipient + m_requestRecipients[1]->processSetupRequest(&m_request, m_largeBuffer, &m_transferBufferLength, maxBufferLength); + } +} + +void Endpoint0::processINpacket() { + switch (m_state) { + case State::DataIn: + sendSomeData(); + break; + case State::LastDataIn: + m_state = State::StatusOut; + // Prepare to receive the OUT Data[] transaction. + setOutNAK(false); + break; + case State::StatusIn: + { + m_state = State::Idle; + // All the data has been received. Callback the request recipient. + uint8_t type = static_cast(m_request.recipientType()); + if (type == 0) { + // Device recipient + m_requestRecipients[0]->wholeDataReceivedCallback(&m_request, m_largeBuffer, &m_transferBufferLength); + } else { + // Interface recipient + m_requestRecipients[1]->wholeDataReceivedCallback(&m_request, m_largeBuffer, &m_transferBufferLength); + } + } + break; + default: + stallTransaction(); + } +} + +void Endpoint0::processOUTpacket() { + switch (m_state) { + case State::DataOut: + if (receiveSomeData() < 0) { + break; + } + if ((m_request.wLength() - m_transferBufferLength) <= k_maxPacketSize) { + m_state = State::LastDataOut; + } + break; + case State::LastDataOut: + if (receiveSomeData() < 0) { + break; + } + // Send the DATA1[] to the host. + writePacket(NULL, 0); + m_state = State::StatusIn; + break; + case State::StatusOut: + { + // Read the DATA1[] sent by the host. + readPacket(NULL, 0); + m_state = State::Idle; + // All the data has been sent. Callback the request recipient. + uint8_t type = static_cast(m_request.recipientType()); + if (type == 0) { + // Device recipient + m_requestRecipients[0]->wholeDataSentCallback(&m_request, m_largeBuffer, &m_transferBufferLength); + } else { + // Interface recipient + m_requestRecipients[1]->wholeDataSentCallback(&m_request, m_largeBuffer, &m_transferBufferLength); + } + } + break; + default: + stallTransaction(); + } +} + +void Endpoint0::flushTxFifo() { + // Set IN endpoint NAK + OTG.DIEPCTL0()->setSNAK(true); + + // Wait for core to respond + while (!OTG.DIEPINT(0)->getINEPNE()) { + } + + // Get the Tx FIFO number + uint32_t fifo = OTG.DIEPCTL0()->getTXFNUM(); + + // Wait for AHB idle + while (!OTG.GRSTCTL()->getAHBIDL()) { + } + + // Flush Tx FIFO + OTG.GRSTCTL()->setTXFNUM(fifo); + OTG.GRSTCTL()->setTXFFLSH(true); + + // Reset packet counter + OTG.DIEPTSIZ0()->set(0); + + // Wait for the flush + while (OTG.GRSTCTL()->getTXFFLSH()) { + } +} + +void Endpoint0::flushRxFifo() { + // Set OUT endpoint NAK + OTG.DOEPCTL0()->setSNAK(true); + + // Wait for AHB idle + while (!OTG.GRSTCTL()->getAHBIDL()) { + } + + // Flush Rx FIFO + OTG.GRSTCTL()->setRXFFLSH(true); + + // Reset packet counter + OTG.DOEPTSIZ0()->set(0); + + // Wait for the flush + while (OTG.GRSTCTL()->getRXFFLSH()) { + } +} + +void Endpoint0::discardUnreadData() { + for (int i = 0; i < m_receivedPacketSize; i += 4) { + OTG.DFIFO0()->get(); + } + m_receivedPacketSize = 0; +} + +void Endpoint0::sendSomeData() { + if (k_maxPacketSize < m_transferBufferLength) { + // More than one packet needs to be sent + writePacket(m_largeBuffer + m_bufferOffset, k_maxPacketSize); + m_state = State::DataIn; + m_bufferOffset += k_maxPacketSize; + m_transferBufferLength -= k_maxPacketSize; + return; + } + // Last data packet sent + writePacket(m_largeBuffer + m_bufferOffset, m_transferBufferLength); + if (m_zeroLengthPacketNeeded) { + m_state = State::DataIn; + } else { + m_state = State::LastDataIn; + } + m_bufferOffset = 0; + m_zeroLengthPacketNeeded = false; + m_transferBufferLength = 0; +} + +void Endpoint0::clearForOutTransactions(uint16_t wLength) { + m_transferBufferLength = 0; + m_state = (wLength > k_maxPacketSize) ? State::DataOut : State::LastDataOut; + setOutNAK(false); +} + +int Endpoint0::receiveSomeData() { + // If it is the first chunk of data to be received, m_transferBufferLength is 0. + uint16_t packetSize = std::min(k_maxPacketSize, m_request.wLength() - m_transferBufferLength); + uint16_t sizeOfPacketRead = readPacket(m_largeBuffer + m_transferBufferLength, packetSize); + if (sizeOfPacketRead != packetSize) { + stallTransaction(); + return -1; + } + m_transferBufferLength += packetSize; + return packetSize; +} + +uint16_t Endpoint0::readPacket(void * buffer, uint16_t length) { + uint32_t * buffer32 = (uint32_t *) buffer; + uint16_t buffer32Length = std::min(length, m_receivedPacketSize); + + int i; + // The RX FIFO is read 4 bytes by 4 bytes + for (i = buffer32Length; i >= 4; i -= 4) { + *buffer32++ = OTG.DFIFO0()->get(); + m_receivedPacketSize -= 4; + } + + if (i) { + /* If there are remaining bytes that should be read, read the next 4 bytes + * and copy only the wanted bytes. */ + uint32_t extraData = OTG.DFIFO0()->get(); + memcpy(buffer32, &extraData, i); + if (m_receivedPacketSize < 4) { + m_receivedPacketSize = 0; + } else { + m_receivedPacketSize -= 4; + } + } + return buffer32Length; +} + +uint16_t Endpoint0::writePacket(const void * buffer, uint16_t length) { + const uint32_t * buffer32 = (uint32_t *) buffer; + + // Return if there is already a packet waiting to be read in the TX FIFO + if (OTG.DIEPTSIZ0()->getPKTCNT()) { + return 0; + } + + // Enable transmission + + class OTG::DIEPTSIZ0 dieptsiz0(0); + // Indicate that the Transfer Size is one packet + dieptsiz0.setPKTCNT(1); + // Indicate the length of the Transfer Size + dieptsiz0.setXFRSIZ(length); + OTG.DIEPTSIZ0()->set(dieptsiz0); + // Enable the endpoint + OTG.DIEPCTL0()->setEPENA(true); + // Clear the NAK bit + OTG.DIEPCTL0()->setCNAK(true); + + // Copy the buffer to the TX FIFO by writing data 32bits by 32 bits. + for (int i = length; i > 0; i -= 4) { + OTG.DFIFO0()->set(*buffer32++); + } + + return length; +} + +void Endpoint0::stallTransaction() { + OTG.DIEPCTL0()->setSTALL(true); + m_state = State::Idle; +} + +void Endpoint0::computeZeroLengthPacketNeeded() { + if (m_transferBufferLength + && m_transferBufferLength < m_request.wLength() + && m_transferBufferLength % k_maxPacketSize == 0) + { + m_zeroLengthPacketNeeded = true; + return; + } + m_zeroLengthPacketNeeded = false; +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/endpoint0.h b/ion/src/device/bootloader/usb/stack/endpoint0.h new file mode 100644 index 00000000000..42d39dbefef --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/endpoint0.h @@ -0,0 +1,79 @@ +#ifndef ION_DEVICE_SHARED_USB_ENDPOINT0_H +#define ION_DEVICE_SHARED_USB_ENDPOINT0_H + +#include "setup_packet.h" + +namespace Ion { +namespace Device { +namespace USB { + +class RequestRecipient; + +class Endpoint0 { +public: + enum class State { + Idle, + Stalled, + DataIn, + LastDataIn, + StatusIn, + DataOut, + LastDataOut, + StatusOut, + }; + + constexpr static int k_maxPacketSize = 64; + constexpr static uint16_t MaxTransferSize = 2048; + + constexpr Endpoint0(RequestRecipient * device, RequestRecipient * interface) : + m_forceNAK(false), + m_bufferOffset(0), + m_transferBufferLength(0), + m_receivedPacketSize(0), + m_zeroLengthPacketNeeded(false), + m_request(), + m_requestRecipients{device, interface}, + m_state(State::Idle), + m_largeBuffer{0} + { + } + void setup(); + void setupOut(); + void setOutNAK(bool nak); + void enableOut(); + void reset(); + bool NAKForced() const { return m_forceNAK; } + void readAndDispatchSetupPacket(); + void processINpacket(); + void processOUTpacket(); + void flushTxFifo(); + void flushRxFifo(); + void setReceivedPacketSize(uint16_t size) { m_receivedPacketSize = size; } + void discardUnreadData(); + void stallTransaction(); + void computeZeroLengthPacketNeeded(); + void setState(State state) { m_state = state; } + void sendSomeData(); // Writes the next data packet and updates the state. + void clearForOutTransactions(uint16_t wLength); + +private: + int receiveSomeData(); + uint16_t readPacket(void * buffer, uint16_t length); + uint16_t writePacket(const void * buffer, uint16_t length); + + bool m_forceNAK; + int m_bufferOffset; // When sending large data stored in the buffer, the offset keeps tracks of which data packet should be sent next. + uint16_t m_transferBufferLength; + uint16_t m_receivedPacketSize; + bool m_zeroLengthPacketNeeded; + SetupPacket m_request; + RequestRecipient * m_requestRecipients[2]; + State m_state; + uint8_t m_largeBuffer[MaxTransferSize]; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/interface.cpp b/ion/src/device/bootloader/usb/stack/interface.cpp new file mode 100644 index 00000000000..835fe2cfb3f --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/interface.cpp @@ -0,0 +1,66 @@ +#include "interface.h" + +namespace Ion { +namespace Device { +namespace USB { + +static inline uint16_t minUint16T(uint16_t x, uint16_t y) { return x < y ? x : y; } + +bool Interface::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (request->requestType() != SetupPacket::RequestType::Standard) { + return false; + } + switch (request->bRequest()) { + case (int) Request::GetStatus: + return getStatus(transferBuffer, transferBufferLength, transferBufferMaxLength); + case (int) Request::SetInterface: + return setInterface(request, transferBufferLength); + case (int) Request::GetInterface: + return getInterface(transferBuffer, transferBufferLength, transferBufferMaxLength); + case (int) Request::ClearFeature: + return clearFeature(transferBufferLength); + case (int) Request::SetFeature: + return setFeature(transferBufferLength); + } + return false; +} + +bool Interface::getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = minUint16T(2, transferBufferMaxLength); + for (int i = 0; i<*transferBufferLength; i++) { + transferBuffer[i] = 0; // Reserved, must be set to 0 + } + return true; +} + +bool Interface::getInterface(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = minUint16T(1, transferBufferMaxLength);; + if (*transferBufferLength > 0) { + transferBuffer[0] = getActiveInterfaceAlternative(); + } + return true; +} + +bool Interface::setInterface(SetupPacket * request, uint16_t * transferBufferLength) { + // We support one interface only + setActiveInterfaceAlternative(request->wValue()); + // There is one interface alternative only, we no need to set it again. + *transferBufferLength = 0; + return true; +} + +bool Interface::clearFeature(uint16_t * transferBufferLength) { + // Not needed for now + *transferBufferLength = 0; + return true; +} + +bool Interface::setFeature(uint16_t * transferBufferLength) { + // Not needed for now + *transferBufferLength = 0; + return true; +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/interface.h b/ion/src/device/bootloader/usb/stack/interface.h new file mode 100644 index 00000000000..d1a98a16c3b --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/interface.h @@ -0,0 +1,42 @@ +#ifndef ION_DEVICE_SHARED_USB_INTERFACE_H +#define ION_DEVICE_SHARED_USB_INTERFACE_H + +#include "endpoint0.h" +#include "request_recipient.h" +#include "setup_packet.h" + +namespace Ion { +namespace Device { +namespace USB { + +class Interface : public RequestRecipient { +public: + Interface(Endpoint0 * ep0) : + RequestRecipient(ep0) + { + } +protected: + virtual void setActiveInterfaceAlternative(uint8_t interfaceAlternativeIndex) = 0; + virtual uint8_t getActiveInterfaceAlternative() = 0; + bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; +private: + // USB Standard Interface Request Codes + enum class Request { + GetStatus = 0, + ClearFeature = 1, + SetFeature = 3, + GetInterface = 10, + SetInterface = 11, + }; + bool getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool getInterface(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool setInterface(SetupPacket * request, uint16_t * transferBufferLength); + bool clearFeature(uint16_t * transferBufferLength); + bool setFeature(uint16_t * transferBufferLength); +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/request_recipient.cpp b/ion/src/device/bootloader/usb/stack/request_recipient.cpp new file mode 100644 index 00000000000..dd781cfa1aa --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/request_recipient.cpp @@ -0,0 +1,32 @@ +#include "request_recipient.h" + +namespace Ion { +namespace Device { +namespace USB { + +bool RequestRecipient::processSetupRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (request->followingTransaction() == SetupPacket::TransactionType::InTransaction) { + // There is no data stage in this transaction, or the data stage will be in IN direction. + if (!processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { + m_ep0->stallTransaction(); + return false; + } + if (*transferBufferLength > 0) { + m_ep0->computeZeroLengthPacketNeeded(); + m_ep0->sendSomeData(); + } else { + m_ep0->sendSomeData(); + // On seeing a zero length packet, sendSomeData changed endpoint0 state to + // LastDataIn, but it should be StatusIn as there was no data stage. + m_ep0->setState(Endpoint0::State::StatusIn); + } + } else { + // The following transaction will be an OUT transaction. + m_ep0->clearForOutTransactions(request->wLength()); + } + return true; +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/request_recipient.h b/ion/src/device/bootloader/usb/stack/request_recipient.h new file mode 100644 index 00000000000..8973fba8c7b --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/request_recipient.h @@ -0,0 +1,29 @@ +#ifndef ION_DEVICE_SHARED_USB_REQUEST_RECIPIENT_H +#define ION_DEVICE_SHARED_USB_REQUEST_RECIPIENT_H + +#include "endpoint0.h" +#include "setup_packet.h" + +namespace Ion { +namespace Device { +namespace USB { + +class RequestRecipient { +public: + RequestRecipient(Endpoint0 * ep0): + m_ep0(ep0) + { + } + bool processSetupRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + virtual void wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) {} + virtual void wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) {} +protected: + virtual bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) = 0; + Endpoint0 * m_ep0; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/setup_packet.cpp b/ion/src/device/bootloader/usb/stack/setup_packet.cpp new file mode 100644 index 00000000000..77882d6a5a6 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/setup_packet.cpp @@ -0,0 +1,38 @@ +#include "setup_packet.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +SetupPacket::SetupPacket(void * buffer) { + memcpy(this, buffer, sizeof(SetupPacket)); +} + +SetupPacket::TransactionType SetupPacket::followingTransaction() const { + if (m_wLength == 0 || (m_bmRequestType & 0b10000000) != 0) { + return TransactionType::InTransaction; + } else { + return TransactionType::OutTransaction; + } +} + +SetupPacket::RequestType SetupPacket::requestType() const { + return (RequestType) ((m_bmRequestType & 0b01100000) >> 5); +} + +SetupPacket::RecipientType SetupPacket::recipientType() const { + return (RecipientType) (m_bmRequestType & 0b00001111); +} + +int SetupPacket::descriptorIndex() { + return m_wValue & 0xFF; +} + +int SetupPacket::descriptorType() { + return m_wValue >> 8; +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/setup_packet.h b/ion/src/device/bootloader/usb/stack/setup_packet.h new file mode 100644 index 00000000000..d68ec32fb8b --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/setup_packet.h @@ -0,0 +1,65 @@ +#ifndef ION_DEVICE_SHARED_USB_SETUP_PACKET_H +#define ION_DEVICE_SHARED_USB_SETUP_PACKET_H + +#include + +namespace Ion { +namespace Device { +namespace USB { + +class SetupPacket { +public: + enum class TransactionType { + SetupTransaction, + InTransaction, + OutTransaction + }; + + enum class RequestType { + Standard = 0, + Class = 1, + Vendor = 2 + }; + + enum class RecipientType { + Device = 0, + Interface = 1, + Endpoint = 2, + Other = 3 + }; + + constexpr SetupPacket() : + m_bmRequestType(0), + m_bRequest(0), + m_wValue(0), + m_wIndex(0), + m_wLength(0) + { + } + + SetupPacket(void * buffer); + TransactionType followingTransaction() const; + RequestType requestType() const; + RecipientType recipientType() const; + int descriptorIndex(); + int descriptorType(); + uint8_t bmRequestType() { return m_bmRequestType; } + uint8_t bRequest() { return m_bRequest; } + uint16_t wValue() { return m_wValue; } + uint16_t wIndex() { return m_wIndex; } + uint16_t wLength() { return m_wLength; } +private: + uint8_t m_bmRequestType; + uint8_t m_bRequest; + uint16_t m_wValue; + uint16_t m_wIndex; + uint16_t m_wLength; +}; + +static_assert(sizeof(SetupPacket) == 8, "SetupData must be packed"); + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/streamable.cpp b/ion/src/device/bootloader/usb/stack/streamable.cpp new file mode 100644 index 00000000000..f680439ce98 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/streamable.cpp @@ -0,0 +1,15 @@ +#include "streamable.h" + +namespace Ion { +namespace Device { +namespace USB { + +uint16_t Streamable::copy(void * target, size_t maxSize) const { + Channel c(target, maxSize); + push(&c); + return maxSize - c.sizeLeft(); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/streamable.h b/ion/src/device/bootloader/usb/stack/streamable.h new file mode 100644 index 00000000000..c6a79ad62b2 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/streamable.h @@ -0,0 +1,44 @@ +#ifndef ION_DEVICE_SHARED_USB_STREAMABLE_H +#define ION_DEVICE_SHARED_USB_STREAMABLE_H + +#include +#include + +namespace Ion { +namespace Device { +namespace USB { + +class Streamable { +public: + uint16_t copy(void * target, size_t maxSize) const; +protected: + class Channel { + public: + Channel(void * pointer, size_t maxSize) : + m_pointer(pointer), + m_sizeLeft(maxSize) + { + } + template + void push(T data) { + if (m_sizeLeft >= sizeof(T)) { + T * typedPointer = static_cast(m_pointer); + *typedPointer++ = data; // Actually push the data + m_pointer = static_cast(typedPointer); + m_sizeLeft -= sizeof(T); + } + } + size_t sizeLeft() { return m_sizeLeft; } + private: + void * m_pointer; + size_t m_sizeLeft; + }; + virtual void push(Channel * c) const = 0; +}; + + +} +} +} + +#endif diff --git a/ion/src/device/flasher/display_light.cpp b/ion/src/device/flasher/display_light.cpp index ec5ba11886a..1549c576335 100644 --- a/ion/src/device/flasher/display_light.cpp +++ b/ion/src/device/flasher/display_light.cpp @@ -1,16 +1,10 @@ #include -#include namespace Flasher { namespace Display { void init() { - KDRect screen = KDRect(0,0,Ion::Display::Width,Ion::Display::Height); - Ion::Display::pushRectUniform(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColor::RGB24(0x5e81ac)); - KDContext * ctx = KDIonContext::sharedContext(); - ctx->setOrigin(KDPointZero); - ctx->setClippingRect(screen); - ctx->drawString("RECOVERY MODE", KDPoint(10, 10), KDFont::LargeFont, KDColorWhite, KDColor::RGB24(0x5e81ac)); + Ion::Display::pushRectUniform(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColor::RGB24(0xFFFF00)); } } diff --git a/ion/src/device/flasher/display_verbose.cpp b/ion/src/device/flasher/display_verbose.cpp index ed7b5ff2983..aad0b6c4598 100644 --- a/ion/src/device/flasher/display_verbose.cpp +++ b/ion/src/device/flasher/display_verbose.cpp @@ -1,55 +1,47 @@ #include #include -#include "image.h" namespace Flasher { - namespace Display { - - constexpr static int sNumberOfMessages = 5; - - constexpr static const char * sMessages[sNumberOfMessages] = { - "RECOVERY MODE", - "Your calculator is waiting", - "for Upsilon to be installed.", - "Follow the instructions", - "on your computer to continue.", - }; - - void init() { - KDRect screen = KDRect(0,0,Ion::Display::Width,Ion::Display::Height); - Ion::Display::pushRectUniform(screen, KDColor::RGB24(0x2B2B2B)); - KDContext * ctx = KDIonContext::sharedContext(); - ctx->setOrigin(KDPointZero); - ctx->setClippingRect(screen); - KDCoordinate margin = 30; - KDCoordinate currentHeight = margin; - - /* Title */ - const char * title = sMessages[0]; - KDSize titleSize = KDFont::LargeFont->stringSize(title); - ctx->drawString(title, KDPoint((Ion::Display::Width-titleSize.width())/2, currentHeight), - KDFont::LargeFont, KDColorWhite, KDColor::RGB24(0x2B2B2B)); - currentHeight = (uint16_t)((Ion::Display::Height*2)/3); - - /* Logo */ - for (int i = 0; i < IMAGE_WIDTH; ++i) { - for (int j = 0; j < IMAGE_HEIGHT; ++j) { - ctx->setPixel(KDPoint(i+(uint16_t)((Ion::Display::Width-IMAGE_WIDTH)/2), - j+(titleSize.height()+margin+15)), - KDColor::RGB16(image[i+(j*IMAGE_WIDTH)])); - } - } - - /* Messages */ - const char * message; - for (int i = 1; i < sNumberOfMessages; ++i) { - message = sMessages[i]; - KDSize messageSize = KDFont::SmallFont->stringSize(message); - ctx->drawString(message, KDPoint((Ion::Display::Width-messageSize.width())/2, currentHeight), - KDFont::SmallFont, KDColorWhite, KDColor::RGB24(0x2B2B2B)); - currentHeight += messageSize.height(); - } - } - +namespace Display { + +constexpr static int sNumberOfMessages = 5; +constexpr static int sNumberOfLanguages = 2; + +constexpr static const char * sMessages[sNumberOfLanguages][sNumberOfMessages] = { + {"RECOVERY MODE", + "Your calculator is waiting", + "for a new software.", + "Follow the instructions", + "on your computer to continue."}, + {"MODE RECUPERATION", + "Votre calculatrice attend", + "l'installation d'un nouveau logiciel.", + "Suivez les instructions sur", + "votre ordinateur pour continuer."} +}; + +void init() { + KDRect screen = KDRect(0,0,Ion::Display::Width,Ion::Display::Height); + Ion::Display::pushRectUniform(screen, KDColorWhite); + KDContext * ctx = KDIonContext::sharedContext(); + ctx->setOrigin(KDPointZero); + ctx->setClippingRect(screen); + KDCoordinate margin = 20; + KDCoordinate currentHeight = 0; + for (int i = 0; i < sNumberOfLanguages; i++) { + currentHeight += margin; + const char * title = sMessages[i][0]; + KDSize titleSize = KDFont::LargeFont->stringSize(title); + ctx->drawString(title, KDPoint((Ion::Display::Width-titleSize.width())/2, currentHeight), KDFont::LargeFont); + currentHeight += 2*titleSize.height(); + for (int j = 1; j < sNumberOfMessages; j++) { + const char * message = sMessages[i][j]; + KDSize messageSize = KDFont::SmallFont->stringSize(message); + ctx->drawString(message, KDPoint((Ion::Display::Width-messageSize.width())/2, currentHeight), KDFont::SmallFont); + currentHeight += messageSize.height(); } + } +} + +} } diff --git a/ion/src/device/flasher/main.cpp b/ion/src/device/flasher/main.cpp index 09f05e8cad0..3dcfcd80d52 100644 --- a/ion/src/device/flasher/main.cpp +++ b/ion/src/device/flasher/main.cpp @@ -11,6 +11,6 @@ void ion_main(int argc, const char * const argv[]) { Ion::USB::enable(); while (!Ion::USB::isEnumerated()) { } - Ion::USB::DFU(false, false, 0); + Ion::USB::DFU(false); } } diff --git a/ion/src/device/n0100/boot/rt0.cpp b/ion/src/device/n0100/boot/rt0.cpp index 4e2f22a5641..454d41c7e19 100644 --- a/ion/src/device/n0100/boot/rt0.cpp +++ b/ion/src/device/n0100/boot/rt0.cpp @@ -1,31 +1,31 @@ #include #include +#include #include #include #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include typedef void (*cxx_constructor)(); extern "C" { -extern char _data_section_start_flash; -extern char _data_section_start_ram; -extern char _data_section_end_ram; -extern char _bss_section_start_ram; -extern char _bss_section_end_ram; -extern cxx_constructor _init_array_start; -extern cxx_constructor _init_array_end; + extern char _data_section_start_flash; + extern char _data_section_start_ram; + extern char _data_section_end_ram; + extern char _bss_section_start_ram; + extern char _bss_section_end_ram; + extern cxx_constructor _init_array_start; + extern cxx_constructor _init_array_end; +} + +void __attribute__((noinline)) abort() { +#ifdef NDEBUG + Ion::Device::Reset::core(); +#else + while (1) { + } +#endif } /* In order to ensure that this method is execute from the external flash, we @@ -38,6 +38,7 @@ static void __attribute__((noinline)) external_flash_start() { * after the Power-On Self-Test if there is one or before switching to the * home app otherwise. */ Ion::Device::Board::initPeripherals(false); + return ion_main(0, nullptr); } @@ -63,154 +64,6 @@ static void __attribute__((noinline)) jump_to_external_flash() { external_flash_start(); } -void __attribute__((noinline)) abort_init() { - Ion::Device::Board::shutdownPeripherals(true); - Ion::Device::Board::initPeripherals(false); - Ion::Timing::msleep(100); - Ion::Backlight::init(); - Ion::LED::setColor(KDColorRed); - Ion::Backlight::setBrightness(180); -} - -void __attribute__((noinline)) abort_economy() { - int brightness = Ion::Backlight::brightness(); - bool plugged = Ion::USB::isPlugged(); - while (brightness > 0) { - brightness--; - Ion::Backlight::setBrightness(brightness); - Ion::Timing::msleep(50); - if(plugged || (!plugged && Ion::USB::isPlugged())){ - Ion::Backlight::setBrightness(180); - return; - } - } - Ion::Backlight::shutdown(); - while (1) { - Ion::Device::Power::sleepConfiguration(); - Ion::Device::WakeUp::onUSBPlugging(); - Ion::Device::WakeUp::onChargingEvent(); - Ion::Device::Power::internalFlashSuspend(true); - if (!plugged && Ion::USB::isPlugged()) { - break; - } - plugged = Ion::USB::isPlugged(); - }; - Ion::Device::Board::setStandardFrequency(Ion::Device::Board::Frequency::High); - Ion::Backlight::init(); - Ion::Backlight::setBrightness(180); -} - -void __attribute__((noinline)) abort_sleeping() { - if (Ion::Battery::level() != Ion::Battery::Charge::EMPTY) { - return; - } - // we don't use Ion::Power::suspend because we don't want to move the exam buffer into the internal - Ion::Device::Board::shutdownPeripherals(true); - bool plugged = Ion::USB::isPlugged(); - while (1) { - Ion::Device::Battery::initGPIO(); - Ion::Device::USB::initGPIO(); - Ion::Device::LED::init(); - Ion::Device::Power::sleepConfiguration(); - Ion::Device::Board::shutdownPeripherals(true); - Ion::Device::WakeUp::onUSBPlugging(); - Ion::Device::WakeUp::onChargingEvent(); - Ion::Device::Power::internalFlashSuspend(true); - Ion::Device::USB::initGPIO(); - if (!plugged && Ion::USB::isPlugged()) { - break; - } - plugged = Ion::USB::isPlugged(); - } - Ion::Device::Board::setStandardFrequency(Ion::Device::Board::Frequency::High); - abort_init(); -} - -void __attribute__((noinline)) abort_core(const char * text) { - Ion::Timing::msleep(100); - int counting; - while (true) { - counting = 0; - if (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) { - abort_sleeping(); - abort_screen(text); - } - - Ion::USB::enable(); - Ion::Battery::Charge previous_state = Ion::Battery::level(); - while (!Ion::USB::isEnumerated()) { - if (Ion::Battery::level() == Ion::Battery::Charge::LOW) { - if (previous_state != Ion::Battery::Charge::LOW) { - previous_state = Ion::Battery::Charge::LOW; - counting = 0; - } - Ion::Timing::msleep(500); - if (counting >= 20) { - abort_sleeping(); - abort_screen(text); - counting = -1; - } - counting++; - - } else { - if (previous_state == Ion::Battery::Charge::LOW) { - previous_state = Ion::Battery::level(); - counting = 0; - } - Ion::Timing::msleep(100); - if (counting >= 300) { - abort_economy(); - counting = -1; - } - counting++; - } - } - Ion::USB::DFU(false, false, 0); - } -} - -void __attribute__((noinline)) abort_screen(const char * text){ - KDRect screen = KDRect(0, 0, Ion::Display::Width, Ion::Display::Height); - Ion::Display::pushRectUniform(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height), KDColor::RGB24(0xffffff)); - KDContext* ctx = KDIonContext::sharedContext(); - ctx->setOrigin(KDPointZero); - ctx->setClippingRect(screen); - ctx->drawString("UPSILON CRASH", KDPoint(90, 10), KDFont::LargeFont, KDColorRed, KDColor::RGB24(0xffffff)); - ctx->drawString("An error occurred", KDPoint(10, 30), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("If you have some important data, please", KDPoint(10, 45), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("use bit.ly/upsiBackup to backup them.", KDPoint(10, 60), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("YOU WILL LOSE ALL YOUR DATA", KDPoint(10, 85), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("→ You can try to reboot by presssing the", KDPoint(10, 110), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("reset button at the back of the calculator", KDPoint(10, 125), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("→ If Upsilon keeps crashing, you can connect", KDPoint(10, 140), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("the calculator to a computer or a phone", KDPoint(10, 160), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("and try to reinstall Upsilon", KDPoint(10, 175), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString(text, KDPoint(220, 200), KDFont::SmallFont, KDColorRed, KDColor::RGB24(0xffffff)); -} - -void __attribute__((noinline)) abort() { - abort_init(); - abort_screen("HARDFAULT"); - abort_core("HARDFAULT"); -} - -void __attribute__((noinline)) nmi_abort() { - abort_init(); - abort_screen("NMIFAULT"); - abort_core("NMIFAULT"); -} - -void __attribute__((noinline)) bf_abort() { - abort_init(); - abort_screen("BUSFAULT"); - abort_core("BUSFAULT"); -} -void __attribute__((noinline)) uf_abort() { - abort_init(); - abort_screen("USAGEFAULT"); - abort_core("USAGEFAULT"); -} - /* When 'start' is executed, the external flash is supposed to be shutdown. We * thus forbid inlining to prevent executing this code from external flash * (just in case 'start' was to be called from the external flash). */ @@ -244,7 +97,7 @@ void __attribute__((noinline)) start() { * call the pointed function. */ #define SUPPORT_CPP_GLOBAL_CONSTRUCTORS 0 #if SUPPORT_CPP_GLOBAL_CONSTRUCTORS - for (cxx_constructor* c = &_init_array_start; c < &_init_array_end; c++) { + for (cxx_constructor * c = &_init_array_start; c<&_init_array_end; c++) { (*c)(); } #else diff --git a/ion/src/device/n0100/flash.ld b/ion/src/device/n0100/flash.ld index d22fe7b9fd8..e9b073bb21e 100644 --- a/ion/src/device/n0100/flash.ld +++ b/ion/src/device/n0100/flash.ld @@ -71,7 +71,7 @@ SECTIONS { /* The data section is written to Flash but linked as if it were in RAM. * * This is required because its initial value matters (so it has to be in - * persistent memory in the first place), but it is a R/W area of memory + * persistant memory in the first place), but it is a R/W area of memory * so it will have to live in RAM upon execution (in linker lingo, that * translates to the data section having a LMA in Flash and a VMA in RAM). * diff --git a/ion/src/device/n0110/Makefile b/ion/src/device/n0110/Makefile index 11c3b224f94..24b1a720469 100644 --- a/ion/src/device/n0110/Makefile +++ b/ion/src/device/n0110/Makefile @@ -1,5 +1,4 @@ ion_device_src += $(addprefix ion/src/device/n0110/drivers/, \ - board.cpp \ cache.cpp \ external_flash.cpp \ led.cpp \ @@ -8,9 +7,21 @@ ion_device_src += $(addprefix ion/src/device/n0110/drivers/, \ usb.cpp \ ) +ifeq ($(filter bootloader, ${MAKECMDGOALS}), bootloader) +ion_device_src += $(addprefix bootloader/drivers/, \ + board.cpp \ +) +ion_device_src += $(addprefix bootloader/boot/, \ + rt0.cpp \ +) +else +ion_device_src += $(addprefix ion/src/device/n0110/drivers/, \ + board.cpp \ +) ion_device_src += $(addprefix ion/src/device/n0110/boot/, \ rt0.cpp \ ) +endif ion_device_src += $(addprefix ion/src/device/n0110/, \ platform_info.cpp \ diff --git a/ion/src/device/n0110/boot/rt0.cpp b/ion/src/device/n0110/boot/rt0.cpp index 4e2f22a5641..454d41c7e19 100644 --- a/ion/src/device/n0110/boot/rt0.cpp +++ b/ion/src/device/n0110/boot/rt0.cpp @@ -1,31 +1,31 @@ #include #include +#include #include #include #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include typedef void (*cxx_constructor)(); extern "C" { -extern char _data_section_start_flash; -extern char _data_section_start_ram; -extern char _data_section_end_ram; -extern char _bss_section_start_ram; -extern char _bss_section_end_ram; -extern cxx_constructor _init_array_start; -extern cxx_constructor _init_array_end; + extern char _data_section_start_flash; + extern char _data_section_start_ram; + extern char _data_section_end_ram; + extern char _bss_section_start_ram; + extern char _bss_section_end_ram; + extern cxx_constructor _init_array_start; + extern cxx_constructor _init_array_end; +} + +void __attribute__((noinline)) abort() { +#ifdef NDEBUG + Ion::Device::Reset::core(); +#else + while (1) { + } +#endif } /* In order to ensure that this method is execute from the external flash, we @@ -38,6 +38,7 @@ static void __attribute__((noinline)) external_flash_start() { * after the Power-On Self-Test if there is one or before switching to the * home app otherwise. */ Ion::Device::Board::initPeripherals(false); + return ion_main(0, nullptr); } @@ -63,154 +64,6 @@ static void __attribute__((noinline)) jump_to_external_flash() { external_flash_start(); } -void __attribute__((noinline)) abort_init() { - Ion::Device::Board::shutdownPeripherals(true); - Ion::Device::Board::initPeripherals(false); - Ion::Timing::msleep(100); - Ion::Backlight::init(); - Ion::LED::setColor(KDColorRed); - Ion::Backlight::setBrightness(180); -} - -void __attribute__((noinline)) abort_economy() { - int brightness = Ion::Backlight::brightness(); - bool plugged = Ion::USB::isPlugged(); - while (brightness > 0) { - brightness--; - Ion::Backlight::setBrightness(brightness); - Ion::Timing::msleep(50); - if(plugged || (!plugged && Ion::USB::isPlugged())){ - Ion::Backlight::setBrightness(180); - return; - } - } - Ion::Backlight::shutdown(); - while (1) { - Ion::Device::Power::sleepConfiguration(); - Ion::Device::WakeUp::onUSBPlugging(); - Ion::Device::WakeUp::onChargingEvent(); - Ion::Device::Power::internalFlashSuspend(true); - if (!plugged && Ion::USB::isPlugged()) { - break; - } - plugged = Ion::USB::isPlugged(); - }; - Ion::Device::Board::setStandardFrequency(Ion::Device::Board::Frequency::High); - Ion::Backlight::init(); - Ion::Backlight::setBrightness(180); -} - -void __attribute__((noinline)) abort_sleeping() { - if (Ion::Battery::level() != Ion::Battery::Charge::EMPTY) { - return; - } - // we don't use Ion::Power::suspend because we don't want to move the exam buffer into the internal - Ion::Device::Board::shutdownPeripherals(true); - bool plugged = Ion::USB::isPlugged(); - while (1) { - Ion::Device::Battery::initGPIO(); - Ion::Device::USB::initGPIO(); - Ion::Device::LED::init(); - Ion::Device::Power::sleepConfiguration(); - Ion::Device::Board::shutdownPeripherals(true); - Ion::Device::WakeUp::onUSBPlugging(); - Ion::Device::WakeUp::onChargingEvent(); - Ion::Device::Power::internalFlashSuspend(true); - Ion::Device::USB::initGPIO(); - if (!plugged && Ion::USB::isPlugged()) { - break; - } - plugged = Ion::USB::isPlugged(); - } - Ion::Device::Board::setStandardFrequency(Ion::Device::Board::Frequency::High); - abort_init(); -} - -void __attribute__((noinline)) abort_core(const char * text) { - Ion::Timing::msleep(100); - int counting; - while (true) { - counting = 0; - if (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) { - abort_sleeping(); - abort_screen(text); - } - - Ion::USB::enable(); - Ion::Battery::Charge previous_state = Ion::Battery::level(); - while (!Ion::USB::isEnumerated()) { - if (Ion::Battery::level() == Ion::Battery::Charge::LOW) { - if (previous_state != Ion::Battery::Charge::LOW) { - previous_state = Ion::Battery::Charge::LOW; - counting = 0; - } - Ion::Timing::msleep(500); - if (counting >= 20) { - abort_sleeping(); - abort_screen(text); - counting = -1; - } - counting++; - - } else { - if (previous_state == Ion::Battery::Charge::LOW) { - previous_state = Ion::Battery::level(); - counting = 0; - } - Ion::Timing::msleep(100); - if (counting >= 300) { - abort_economy(); - counting = -1; - } - counting++; - } - } - Ion::USB::DFU(false, false, 0); - } -} - -void __attribute__((noinline)) abort_screen(const char * text){ - KDRect screen = KDRect(0, 0, Ion::Display::Width, Ion::Display::Height); - Ion::Display::pushRectUniform(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height), KDColor::RGB24(0xffffff)); - KDContext* ctx = KDIonContext::sharedContext(); - ctx->setOrigin(KDPointZero); - ctx->setClippingRect(screen); - ctx->drawString("UPSILON CRASH", KDPoint(90, 10), KDFont::LargeFont, KDColorRed, KDColor::RGB24(0xffffff)); - ctx->drawString("An error occurred", KDPoint(10, 30), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("If you have some important data, please", KDPoint(10, 45), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("use bit.ly/upsiBackup to backup them.", KDPoint(10, 60), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("YOU WILL LOSE ALL YOUR DATA", KDPoint(10, 85), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("→ You can try to reboot by presssing the", KDPoint(10, 110), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("reset button at the back of the calculator", KDPoint(10, 125), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("→ If Upsilon keeps crashing, you can connect", KDPoint(10, 140), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("the calculator to a computer or a phone", KDPoint(10, 160), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString("and try to reinstall Upsilon", KDPoint(10, 175), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); - ctx->drawString(text, KDPoint(220, 200), KDFont::SmallFont, KDColorRed, KDColor::RGB24(0xffffff)); -} - -void __attribute__((noinline)) abort() { - abort_init(); - abort_screen("HARDFAULT"); - abort_core("HARDFAULT"); -} - -void __attribute__((noinline)) nmi_abort() { - abort_init(); - abort_screen("NMIFAULT"); - abort_core("NMIFAULT"); -} - -void __attribute__((noinline)) bf_abort() { - abort_init(); - abort_screen("BUSFAULT"); - abort_core("BUSFAULT"); -} -void __attribute__((noinline)) uf_abort() { - abort_init(); - abort_screen("USAGEFAULT"); - abort_core("USAGEFAULT"); -} - /* When 'start' is executed, the external flash is supposed to be shutdown. We * thus forbid inlining to prevent executing this code from external flash * (just in case 'start' was to be called from the external flash). */ @@ -244,7 +97,7 @@ void __attribute__((noinline)) start() { * call the pointed function. */ #define SUPPORT_CPP_GLOBAL_CONSTRUCTORS 0 #if SUPPORT_CPP_GLOBAL_CONSTRUCTORS - for (cxx_constructor* c = &_init_array_start; c < &_init_array_end; c++) { + for (cxx_constructor * c = &_init_array_start; c<&_init_array_end; c++) { (*c)(); } #else diff --git a/ion/src/device/n0110/drivers/board.cpp b/ion/src/device/n0110/drivers/board.cpp index ae6b8ac0638..23579c5a262 100644 --- a/ion/src/device/n0110/drivers/board.cpp +++ b/ion/src/device/n0110/drivers/board.cpp @@ -51,10 +51,8 @@ void initMPU() { // 1.1 Memory barrier Cache::dmb(); - // 1.2 Enable fault exceptions - CORTEX.SHCRS()->setMEMFAULTENA(true); - CORTEX.SHCRS()->setBUSFAULTENA(true); - CORTEX.SHCRS()->setUSGFAULTENA(true); + // 1.2 Disable fault exceptions + CORTEX.SHCRS()->setMEMFAULTENA(false); // 1.3 Disable the MPU and clear the control register MPU.CTRL()->setENABLE(false); @@ -64,7 +62,7 @@ void initMPU() { /* This is needed for interfacing with the LCD * We define the whole FMC memory bank 1 as strongly ordered, non-executable * and not accessible. We define the FMC command and data addresses as - * writeable non-cacheable, non-buffereable and non shareable. */ + * writeable non-cachable, non-buffereable and non shareable. */ int sector = 0; MPU.RNR()->setREGION(sector++); MPU.RBAR()->setADDR(0x60000000); @@ -106,7 +104,7 @@ void initMPU() { * then an AHB error is given (AN4760). To prevent this to happen, we * configure the MPU to define the whole Quad-SPI addressable space as * strongly ordered, non-executable and not accessible. Plus, we define the - * Quad-SPI region corresponding to the External Chip as executable and + * Quad-SPI region corresponding to the Expternal Chip as executable and * fully accessible (AN4861). */ MPU.RNR()->setREGION(sector++); MPU.RBAR()->setADDR(0x90000000); diff --git a/ion/src/device/n0110/drivers/config/clocks.h b/ion/src/device/n0110/drivers/config/clocks.h index 9043f71cac6..0cc551cb03b 100644 --- a/ion/src/device/n0110/drivers/config/clocks.h +++ b/ion/src/device/n0110/drivers/config/clocks.h @@ -59,7 +59,7 @@ constexpr static double modulationDepth = 0.25; // Must be (0.25% <= md <= 2%) constexpr static uint32_t SSCG_INCSTEP = (32767*modulationDepth*PLL_N)/(1.0*100*5*SSCG_MODPER); static_assert(SSCG_MODPER == 250, "SSCG_MODPER changed"); static_assert(SSCG_INCSTEP == 25, "SSCG_INCSTEP changed"); -static_assert(SSCG_INCSTEP * SSCG_MODPER < 32767, "Wrong values for the Spread spectrum clock generator"); +static_assert(SSCG_INCSTEP * SSCG_MODPER < 32767, "Wrong values for the Spread spectrun clock generator"); } } } diff --git a/ion/src/device/n0110/drivers/external_flash.cpp b/ion/src/device/n0110/drivers/external_flash.cpp index 612efc01796..5e38449852e 100644 --- a/ion/src/device/n0110/drivers/external_flash.cpp +++ b/ion/src/device/n0110/drivers/external_flash.cpp @@ -89,10 +89,15 @@ class ExternalFlashStatusRegister { public: using Register8::Register8; REGS_BOOL_FIELD_R(BUSY, 0); + REGS_BOOL_FIELD(BP, 2); + REGS_BOOL_FIELD(BP1, 3); + REGS_BOOL_FIELD(BP2, 4); + REGS_BOOL_FIELD(TB, 5); }; class StatusRegister2 : public Register8 { public: using Register8::Register8; + REGS_BOOL_FIELD(SRP1, 0); REGS_BOOL_FIELD(QE, 1); }; }; @@ -428,6 +433,46 @@ void unlockFlash() { wait(); } +void LockSlotA() { + unset_memory_mapped_mode(); + unlockFlash(); + send_command(Command::WriteEnable); + wait(); + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + ExternalFlashStatusRegister::StatusRegister2 currentStatusRegister2(0); + send_read_command(Command::ReadStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(¤tStatusRegister2), sizeof(currentStatusRegister2)); + statusRegister2.setQE(currentStatusRegister2.getQE()); + statusRegister2.setSRP1(true); + statusRegister1.setTB(true); + statusRegister1.setBP2(true); + statusRegister1.setBP1(true); + uint8_t registers[] = {statusRegister1.get(), statusRegister2.get()}; + send_write_command(Command::WriteStatusRegister, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(registers), sizeof(registers), sOperatingModes101); + wait(); + set_as_memory_mapped(); +} + +void LockSlotB() { + unset_memory_mapped_mode(); + unlockFlash(); + send_command(Command::WriteEnable); + wait(); + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + ExternalFlashStatusRegister::StatusRegister2 currentStatusRegister2(0); + send_read_command(Command::ReadStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(¤tStatusRegister2), sizeof(currentStatusRegister2)); + statusRegister2.setQE(currentStatusRegister2.getQE()); + statusRegister2.setSRP1(true); + statusRegister1.setTB(false); + statusRegister1.setBP2(true); + statusRegister1.setBP1(true); + uint8_t registers[] = {statusRegister1.get(), statusRegister2.get()}; + send_write_command(Command::WriteStatusRegister, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(registers), sizeof(registers), sOperatingModes101); + wait(); + set_as_memory_mapped(); +} + void MassErase() { if (Config::NumberOfSectors == 0) { return; diff --git a/ion/src/device/n0110/flash.ld b/ion/src/device/n0110/flash.ld index dccce34bc41..eb5578ac61f 100644 --- a/ion/src/device/n0110/flash.ld +++ b/ion/src/device/n0110/flash.ld @@ -63,14 +63,10 @@ SECTIONS { . = ALIGN(4); *(.text.start) *(.text.abort) - *(.text.uf_abort) - *(.text.bf_abort) - *(.text.nmi_abort) - *(.text.abort_init) - *(.text.abort_core) - *(.text.abort_sleeping) - *(.text.abort_economy) - *(.text.abort_screen) + *(.text.hard_fault_handler) + *(.text.mem_fault_handler) + *(.text.usage_fault_handler) + *(.text.bus_fault_handler) *(.text.isr_systick) *(.text.__assert) *(.text.memcpy) @@ -97,123 +93,6 @@ SECTIONS { /* 'abort' dependencies */ *(.text._ZN3Ion6Device5Reset4coreEv) - *(.text._ZN3Ion3LED8setColorE7KDColor) - *(.text._ZN3Ion3LED11setBlinkingEtf) - *(.text._ZN3Ion6Device3LED*) - *(.text._ZNK3Ion6Device4Regs3TIMINS1_8RegisterItEEE4CCMREv) - *(.text._ZNK3Ion6Device4Regs3TIMINS1_8RegisterItEEE4BaseEv) - *(.text._ZNK3Ion6Device4Regs3*) - *(.text.___ZN3Ion6Device4Regs8*) - *(.text._ZNK3Ion6Device4Regs3TIMINS1_8RegisterItEEE4CCMREv) - *(.text._ZNK3Ion6Device4Regs3TIMINS1_8RegisterItEEE4CCMREv*) - *(.text._ZN3Ion6Device5Board15initPeripheralsEb) - *(.text._ZN3Ion6Device9Backlight4initEv) - *(.text._ZN3Ion6Device6Timing4initEv) - *(.text._ZN3Ion6Timing6msleepEj) - *(.text._ZN3Ion6Device8Keyboard4initEv) - *(.text._ZN3Ion6Device7Battery8initGPIOEv) - *(.text._ZN3Ion6Device3USB8initGPIOEv) - *(.text._ZN3Ion6Device9Backlight8setLevelEh) - *(.text._ZN3Ion6Device6Timing19setSysTickFrequencyEi) - *(.text._ZN3Ion6Device5Board17setClockFrequencyENS1_9FrequencyE) - *(.text._ZN3Ion6Device9Backlight10sendPulsesEi) - *(.text._ZN3Ion6Device5Board19shutdownPeripheralsEb) - *(.text._ZN3Ion6Device6Timing8shutdownEv) - *(.text._ZN3Ion6Device8Keyboard8shutdownEv) - *(.text._ZN9KDContext10drawStringEPKc7KDPointPK6KDFont7KDColorS6_i) - *(.text._ZNK8TextArea11ContentView12drawStringAtEP9KDContextiiPKci7KDColorS5_S4_S4_S5_*) - *(.text._ZL11KDPointZero*) - *(.text._ZGVZN12KDIonContext13sharedContextEvE7context) - *(.text._ZZN12KDIonContext13sharedContextEvE7context) - *(.text._ZN12KDIonContext13sharedContextEv) - *(.text._ZN20KDPostProcessContext15pushRectUniformE6KDRect7KDColor) - *(.text._ZN26KDPostProcessInvertContext15pushRectUniformE6KDRect7KDColor) - *(.text._ZN12KDIonContext15pushRectUniformE6KDRect7KDColor) - *(.text._ZN24KDPostProcessZoomContext15pushRectUniformE6KDRect7KDColor) - *(.text._ZN25KDPostProcessGammaContext15pushRectUniformE6KDRect7KDColor) - *(.text._ZN16KDRealIonContext15pushRectUniformE6KDRect7KDColor) - *(.text._ZN3Ion7Display15pushRectUniformE6KDRect7KDColor) - *(.text._ZNK6KDRect15intersectedWithERKS_) - *(.text._ZN7KDColor6RGB888Ehhh) - *(.text._ZN3Ion6Device7Display14setDrawingAreaE6KDRectNS1_11OrientationE) - *(.text.powf) - *(.text._ZN9KDContext16pushOrPullStringEPKc7KDPointPK6KDFont7KDColorS6_ibPi) - *(.text._ZNK7KDPoint12translatedByES_) - *(.text._ZNK6KDRect12translatedByE7KDPoint) - *(.text._Z7toGammai) - *(.text._ZN3Ion7Display8pullRectE6KDRectP7KDColor) - *(.text._ZN24KDPostProcessZoomContext8pullRectE6KDRectP7KDColor) - *(.text._ZN16KDRealIonContext8pullRectE6KDRectP7KDColor) - *(.text._ZN25KDPostProcessGammaContext8pullRectE6KDRectP7KDColor) - *(.text._ZN12KDIonContext8pullRectE6KDRectP7KDColor) - *(.text._ZN26KDPostProcessInvertContext8pullRectE6KDRectP7KDColor) - *(.text._ZN20KDPostProcessContext8pullRectE6KDRectP7KDColor) - *(.text.sqrtf) - *(.text._ZN11UTF8Decoder13nextCodePointEv) - *(.text._ZN7KDColor5blendES_S_h) - *(.text._ZN9KDContext17blendRectWithMaskE6KDRect7KDColorPKhPS1_) - *(.text.scalbnf) - *(.text._ZNK6KDRect10intersectsERKS_) - *(.text._ZN9KDContext18fillRectWithPixelsE6KDRectPK7KDColorPS1_) - *(.text._ZN9KDContext15setClippingRectE6KDRect) - *(.text._ZN9KDContext9setOriginE7KDPoint) - *(.text._ZN20KDPostProcessContext9setOriginE7KDPoint) - *(.text._ZN20KDPostProcessContext15pushRectUniformE6KDRect7KDColor) - *(.text._ZN26KDPostProcessInvertContext15pushRectUniformE6KDRect7KDColor) - *(.text._ZN20KDPostProcessContext8pushRectE6KDRectPK7KDColor) - *(.text._ZN12KDIonContext15pushRectUniformE6KDRect7KDColor) - *(.text._ZN12KDIonContext8pushRectE6KDRectPK7KDColor) - *(.text._ZN26KDPostProcessInvertContext8pushRectE6KDRectPK7KDColor) - *(.text._ZN24KDPostProcessZoomContext15pushRectUniformE6KDRect7KDColor) - *(.text._ZN24KDPostProcessZoomContext8pushRectE6KDRectPK7KDColor) - *(.text._ZN25KDPostProcessGammaContext15pushRectUniformE6KDRect7KDColor) - *(.text._ZN25KDPostProcessGammaContext8pushRectE6KDRectPK7KDColor) - *(.text._ZN16KDRealIonContext15pushRectUniformE6KDRect7KDColor) - *(.text._ZN16KDRealIonContext8pushRectE6KDRectPK7KDColor) - *(.text._ZN3Ion7Display8pushRectE6KDRectPK7KDColor) - *(.text._ZN3Ion7Display15pushRectUniformE6KDRect7KDColor) - *(.text._ZNK6KDRect7isEmptyEv) - *(.text._ZN20KDPostProcessContext15setClippingRectE6KDRect) - *(.text._ZNK6KDFont17indexForCodePointE9CodePoint) - *(.text._ZNK6KDFont26fetchGrayscaleGlyphAtIndexEhPh) - *(.text.LZ4_decompress_safe*) - *(.text.LZ4_wildCopy*) - *(.text.*DFU*) - *(.text.*isEnumerated*) - *(.text._ZN3Ion3USB6enableEv) - *(.text._ZN3Ion7Battery5levelEv) - *(.text._ZN3Ion7Battery7voltageEv) - *(.text._ZN3Ion3USB9isPluggedEv) - *(.text.*sleepConfiguration*) - *(.text.*onOnOffKeyDown*) - *(.text.*WakeUp*) - *(.text._ZN3Ion6Device9Backlight*) - *(.text._ZN3Ion9Backlight*) - - *(.text._ZNK10Statistics5Store6medianEi) - *(.text._ZNK10Regression5Store12meanOfColumnEiib) - *(.text._ZNK6Shared15DoublePairStore11sumOfColumnEiib) - *(.text._ZNK10Statistics5Store13firstQuartileEi) - *(.text._ZNK10Regression5Store23squaredValueSumOfColumnEiib) - *(.text._ZNK10Regression5Store16varianceOfColumnEiib) - *(.text._ZNK10Statistics5Store8maxValueEi) - *(.text._ZNK10Regression5Store25standardDeviationOfColumnEiib) - *(.text._ZNK10Statistics5Store13thirdQuartileEi) - *(.text._ZNK10Statistics5Store8minValueEi) - *(.text._ZNK6Shared8Interval18IntervalParameters*) - *(.text._ZN6Shared8Interval18IntervalParameters*) - *(.text.sqrt) - *(.text.log) - *(.text._ZN17GlobalPreferences23sharedGlobalPreferencesEv) - *(.text._ZNK10Statistics5Store16sumOfOccurrencesEi) - *(.text.floor) - *(.text.ceil) - *(.text._ZNK10Statistics5Store21frequenciesAreIntegerEi) - *(.text._ZNK10Statistics5Store34sortedElementAtCumulatedPopulationEidb) - *(.text._ZNK10Statistics5Store33sortedElementAtCumulatedFrequencyEidb) - *(.text.round) - *(.text._ZNK10Statistics5Store8minIndexEPdi*) - *(.text.LZ4_decompress_safe*) /* 'standby' dependencies '*/ *(.text._ZN3Ion6Device5Power20internalFlashStandbyEv) @@ -236,60 +115,9 @@ SECTIONS { . = ALIGN(4); *(.rodata._ZN3Ion6Device13ExternalFlash*) /* 'start' dependencies */ - *(.rodata._ZN3Ion6Device8Keyboard6ConfigL10ColumnGPIOE*) - *(.rodata._ZN3Ion6Device8Keyboard6ConfigL7RowGPIOE*) *(.rodata._ZN3Ion6Device4RegsL5GPIOAE) *(.rodata._ZN3Ion6Device4RegsL5GPIOBE) - *(.rodata._ZN3Ion6Device3LED6ConfigL7RGBPinsE) *(.rodata._ZN3Ion6Device5Board4initEv.str1.4) - *(.rodata._ZL12KDColorWhite*) - *(.rodata._ZL10KDColorRed*) - *(.rodata._ZL12KDColorBlack*) - *(.rodata._ZN4CodeL15BackgroundColorE*) - *(.rodata._ZN3Ion6Device3SWD6ConfigL4PinsE) - *(.rodata._ZN3Ion6Device7Display6ConfigL8FSMCPinsE) - *(.rodata._ZZN3Ion6Device7Display9initPanelEvE11calibration) - *(.rodata._ZN3Ion6Device3USB6ConfigL5DmPinE) - *(.rodata._ZN3Ion6Device3USB6ConfigL5DpPinE) - *(.rodata._ZN3Ion6Device3USB6ConfigL7VbusPinE) - *(.rodata._ZN3Ion6Device8Keyboard6ConfigL10ColumnPinsE) - *(.rodata._ZN3Ion6Device8Keyboard6ConfigL7RowPinsE) - *(.rodata._ZZN3Ion6Device3USB12shutdownGPIOEvE4Pins) - *(.rodata._ZN6KDFont16privateLargeFontE) - *(.rodata.abort.str1.1) - *(.rodata.uf_abort.str1.1) - *(.rodata.bf_abort.str1.1) - *(.rodata.nmi_abort.str1.1) - *(.rodata.abort_screen.str1.1) - *(.rodata._ZL5table*) - *(.rodata._ZL15glyphDataOffset*) - *(.rodata._ZL11KDPointZero*) - *(.rodata._ZL9glyphData*) - *(.rodata._ZN4CodeL14HighlightColorE*) - *(.rodata._ZTV12KDIonContext) - *(.rodata._ZTV16KDRealIonContext) - *(.rodata._ZTV24KDPostProcessZoomContext) - *(.rodata._ZTV25KDPostProcessGammaContext) - *(.rodata._ZTV26KDPostProcessInvertContext) - *(.rodata.bp) - *(.rodata.dp_h) - *(.rodata.dp_l) - *(.rodata._ZN11MicroPython5Color5ParseEPvNS0_4ModeE*) - *(.rodata._ZN9Clipboard10storedTextEv*) - *(.rodata._ZN8Sequence14ListController27toolboxForInputEventHandlerEP17InputEventHandler*) - *(.rodata._ZN8Sequence23TypeParameterController25willDisplayCellAtLocationEP13HighlightCellii*) - *(.rodata._ZN6KDFont16privateSmallFontE) - *(.rodata._ZN4I18nL23CountryPreferencesArrayE) - *(.rodata._ZN3Ion6Device3LED6ConfigL7RGBPinsE*) - *(.rodata._ZN4I18nL23CountryPreferencesArrayE*) - *(.rodata._ZN3Ion6Device3USB6ConfigL7VbusPinE*) - *(.rodata.bp*) - *(.rodata.dp_l*) - *(.rodata.dp_h*) - *(.rodata.abort_sleeping.str1.1) - *(.rodata.abort_core.str1.1) - *(.rodata.dfu_bootloader) - *(.rodata) } >INTERNAL_FLASH .exam_mode_buffer ORIGIN(EXTERNAL_FLASH) : { @@ -323,7 +151,7 @@ SECTIONS { /* The data section is written to Flash but linked as if it were in RAM. * * This is required because its initial value matters (so it has to be in - * persistent memory in the first place), but it is a R/W area of memory + * persistant memory in the first place), but it is a R/W area of memory * so it will have to live in RAM upon execution (in linker lingo, that * translates to the data section having a LMA in Flash and a VMA in RAM). * diff --git a/ion/src/device/n0110/internal_flash.ld b/ion/src/device/n0110/internal_flash.ld index 2094a6469d9..924c67f7d01 100644 --- a/ion/src/device/n0110/internal_flash.ld +++ b/ion/src/device/n0110/internal_flash.ld @@ -64,7 +64,7 @@ SECTIONS { /* The data section is written to Flash but linked as if it were in RAM. * * This is required because its initial value matters (so it has to be in - * persistent memory in the first place), but it is a R/W area of memory + * persistant memory in the first place), but it is a R/W area of memory * so it will have to live in RAM upon execution (in linker lingo, that * translates to the data section having a LMA in Flash and a VMA in RAM). * diff --git a/ion/src/device/shared/Makefile b/ion/src/device/shared/Makefile index 2db3b7db46e..4401005263b 100644 --- a/ion/src/device/shared/Makefile +++ b/ion/src/device/shared/Makefile @@ -1,5 +1,9 @@ include ion/src/device/shared/boot/Makefile +ifneq ($(filter bootloader, ${MAKECMDGOALS}), bootloader) include ion/src/device/shared/usb/Makefile +else +include ion/src/device/bootloader/usb/Makefile +endif include ion/src/device/shared/drivers/Makefile ion_device_src += $(addprefix ion/src/device/shared/, \ diff --git a/ion/src/device/shared/boot/Makefile b/ion/src/device/shared/boot/Makefile index 17dc4a4adbd..66415b3354d 100644 --- a/ion/src/device/shared/boot/Makefile +++ b/ion/src/device/shared/boot/Makefile @@ -1,3 +1,9 @@ +ifeq ($(filter bootloader, ${MAKECMDGOALS}), bootloader) +ion_device_src += $(addprefix bootloader/boot/, \ + isr.c \ +) +else ion_device_src += $(addprefix ion/src/device/shared/boot/, \ isr.c \ ) +endif diff --git a/ion/src/device/shared/boot/isr.c b/ion/src/device/shared/boot/isr.c index 2ebde5c645d..5c201aa6774 100644 --- a/ion/src/device/shared/boot/isr.c +++ b/ion/src/device/shared/boot/isr.c @@ -18,11 +18,11 @@ ISR InitialisationVector[INITIALISATION_VECTOR_SIZE] = { (ISR)&_stack_start, // Stack start start, // Reset service routine, - nmi_abort, // NMI service routine, + 0, // NMI service routine, abort, // HardFault service routine, 0, // MemManage service routine, - bf_abort, // BusFault service routine, - uf_abort, // UsageFault service routine, + 0, // BusFault service routine, + 0, // UsageFault service routine, 0, 0, 0, 0, // Reserved 0, // SVCall service routine, 0, // DebugMonitor service routine, diff --git a/ion/src/device/shared/boot/isr.h b/ion/src/device/shared/boot/isr.h index 05812cd60a3..ca5becb1399 100644 --- a/ion/src/device/shared/boot/isr.h +++ b/ion/src/device/shared/boot/isr.h @@ -5,14 +5,6 @@ extern "C" { #endif -void bf_abort(); -void uf_abort(); -void nmi_abort(); -void abort_init(); -void abort_core(const char *); -void abort_screen(const char *); -void abort_sleeping(); -void abort_economy(); void start(); void abort(); void isr_systick(); diff --git a/ion/src/device/shared/drivers/Makefile b/ion/src/device/shared/drivers/Makefile index 67bfb6ff5ad..2db043c5fa4 100644 --- a/ion/src/device/shared/drivers/Makefile +++ b/ion/src/device/shared/drivers/Makefile @@ -25,6 +25,5 @@ ion_device_src += $(addprefix ion/src/device/shared/drivers/, \ swd.cpp \ timing.cpp \ usb.cpp \ - usb_desc.cpp \ wakeup.cpp \ ) diff --git a/ion/src/device/shared/drivers/battery.cpp b/ion/src/device/shared/drivers/battery.cpp index 8686c2a97bc..c0f4877bd64 100644 --- a/ion/src/device/shared/drivers/battery.cpp +++ b/ion/src/device/shared/drivers/battery.cpp @@ -4,10 +4,10 @@ /* To measure the battery voltage, we're using the internal ADC. The ADC works * by comparing the input voltage to a reference voltage. The only fixed voltage - * we have around is 2.8V, so that's the one we're using as a reference. However, + * we have around is 2.8V, so that's the one we're using as a refrence. However, * and ADC can only measure voltage that is lower than the reference voltage. So * we need to use a voltage divider before sampling Vbat. - * To avoid draining the battery, we're using a high-impedance voltage divider, + * To avoid draining the battery, we're using an high-impedence voltage divider, * so we need to be careful when sampling the ADC. See AN2834 for more info. */ diff --git a/ion/src/device/shared/drivers/display.cpp b/ion/src/device/shared/drivers/display.cpp index 6cc1f762c83..508242ddeb3 100644 --- a/ion/src/device/shared/drivers/display.cpp +++ b/ion/src/device/shared/drivers/display.cpp @@ -51,7 +51,7 @@ bool waitForVBlank() { uint64_t startTime = Timing::millis(); uint64_t timeout = startTime + timeoutDelta; - /* If current time is big enough, currentTime + timeout wraps around the + /* If current time is big enough, currentTime + timeout wraps aroud the * uint64_t. We need to take this into account when computing the terminating * event. * diff --git a/ion/src/device/shared/drivers/exam_mode.cpp b/ion/src/device/shared/drivers/exam_mode.cpp index 0f319bc0df6..51fd7be06f2 100644 --- a/ion/src/device/shared/drivers/exam_mode.cpp +++ b/ion/src/device/shared/drivers/exam_mode.cpp @@ -51,28 +51,28 @@ size_t numberOfBitsAfterLeadingZeroes(int i) { } uint8_t * SignificantExamModeAddress() { - uint32_t * persistence_start_32 = (uint32_t *)&_exam_mode_buffer_start; - uint32_t * persistence_end_32 = (uint32_t *)&_exam_mode_buffer_end; - assert((persistence_end_32 - persistence_start_32) % 4 == 0); - while (persistence_start_32 < persistence_end_32 && *persistence_start_32 == 0x0) { + uint32_t * persitence_start_32 = (uint32_t *)&_exam_mode_buffer_start; + uint32_t * persitence_end_32 = (uint32_t *)&_exam_mode_buffer_end; + assert((persitence_end_32 - persitence_start_32) % 4 == 0); + while (persitence_start_32 < persitence_end_32 && *persitence_start_32 == 0x0) { // Scan by groups of 32 bits to reach first non-zero bit - persistence_start_32++; + persitence_start_32++; } - uint8_t * persistence_start_8 = (uint8_t *)persistence_start_32; - uint8_t * persistence_end_8 = (uint8_t *)persistence_end_32; - while (persistence_start_8 < persistence_end_8 && *persistence_start_8 == 0x0) { + uint8_t * persitence_start_8 = (uint8_t *)persitence_start_32; + uint8_t * persitence_end_8 = (uint8_t *)persitence_end_32; + while (persitence_start_8 < persitence_end_8 && *persitence_start_8 == 0x0) { // Scan by groups of 8 bits to reach first non-zero bit - persistence_start_8++; + persitence_start_8++; } - if (persistence_start_8 == persistence_end_8 + if (persitence_start_8 == persitence_end_8 // we can't toggle from 0[3] to 2[3] when there is only one 1 bit in the whole sector - || (persistence_start_8 + 1 == persistence_end_8 && *persistence_start_8 == 1)) { + || (persitence_start_8 + 1 == persitence_end_8 && *persitence_start_8 == 1)) { assert(Ion::Device::Flash::SectorAtAddress((uint32_t)&_exam_mode_buffer_start) >= 0); Ion::Device::Flash::EraseSector(Ion::Device::Flash::SectorAtAddress((uint32_t)&_exam_mode_buffer_start)); return (uint8_t *)&_exam_mode_buffer_start; } - return persistence_start_8; + return persitence_start_8; } uint8_t FetchExamMode() { diff --git a/ion/src/device/shared/drivers/external_flash.h b/ion/src/device/shared/drivers/external_flash.h index f2c1ddd6fa2..08ce49fb051 100644 --- a/ion/src/device/shared/drivers/external_flash.h +++ b/ion/src/device/shared/drivers/external_flash.h @@ -35,6 +35,9 @@ void EraseSector(int i); void WriteMemory(uint8_t * destination, const uint8_t * source, size_t length); void JDECid(uint8_t * manufacturerID, uint8_t * memoryType, uint8_t * capacityType); +void LockSlotA(); +void LockSlotB(); + static constexpr uint8_t NumberOfAddressBitsInChip = 23; // 2^23 Bytes = 8 MBytes static constexpr uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip; diff --git a/ion/src/device/shared/drivers/flash.cpp b/ion/src/device/shared/drivers/flash.cpp index 692bd4df8ba..0d2fb959ff4 100644 --- a/ion/src/device/shared/drivers/flash.cpp +++ b/ion/src/device/shared/drivers/flash.cpp @@ -50,6 +50,26 @@ void WriteMemory(uint8_t * destination, uint8_t * source, size_t length) { } } +void DisableInternalProtection() { + InternalFlash::DisableProtection(); +} + +void EnableInternalProtection() { + InternalFlash::EnableProtection(); +} + +void SetInternalSectorProtection(int i, bool protect) { + InternalFlash::SetSectorProtection(i, protect); +} + +void LockSlotA() { + ExternalFlash::LockSlotA(); +} + +void LockSlotB() { + ExternalFlash::LockSlotB(); +} + } } } diff --git a/ion/src/device/shared/drivers/flash.h b/ion/src/device/shared/drivers/flash.h index 4e61b1fa912..d385add1f20 100644 --- a/ion/src/device/shared/drivers/flash.h +++ b/ion/src/device/shared/drivers/flash.h @@ -15,6 +15,12 @@ void MassErase(); void EraseSector(int i); void WriteMemory(uint8_t * destination, uint8_t * source, size_t length); +void DisableInternalProtection(); +void EnableInternalProtection(); +void SetInternalSectorProtection(int i, bool protect); +void LockSlotA(); +void LockSlotB(); + } } } diff --git a/ion/src/device/shared/drivers/internal_flash.cpp b/ion/src/device/shared/drivers/internal_flash.cpp index cd8fa60bd43..f71d8f7ac76 100644 --- a/ion/src/device/shared/drivers/internal_flash.cpp +++ b/ion/src/device/shared/drivers/internal_flash.cpp @@ -31,6 +31,87 @@ static void open() { FLASH.CR()->setPSIZE(MemoryAccessWidth); } +static void open_protection() { + if (FLASH.OPTCR()->getLOCK()) { + FLASH.OPTKEYR()->set(0x08192A3B); + FLASH.OPTKEYR()->set(0x4C5D6E7F); + } +} + +static void close_protection() { + if(!FLASH.OPTCR()->getLOCK()) { + FLASH.OPTCR()->setLOCK(true); + } +} + +static void enable_protection_at(int i) { + if (!FLASH.OPTCR()->getLOCK()) { + switch (i) + { + case 0: + FLASH.OPTCR()->setnWRP0(true); + break; + case 1: + FLASH.OPTCR()->setnWRP1(true); + break; + case 2: + FLASH.OPTCR()->setnWRP2(true); + break; + case 3: + FLASH.OPTCR()->setnWRP3(true); + break; + case 4: + FLASH.OPTCR()->setnWRP4(true); + break; + case 5: + FLASH.OPTCR()->setnWRP5(true); + break; + case 6: + FLASH.OPTCR()->setnWRP6(true); + break; + case 7: + FLASH.OPTCR()->setnWRP7(true); + break; + default: + break; + } + } +} + +static void disable_protection_at(int i) { + if (!FLASH.OPTCR()->getLOCK()) { + switch (i) + { + case 0: + FLASH.OPTCR()->setnWRP0(false); + break; + case 1: + FLASH.OPTCR()->setnWRP1(false); + break; + case 2: + FLASH.OPTCR()->setnWRP2(false); + break; + case 3: + FLASH.OPTCR()->setnWRP3(false); + break; + case 4: + FLASH.OPTCR()->setnWRP4(false); + break; + case 5: + FLASH.OPTCR()->setnWRP5(false); + break; + case 6: + FLASH.OPTCR()->setnWRP6(false); + break; + case 7: + FLASH.OPTCR()->setnWRP7(false); + break; + default: + break; + } + } +} + static void close() { // Clear error flags class FLASH::SR sr(0); @@ -247,6 +328,22 @@ void WriteMemory(uint8_t * destination, uint8_t * source, size_t length) { close(); } +void EnableProtection() { + close_protection(); +} + +void DisableProtection() { + open_protection(); +} + +void SetSectorProtection(int i, bool protect) { + if (protect) { + enable_protection_at(i); + } else { + disable_protection_at(i); + } +} + } } } diff --git a/ion/src/device/shared/drivers/internal_flash.h b/ion/src/device/shared/drivers/internal_flash.h index befe83b3ef5..3961884f2c4 100644 --- a/ion/src/device/shared/drivers/internal_flash.h +++ b/ion/src/device/shared/drivers/internal_flash.h @@ -15,6 +15,10 @@ void EraseSector(int i); void WriteMemory(uint8_t * destination, uint8_t * source, size_t length); +void EnableProtection(); +void DisableProtection(); +void SetSectorProtection(int i, bool protect); + /* The Device is powered by a 2.8V LDO. This allows us to perform writes to the * Flash 32 bits at once. */ constexpr Regs::FLASH::CR::PSIZE MemoryAccessWidth = Regs::FLASH::CR::PSIZE::X32; diff --git a/ion/src/device/shared/drivers/reset.cpp b/ion/src/device/shared/drivers/reset.cpp index 67e52f3d6b4..ef605357004 100644 --- a/ion/src/device/shared/drivers/reset.cpp +++ b/ion/src/device/shared/drivers/reset.cpp @@ -49,7 +49,7 @@ void jump(uint32_t jumpIsrVectorAddress) { // Disable cache before reset Ion::Device::Cache::disable(); - /* Shutdown all clocks and peripherals to mimic a hardware reset. */ + /* Shutdown all clocks and periherals to mimic a hardware reset. */ Board::shutdownPeripherals(); internalFlashJump(jumpIsrVectorAddress); diff --git a/ion/src/device/shared/drivers/wakeup.h b/ion/src/device/shared/drivers/wakeup.h index 7bf12d585f1..d8cb22c7b81 100644 --- a/ion/src/device/shared/drivers/wakeup.h +++ b/ion/src/device/shared/drivers/wakeup.h @@ -8,7 +8,7 @@ namespace Device { namespace WakeUp { /* All wakeup functions can be called together without overwriting the same - * register. All together, they will set SYSCFG and EXTi registers as follow: + * register. All togethed, they will set SYSCFG and EXTi registers as follow: * * GPIO Pin Number|EXTI_EMR|EXTI_FTSR|EXTI_RTSR|EXTICR1|EXTICR2|EXTICR3| Wake up * ---------------+--------+---------+---------+-------+-------+-------+------------------------- diff --git a/ion/src/device/shared/ram.ld b/ion/src/device/shared/ram.ld index ef027f3e0b2..6cf176697df 100644 --- a/ion/src/device/shared/ram.ld +++ b/ion/src/device/shared/ram.ld @@ -22,11 +22,7 @@ MEMORY { * object). Using a stack too small would result in some memory being * overwritten (for instance, vtables that live in the .rodata section). */ -/* The image is quite large too! - * So we put the stack to 18K so there's still space - * for our image, if not LD will throw an error. */ - -STACK_SIZE = 18K; +STACK_SIZE = 32K; SECTIONS { .isr_vector_table ORIGIN(RAM_BUFFER) : { diff --git a/ion/src/device/shared/regs/flash.h b/ion/src/device/shared/regs/flash.h index f17637710dc..b289d022784 100644 --- a/ion/src/device/shared/regs/flash.h +++ b/ion/src/device/shared/regs/flash.h @@ -28,6 +28,9 @@ class FLASH { class KEYR : public Register32 { }; + class OPTKEYR : public Register32 { + }; + class CR : public Register32 { public: enum class PSIZE : uint8_t { @@ -56,11 +59,26 @@ class FLASH { REGS_BOOL_FIELD(EOP, 0); }; + class OPTCR : public Register32 { + public: + REGS_BOOL_FIELD(nWRP0, 16); + REGS_BOOL_FIELD(nWRP1, 17); + REGS_BOOL_FIELD(nWRP2, 18); + REGS_BOOL_FIELD(nWRP3, 19); + REGS_BOOL_FIELD(nWRP4, 20); + REGS_BOOL_FIELD(nWRP5, 21); + REGS_BOOL_FIELD(nWRP6, 22); + REGS_BOOL_FIELD(nWRP7, 23); + REGS_BOOL_FIELD(LOCK, 0); + }; + constexpr FLASH() {}; REGS_REGISTER_AT(ACR, 0x00); REGS_REGISTER_AT(KEYR, 0x04); + REGS_REGISTER_AT(OPTKEYR, 0x08); REGS_REGISTER_AT(SR, 0x0C); REGS_REGISTER_AT(CR, 0x10); + REGS_REGISTER_AT(OPTCR, 0x14); private: constexpr uint32_t Base() const { return 0x40023C00; diff --git a/ion/src/device/shared/regs/tim.h b/ion/src/device/shared/regs/tim.h index 704bd4fa901..f92d119e6da 100644 --- a/ion/src/device/shared/regs/tim.h +++ b/ion/src/device/shared/regs/tim.h @@ -17,7 +17,7 @@ class TIM { }; class CCMR : Register64 { - /* We're declaring CCMR as a 64 bits register. CCMR doesn't exist per se, + /* We're declaring CCMR as a 64 bits register. CCMR doesn't exsist per se, * it is in fact the consolidation of CCMR1 and CCMR2. Both are 16 bits * registers, so one could expect the consolidation to be 32 bits. However, * both CCMR1 and CCMR2 live on 32-bits boundaries, so the consolidation has diff --git a/ion/src/device/shared/usb/Makefile b/ion/src/device/shared/usb/Makefile index 02e931b6a21..3ed92c4bd59 100644 --- a/ion/src/device/shared/usb/Makefile +++ b/ion/src/device/shared/usb/Makefile @@ -93,3 +93,7 @@ $(BUILD_DIR)/ion/src/device/shared/usb/dfu.o: $(BUILD_DIR)/ion/src/device/shared ion_device_src += ion/src/device/shared/usb/dfu.cpp:-usbxip ion_device_src += ion/src/device/shared/usb/dfu_relocated.cpp:-usbxip + +ion_device_src += $(addprefix ion/src/device/shared/drivers/, \ + usb_desc.cpp \ +) diff --git a/ion/src/device/shared/usb/calculator.cpp b/ion/src/device/shared/usb/calculator.cpp index 6d0174390f6..73d6bf5794e 100644 --- a/ion/src/device/shared/usb/calculator.cpp +++ b/ion/src/device/shared/usb/calculator.cpp @@ -7,11 +7,11 @@ namespace Ion { namespace Device { namespace USB { -void Calculator::PollAndReset(bool exitWithKeyboard, bool unlock, int level) { +void Calculator::PollAndReset(bool exitWithKeyboard) { char serialNumber[Ion::Device::SerialNumber::Length+1]; Ion::Device::SerialNumber::copy(serialNumber); Calculator c(serialNumber); - + /* Leave DFU mode if the Back key is pressed, the calculator unplugged or the * USB core soft-disconnected. */ Ion::Keyboard::Key exitKey = Ion::Keyboard::Key::Back; @@ -19,10 +19,6 @@ void Calculator::PollAndReset(bool exitWithKeyboard, bool unlock, int level) { uint8_t exitKeyColumn = Ion::Device::Keyboard::columnForKey(exitKey); Ion::Device::Keyboard::activateRow(exitKeyRow); - c.m_dfuInterface.setLevel(level); - if (unlock) { - c.m_dfuInterface.unlockDfu(); - } while (!(exitWithKeyboard && !c.isErasingAndWriting() && Ion::Device::Keyboard::columnIsActive(exitKeyColumn)) && Ion::USB::isPlugged() && diff --git a/ion/src/device/shared/usb/calculator.h b/ion/src/device/shared/usb/calculator.h index a2a3581ea20..9c8b869fb28 100644 --- a/ion/src/device/shared/usb/calculator.h +++ b/ion/src/device/shared/usb/calculator.h @@ -26,7 +26,7 @@ namespace USB { class Calculator : public Device { public: - static void PollAndReset(bool exitWithKeyboard, bool unlocked, int level) + static void PollAndReset(bool exitWithKeyboard) __attribute__((section(".dfu_entry_point"))) // Needed to pinpoint this symbol in the linker script __attribute__((used)) // Make sure this symbol is not discarded at link time ; // Return true if reset is needed @@ -93,7 +93,7 @@ class Calculator : public Device { &m_webUSBPlatformDescriptor), m_languageStringDescriptor(), m_manufacturerStringDescriptor("NumWorks"), - m_productStringDescriptor("NumWorks Calculator"), + m_productStringDescriptor("Upsilon Calculator"), m_serialNumberStringDescriptor(serialNumber), m_interfaceStringDescriptor(stringDescriptor()), //m_interfaceStringDescriptor("@SRAM/0x20000000/01*256Ke"), @@ -155,7 +155,7 @@ class Calculator : public Device { ExtendedCompatIDDescriptor m_extendedCompatIdDescriptor; Descriptor * m_descriptors[8]; - /* m_descriptors contains only descriptors that should be returned via the + /* m_descriptors contains only descriptors that sould be returned via the * method descriptor(uint8_t type, uint8_t index), so do not count descriptors * included in other descriptors or returned by other functions. */ diff --git a/ion/src/device/shared/usb/dfu.ld b/ion/src/device/shared/usb/dfu.ld index 70aff4accbb..957bcfdd536 100644 --- a/ion/src/device/shared/usb/dfu.ld +++ b/ion/src/device/shared/usb/dfu.ld @@ -1,5 +1,5 @@ /* DFU transfers can serve two purposes: - * - Transferring RAM data between the machine and the host, e.g. Python scripts + * - Transfering RAM data between the machine and the host, e.g. Python scripts * - Upgrading the flash memory to perform a software update * * The second case raises a huge issue: code cannot be executed from memory that diff --git a/ion/src/device/shared/usb/dfu_interface.cpp b/ion/src/device/shared/usb/dfu_interface.cpp index 836153d4a66..2b56a2ebc96 100644 --- a/ion/src/device/shared/usb/dfu_interface.cpp +++ b/ion/src/device/shared/usb/dfu_interface.cpp @@ -1,15 +1,7 @@ - #include "dfu_interface.h" - -#include -#include -#include +#include #include -#include -#include -#include #include -#include namespace Ion { namespace Device { @@ -17,7 +9,7 @@ namespace USB { static inline uint32_t minUint32T(uint32_t x, uint32_t y) { return x < y ? x : y; } -void DFUInterface::StatusData::push(Channel *c) const { +void DFUInterface::StatusData::push(Channel * c) const { c->push(m_bStatus); c->push(m_bwPollTimeout[2]); c->push(m_bwPollTimeout[1]); @@ -26,20 +18,20 @@ void DFUInterface::StatusData::push(Channel *c) const { c->push(m_iString); } -void DFUInterface::StateData::push(Channel *c) const { +void DFUInterface::StateData::push(Channel * c) const { c->push(m_bState); } -void DFUInterface::wholeDataReceivedCallback(SetupPacket *request, uint8_t *transferBuffer, uint16_t *transferBufferLength) { - if (request->bRequest() == (uint8_t)DFURequest::Download) { +void DFUInterface::wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) { + if (request->bRequest() == (uint8_t) DFURequest::Download) { // Handle a download request if (request->wValue() == 0) { // The request is a special command switch (transferBuffer[0]) { - case (uint8_t)DFUDownloadCommand::SetAddressPointer: + case (uint8_t) DFUDownloadCommand::SetAddressPointer: setAddressPointerCommand(request, transferBuffer, *transferBufferLength); return; - case (uint8_t)DFUDownloadCommand::Erase: + case (uint8_t) DFUDownloadCommand::Erase: eraseCommand(transferBuffer, *transferBufferLength); return; default: @@ -63,17 +55,17 @@ void DFUInterface::wholeDataReceivedCallback(SetupPacket *request, uint8_t *tran } } -void DFUInterface::wholeDataSentCallback(SetupPacket *request, uint8_t *transferBuffer, uint16_t *transferBufferLength) { - if (request->bRequest() == (uint8_t)DFURequest::GetStatus) { +void DFUInterface::wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) { + if (request->bRequest() == (uint8_t) DFURequest::GetStatus) { // Do any needed action after the GetStatus request. if (m_state == State::dfuMANIFEST) { /* If we leave the DFU and reset immediately, dfu-util outputs an error: - * "File downloaded successfully - * dfu-util: Error during download get_status" - * If we sleep 1us here, there is no error. We put 1ms for security. - * This error might be due to the USB connection being cut too soon after - * the last USB exchange, so the host does not have time to process the - * answer received for the last GetStatus request. */ + * "File downloaded successfully + * dfu-util: Error during download get_status" + * If we sleep 1us here, there is no error. We put 1ms for security. + * This error might be due to the USB connection being cut too soon after + * the last USB exchange, so the host does not have time to process the + * answer received for the last GetStatus request. */ Ion::Timing::msleep(1); // Leave DFU routine: Leave DFU, reset device, jump to application code leaveDFUAndReset(); @@ -89,34 +81,31 @@ void DFUInterface::wholeDataSentCallback(SetupPacket *request, uint8_t *transfer } } -bool DFUInterface::processSetupInRequest(SetupPacket *request, uint8_t *transferBuffer, uint16_t *transferBufferLength, uint16_t transferBufferMaxLength) { +bool DFUInterface::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { if (Interface::processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { return true; } switch (request->bRequest()) { - case (uint8_t)DFURequest::Detach: + case (uint8_t) DFURequest::Detach: m_device->detach(); return true; - case (uint8_t)DFURequest::Download: + case (uint8_t) DFURequest::Download: return processDownloadRequest(request->wLength(), transferBufferLength); - case (uint8_t)DFURequest::Upload: + case (uint8_t) DFURequest::Upload: return processUploadRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength); - case (uint8_t)DFURequest::GetStatus: + case (uint8_t) DFURequest::GetStatus: return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); - case (uint8_t)DFURequest::ClearStatus: + case (uint8_t) DFURequest::ClearStatus: return clearStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); - case (uint8_t)DFURequest::GetState: + case (uint8_t) DFURequest::GetState: return getState(transferBuffer, transferBufferLength, transferBufferMaxLength); - case (uint8_t)DFURequest::Abort: + case (uint8_t) DFURequest::Abort: return dfuAbort(transferBufferLength); - case (uint8_t)DFURequest::Unlock: - m_dfuUnlocked = true; - return true; } return false; } -bool DFUInterface::processDownloadRequest(uint16_t wLength, uint16_t *transferBufferLength) { +bool DFUInterface::processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength) { if (m_state != State::dfuIDLE && m_state != State::dfuDNLOADIDLE) { m_state = State::dfuERROR; m_status = Status::errNOTDONE; @@ -134,8 +123,8 @@ bool DFUInterface::processDownloadRequest(uint16_t wLength, uint16_t *transferBu return true; } -bool DFUInterface::processUploadRequest(SetupPacket *request, uint8_t *transferBuffer, uint16_t *transferBufferLength, uint16_t transferBufferMaxLength) { - if (m_state != State::dfuIDLE && m_state != State::dfuUPLOADIDLE) { +bool DFUInterface::processUploadRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (m_state != State::dfuIDLE && m_state != State::dfuUPLOADIDLE) { m_ep0->stallTransaction(); return false; } @@ -145,7 +134,7 @@ bool DFUInterface::processUploadRequest(SetupPacket *request, uint8_t *transferB * the command codes for : * Get command / Set Address Pointer / Erase / Read Unprotect * We no not need it for now. */ - return false; + return false; } else if (request->wValue() == 1) { m_ep0->stallTransaction(); return false; @@ -164,7 +153,7 @@ bool DFUInterface::processUploadRequest(SetupPacket *request, uint8_t *transferB return true; } -void DFUInterface::setAddressPointerCommand(SetupPacket *request, uint8_t *transferBuffer, uint16_t transferBufferLength) { +void DFUInterface::setAddressPointerCommand(SetupPacket * request, uint8_t * transferBuffer, uint16_t transferBufferLength) { assert(transferBufferLength == 5); // Compute the new address but change it after the next getStatus request. m_potentialNewAddressPointer = transferBuffer[1] @@ -186,7 +175,7 @@ void DFUInterface::changeAddressPointerIfNeeded() { m_status = Status::OK; } -void DFUInterface::eraseCommand(uint8_t *transferBuffer, uint16_t transferBufferLength) { +void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBufferLength) { /* We determine whether the commands asks for a mass erase or which sector to * erase. The erase must be done after the next getStatus request. */ m_state = State::dfuDNLOADSYNC; @@ -200,12 +189,12 @@ void DFUInterface::eraseCommand(uint8_t *transferBuffer, uint16_t transferBuffer // Sector erase assert(transferBufferLength == 5); - m_eraseAddress = transferBuffer[1] + uint32_t eraseAddress = transferBuffer[1] + (transferBuffer[2] << 8) + (transferBuffer[3] << 16) + (transferBuffer[4] << 24); - m_erasePage = Flash::SectorAtAddress(m_eraseAddress); + m_erasePage = Flash::SectorAtAddress(eraseAddress); if (m_erasePage < 0) { // Unrecognized sector m_state = State::dfuERROR; @@ -213,26 +202,25 @@ void DFUInterface::eraseCommand(uint8_t *transferBuffer, uint16_t transferBuffer } } + void DFUInterface::eraseMemoryIfNeeded() { if (m_erasePage < 0) { + // There was no erase waiting. return; } willErase(); - - #if 0 // We don't erase now the flash memory to avoid crash if writing is refused if (m_erasePage == Flash::TotalNumberOfSectors()) { Flash::MassErase(); + } else { + Flash::EraseSector(m_erasePage); } - #endif - if ((m_eraseAddress >= k_ExternalBorderAddress && m_eraseAddress < ExternalFlash::Config::EndAddress) || m_dfuUnlocked) { - int32_t order = Flash::SectorAtAddress(m_eraseAddress); - Flash::EraseSector(order); - } + /* Put an out of range value in m_erasePage to indicate that no erase is + * waiting. */ + m_erasePage = -1; m_state = State::dfuDNLOADIDLE; m_status = Status::OK; - m_erasePage = -1; } void DFUInterface::writeOnMemory() { @@ -240,90 +228,7 @@ void DFUInterface::writeOnMemory() { // Write on SRAM // FIXME We should check that we are not overriding the current instructions. memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); - resetFlashParameters(); // We are writing in SRAM, so we can reset flash parameters } else if (Flash::SectorAtAddress(m_writeAddress) >= 0) { - if (m_dfuLevel == 2) { // We don't accept update - m_largeBufferLength = 0; - m_state = State::dfuERROR; - m_status = Status::errWRITE; - return; - } - - int currentMemoryType; // Detection of the current memory type (Internal or External) - - if (m_writeAddress >= InternalFlash::Config::StartAddress && m_writeAddress <= InternalFlash::Config::EndAddress) { - // We are writing in Internal where live the internal recovery (it's the most sensitive memory type) - if (m_isInternalLocked && !m_dfuUnlocked) { - // We have to check if external was written in order to - // prevent recovery mode loop or the necessity to activate STM bootloader (which is like a superuser mode) - // Nevertheless, unlike NumWorks, we don't forbid its access. - m_largeBufferLength = 0; - m_state = State::dfuERROR; - m_status = Status::errTARGET; - leaveDFUAndReset(false); - return; - } - - currentMemoryType = 0; - - // If the protection is activated, - // we check the internal magic code in order to prevent the NumWorks' Bootloader flash - - if (m_isFirstInternalPacket && !m_dfuUnlocked) { - for (int i = 0; i < 4; i++) { - if (k_omegaMagic[i] != m_largeBuffer[k_internalMagicAddress + i]) { - m_largeBufferLength = 0; - m_state = State::dfuERROR; - m_status = Status::errVERIFY; - return; - } - } - // We only check the first packet because there is some predictable data in there - m_isFirstInternalPacket = false; - } - } else { - - currentMemoryType = 1; - // We are writing in the external part where live the users apps. It's not a sensitive memory, - // but we check it in Upsilon Mode to ensure compatibility between the internal and the external. - if (m_writeAddress < k_ExternalBorderAddress && m_isFirstExternalPacket && m_dfuLevel == 0 && - !m_dfuUnlocked) { - // We skip any data verification if the user is writing in the Optionals Applications part in the - // external (Externals Apps) - for (int i = 0; i < 4; i++) { - if (k_externalUpsilonMagic[i] != m_largeBuffer[k_externalMagicAddress + i]) { - m_largeBufferLength = 0; - leaveDFUAndReset(false); - return; - } - m_largeBuffer[k_externalMagicAddress + i] = 0; - } - } - // We only check the first packet because there is some predictable data in there, - // and we unlock the internal memory - m_isFirstExternalPacket = false; - m_isInternalLocked = false; - } - - // We check if we changed the memory type where we are writing from last time. - if (m_lastMemoryType >= 0 && currentMemoryType != m_lastMemoryType) { - m_lastMemoryType = -1; - } - - m_erasePage = Flash::SectorAtAddress(m_writeAddress); - - // We check if the Sector where we are writing was not already erased and if not, we erase it. - if ((m_lastMemoryType < 0 || m_erasePage != m_lastPageErased) && - m_writeAddress < k_ExternalBorderAddress && !m_dfuUnlocked) { - Flash::EraseSector(m_erasePage); - m_lastMemoryType = currentMemoryType; - } - - m_lastPageErased = m_erasePage; - m_erasePage = -1; - - // We wait a little before writing in order to prevent some memory error. - Ion::Timing::msleep(1); Flash::WriteMemory(reinterpret_cast(m_writeAddress), m_largeBuffer, m_largeBufferLength); } else { // Invalid write address @@ -332,6 +237,7 @@ void DFUInterface::writeOnMemory() { m_status = Status::errTARGET; return; } + // Reset the buffer length m_largeBufferLength = 0; // Change the interface state and status @@ -339,7 +245,8 @@ void DFUInterface::writeOnMemory() { m_status = Status::OK; } -bool DFUInterface::getStatus(SetupPacket *request, uint8_t *transferBuffer, uint16_t *transferBufferLength, uint16_t transferBufferMaxLength) { + +bool DFUInterface::getStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { // Change the status if needed if (m_state == State::dfuMANIFESTSYNC) { m_state = State::dfuMANIFEST; @@ -351,30 +258,26 @@ bool DFUInterface::getStatus(SetupPacket *request, uint8_t *transferBuffer, uint return true; } -bool DFUInterface::clearStatus(SetupPacket *request, uint8_t *transferBuffer, uint16_t *transferBufferLength, uint16_t transferBufferMaxLength) { +bool DFUInterface::clearStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { m_status = Status::OK; m_state = State::dfuIDLE; return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); } -bool DFUInterface::getState(uint8_t *transferBuffer, uint16_t *transferBufferLength, uint16_t maxSize) { +bool DFUInterface::getState(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t maxSize) { *transferBufferLength = StateData(m_state).copy(transferBuffer, maxSize); return true; } -bool DFUInterface::dfuAbort(uint16_t *transferBufferLength) { +bool DFUInterface::dfuAbort(uint16_t * transferBufferLength) { m_status = Status::OK; m_state = State::dfuIDLE; *transferBufferLength = 0; return true; } -void DFUInterface::leaveDFUAndReset(bool do_reset) { - resetFlashParameters(); - m_isInternalLocked = true; - m_isFirstInternalPacket = true; - m_isFirstExternalPacket = true; - m_device->setResetOnDisconnect(do_reset); +void DFUInterface::leaveDFUAndReset() { + m_device->setResetOnDisconnect(true); m_device->detach(); } diff --git a/ion/src/device/shared/usb/dfu_interface.h b/ion/src/device/shared/usb/dfu_interface.h index 44ccdb5582d..0ae25d87488 100644 --- a/ion/src/device/shared/usb/dfu_interface.h +++ b/ion/src/device/shared/usb/dfu_interface.h @@ -8,20 +8,15 @@ #include "stack/endpoint0.h" #include "stack/setup_packet.h" #include "stack/streamable.h" -#include -#include -namespace Ion -{ -namespace Device -{ -namespace USB -{ +namespace Ion { +namespace Device { +namespace USB { class DFUInterface : public Interface { public: - DFUInterface(Device *device, Endpoint0 *ep0, uint8_t bInterfaceAlternateSetting) : + DFUInterface(Device * device, Endpoint0 * ep0, uint8_t bInterfaceAlternateSetting) : Interface(ep0), m_device(device), m_status(Status::OK), @@ -32,25 +27,14 @@ class DFUInterface : public Interface { m_largeBuffer{0}, m_largeBufferLength(0), m_writeAddress(0), - m_eraseAddress(0), m_bInterfaceAlternateSetting(bInterfaceAlternateSetting), - m_isErasingAndWriting(false), - m_isFirstInternalPacket(true), - m_isInternalLocked(true), - m_isFirstExternalPacket(true), - m_lastMemoryType(-1), - m_lastPageErased(-1), - m_dfuUnlocked(false), - m_dfuLevel(0) { + m_isErasingAndWriting(false) + { } - uint32_t addressPointer() const { return m_addressPointer; } - - void wholeDataReceivedCallback(SetupPacket *request, uint8_t *transferBuffer, uint16_t *transferBufferLength) override; - void wholeDataSentCallback(SetupPacket *request, uint8_t *transferBuffer, uint16_t *transferBufferLength) override; + void wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) override; + void wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) override; bool isErasingAndWriting() const { return m_isErasingAndWriting; } - void unlockDfu() { m_dfuUnlocked = true; }; - void setLevel(uint8_t lvl) { m_dfuLevel = lvl; } protected: void setActiveInterfaceAlternative(uint8_t interfaceAlternativeIndex) override { @@ -59,7 +43,7 @@ class DFUInterface : public Interface { uint8_t getActiveInterfaceAlternative() override { return m_bInterfaceAlternateSetting; } - bool processSetupInRequest(SetupPacket *request, uint8_t *transferBuffer, uint16_t *transferBufferLength, uint16_t transferBufferMaxLength) override; + bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; private: // DFU Request Codes @@ -70,11 +54,10 @@ class DFUInterface : public Interface { GetStatus = 3, ClearStatus = 4, GetState = 5, - Abort = 6, - Unlock = 11 + Abort = 6 }; - // DFU Download Command Codes + // DFU Download Commmand Codes enum class DFUDownloadCommand { GetCommand = 0x00, SetAddressPointer = 0x21, @@ -102,97 +85,82 @@ class DFUInterface : public Interface { }; enum class State : uint8_t { - appIDLE = 0, - appDETACH = 1, - dfuIDLE = 2, - dfuDNLOADSYNC = 3, - dfuDNBUSY = 4, - dfuDNLOADIDLE = 5, - dfuMANIFESTSYNC = 6, - dfuMANIFEST = 7, + appIDLE = 0, + appDETACH = 1, + dfuIDLE = 2, + dfuDNLOADSYNC = 3, + dfuDNBUSY = 4, + dfuDNLOADIDLE = 5, + dfuMANIFESTSYNC = 6, + dfuMANIFEST = 7, dfuMANIFESTWAITRESET = 8, - dfuUPLOADIDLE = 9, - dfuERROR = 10 + dfuUPLOADIDLE = 9, + dfuERROR = 10 }; class StatusData : public Streamable { public: - StatusData(Status status, State state, uint32_t pollTimeout = 10) : - /* We put a default pollTimeout value of 1ms: if the device is busy, the - * host has to wait 1ms before sending a getStatus Request. */ - m_bStatus((uint8_t) status), - m_bwPollTimeout{uint8_t((pollTimeout >> 16) & 0xFF), uint8_t((pollTimeout >> 8) & 0xFF),uint8_t(pollTimeout & 0xFF)}, - m_bState((uint8_t) state), + StatusData(Status status, State state, uint32_t pollTimeout = 1) : + /* We put a default pollTimeout value of 1ms: if the device is busy, the + * host has to wait 1ms before sending a getStatus Request. */ + m_bStatus((uint8_t)status), + m_bwPollTimeout{uint8_t((pollTimeout>>16) & 0xFF), uint8_t((pollTimeout>>8) & 0xFF), uint8_t(pollTimeout & 0xFF)}, + m_bState((uint8_t)state), m_iString(0) - { - } - + { + } protected: - void push(Channel *c) const override; + void push(Channel * c) const override; private: - uint8_t m_bStatus; // Status resulting from the execution of the most recent request + uint8_t m_bStatus; // Status resulting from the execution of the most recent request uint8_t m_bwPollTimeout[3]; // m_bwPollTimeout is 24 bits - uint8_t m_bState; // State of the device immediately following transmission of this response + uint8_t m_bState; // State of the device immediately following transmission of this response uint8_t m_iString; }; - class StateData : public Streamable - { + class StateData : public Streamable { public: - StateData(State state) : m_bState((uint8_t) state) {} + StateData(State state) : m_bState((uint8_t)state) {} protected: - void push(Channel *c) const override; + void push(Channel * c) const override; private: uint8_t m_bState; // Current state of the device }; /* The Flash and SRAM addresses are in flash.ld. However, dfu_interface is -* linked with dfu.ld, so we cannot access the values. */ - constexpr static uint32_t k_sramStartAddress = 0x20000000; - constexpr static uint32_t k_sramEndAddress = 0x20040000; - constexpr static uint32_t k_ExternalBorderAddress = 0x90200000; - - const static int k_internalMagicAddress = 0x1C4; - constexpr static int k_externalMagicAddress = 0x44f; - constexpr static uint8_t k_omegaMagic[4] = {0xF0, 0x0D, 0xC0, 0xDE}; - // TODO maybe do: add seperated upsilon magic (k_upsilonMagic) - constexpr static uint8_t k_externalUpsilonMagic[4] = {0x32, 0x30, 0x30, 0x36}; + * linked with dfu.ld, so we cannot access the values. */ + constexpr static uint32_t k_sramStartAddress = 0x20000000; + constexpr static uint32_t k_sramEndAddress = 0x20040000; // Download and upload - bool processDownloadRequest(uint16_t wLength, uint16_t *transferBufferLength); - bool processUploadRequest(SetupPacket *request, uint8_t *transferBuffer, uint16_t *transferBufferLength, uint16_t transferBufferMaxLength); + bool processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength); + bool processUploadRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); // Address pointer - void setAddressPointerCommand(SetupPacket *request, uint8_t *transferBuffer, uint16_t transferBufferLength); + void setAddressPointerCommand(SetupPacket * request, uint8_t * transferBuffer, uint16_t transferBufferLength); void changeAddressPointerIfNeeded(); // Access memory - void eraseCommand(uint8_t *transferBuffer, uint16_t transferBufferLength); + void eraseCommand(uint8_t * transferBuffer, uint16_t transferBufferLength); void eraseMemoryIfNeeded(); - void eraseMemoryIfNeededWithoutErasingAtAll(); void writeOnMemory(); void unlockFlashMemory(); void lockFlashMemoryAndPurgeCaches(); // Status - bool getStatus(SetupPacket *request, uint8_t *transferBuffer, uint16_t *transferBufferLength, uint16_t transferBufferMaxLength); - bool clearStatus(SetupPacket *request, uint8_t *transferBuffer, uint16_t *transferBufferLength, uint16_t transferBufferMaxLength); + bool getStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool clearStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); // State - bool getState(uint8_t *transferBuffer, uint16_t *transferBufferLength, uint16_t maxSize); + bool getState(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t maxSize); // Abort - bool dfuAbort(uint16_t *transferBufferLength); + bool dfuAbort(uint16_t * transferBufferLength); // Leave DFU - void leaveDFUAndReset(bool do_reset = true); - + void leaveDFUAndReset(); /* Erase and Write state. After starting the erase of flash memory, the user * can no longer leave DFU mode by pressing the Back key of the keyboard. This * way, we prevent the user from interrupting a software download. After every * software download, the calculator resets, which unlocks the "exit on * pressing back". */ void willErase() { m_isErasingAndWriting = true; } - void resetFlashParameters() { - m_lastMemoryType = -1; - m_lastPageErased = -1; - } - Device *m_device; + Device * m_device; Status m_status; State m_state; uint32_t m_addressPointer; @@ -201,16 +169,8 @@ class DFUInterface : public Interface { uint8_t m_largeBuffer[Endpoint0::MaxTransferSize]; uint16_t m_largeBufferLength; uint32_t m_writeAddress; - uint32_t m_eraseAddress; uint8_t m_bInterfaceAlternateSetting; bool m_isErasingAndWriting; - bool m_isFirstInternalPacket; - bool m_isInternalLocked; - bool m_isFirstExternalPacket; - uint8_t m_lastMemoryType; // -1: None; 0: internal; 1: external - uint8_t m_lastPageErased; // -1 default value - bool m_dfuUnlocked; - uint8_t m_dfuLevel; // 0: Upsilon only, 1: Omega-forked only, 2: No update }; } diff --git a/ion/src/device/shared/usb/dfu_relocated.cpp b/ion/src/device/shared/usb/dfu_relocated.cpp index 77433ee4c7a..7669a31036d 100644 --- a/ion/src/device/shared/usb/dfu_relocated.cpp +++ b/ion/src/device/shared/usb/dfu_relocated.cpp @@ -12,9 +12,9 @@ extern char _dfu_bootloader_flash_end; namespace Ion { namespace USB { -typedef void (*PollFunctionPointer)(bool exitWithKeyboard, bool unlocked, int level); +typedef void (*PollFunctionPointer)(bool exitWithKeyboard, void * data); -void DFU(bool exitWithKeyboard, bool unlocked, int level) { +void DFU(bool exitWithKeyboard, void * data) { Ion::updateSlotInfo(); /* DFU transfers can serve two purposes: @@ -59,7 +59,7 @@ void DFU(bool exitWithKeyboard, bool unlocked, int level) { /* 4- Disable all interrupts * The interrupt service routines live in the Flash and could be overwritten - * by garbage during a firmware upgrade operation, so we disable them. */ + * by garbage during a firmware upgrade opration, so we disable them. */ Device::Timing::shutdown(); /* 5- Jump to DFU bootloader code. We made sure in the linker script that the @@ -76,7 +76,7 @@ void DFU(bool exitWithKeyboard, bool unlocked, int level) { * add-symbol-file ion/src/device/usb/dfu.elf 0x20038000 */ - dfu_bootloader_entry(exitWithKeyboard, unlocked, level); + dfu_bootloader_entry(exitWithKeyboard, data); /* 5- Restore interrupts */ Device::Timing::init(); diff --git a/ion/src/device/shared/usb/dfu_xip.cpp b/ion/src/device/shared/usb/dfu_xip.cpp index b94a77bd56e..1a223d4f5dc 100644 --- a/ion/src/device/shared/usb/dfu_xip.cpp +++ b/ion/src/device/shared/usb/dfu_xip.cpp @@ -4,9 +4,9 @@ namespace Ion { namespace USB { -void DFU(bool exitWithKeyboard, bool unlocked, int level) { +void DFU(bool exitWithKeyboard, void * data) { Ion::updateSlotInfo(); - Ion::Device::USB::Calculator::PollAndReset(exitWithKeyboard, unlocked, level); + Ion::Device::USB::Calculator::PollAndReset(exitWithKeyboard); } } diff --git a/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h index 8b67cabc6ca..f434352f24b 100644 --- a/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h @@ -1,5 +1,5 @@ -#ifndef ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABILITY_DESCRIPTOR_H -#define ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABILITY_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H #include "descriptor.h" diff --git a/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h index 3c117348c50..d2c425d4a40 100644 --- a/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h @@ -1,5 +1,5 @@ -#ifndef ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABILITY_DESCRIPTOR_H -#define ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABILITY_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H #include "device_capability_descriptor.h" diff --git a/ion/src/device/shared/usb/stack/device.cpp b/ion/src/device/shared/usb/stack/device.cpp index 6bfcbef7d6a..294b647e12c 100644 --- a/ion/src/device/shared/usb/stack/device.cpp +++ b/ion/src/device/shared/usb/stack/device.cpp @@ -108,7 +108,7 @@ bool Device::processSetupInRequest(SetupPacket * request, uint8_t * transferBuff case (int) Request::GetStatus: return getStatus(transferBuffer, transferBufferLength, transferBufferMaxLength); case (int) Request::SetAddress: - // Make sure the request is address is valid. + // Make sure the request is adress is valid. assert(request->wValue() < 128); /* According to the reference manual, the address should be set after the * Status stage of the current transaction, but this is not true. diff --git a/themes/icons.json b/themes/icons.json index 953bca5cf27..514a92601b5 100644 --- a/themes/icons.json +++ b/themes/icons.json @@ -43,6 +43,5 @@ "apps/probability/images/student_icon.png" : "probability/student_icon.png", "apps/probability/images/uniform_icon.png" : "probability/uniform_icon.png", - "bootloader/cable.png": "bootloader/cable.png", "bootloader/computer.png": "bootloader/computer.png" } diff --git a/themes/themes/local/upsilon_light/bootloader/cable.png b/themes/themes/local/upsilon_light/bootloader/cable.png deleted file mode 100644 index ac0996ca74685d68c185ee0bbc4cc3a05cdb6aec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^Y(VV7!2~3A?JidUsVYww$B>FS$tej5D^{!s2nb9_ zNT?`y?&$8$F3il#+`^x3V3@$52{5#SsAJA*S1~lk!zb;$B{EN4o7dwGqi2xG= YtES2ht{+eQfv#ZiboFyt=akR{0N5@#iU0rr diff --git a/themes/themes/local/upsilon_light/bootloader/computer.png b/themes/themes/local/upsilon_light/bootloader/computer.png index d30a99af2af41be5ed19fed9df7ea01d38eb3acb..7768daf7310fecdcd556d2be5f7210cb94f9057d 100644 GIT binary patch delta 434 zcmV;j0Zsnp0>J~28Gi-<005s-_PPK70fka}YJfM_{*l!;_Xv2hr$(f>1CWXm>>zusL9Vi6Ha=;igDAYc2@_u08C_a2rvN?FaZ++Ou)QB zort2{E84^MFsJZbYi-#uV;x{0ps{l_w3F48H-GzRqF~%uxNwmIEh4o0JeCd9fk?S( z{an-OY^gGaa(~9t;7S$m%&en?=wXou8FO&a321w0NUmHq(gIRdHg^sk60({=s(5F{ z;QW=*wLw(f0kvV@O;nmeDsLx6WyVHt4w!&>)8Za5;Y?`ub!L#hy*5&W0rP5><{907*qoM6N<$f)DM@%K!iX literal 357 zcmeAS@N?(olHy`uVBq!ia0vp^!9c9f!2~1)3=-cmFfi(Px;TbZ%z1lDvFNaYK7}4lH07&=AltcwxxJ!ozZ6At1t`aiZjyXlCbA z&vN@|r8b}I^4L0=aSo2B8^(o^QSwf_z?GwA>L^kc)t8E*qE z?Lzwar#^Om`XhiyW<9qeLqm|(|0z@NMCHYLyMC&+;D45|Yv1WjTJwYJz4UTqj=%aE zU;F*L_~++mPA$J=slN11l}}h{tTDrbIj`ql%a5}2R%Chp=eqg(ke-W{>CvzE{%0x` XaGfW8;hGOH_!vB0{an^LB{Ts5Syh Date: Thu, 7 Apr 2022 20:07:03 +0200 Subject: [PATCH 201/355] [DFU] Fix simulators --- ion/src/shared/dummy/usb.cpp | 2 +- ion/src/simulator/3ds/driver/usb.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/shared/dummy/usb.cpp b/ion/src/shared/dummy/usb.cpp index 6a7fc8e1953..904b056bc82 100644 --- a/ion/src/shared/dummy/usb.cpp +++ b/ion/src/shared/dummy/usb.cpp @@ -15,7 +15,7 @@ bool isEnumerated() { void clearEnumerationInterrupt() { } -void DFU(bool, bool, int) { +void DFU(bool, void*) { } void enable() { diff --git a/ion/src/simulator/3ds/driver/usb.cpp b/ion/src/simulator/3ds/driver/usb.cpp index 17cea3ce78d..27d63274cc4 100644 --- a/ion/src/simulator/3ds/driver/usb.cpp +++ b/ion/src/simulator/3ds/driver/usb.cpp @@ -13,7 +13,7 @@ bool Ion::USB::isEnumerated() { void Ion::USB::clearEnumerationInterrupt() { } -void Ion::USB::DFU(bool, bool, int) { +void Ion::USB::DFU(bool, void *) { } void Ion::USB::enable() { From c65b1c6d3cf613d25e0b4003c57311b9a93eff5b Mon Sep 17 00:00:00 2001 From: devdl11 Date: Thu, 7 Apr 2022 22:18:05 +0200 Subject: [PATCH 202/355] [Review] Review Update --- apps/apps_container.cpp | 3 +-- bootloader/boot.cpp | 8 ++++---- bootloader/boot.h | 2 +- bootloader/interface.cpp | 2 +- bootloader/interface.h | 2 +- bootloader/usb_data.cpp | 2 +- bootloader/usb_data.h | 2 +- 7 files changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index 0b41800599d..f464b07c187 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -147,8 +147,7 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { * pictogram. */ updateBatteryState(); if (switchTo(usbConnectedAppSnapshot())) { - Ion::USB::DFU(true - ); + Ion::USB::DFU(true); // Update LED when exiting DFU mode Ion::LED::updateColorWithPlugAndCharge(); bool switched = switchTo(activeSnapshot); diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index c46dc8cf6e7..292ac5c5972 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -98,7 +98,7 @@ void Boot::installerMenu() { continue; } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Two)) { scan = Ion::Keyboard::State(Ion::Keyboard::Key::Back); - blupdate(); + bootloaderUpdate(); continue; } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { Ion::Power::standby(); // Force a core reset to exit @@ -127,11 +127,11 @@ void Boot::aboutMenu() { } -void Boot::blupdate() { - USBData data = USBData::BLUPDATE(); +void Boot::bootloaderUpdate() { + USBData data = USBData::BOOTLOADER_UPDATE(); for (;;) { - Bootloader::Interface::drawBLUpdate(); + Bootloader::Interface::drawBootloaderUpdate(); Ion::USB::enable(); do { uint64_t scan = Ion::Keyboard::scan(); diff --git a/bootloader/boot.h b/bootloader/boot.h index 361d881441b..0414ed22d69 100644 --- a/bootloader/boot.h +++ b/bootloader/boot.h @@ -24,7 +24,7 @@ class Boot { static void bootloader(); static void aboutMenu(); static void installerMenu(); - static void blupdate(); + static void bootloaderUpdate(); static void bootSlot(Bootloader::Slot slot); static void lockInternal(); }; diff --git a/bootloader/interface.cpp b/bootloader/interface.cpp index 42b81fa9866..8367e6f67d1 100644 --- a/bootloader/interface.cpp +++ b/bootloader/interface.cpp @@ -283,7 +283,7 @@ void Interface::drawInstallerSelection() { ctx->drawString(Messages::installerText3, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); } -void Interface::drawBLUpdate() { +void Interface::drawBootloaderUpdate() { Interface::drawHeader(); KDContext * ctx = KDIonContext::sharedContext(); int y = ImageStore::Computer->height() + (KDFont::LargeFont->glyphSize().height() + 10) + (KDFont::SmallFont->glyphSize().height() + 10); diff --git a/bootloader/interface.h b/bootloader/interface.h index dcecdcda11b..b21eb0e38e5 100644 --- a/bootloader/interface.h +++ b/bootloader/interface.h @@ -21,7 +21,7 @@ class Interface { static void drawCrash(const char * error); static void drawRecovery(); static void drawInstallerSelection(); - static void drawBLUpdate(); + static void drawBootloaderUpdate(); static void drawEpsilonAdvertisement(); }; diff --git a/bootloader/usb_data.cpp b/bootloader/usb_data.cpp index 2570b80eacd..65e6f222578 100644 --- a/bootloader/usb_data.cpp +++ b/bootloader/usb_data.cpp @@ -30,7 +30,7 @@ const Bootloader::USBData Bootloader::USBData::DEFAULT() { return USBData("@Flash/0x90000000/08*004Kg,01*032Kg,63*064Kg,64*064Kg", Messages::upsilonBootloader, DFUData()); } -const Bootloader::USBData Bootloader::USBData::BLUPDATE() { +const Bootloader::USBData Bootloader::USBData::BOOTLOADER_UPDATE() { return USBData("@Flash/0x08000000/04*016Kg", Messages::bootloaderUpdate, DFUData(true, false)); } diff --git a/bootloader/usb_data.h b/bootloader/usb_data.h index 2fba6a27d92..8cca7475fc8 100644 --- a/bootloader/usb_data.h +++ b/bootloader/usb_data.h @@ -42,7 +42,7 @@ class USBData { static const char * buildStringDescriptor(StringHeader header, uint32_t startAddress, uint32_t size); static const USBData DEFAULT(); - static const USBData BLUPDATE(); + static const USBData BOOTLOADER_UPDATE(); static USBData Recovery(uint32_t startAddress, uint32_t size); private: From 3328bc28b9fcb6fccc2ec58f145db2bfb6c82163 Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Fri, 8 Apr 2022 13:16:52 +0200 Subject: [PATCH 203/355] Update bootloader/itoa.cpp Co-authored-by: Lauryy06 <80424145+Lauryy06@users.noreply.github.com> --- bootloader/itoa.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootloader/itoa.cpp b/bootloader/itoa.cpp index 34aa6069281..41e6bdc0e11 100644 --- a/bootloader/itoa.cpp +++ b/bootloader/itoa.cpp @@ -61,4 +61,4 @@ char* Bootloader::Utility::itoa(int value, char* buffer, int base) // reverse the string and return it return reverse(buffer, 0, i - 1); -} \ No newline at end of file +} From 8db051474c7ba7afffab5042687f86473cfd5d33 Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Fri, 8 Apr 2022 13:17:04 +0200 Subject: [PATCH 204/355] Update bootloader/itoa.cpp Co-authored-by: Lauryy06 <80424145+Lauryy06@users.noreply.github.com> --- bootloader/itoa.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bootloader/itoa.cpp b/bootloader/itoa.cpp index 41e6bdc0e11..c5a3203db77 100644 --- a/bootloader/itoa.cpp +++ b/bootloader/itoa.cpp @@ -10,8 +10,7 @@ void swap(char *x, char *y) { } // Function to reverse `buffer[i…j]` -char* reverse(char *buffer, int i, int j) -{ +char* reverse(char *buffer, int i, int j) { while (i < j) { swap(&buffer[i++], &buffer[j--]); } From 3af3d700550ca5331be5d12f59e8aebc2e4f9361 Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Fri, 8 Apr 2022 13:17:11 +0200 Subject: [PATCH 205/355] Update bootloader/itoa.cpp Co-authored-by: Lauryy06 <80424145+Lauryy06@users.noreply.github.com> --- bootloader/itoa.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bootloader/itoa.cpp b/bootloader/itoa.cpp index c5a3203db77..1dea9e28483 100644 --- a/bootloader/itoa.cpp +++ b/bootloader/itoa.cpp @@ -30,8 +30,7 @@ char* Bootloader::Utility::itoa(int value, char* buffer, int base) int n = abs(value); int i = 0; - while (n) - { + while (n) { int r = n % base; if (r >= 10) { From d3754a931a185873bb69ce3607c369b975a8eee2 Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Fri, 8 Apr 2022 13:17:27 +0200 Subject: [PATCH 206/355] Update bootloader/itoa.h Co-authored-by: Lauryy06 <80424145+Lauryy06@users.noreply.github.com> --- bootloader/itoa.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootloader/itoa.h b/bootloader/itoa.h index 1a8602528c5..f7028f77f24 100644 --- a/bootloader/itoa.h +++ b/bootloader/itoa.h @@ -8,4 +8,4 @@ namespace Bootloader { }; } -#endif // _BOOTLOADER_ITOA_H_ \ No newline at end of file +#endif From 80f099c274420b08c1bc0e5cc23ebefa0f9ae1fb Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Fri, 8 Apr 2022 13:17:43 +0200 Subject: [PATCH 207/355] Update bootloader/usb_data.cpp Co-authored-by: Lauryy06 <80424145+Lauryy06@users.noreply.github.com> --- bootloader/usb_data.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootloader/usb_data.cpp b/bootloader/usb_data.cpp index 65e6f222578..edb1ab06b01 100644 --- a/bootloader/usb_data.cpp +++ b/bootloader/usb_data.cpp @@ -36,4 +36,4 @@ const Bootloader::USBData Bootloader::USBData::BOOTLOADER_UPDATE() { Bootloader::USBData Bootloader::USBData::Recovery(uint32_t startAddress, uint32_t size) { return USBData(buildStringDescriptor(StringHeader::SRAM(), startAddress, size), Messages::upsilonRecovery, DFUData(false, false)); -} \ No newline at end of file +} From 7a6c211001226db17b7fd36ca65ea89c4f1daa24 Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Fri, 8 Apr 2022 13:20:56 +0200 Subject: [PATCH 208/355] Update bootloader/itoa.cpp Co-authored-by: Lauryy06 <80424145+Lauryy06@users.noreply.github.com> --- bootloader/itoa.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bootloader/itoa.cpp b/bootloader/itoa.cpp index 1dea9e28483..cd47daaaa0f 100644 --- a/bootloader/itoa.cpp +++ b/bootloader/itoa.cpp @@ -19,8 +19,7 @@ char* reverse(char *buffer, int i, int j) { } // Iterative function to implement `itoa()` function in C -char* Bootloader::Utility::itoa(int value, char* buffer, int base) -{ +char* Bootloader::Utility::itoa(int value, char* buffer, int base) { // invalid input if (base < 2 || base > 32) { return buffer; From 62e98107f760df9dce7dfcbc7edfc72b5b2830e0 Mon Sep 17 00:00:00 2001 From: devdl11 Date: Fri, 8 Apr 2022 13:49:44 +0200 Subject: [PATCH 209/355] [Reviews] Code correction and improvement --- bootloader/Makefile | 1 + bootloader/boot.cpp | 11 +++-------- bootloader/itoa.cpp | 2 +- bootloader/kernel_header.cpp | 13 ++++--------- bootloader/kernel_header.h | 2 +- bootloader/main.cpp | 4 ++-- bootloader/usb_data.cpp | 8 ++++---- bootloader/usb_data.h | 10 +++++----- bootloader/userland_header.cpp | 4 ++-- bootloader/userland_header.h | 8 ++++---- bootloader/utility.cpp | 10 ++++++++++ bootloader/{itoa.h => utility.h} | 3 ++- ion/src/device/bootloader/usb/calculator.h | 2 +- ion/src/device/bootloader/usb/dfu_interface.cpp | 6 +++--- ion/src/device/bootloader/usb/dfu_interface.h | 8 ++++---- .../stack/descriptor/device_capability_descriptor.h | 4 ++-- .../platform_device_capability_descriptor.h | 4 ++-- ion/src/device/n0110/drivers/board.cpp | 2 +- ion/src/device/shared/drivers/display.cpp | 2 +- ion/src/device/shared/drivers/reset.cpp | 2 +- ion/src/device/shared/drivers/wakeup.h | 2 +- ion/src/device/shared/regs/tim.h | 2 +- ion/src/device/shared/usb/calculator.h | 2 +- .../stack/descriptor/device_capability_descriptor.h | 4 ++-- .../platform_device_capability_descriptor.h | 4 ++-- 25 files changed, 61 insertions(+), 59 deletions(-) create mode 100644 bootloader/utility.cpp rename bootloader/{itoa.h => utility.h} (71%) diff --git a/bootloader/Makefile b/bootloader/Makefile index 41648a0e382..56648ddba42 100644 --- a/bootloader/Makefile +++ b/bootloader/Makefile @@ -12,6 +12,7 @@ bootloader_src += $(addprefix bootloader/,\ recovery.cpp \ usb_data.cpp \ itoa.cpp \ + utility.cpp \ ) bootloader_images = $(addprefix bootloader/, \ diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index 292ac5c5972..cc71d711d6b 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -25,14 +26,8 @@ void Boot::bootSlot(Bootloader::Slot s) { // We are trying to boot epsilon, so we check the version and show an advertisement if needed const char * version = s.userlandHeader()->version(); const char * min = "18.2.4"; - int vsum = 0; - for (int i = 0; i < strlen(version); i++) { - vsum += version[i] * (100-i*15); - } - int minsum = 0; - for (int i = 0; i < strlen(min); i++) { - minsum += min[i] * (100-i*15); - } + int vsum = Bootloader::Utility::versionSum(version, strlen(version)); + int minsum = Bootloader::Utility::versionSum(min, strlen(min)); if (vsum >= minsum) { Interface::drawEpsilonAdvertisement(); uint64_t scan = 0; diff --git a/bootloader/itoa.cpp b/bootloader/itoa.cpp index 34aa6069281..2afd28b025c 100644 --- a/bootloader/itoa.cpp +++ b/bootloader/itoa.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include // https://www.techiedelight.com/implement-itoa-function-in-c/ diff --git a/bootloader/kernel_header.cpp b/bootloader/kernel_header.cpp index 5b12d46f694..7f55b9fc3d7 100644 --- a/bootloader/kernel_header.cpp +++ b/bootloader/kernel_header.cpp @@ -1,4 +1,5 @@ #include +#include namespace Bootloader { @@ -22,16 +23,10 @@ const void(*KernelHeader::startPointer() const)() { return m_startPointer; } -const bool KernelHeader::isNewVersion() const { - int sum = 0; - for (int i = 0; i < 2; i++) { - sum += m_version[i] * (5 - i); - } +const bool KernelHeader::isAboveVersion16 () const { + int sum = Bootloader::Utility::versionSum(m_version, 2); char newVersion[] = "16"; - int min = 0; - for (int i = 0; i < 2; i++) { - min += newVersion[i] * (5 - i); - } + int min = Bootloader::Utility::versionSum(newVersion, 2); return sum >= min; } diff --git a/bootloader/kernel_header.h b/bootloader/kernel_header.h index c62c1590c6d..44963851dc4 100644 --- a/bootloader/kernel_header.h +++ b/bootloader/kernel_header.h @@ -10,7 +10,7 @@ class KernelHeader { const char * version() const; const char * patchLevel() const; const bool isValid() const; - const bool isNewVersion() const; + const bool isAboveVersion16() const; const uint32_t* stackPointer() const; const void(*startPointer() const)(); diff --git a/bootloader/main.cpp b/bootloader/main.cpp index 3b052bc518c..c847ad88223 100644 --- a/bootloader/main.cpp +++ b/bootloader/main.cpp @@ -20,7 +20,7 @@ __attribute__ ((noreturn)) void ion_main(int argc, const char * const argv[]) { bool isSlotA = Bootloader::Slot::A().kernelHeader()->isValid(); if (isSlotA) { - Bootloader::ExamMode::ExamMode SlotAExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotAExamMode(Bootloader::Slot::A().kernelHeader()->isNewVersion()); + Bootloader::ExamMode::ExamMode SlotAExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotAExamMode(Bootloader::Slot::A().kernelHeader()->isAboveVersion16()); if (SlotAExamMode != Bootloader::ExamMode::ExamMode::Off && SlotAExamMode != Bootloader::ExamMode::ExamMode::Unknown) { // We boot the slot in exam_mode Bootloader::Slot::A().boot(); @@ -30,7 +30,7 @@ __attribute__ ((noreturn)) void ion_main(int argc, const char * const argv[]) { bool isSlotB = Bootloader::Slot::B().kernelHeader()->isValid(); if (isSlotB) { - Bootloader::ExamMode::ExamMode SlotBExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotBExamMode(Bootloader::Slot::B().kernelHeader()->isNewVersion()); + Bootloader::ExamMode::ExamMode SlotBExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotBExamMode(Bootloader::Slot::B().kernelHeader()->isAboveVersion16()); if (SlotBExamMode != Bootloader::ExamMode::ExamMode::Off && SlotBExamMode != Bootloader::ExamMode::ExamMode::Unknown && isSlotB) { // We boot the slot in exam_mode Bootloader::Slot::B().boot(); diff --git a/bootloader/usb_data.cpp b/bootloader/usb_data.cpp index 65e6f222578..c97956e213a 100644 --- a/bootloader/usb_data.cpp +++ b/bootloader/usb_data.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include #include @@ -27,13 +27,13 @@ const char * Bootloader::USBData::buildStringDescriptor(StringHeader header, uin } const Bootloader::USBData Bootloader::USBData::DEFAULT() { - return USBData("@Flash/0x90000000/08*004Kg,01*032Kg,63*064Kg,64*064Kg", Messages::upsilonBootloader, DFUData()); + return USBData("@Flash/0x90000000/08*004Kg,01*032Kg,63*064Kg,64*064Kg", Messages::upsilonBootloader, ProtectionState()); } const Bootloader::USBData Bootloader::USBData::BOOTLOADER_UPDATE() { - return USBData("@Flash/0x08000000/04*016Kg", Messages::bootloaderUpdate, DFUData(true, false)); + return USBData("@Flash/0x08000000/04*016Kg", Messages::bootloaderUpdate, ProtectionState(true, false)); } Bootloader::USBData Bootloader::USBData::Recovery(uint32_t startAddress, uint32_t size) { - return USBData(buildStringDescriptor(StringHeader::SRAM(), startAddress, size), Messages::upsilonRecovery, DFUData(false, false)); + return USBData(buildStringDescriptor(StringHeader::SRAM(), startAddress, size), Messages::upsilonRecovery, ProtectionState(false, false)); } \ No newline at end of file diff --git a/bootloader/usb_data.h b/bootloader/usb_data.h index 8cca7475fc8..aea3f16ec10 100644 --- a/bootloader/usb_data.h +++ b/bootloader/usb_data.h @@ -6,9 +6,9 @@ namespace Bootloader { -class DFUData { +class ProtectionState { public: - DFUData(bool unlockInternal = false, bool unlockExternal = true) : m_protectInternal(!unlockInternal), m_protectExternal(!unlockExternal) {}; + ProtectionState(bool unlockInternal = false, bool unlockExternal = true) : m_protectInternal(!unlockInternal), m_protectExternal(!unlockExternal) {}; bool isProtectedInternal() const { return m_protectInternal; } bool isProtectedExternal() const { return m_protectExternal; } @@ -33,11 +33,11 @@ class USBData { const char * m_string; }; - USBData(const char * desc, const char * name, DFUData data = DFUData()) : m_stringDescriptor(desc), m_name(name), m_data(&data) {}; + USBData(const char * desc, const char * name, ProtectionState data = ProtectionState()) : m_stringDescriptor(desc), m_name(name), m_data(&data) {}; const char * stringDescriptor() const { return m_stringDescriptor; } const char * getName() const { return m_name; } - DFUData * getData() const { return m_data; } + ProtectionState * getData() const { return m_data; } static const char * buildStringDescriptor(StringHeader header, uint32_t startAddress, uint32_t size); @@ -48,7 +48,7 @@ class USBData { private: const char * m_stringDescriptor; const char * m_name; - DFUData * m_data; + ProtectionState * m_data; }; } diff --git a/bootloader/userland_header.cpp b/bootloader/userland_header.cpp index a86a62ce8ef..ee1f8b15d65 100644 --- a/bootloader/userland_header.cpp +++ b/bootloader/userland_header.cpp @@ -14,7 +14,7 @@ const bool UserlandHeader::isValid() const { } const bool UserlandHeader::isOmega() const { - return m_ohm_header == OmegaMagic && m_ohm_footer == OmegaMagic; + return m_omegaMagicHeader == OmegaMagic && m_omegaMagicFooter == OmegaMagic; } @@ -23,7 +23,7 @@ const char * UserlandHeader::omegaVersion() const { } const bool UserlandHeader::isUpsilon() const { - return m_ups_header == UpsilonMagic && m_ups_footer == UpsilonMagic; + return m_upsilonMagicHeader == UpsilonMagic && m_upsilonMagicHeader == UpsilonMagic; } const char * UserlandHeader::upsilonVersion() const { diff --git a/bootloader/userland_header.h b/bootloader/userland_header.h index 27359b17606..39356f3d3ca 100644 --- a/bootloader/userland_header.h +++ b/bootloader/userland_header.h @@ -34,14 +34,14 @@ class UserlandHeader { uint32_t m_externalAppsRAMStart; uint32_t m_externalAppsRAMEnd; uint32_t m_footer; - uint32_t m_ohm_header; + uint32_t m_omegaMagicHeader; const char m_omegaVersion[16]; const volatile char m_username[16]; - uint32_t m_ohm_footer; - uint32_t m_ups_header; + uint32_t m_omegaMagicFooter; + uint32_t m_upsilonMagicHeader; const char m_UpsilonVersion[16]; uint32_t m_osType; - uint32_t m_ups_footer; + uint32_t m_upsilonMagicFooter; }; extern const UserlandHeader* s_userlandHeaderA; diff --git a/bootloader/utility.cpp b/bootloader/utility.cpp new file mode 100644 index 00000000000..1329351e5f3 --- /dev/null +++ b/bootloader/utility.cpp @@ -0,0 +1,10 @@ +#include +#include + +int Bootloader::Utility::versionSum(const char * version, int length) { + int sum = 0; + for (int i = 0; i < length; i++) { + sum += version[i] * (strlen(version) * 100 - i * 10); + } + return sum; +} diff --git a/bootloader/itoa.h b/bootloader/utility.h similarity index 71% rename from bootloader/itoa.h rename to bootloader/utility.h index 1a8602528c5..f0739f35ab1 100644 --- a/bootloader/itoa.h +++ b/bootloader/utility.h @@ -5,7 +5,8 @@ namespace Bootloader { class Utility { public: static char * itoa(int value, char * result, int base); + static int versionSum(const char * version, int length); }; } -#endif // _BOOTLOADER_ITOA_H_ \ No newline at end of file +#endif diff --git a/ion/src/device/bootloader/usb/calculator.h b/ion/src/device/bootloader/usb/calculator.h index e4954edaea6..68e967cf8fa 100644 --- a/ion/src/device/bootloader/usb/calculator.h +++ b/ion/src/device/bootloader/usb/calculator.h @@ -161,7 +161,7 @@ class Calculator : public Device { ExtendedCompatIDDescriptor m_extendedCompatIdDescriptor; Descriptor * m_descriptors[8]; - /* m_descriptors contains only descriptors that sould be returned via the + /* m_descriptors contains only descriptors that should be returned via the * method descriptor(uint8_t type, uint8_t index), so do not count descriptors * included in other descriptors or returned by other functions. */ diff --git a/ion/src/device/bootloader/usb/dfu_interface.cpp b/ion/src/device/bootloader/usb/dfu_interface.cpp index 18562aef30c..0b6abb61178 100644 --- a/ion/src/device/bootloader/usb/dfu_interface.cpp +++ b/ion/src/device/bootloader/usb/dfu_interface.cpp @@ -211,7 +211,7 @@ void DFUInterface::eraseMemoryIfNeeded() { willErase(); - Bootloader::DFUData * config = getDfuConfig(); + Bootloader::ProtectionState * config = getDfuConfig(); if (config != nullptr) { // More simple to read @@ -242,7 +242,7 @@ void DFUInterface::writeOnMemory() { memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); } else if (Flash::SectorAtAddress(m_writeAddress) >= 0) { - Bootloader::DFUData * config = getDfuConfig(); + Bootloader::ProtectionState * config = getDfuConfig(); if (config != nullptr) { if (m_writeAddress >= 0x08000000 && m_writeAddress <= 0x08010000 && !m_dfuData.isProtectedInternal()) { @@ -306,7 +306,7 @@ void DFUInterface::leaveDFUAndReset() { } void DFUInterface::copyDfuData() { - m_dfuData = Bootloader::DFUData(!m_dfuConfig->isProtectedInternal(), !m_dfuConfig->isProtectedExternal()); + m_dfuData = Bootloader::ProtectionState(!m_dfuConfig->isProtectedInternal(), !m_dfuConfig->isProtectedExternal()); } } diff --git a/ion/src/device/bootloader/usb/dfu_interface.h b/ion/src/device/bootloader/usb/dfu_interface.h index 5602002ca83..2cb2eaeee95 100644 --- a/ion/src/device/bootloader/usb/dfu_interface.h +++ b/ion/src/device/bootloader/usb/dfu_interface.h @@ -40,8 +40,8 @@ class DFUInterface : public Interface { void wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) override; bool isErasingAndWriting() const { return m_isErasingAndWriting; } - void setDfuConfig(Bootloader::DFUData * data) { m_dfuConfig = data; copyDfuData(); } - Bootloader::DFUData * getDfuConfig() const { return m_dfuConfig; } + void setDfuConfig(Bootloader::ProtectionState * data) { m_dfuConfig = data; copyDfuData(); } + Bootloader::ProtectionState * getDfuConfig() const { return m_dfuConfig; } protected: void setActiveInterfaceAlternative(uint8_t interfaceAlternativeIndex) override { @@ -180,9 +180,9 @@ class DFUInterface : public Interface { uint32_t m_writeAddress; uint8_t m_bInterfaceAlternateSetting; bool m_isErasingAndWriting; - Bootloader::DFUData * m_dfuConfig; + Bootloader::ProtectionState * m_dfuConfig; uint32_t m_eraseAddress; - Bootloader::DFUData m_dfuData; + Bootloader::ProtectionState m_dfuData; }; } diff --git a/ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.h index f434352f24b..8b67cabc6ca 100644 --- a/ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.h +++ b/ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.h @@ -1,5 +1,5 @@ -#ifndef ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H -#define ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABILITY_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABILITY_DESCRIPTOR_H #include "descriptor.h" diff --git a/ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.h index d2c425d4a40..3c117348c50 100644 --- a/ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.h +++ b/ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.h @@ -1,5 +1,5 @@ -#ifndef ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H -#define ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABILITY_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABILITY_DESCRIPTOR_H #include "device_capability_descriptor.h" diff --git a/ion/src/device/n0110/drivers/board.cpp b/ion/src/device/n0110/drivers/board.cpp index 23579c5a262..c133d492b81 100644 --- a/ion/src/device/n0110/drivers/board.cpp +++ b/ion/src/device/n0110/drivers/board.cpp @@ -104,7 +104,7 @@ void initMPU() { * then an AHB error is given (AN4760). To prevent this to happen, we * configure the MPU to define the whole Quad-SPI addressable space as * strongly ordered, non-executable and not accessible. Plus, we define the - * Quad-SPI region corresponding to the Expternal Chip as executable and + * Quad-SPI region corresponding to the External Chip as executable and * fully accessible (AN4861). */ MPU.RNR()->setREGION(sector++); MPU.RBAR()->setADDR(0x90000000); diff --git a/ion/src/device/shared/drivers/display.cpp b/ion/src/device/shared/drivers/display.cpp index 508242ddeb3..6cc1f762c83 100644 --- a/ion/src/device/shared/drivers/display.cpp +++ b/ion/src/device/shared/drivers/display.cpp @@ -51,7 +51,7 @@ bool waitForVBlank() { uint64_t startTime = Timing::millis(); uint64_t timeout = startTime + timeoutDelta; - /* If current time is big enough, currentTime + timeout wraps aroud the + /* If current time is big enough, currentTime + timeout wraps around the * uint64_t. We need to take this into account when computing the terminating * event. * diff --git a/ion/src/device/shared/drivers/reset.cpp b/ion/src/device/shared/drivers/reset.cpp index ef605357004..67e52f3d6b4 100644 --- a/ion/src/device/shared/drivers/reset.cpp +++ b/ion/src/device/shared/drivers/reset.cpp @@ -49,7 +49,7 @@ void jump(uint32_t jumpIsrVectorAddress) { // Disable cache before reset Ion::Device::Cache::disable(); - /* Shutdown all clocks and periherals to mimic a hardware reset. */ + /* Shutdown all clocks and peripherals to mimic a hardware reset. */ Board::shutdownPeripherals(); internalFlashJump(jumpIsrVectorAddress); diff --git a/ion/src/device/shared/drivers/wakeup.h b/ion/src/device/shared/drivers/wakeup.h index d8cb22c7b81..7bf12d585f1 100644 --- a/ion/src/device/shared/drivers/wakeup.h +++ b/ion/src/device/shared/drivers/wakeup.h @@ -8,7 +8,7 @@ namespace Device { namespace WakeUp { /* All wakeup functions can be called together without overwriting the same - * register. All togethed, they will set SYSCFG and EXTi registers as follow: + * register. All together, they will set SYSCFG and EXTi registers as follow: * * GPIO Pin Number|EXTI_EMR|EXTI_FTSR|EXTI_RTSR|EXTICR1|EXTICR2|EXTICR3| Wake up * ---------------+--------+---------+---------+-------+-------+-------+------------------------- diff --git a/ion/src/device/shared/regs/tim.h b/ion/src/device/shared/regs/tim.h index f92d119e6da..704bd4fa901 100644 --- a/ion/src/device/shared/regs/tim.h +++ b/ion/src/device/shared/regs/tim.h @@ -17,7 +17,7 @@ class TIM { }; class CCMR : Register64 { - /* We're declaring CCMR as a 64 bits register. CCMR doesn't exsist per se, + /* We're declaring CCMR as a 64 bits register. CCMR doesn't exist per se, * it is in fact the consolidation of CCMR1 and CCMR2. Both are 16 bits * registers, so one could expect the consolidation to be 32 bits. However, * both CCMR1 and CCMR2 live on 32-bits boundaries, so the consolidation has diff --git a/ion/src/device/shared/usb/calculator.h b/ion/src/device/shared/usb/calculator.h index 9c8b869fb28..308beb66b1d 100644 --- a/ion/src/device/shared/usb/calculator.h +++ b/ion/src/device/shared/usb/calculator.h @@ -155,7 +155,7 @@ class Calculator : public Device { ExtendedCompatIDDescriptor m_extendedCompatIdDescriptor; Descriptor * m_descriptors[8]; - /* m_descriptors contains only descriptors that sould be returned via the + /* m_descriptors contains only descriptors that should be returned via the * method descriptor(uint8_t type, uint8_t index), so do not count descriptors * included in other descriptors or returned by other functions. */ diff --git a/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h index f434352f24b..8b67cabc6ca 100644 --- a/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h @@ -1,5 +1,5 @@ -#ifndef ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H -#define ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABILITY_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABILITY_DESCRIPTOR_H #include "descriptor.h" diff --git a/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h index d2c425d4a40..3c117348c50 100644 --- a/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h @@ -1,5 +1,5 @@ -#ifndef ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H -#define ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABILITY_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABILITY_DESCRIPTOR_H #include "device_capability_descriptor.h" From 3c26035feb3418ab89b2fb8bccd395dd90c3c188 Mon Sep 17 00:00:00 2001 From: devdl11 Date: Fri, 8 Apr 2022 14:01:55 +0200 Subject: [PATCH 210/355] [Review] Class to namespace --- bootloader/boot.cpp | 4 ++-- bootloader/itoa.cpp | 2 +- bootloader/kernel_header.cpp | 4 ++-- bootloader/usb_data.cpp | 4 ++-- bootloader/utility.cpp | 2 +- bootloader/utility.h | 10 ++++------ 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index cc71d711d6b..284acf8adf2 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -26,8 +26,8 @@ void Boot::bootSlot(Bootloader::Slot s) { // We are trying to boot epsilon, so we check the version and show an advertisement if needed const char * version = s.userlandHeader()->version(); const char * min = "18.2.4"; - int vsum = Bootloader::Utility::versionSum(version, strlen(version)); - int minsum = Bootloader::Utility::versionSum(min, strlen(min)); + int vsum = Utility::versionSum(version, strlen(version)); + int minsum = Utility::versionSum(min, strlen(min)); if (vsum >= minsum) { Interface::drawEpsilonAdvertisement(); uint64_t scan = 0; diff --git a/bootloader/itoa.cpp b/bootloader/itoa.cpp index 8235e7b32a6..7d712ebdd11 100644 --- a/bootloader/itoa.cpp +++ b/bootloader/itoa.cpp @@ -19,7 +19,7 @@ char* reverse(char *buffer, int i, int j) { } // Iterative function to implement `itoa()` function in C -char* Bootloader::Utility::itoa(int value, char* buffer, int base) { +char* Utility::itoa(int value, char* buffer, int base) { // invalid input if (base < 2 || base > 32) { return buffer; diff --git a/bootloader/kernel_header.cpp b/bootloader/kernel_header.cpp index 7f55b9fc3d7..53e02cfaf5f 100644 --- a/bootloader/kernel_header.cpp +++ b/bootloader/kernel_header.cpp @@ -24,9 +24,9 @@ const void(*KernelHeader::startPointer() const)() { } const bool KernelHeader::isAboveVersion16 () const { - int sum = Bootloader::Utility::versionSum(m_version, 2); + int sum = Utility::versionSum(m_version, 2); char newVersion[] = "16"; - int min = Bootloader::Utility::versionSum(newVersion, 2); + int min = Utility::versionSum(newVersion, 2); return sum >= min; } diff --git a/bootloader/usb_data.cpp b/bootloader/usb_data.cpp index b161df94c80..1074d7223ba 100644 --- a/bootloader/usb_data.cpp +++ b/bootloader/usb_data.cpp @@ -13,13 +13,13 @@ static char data[255]; const char * Bootloader::USBData::buildStringDescriptor(StringHeader header, uint32_t startAddress, uint32_t size) { strlcpy(data, header.getString(), sizeof(data)); - Bootloader::Utility::itoa((int32_t)startAddress, &data[strlen(header.getString())], 16); + Utility::itoa((int32_t)startAddress, &data[strlen(header.getString())], 16); data[strlen(header.getString()) + 8] = '/'; data[strlen(header.getString()) + 8 + 1] = '0'; data[strlen(header.getString()) + 8 + 2] = '1'; data[strlen(header.getString()) + 8 + 3] = '*'; data[strlen(header.getString()) + 8 + 4] = '0'; - Bootloader::Utility::itoa((int32_t)size/1024, &data[strlen(header.getString()) + 8 + 5], 10); + Utility::itoa((int32_t)size/1024, &data[strlen(header.getString()) + 8 + 5], 10); data[strlen(header.getString()) + 8 + 5 + 2] = 'K'; data[strlen(header.getString()) + 8 + 5 + 2 + 1] = 'g'; data[strlen(header.getString()) + 8 + 5 + 2 + 2] = '\0'; diff --git a/bootloader/utility.cpp b/bootloader/utility.cpp index 1329351e5f3..d2bdc44a040 100644 --- a/bootloader/utility.cpp +++ b/bootloader/utility.cpp @@ -1,7 +1,7 @@ #include #include -int Bootloader::Utility::versionSum(const char * version, int length) { +int Utility::versionSum(const char * version, int length) { int sum = 0; for (int i = 0; i < length; i++) { sum += version[i] * (strlen(version) * 100 - i * 10); diff --git a/bootloader/utility.h b/bootloader/utility.h index f0739f35ab1..1a4589541e8 100644 --- a/bootloader/utility.h +++ b/bootloader/utility.h @@ -1,12 +1,10 @@ #ifndef _BOOTLOADER_ITOA_H_ #define _BOOTLOADER_ITOA_H_ -namespace Bootloader { - class Utility { - public: - static char * itoa(int value, char * result, int base); - static int versionSum(const char * version, int length); - }; +namespace Utility { + + static char * itoa(int value, char * result, int base); + static int versionSum(const char * version, int length); } #endif From 7ce3aac2f6184a4c9b8556075123b89c41d6b69c Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Fri, 8 Apr 2022 18:02:56 +0200 Subject: [PATCH 211/355] Update bootloader/messages.h Co-authored-by: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> --- bootloader/messages.h | 1 - 1 file changed, 1 deletion(-) diff --git a/bootloader/messages.h b/bootloader/messages.h index 1b18dcae325..f212cdebb0c 100644 --- a/bootloader/messages.h +++ b/bootloader/messages.h @@ -6,7 +6,6 @@ namespace Bootloader { class Messages { public: // TODO: Remove it when this fork will be updated - #define UPSILON_VERSION "1.0.0-dev" #ifdef UPSILON_VERSION constexpr static const char * mainTitle = "Upsilon Calculator"; #elif defined OMEGA_VERSION From e886863230b2d604323fb18cdde2f1b0e1c1dfd4 Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Fri, 8 Apr 2022 18:03:11 +0200 Subject: [PATCH 212/355] Update bootloader/messages.h Co-authored-by: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> --- bootloader/messages.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootloader/messages.h b/bootloader/messages.h index f212cdebb0c..9f2f0180835 100644 --- a/bootloader/messages.h +++ b/bootloader/messages.h @@ -56,4 +56,4 @@ class Messages { }; -#endif \ No newline at end of file +#endif From 3c098b684bd5a179f04f4fee579b235704e739f2 Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Fri, 8 Apr 2022 18:03:19 +0200 Subject: [PATCH 213/355] Update bootloader/usb_data.h Co-authored-by: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> --- bootloader/usb_data.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootloader/usb_data.h b/bootloader/usb_data.h index aea3f16ec10..33398595d43 100644 --- a/bootloader/usb_data.h +++ b/bootloader/usb_data.h @@ -53,4 +53,4 @@ class USBData { }; } -#endif //BOOTLOADER_USB_DATA_H_ \ No newline at end of file +#endif //BOOTLOADER_USB_DATA_H_ From 01c3f767e44a7374b3c0904240dc5f82f3f8be58 Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Fri, 8 Apr 2022 18:03:32 +0200 Subject: [PATCH 214/355] Update ion/src/device/bootloader/usb/Makefile Co-authored-by: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> --- ion/src/device/bootloader/usb/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/bootloader/usb/Makefile b/ion/src/device/bootloader/usb/Makefile index e2b51e2a212..f3d20253040 100644 --- a/ion/src/device/bootloader/usb/Makefile +++ b/ion/src/device/bootloader/usb/Makefile @@ -96,4 +96,4 @@ ion_device_src += ion/src/device/bootloader/usb/dfu_relocated.cpp:-usbxip ion_device_src += $(addprefix ion/src/device/bootloader/drivers/, \ usb_desc.cpp \ -) \ No newline at end of file +) From 4a830a0f30d6825822a7152cbf2a714f3d06acd8 Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Fri, 8 Apr 2022 18:05:57 +0200 Subject: [PATCH 215/355] Update ion/src/device/n0100/flash.ld Co-authored-by: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> --- ion/src/device/n0100/flash.ld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/n0100/flash.ld b/ion/src/device/n0100/flash.ld index e9b073bb21e..d22fe7b9fd8 100644 --- a/ion/src/device/n0100/flash.ld +++ b/ion/src/device/n0100/flash.ld @@ -71,7 +71,7 @@ SECTIONS { /* The data section is written to Flash but linked as if it were in RAM. * * This is required because its initial value matters (so it has to be in - * persistant memory in the first place), but it is a R/W area of memory + * persistent memory in the first place), but it is a R/W area of memory * so it will have to live in RAM upon execution (in linker lingo, that * translates to the data section having a LMA in Flash and a VMA in RAM). * From 5f50f3fc9cc4d54d0bf41f3726a2dddb75b83283 Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Fri, 8 Apr 2022 18:06:07 +0200 Subject: [PATCH 216/355] Update ion/src/device/shared/usb/stack/device.cpp Co-authored-by: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> --- ion/src/device/shared/usb/stack/device.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/shared/usb/stack/device.cpp b/ion/src/device/shared/usb/stack/device.cpp index 294b647e12c..6bfcbef7d6a 100644 --- a/ion/src/device/shared/usb/stack/device.cpp +++ b/ion/src/device/shared/usb/stack/device.cpp @@ -108,7 +108,7 @@ bool Device::processSetupInRequest(SetupPacket * request, uint8_t * transferBuff case (int) Request::GetStatus: return getStatus(transferBuffer, transferBufferLength, transferBufferMaxLength); case (int) Request::SetAddress: - // Make sure the request is adress is valid. + // Make sure the request is address is valid. assert(request->wValue() < 128); /* According to the reference manual, the address should be set after the * Status stage of the current transaction, but this is not true. From bf1d57062335fa789a22cb7c56f5bd948e24647c Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Fri, 8 Apr 2022 18:06:15 +0200 Subject: [PATCH 217/355] Update ion/src/device/shared/usb/dfu_relocated.cpp Co-authored-by: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> --- ion/src/device/shared/usb/dfu_relocated.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/shared/usb/dfu_relocated.cpp b/ion/src/device/shared/usb/dfu_relocated.cpp index 7669a31036d..72b9b61be26 100644 --- a/ion/src/device/shared/usb/dfu_relocated.cpp +++ b/ion/src/device/shared/usb/dfu_relocated.cpp @@ -59,7 +59,7 @@ void DFU(bool exitWithKeyboard, void * data) { /* 4- Disable all interrupts * The interrupt service routines live in the Flash and could be overwritten - * by garbage during a firmware upgrade opration, so we disable them. */ + * by garbage during a firmware upgrade operation, so we disable them. */ Device::Timing::shutdown(); /* 5- Jump to DFU bootloader code. We made sure in the linker script that the From 0e46954f8040843588d3c607cce5bdf70c27107d Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Fri, 8 Apr 2022 18:06:22 +0200 Subject: [PATCH 218/355] Update ion/src/device/shared/usb/dfu_interface.h Co-authored-by: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> --- ion/src/device/shared/usb/dfu_interface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/shared/usb/dfu_interface.h b/ion/src/device/shared/usb/dfu_interface.h index 0ae25d87488..762243f06ad 100644 --- a/ion/src/device/shared/usb/dfu_interface.h +++ b/ion/src/device/shared/usb/dfu_interface.h @@ -57,7 +57,7 @@ class DFUInterface : public Interface { Abort = 6 }; - // DFU Download Commmand Codes + // DFU Download Command Codes enum class DFUDownloadCommand { GetCommand = 0x00, SetAddressPointer = 0x21, From 3b955c2eb6d854f9a7d45eaa685783f61c6a485a Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Fri, 8 Apr 2022 18:06:59 +0200 Subject: [PATCH 219/355] Update ion/src/device/n0110/drivers/board.cpp Co-authored-by: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> --- ion/src/device/n0110/drivers/board.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/n0110/drivers/board.cpp b/ion/src/device/n0110/drivers/board.cpp index c133d492b81..8e93e4d3c5f 100644 --- a/ion/src/device/n0110/drivers/board.cpp +++ b/ion/src/device/n0110/drivers/board.cpp @@ -62,7 +62,7 @@ void initMPU() { /* This is needed for interfacing with the LCD * We define the whole FMC memory bank 1 as strongly ordered, non-executable * and not accessible. We define the FMC command and data addresses as - * writeable non-cachable, non-buffereable and non shareable. */ + * writeable non-cacheable, non-buffereable and non shareable. */ int sector = 0; MPU.RNR()->setREGION(sector++); MPU.RBAR()->setADDR(0x60000000); From 225b942989b2a93cce22237f515d1b7f9c588898 Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Fri, 8 Apr 2022 18:07:11 +0200 Subject: [PATCH 220/355] Update ion/src/device/shared/usb/dfu.ld Co-authored-by: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> --- ion/src/device/shared/usb/dfu.ld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/shared/usb/dfu.ld b/ion/src/device/shared/usb/dfu.ld index 957bcfdd536..70aff4accbb 100644 --- a/ion/src/device/shared/usb/dfu.ld +++ b/ion/src/device/shared/usb/dfu.ld @@ -1,5 +1,5 @@ /* DFU transfers can serve two purposes: - * - Transfering RAM data between the machine and the host, e.g. Python scripts + * - Transferring RAM data between the machine and the host, e.g. Python scripts * - Upgrading the flash memory to perform a software update * * The second case raises a huge issue: code cannot be executed from memory that From f4e84bc2087ddf9cd737bc99f54fb33c6bc91030 Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Fri, 8 Apr 2022 18:07:28 +0200 Subject: [PATCH 221/355] Update ion/src/device/n0110/internal_flash.ld Co-authored-by: Yaya-Cout <67095734+Yaya-Cout@users.noreply.github.com> --- ion/src/device/n0110/internal_flash.ld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/n0110/internal_flash.ld b/ion/src/device/n0110/internal_flash.ld index 924c67f7d01..2094a6469d9 100644 --- a/ion/src/device/n0110/internal_flash.ld +++ b/ion/src/device/n0110/internal_flash.ld @@ -64,7 +64,7 @@ SECTIONS { /* The data section is written to Flash but linked as if it were in RAM. * * This is required because its initial value matters (so it has to be in - * persistant memory in the first place), but it is a R/W area of memory + * persistent memory in the first place), but it is a R/W area of memory * so it will have to live in RAM upon execution (in linker lingo, that * translates to the data section having a LMA in Flash and a VMA in RAM). * From a0caf67d7cd0ce102a2b06235d95a0dd42d307c4 Mon Sep 17 00:00:00 2001 From: devdl11 Date: Fri, 8 Apr 2022 18:33:27 +0200 Subject: [PATCH 222/355] [review] revert flasher code --- bootloader/utility.h | 1 - ion/src/device/flasher/display_light.cpp | 8 +- ion/src/device/flasher/display_verbose.cpp | 90 ++++++++++++---------- ion/src/device/flasher/main.cpp | 2 +- 4 files changed, 57 insertions(+), 44 deletions(-) diff --git a/bootloader/utility.h b/bootloader/utility.h index 1a4589541e8..1d24f989a87 100644 --- a/bootloader/utility.h +++ b/bootloader/utility.h @@ -2,7 +2,6 @@ #define _BOOTLOADER_ITOA_H_ namespace Utility { - static char * itoa(int value, char * result, int base); static int versionSum(const char * version, int length); } diff --git a/ion/src/device/flasher/display_light.cpp b/ion/src/device/flasher/display_light.cpp index 1549c576335..ec5ba11886a 100644 --- a/ion/src/device/flasher/display_light.cpp +++ b/ion/src/device/flasher/display_light.cpp @@ -1,10 +1,16 @@ #include +#include namespace Flasher { namespace Display { void init() { - Ion::Display::pushRectUniform(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColor::RGB24(0xFFFF00)); + KDRect screen = KDRect(0,0,Ion::Display::Width,Ion::Display::Height); + Ion::Display::pushRectUniform(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColor::RGB24(0x5e81ac)); + KDContext * ctx = KDIonContext::sharedContext(); + ctx->setOrigin(KDPointZero); + ctx->setClippingRect(screen); + ctx->drawString("RECOVERY MODE", KDPoint(10, 10), KDFont::LargeFont, KDColorWhite, KDColor::RGB24(0x5e81ac)); } } diff --git a/ion/src/device/flasher/display_verbose.cpp b/ion/src/device/flasher/display_verbose.cpp index aad0b6c4598..ed7b5ff2983 100644 --- a/ion/src/device/flasher/display_verbose.cpp +++ b/ion/src/device/flasher/display_verbose.cpp @@ -1,47 +1,55 @@ #include #include +#include "image.h" namespace Flasher { -namespace Display { - -constexpr static int sNumberOfMessages = 5; -constexpr static int sNumberOfLanguages = 2; - -constexpr static const char * sMessages[sNumberOfLanguages][sNumberOfMessages] = { - {"RECOVERY MODE", - "Your calculator is waiting", - "for a new software.", - "Follow the instructions", - "on your computer to continue."}, - {"MODE RECUPERATION", - "Votre calculatrice attend", - "l'installation d'un nouveau logiciel.", - "Suivez les instructions sur", - "votre ordinateur pour continuer."} -}; - -void init() { - KDRect screen = KDRect(0,0,Ion::Display::Width,Ion::Display::Height); - Ion::Display::pushRectUniform(screen, KDColorWhite); - KDContext * ctx = KDIonContext::sharedContext(); - ctx->setOrigin(KDPointZero); - ctx->setClippingRect(screen); - KDCoordinate margin = 20; - KDCoordinate currentHeight = 0; - for (int i = 0; i < sNumberOfLanguages; i++) { - currentHeight += margin; - const char * title = sMessages[i][0]; - KDSize titleSize = KDFont::LargeFont->stringSize(title); - ctx->drawString(title, KDPoint((Ion::Display::Width-titleSize.width())/2, currentHeight), KDFont::LargeFont); - currentHeight += 2*titleSize.height(); - for (int j = 1; j < sNumberOfMessages; j++) { - const char * message = sMessages[i][j]; - KDSize messageSize = KDFont::SmallFont->stringSize(message); - ctx->drawString(message, KDPoint((Ion::Display::Width-messageSize.width())/2, currentHeight), KDFont::SmallFont); - currentHeight += messageSize.height(); - } - } -} + namespace Display { -} + constexpr static int sNumberOfMessages = 5; + + constexpr static const char * sMessages[sNumberOfMessages] = { + "RECOVERY MODE", + "Your calculator is waiting", + "for Upsilon to be installed.", + "Follow the instructions", + "on your computer to continue.", + }; + + void init() { + KDRect screen = KDRect(0,0,Ion::Display::Width,Ion::Display::Height); + Ion::Display::pushRectUniform(screen, KDColor::RGB24(0x2B2B2B)); + KDContext * ctx = KDIonContext::sharedContext(); + ctx->setOrigin(KDPointZero); + ctx->setClippingRect(screen); + KDCoordinate margin = 30; + KDCoordinate currentHeight = margin; + + /* Title */ + const char * title = sMessages[0]; + KDSize titleSize = KDFont::LargeFont->stringSize(title); + ctx->drawString(title, KDPoint((Ion::Display::Width-titleSize.width())/2, currentHeight), + KDFont::LargeFont, KDColorWhite, KDColor::RGB24(0x2B2B2B)); + currentHeight = (uint16_t)((Ion::Display::Height*2)/3); + + /* Logo */ + for (int i = 0; i < IMAGE_WIDTH; ++i) { + for (int j = 0; j < IMAGE_HEIGHT; ++j) { + ctx->setPixel(KDPoint(i+(uint16_t)((Ion::Display::Width-IMAGE_WIDTH)/2), + j+(titleSize.height()+margin+15)), + KDColor::RGB16(image[i+(j*IMAGE_WIDTH)])); + } + } + + /* Messages */ + const char * message; + for (int i = 1; i < sNumberOfMessages; ++i) { + message = sMessages[i]; + KDSize messageSize = KDFont::SmallFont->stringSize(message); + ctx->drawString(message, KDPoint((Ion::Display::Width-messageSize.width())/2, currentHeight), + KDFont::SmallFont, KDColorWhite, KDColor::RGB24(0x2B2B2B)); + currentHeight += messageSize.height(); + } + } + + } } diff --git a/ion/src/device/flasher/main.cpp b/ion/src/device/flasher/main.cpp index 3dcfcd80d52..09f05e8cad0 100644 --- a/ion/src/device/flasher/main.cpp +++ b/ion/src/device/flasher/main.cpp @@ -11,6 +11,6 @@ void ion_main(int argc, const char * const argv[]) { Ion::USB::enable(); while (!Ion::USB::isEnumerated()) { } - Ion::USB::DFU(false); + Ion::USB::DFU(false, false, 0); } } From c8377098018d10a525afc7e73663f142a108cbf6 Mon Sep 17 00:00:00 2001 From: devdl11 Date: Fri, 8 Apr 2022 18:40:08 +0200 Subject: [PATCH 223/355] [flasher] Fix old dfu code --- ion/src/device/flasher/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/flasher/main.cpp b/ion/src/device/flasher/main.cpp index 09f05e8cad0..3dcfcd80d52 100644 --- a/ion/src/device/flasher/main.cpp +++ b/ion/src/device/flasher/main.cpp @@ -11,6 +11,6 @@ void ion_main(int argc, const char * const argv[]) { Ion::USB::enable(); while (!Ion::USB::isEnumerated()) { } - Ion::USB::DFU(false, false, 0); + Ion::USB::DFU(false); } } From 21f08a2d06e7b86ce9b6f2bac6b013035e5b2c2e Mon Sep 17 00:00:00 2001 From: devdl11 Date: Fri, 8 Apr 2022 18:53:26 +0200 Subject: [PATCH 224/355] [bootloader/interface] Yaya-cout commit --- bootloader/interface.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/bootloader/interface.cpp b/bootloader/interface.cpp index 8367e6f67d1..3c64f4bcef6 100644 --- a/bootloader/interface.cpp +++ b/bootloader/interface.cpp @@ -90,12 +90,19 @@ void Interface::drawMenu() { x += strlen(converted) * smallSize.width(); // Draw the slot version ctx->drawString(slot.userlandHeader()->version(), KDPoint(x, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - // Increment the x position - x += strlen(slot.userlandHeader()->version()) * smallSize.width() + smallSize.width() * 2; - // Draw the slot commit - ctx->drawString(slot.kernelHeader()->patchLevel(), KDPoint(x, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - // Increment the x position - x += strlen(slot.kernelHeader()->patchLevel()) * smallSize.width() + smallSize.width(); + + // Get if the commit (patchLevel) isn't empty + if (slot.kernelHeader()->patchLevel()[0] != '\0') { + // Increment the x position + x += strlen(slot.userlandHeader()->version()) * smallSize.width() + smallSize.width() * 2; + // Draw the slot commit + ctx->drawString(slot.kernelHeader()->patchLevel(), KDPoint(x, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); + // Increment the x position + x += strlen(slot.kernelHeader()->patchLevel()) * smallSize.width() + smallSize.width(); + } else { + // Increment the x position + x += strlen(slot.userlandHeader()->version()) * smallSize.width() + smallSize.width(); + } const char * OSName = ""; const char * OSVersion = ""; @@ -283,7 +290,7 @@ void Interface::drawInstallerSelection() { ctx->drawString(Messages::installerText3, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); } -void Interface::drawBootloaderUpdate() { +void Interface::drawBLUpdate() { Interface::drawHeader(); KDContext * ctx = KDIonContext::sharedContext(); int y = ImageStore::Computer->height() + (KDFont::LargeFont->glyphSize().height() + 10) + (KDFont::SmallFont->glyphSize().height() + 10); From ff307b8df819799c53f49b0884f3c9c65af42f34 Mon Sep 17 00:00:00 2001 From: devdl11 Date: Fri, 8 Apr 2022 23:44:24 +0200 Subject: [PATCH 225/355] [bootloader > interfaces] Interface improvement --- bootloader/interface.h | 5 +- bootloader/interface/menus/about/about.cpp | 13 +++ bootloader/interface/menus/about/about.h | 17 ++++ bootloader/interface/menus/home/home.cpp | 62 +++++++++++++ bootloader/interface/menus/home/home.h | 23 +++++ .../interface/menus/installer/installer.h | 15 ++++ bootloader/interface/src/menu.cpp | 88 +++++++++++++++++++ bootloader/interface/src/menu.h | 83 +++++++++++++++++ bootloader/messages.h | 1 + bootloader/slot.h | 4 + 10 files changed, 307 insertions(+), 4 deletions(-) create mode 100644 bootloader/interface/menus/about/about.cpp create mode 100644 bootloader/interface/menus/about/about.h create mode 100644 bootloader/interface/menus/home/home.cpp create mode 100644 bootloader/interface/menus/home/home.h create mode 100644 bootloader/interface/menus/installer/installer.h create mode 100644 bootloader/interface/src/menu.cpp create mode 100644 bootloader/interface/src/menu.h diff --git a/bootloader/interface.h b/bootloader/interface.h index b21eb0e38e5..b89c3ce2513 100644 --- a/bootloader/interface.h +++ b/bootloader/interface.h @@ -6,13 +6,10 @@ #include namespace Bootloader { - class Interface { -private: - static void drawImage(KDContext * ctx, const Image * image, int offset); - public: + static void drawImage(KDContext * ctx, const Image * image, int offset); static void drawLoading(); static void drawHeader(); static void drawMenu(); diff --git a/bootloader/interface/menus/about/about.cpp b/bootloader/interface/menus/about/about.cpp new file mode 100644 index 00000000000..4c73b95bc9e --- /dev/null +++ b/bootloader/interface/menus/about/about.cpp @@ -0,0 +1,13 @@ +#include "about.h" + +Bootloader::AboutMenu::AboutMenu() : Menu(KDColorBlack, KDColorWhite, Messages::aboutMenuTitle, Messages::bootloaderVersion) { + setup(); +} + +void Bootloader::AboutMenu::setup() { + m_colomns[0] = Colomn("This is the bootloader of", k_small_font, 0, true); + m_colomns[1] = Colomn("the Upsilon Calculator.", k_small_font, 0, true); + m_colomns[2] = Colomn("It is used to install", k_small_font, 0, true); + m_colomns[3] = Colomn("and select the OS", k_small_font, 0, true); + m_colomns[4] = Colomn("to boot.", k_small_font, 0, true); +} \ No newline at end of file diff --git a/bootloader/interface/menus/about/about.h b/bootloader/interface/menus/about/about.h new file mode 100644 index 00000000000..96dc86c111a --- /dev/null +++ b/bootloader/interface/menus/about/about.h @@ -0,0 +1,17 @@ +#ifndef _BOOTLOADER_INTERFACE_ABOUT_ABOUT_H_ +#define _BOOTLOADER_INTERFACE_ABOUT_ABOUT_H_ + +#include + +namespace Bootloader { + class AboutMenu : public Menu { + public: + AboutMenu(); + + void setup() override; + + }; +} + + +#endif \ No newline at end of file diff --git a/bootloader/interface/menus/home/home.cpp b/bootloader/interface/menus/home/home.cpp new file mode 100644 index 00000000000..df909d06fe3 --- /dev/null +++ b/bootloader/interface/menus/home/home.cpp @@ -0,0 +1,62 @@ +#include "home.h" +#include +#include + +Bootloader::AboutMenu * Bootloader::HomeMenu::aboutMenu() { + static AboutMenu * aboutMenu = new AboutMenu(); + return aboutMenu; +} + +Bootloader::HomeMenu::HomeMenu() : Menu(KDColorBlack, KDColorWhite, Messages::mainMenuTitle, Messages::mainTitle) { + setup(); +} + +bool slotA_submenu() { + if (Bootloader::Slot::isFullyValid(Bootloader::Slot::A())) { + + } + return false; +} + +bool slotKhi_submenu() { + if (Bootloader::Slot::isFullyValid(Bootloader::Slot::Khi())) { + + } + return false; +} + +bool slotB_submenu() { + if (Bootloader::Slot::isFullyValid(Bootloader::Slot::B())) { + + } + return false; +} + +bool installer_submenu() { + +} + +bool about_submenu() { + Bootloader::HomeMenu::aboutMenu()->open(); + return true; +} + +const char * Bootloader::HomeMenu::slotA_text() { + return Slot::isFullyValid(Slot::A()) ? "1 - Slot A" : Messages::invalidSlot; +} + +const char * Bootloader::HomeMenu::slotKhi_text() { + return Slot::isFullyValid(Slot::Khi()) ? "2 - Slot Khi" : Messages::invalidSlot; +} + +const char * Bootloader::HomeMenu::slotB_text() { + return Slot::isFullyValid(Slot::B()) ? "3 - Slot B" : Messages::invalidSlot; +} + +void Bootloader::HomeMenu::setup() { + m_colomns[0] = Colomn(slotA_text(), Ion::Keyboard::Key::One, k_large_font, 10, false, &slotA_submenu); + m_colomns[1] = Colomn(slotKhi_text(), Ion::Keyboard::Key::Two, k_large_font, 10, false, &slotKhi_submenu); + m_colomns[2] = Colomn(slotB_text(), Ion::Keyboard::Key::Three, k_large_font, 10, false, &slotB_submenu); + m_colomns[3] = Colomn("4- installer", Ion::Keyboard::Key::Four, k_large_font, 10, false, &installer_submenu); + m_colomns[4] = Colomn("5- about", Ion::Keyboard::Key::Five, k_large_font, 10, false, &about_submenu); +} \ No newline at end of file diff --git a/bootloader/interface/menus/home/home.h b/bootloader/interface/menus/home/home.h new file mode 100644 index 00000000000..6f6c4303779 --- /dev/null +++ b/bootloader/interface/menus/home/home.h @@ -0,0 +1,23 @@ +#ifndef _BOOTLOADER_INTERFACE_HOME_HOME_H_ +#define _BOOTLOADER_INTERFACE_HOME_HOME_H_ + +#include +#include + +namespace Bootloader { + class HomeMenu : public Menu { + public: + HomeMenu(); + + void setup() override; + static AboutMenu * aboutMenu(); + + private: + const char * slotA_text(); + const char * slotKhi_text(); + const char * slotB_text(); + + }; +} + +#endif diff --git a/bootloader/interface/menus/installer/installer.h b/bootloader/interface/menus/installer/installer.h new file mode 100644 index 00000000000..be9f49fb415 --- /dev/null +++ b/bootloader/interface/menus/installer/installer.h @@ -0,0 +1,15 @@ +#ifndef _BOOTLOADER_INTERFACE_MENUS_INSTALLER_INSTALLER_H_ +#define _BOOTLOADER_INTERFACE_MENUS_INSTALLER_INSTALLER_H_ + +#include + +namespace Bootloader { + class InstallerMenu : public Menu { + public: + InstallerMenu(); + + void setup() override; + }; +} + +#endif \ No newline at end of file diff --git a/bootloader/interface/src/menu.cpp b/bootloader/interface/src/menu.cpp new file mode 100644 index 00000000000..09ccaa15a76 --- /dev/null +++ b/bootloader/interface/src/menu.cpp @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include + +#include "computer.h" + +void Bootloader::Menu::setup() { + // Here we add the colomns to the menu. +} + +void Bootloader::Menu::open() { + showMenu(); + + uint64_t scan = 0; + bool exit = false; + + while(!exit) { + scan = Ion::Keyboard::scan(); + exit = !handleKey(scan); + } +} + +int Bootloader::Menu::calculateCenterX(const char * text, int fontWidth) { + return (k_screen.width() - fontWidth * strlen(text)) / 2; +} + +void Bootloader::Menu::showMenu() { + KDContext * ctx = KDIonContext::sharedContext(); + ctx->fillRect(k_screen, m_background); + Interface::drawImage(ctx, ImageStore::Computer, 25); + int y = ImageStore::Computer->height() + 25 + 10; + int x = calculateCenterX(m_title, largeFontWidth()); + ctx->drawString(m_title, KDPoint(x, y), k_large_font, m_foreground, m_background); + y += largeFontHeight() + 10; + + //TODO: center the colomns if m_centerY is true + + for (Colomn & colomn : m_colomns) { + if (colomn.isNull()) { + break; + } + y += colomn.draw(ctx, y, m_background, m_foreground) + k_colomns_margin; + } + + if (m_bottom != nullptr) { + y = k_screen.height() - smallFontHeight() - 10; + x = calculateCenterX(m_bottom, smallFontWidth()); + ctx->drawString(m_bottom, KDPoint(x, y), k_small_font, m_foreground, m_background); + } +} + +bool Bootloader::Menu::handleKey(uint64_t key) { + for (Ion::Keyboard::Key breaking : this->k_breaking_keys) { + if (Ion::Keyboard::State(breaking) == key) { + return false; + } + } + if (key == Ion::Keyboard::State(Ion::Keyboard::Key::Power)) { + Ion::Power::standby(); + return false; + } + for (Colomn & colomn : this->m_colomns) { + if (colomn.isNull() || !colomn.isClickable()) { + continue; + } else { + colomn.didHandledEvent(key); + } + } + return true; +} + +bool Bootloader::Menu::Colomn::didHandledEvent(uint64_t key) { + if (isMyKey(key) && isClickable()) { + return m_callback(); + } + return false; +} + +int Bootloader::Menu::Colomn::draw(KDContext * ctx, int y, KDColor background, KDColor foreground) { + int x = m_extraX; + if (m_center) { + x += Bootloader::Menu::calculateCenterX(m_text, m_font->glyphSize().width()); + } + ctx->drawString(m_text, KDPoint(x, y), m_font, foreground, background); + return m_font->glyphSize().height(); +} diff --git a/bootloader/interface/src/menu.h b/bootloader/interface/src/menu.h new file mode 100644 index 00000000000..9b9c5bf00df --- /dev/null +++ b/bootloader/interface/src/menu.h @@ -0,0 +1,83 @@ +#ifndef _BOOTLOADER_MENU_H_ +#define _BOOTLOADER_MENU_H_ + +#include +#include +#include + +namespace Bootloader { + class Menu { + public: + Menu() : m_colomns(), m_background(KDColorWhite), m_title(Messages::mainTitle), m_foreground(KDColorBlack), m_bottom(nullptr), m_centerY(false) { + setup(); + }; + Menu(KDColor forground, KDColor background, const char * title) : m_colomns(), m_background(background), m_title(title), m_foreground(forground), m_bottom(nullptr), m_centerY(false) { + setup(); + }; + Menu(KDColor forground, KDColor background, const char * title, const char * bottom) : m_colomns(), m_background(background), m_title(title), m_foreground(forground), m_bottom(bottom), m_centerY(false) { + setup(); + }; + Menu(KDColor forground, KDColor background, const char * title, const char * bottom, bool centerY) : m_colomns(), m_background(background), m_title(title), m_foreground(forground), m_bottom(bottom), m_centerY(centerY) { + setup(); + } + + virtual void setup() = 0; + + class Colomn { + public: + Colomn() : m_text(nullptr), m_key(Ion::Keyboard::Key::None), m_font(KDFont::SmallFont), m_extraX(0), m_center(false), m_callback(nullptr) {}; + Colomn(const char * t, Ion::Keyboard::Key k, const KDFont * font, int extraX, bool center, bool(*pointer)()) : m_text(t), m_key(k), m_font(font), m_extraX(extraX), m_center(center), m_callback(pointer), m_clickable(true) {}; + Colomn(const char * t, const KDFont * font, int extraX, bool center) : m_text(t), m_key(Ion::Keyboard::Key::None), m_font(font), m_extraX(extraX), m_center(center), m_callback(nullptr), m_clickable(false) {}; + + bool isNull() const { return m_text == nullptr; }; + bool isClickable() const { return m_clickable; }; + bool didHandledEvent(uint64_t key); + int draw(KDContext * ctx, int y, KDColor background, KDColor foreground); + + private: + bool isMyKey(uint64_t key) const { return Ion::Keyboard::State(m_key) == key; }; + + const char * m_text; + Ion::Keyboard::Key m_key; + const KDFont * m_font; + int m_extraX; + bool m_center; + bool m_clickable; + bool (*m_callback)(); + }; + + void open(); + void redraw() { showMenu(); }; + + static int calculateCenterX(const char * text, int fontWidth); + + static constexpr const KDFont * k_small_font = KDFont::SmallFont; + static constexpr const KDFont * k_large_font = KDFont::LargeFont; + + private: + static const int k_max_colomns = 5; + static const int k_colomns_margin = 5; + + static constexpr Ion::Keyboard::Key k_breaking_keys[] = {Ion::Keyboard::Key::Back, Ion::Keyboard::Key::Home}; + static constexpr KDRect k_screen = KDRect(0, 0, 320, 240); + + int smallFontHeight() const { return k_small_font->glyphSize().height(); }; + int largeFontHeight() const { return k_large_font->glyphSize().height(); }; + + int smallFontWidth() const { return k_small_font->glyphSize().width(); }; + int largeFontWidth() const { return k_large_font->glyphSize().width(); }; + + bool handleKey(uint64_t key); + void showMenu(); + + protected: + Colomn m_colomns[k_max_colomns]; + KDColor m_background; + KDColor m_foreground; + const char * m_title; + const char * m_bottom; + bool m_centerY; + }; +} + +#endif // _BOOTLOADER_MENU_H_ \ No newline at end of file diff --git a/bootloader/messages.h b/bootloader/messages.h index 9f2f0180835..6d8e90c3905 100644 --- a/bootloader/messages.h +++ b/bootloader/messages.h @@ -45,6 +45,7 @@ class Messages { constexpr static const char * epsilonWarningText5 = "EXE - Yes"; constexpr static const char * epsilonWarningText6 = "BACK - No"; constexpr static const char * bootloaderVersion = "Version 1.0.0 - FREEDOM"; + constexpr static const char * aboutMenuTitle = "About"; //USB NAMES diff --git a/bootloader/slot.h b/bootloader/slot.h index f82b5b6d99e..0550b02225c 100644 --- a/bootloader/slot.h +++ b/bootloader/slot.h @@ -24,6 +24,10 @@ class Slot { static const Slot B(); static const Slot Khi(); + static bool isFullyValid(const Slot& slot) { + return slot.kernelHeader()->isValid() && slot.userlandHeader()->isValid(); + } + private: const KernelHeader* m_kernelHeader; const UserlandHeader* m_userlandHeader; From eeff886cc1da162de905c475c16c362b9da3dfc5 Mon Sep 17 00:00:00 2001 From: devdl11 Date: Sat, 9 Apr 2022 11:02:34 +0200 Subject: [PATCH 226/355] [bootloader > menus] Improvement --- bootloader/Makefile | 14 ++++++++++++ bootloader/boot.cpp | 27 +++--------------------- bootloader/interface.cpp | 2 +- bootloader/interface/menus/home/home.cpp | 10 ++++----- bootloader/interface/src/menu.cpp | 21 ++++++++++++------ bootloader/interface/src/menu.h | 5 +++-- bootloader/itoa.cpp | 2 +- bootloader/kernel_header.h | 1 + bootloader/usb_data.cpp | 4 ++-- bootloader/utility.cpp | 2 +- bootloader/utility.h | 8 ++++--- 11 files changed, 50 insertions(+), 46 deletions(-) diff --git a/bootloader/Makefile b/bootloader/Makefile index 56648ddba42..ff7a5599a31 100644 --- a/bootloader/Makefile +++ b/bootloader/Makefile @@ -19,6 +19,20 @@ bootloader_images = $(addprefix bootloader/, \ computer.png \ ) +bootloader_src += $(addprefix bootloader/interface/src/,\ + menu.cpp \ +) + +bootloader_src += $(addprefix bootloader/interface/menus/about/,\ + about.cpp \ +) + +bootloader_src += $(addprefix bootloader/interface/menus/home/,\ + home.cpp \ +) + + bootloader_src += $(ion_src) $(simple_kandinsky_src) $(liba_src) $(libaxx_src) $(bootloader_images) $(eval $(call depends_on_image,bootloader/interface.cpp,$(bootloader_images))) +$(eval $(call depends_on_image,bootloader/interface/menus/home/home.cpp,$(bootloader_images))) diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index 284acf8adf2..765368b39ae 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -51,31 +52,9 @@ void Boot::bootSlot(Bootloader::Slot s) { __attribute__((noreturn)) void Boot::boot() { assert(mode() != BootMode::Unknown); - bool isSlotA = Slot::A().kernelHeader()->isValid(); - bool isSlotB = Slot::B().kernelHeader()->isValid(); - bool isSlotKhi = Slot::Khi().kernelHeader()->isValid(); - - Interface::drawMenu(); - while (true) { - uint64_t scan = Ion::Keyboard::scan(); - if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::One) && isSlotA) { - Boot::bootSlot(Slot::A()); - } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Two) && isSlotKhi) { - Boot::bootSlot(Slot::Khi()); - } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Three) && isSlotB) { - Boot::bootSlot(Slot::B()); - } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Four)) { - installerMenu(); - } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Five)) { - aboutMenu(); - } - // else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Six)) { - // Ion::Device::Reset::core(); - // } - else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { - Ion::Power::standby(); // Force a core reset to exit - } + HomeMenu menu = HomeMenu(); + menu.open(true); } // Achievement unlocked: How Did We Get Here? diff --git a/bootloader/interface.cpp b/bootloader/interface.cpp index 3c64f4bcef6..7d54da274a5 100644 --- a/bootloader/interface.cpp +++ b/bootloader/interface.cpp @@ -290,7 +290,7 @@ void Interface::drawInstallerSelection() { ctx->drawString(Messages::installerText3, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); } -void Interface::drawBLUpdate() { +void Interface::drawBootloaderUpdate() { Interface::drawHeader(); KDContext * ctx = KDIonContext::sharedContext(); int y = ImageStore::Computer->height() + (KDFont::LargeFont->glyphSize().height() + 10) + (KDFont::SmallFont->glyphSize().height() + 10); diff --git a/bootloader/interface/menus/home/home.cpp b/bootloader/interface/menus/home/home.cpp index df909d06fe3..ef367c8eb62 100644 --- a/bootloader/interface/menus/home/home.cpp +++ b/bootloader/interface/menus/home/home.cpp @@ -54,9 +54,9 @@ const char * Bootloader::HomeMenu::slotB_text() { } void Bootloader::HomeMenu::setup() { - m_colomns[0] = Colomn(slotA_text(), Ion::Keyboard::Key::One, k_large_font, 10, false, &slotA_submenu); - m_colomns[1] = Colomn(slotKhi_text(), Ion::Keyboard::Key::Two, k_large_font, 10, false, &slotKhi_submenu); - m_colomns[2] = Colomn(slotB_text(), Ion::Keyboard::Key::Three, k_large_font, 10, false, &slotB_submenu); - m_colomns[3] = Colomn("4- installer", Ion::Keyboard::Key::Four, k_large_font, 10, false, &installer_submenu); - m_colomns[4] = Colomn("5- about", Ion::Keyboard::Key::Five, k_large_font, 10, false, &about_submenu); + m_colomns[0] = Colomn(slotA_text(), Ion::Keyboard::Key::One, k_small_font, 30, false, &slotA_submenu); + m_colomns[1] = Colomn(slotKhi_text(), Ion::Keyboard::Key::Two, k_small_font, 30, false, &slotKhi_submenu); + m_colomns[2] = Colomn(slotB_text(), Ion::Keyboard::Key::Three, k_small_font, 30, false, &slotB_submenu); + m_colomns[3] = Colomn("4- installer", Ion::Keyboard::Key::Four, k_small_font, 30, false, &installer_submenu); + m_colomns[4] = Colomn("5- about", Ion::Keyboard::Key::Five, k_small_font, 30, false, &about_submenu); } \ No newline at end of file diff --git a/bootloader/interface/src/menu.cpp b/bootloader/interface/src/menu.cpp index 09ccaa15a76..6b197e4d121 100644 --- a/bootloader/interface/src/menu.cpp +++ b/bootloader/interface/src/menu.cpp @@ -4,13 +4,15 @@ #include #include -#include "computer.h" +#include + +const Ion::Keyboard::Key Bootloader::Menu::k_breaking_keys[]; void Bootloader::Menu::setup() { // Here we add the colomns to the menu. } -void Bootloader::Menu::open() { +void Bootloader::Menu::open(bool noreturn) { showMenu(); uint64_t scan = 0; @@ -19,16 +21,19 @@ void Bootloader::Menu::open() { while(!exit) { scan = Ion::Keyboard::scan(); exit = !handleKey(scan); + if (noreturn) { + exit = false; + } } } int Bootloader::Menu::calculateCenterX(const char * text, int fontWidth) { - return (k_screen.width() - fontWidth * strlen(text)) / 2; + return (getScreen().width() - fontWidth * strlen(text)) / 2; } void Bootloader::Menu::showMenu() { KDContext * ctx = KDIonContext::sharedContext(); - ctx->fillRect(k_screen, m_background); + ctx->fillRect(getScreen(), m_background); Interface::drawImage(ctx, ImageStore::Computer, 25); int y = ImageStore::Computer->height() + 25 + 10; int x = calculateCenterX(m_title, largeFontWidth()); @@ -45,7 +50,7 @@ void Bootloader::Menu::showMenu() { } if (m_bottom != nullptr) { - y = k_screen.height() - smallFontHeight() - 10; + y = getScreen().height() - smallFontHeight() - 10; x = calculateCenterX(m_bottom, smallFontWidth()); ctx->drawString(m_bottom, KDPoint(x, y), k_small_font, m_foreground, m_background); } @@ -57,7 +62,7 @@ bool Bootloader::Menu::handleKey(uint64_t key) { return false; } } - if (key == Ion::Keyboard::State(Ion::Keyboard::Key::Power)) { + if (key == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { Ion::Power::standby(); return false; } @@ -65,7 +70,9 @@ bool Bootloader::Menu::handleKey(uint64_t key) { if (colomn.isNull() || !colomn.isClickable()) { continue; } else { - colomn.didHandledEvent(key); + if (colomn.didHandledEvent(key)) { + redraw(); + } } } return true; diff --git a/bootloader/interface/src/menu.h b/bootloader/interface/src/menu.h index 9b9c5bf00df..46cb5601741 100644 --- a/bootloader/interface/src/menu.h +++ b/bootloader/interface/src/menu.h @@ -46,20 +46,21 @@ namespace Bootloader { bool (*m_callback)(); }; - void open(); + void open(bool noreturn = false); void redraw() { showMenu(); }; static int calculateCenterX(const char * text, int fontWidth); static constexpr const KDFont * k_small_font = KDFont::SmallFont; static constexpr const KDFont * k_large_font = KDFont::LargeFont; + + static const KDRect getScreen() { return KDRect(0, 0, 320, 240); }; private: static const int k_max_colomns = 5; static const int k_colomns_margin = 5; static constexpr Ion::Keyboard::Key k_breaking_keys[] = {Ion::Keyboard::Key::Back, Ion::Keyboard::Key::Home}; - static constexpr KDRect k_screen = KDRect(0, 0, 320, 240); int smallFontHeight() const { return k_small_font->glyphSize().height(); }; int largeFontHeight() const { return k_large_font->glyphSize().height(); }; diff --git a/bootloader/itoa.cpp b/bootloader/itoa.cpp index 7d712ebdd11..8235e7b32a6 100644 --- a/bootloader/itoa.cpp +++ b/bootloader/itoa.cpp @@ -19,7 +19,7 @@ char* reverse(char *buffer, int i, int j) { } // Iterative function to implement `itoa()` function in C -char* Utility::itoa(int value, char* buffer, int base) { +char* Bootloader::Utility::itoa(int value, char* buffer, int base) { // invalid input if (base < 2 || base > 32) { return buffer; diff --git a/bootloader/kernel_header.h b/bootloader/kernel_header.h index 44963851dc4..ad7fbaf4b71 100644 --- a/bootloader/kernel_header.h +++ b/bootloader/kernel_header.h @@ -2,6 +2,7 @@ #define BOOTLOADER_KERNEL_HEADER_H #include +#include namespace Bootloader { diff --git a/bootloader/usb_data.cpp b/bootloader/usb_data.cpp index 1074d7223ba..b161df94c80 100644 --- a/bootloader/usb_data.cpp +++ b/bootloader/usb_data.cpp @@ -13,13 +13,13 @@ static char data[255]; const char * Bootloader::USBData::buildStringDescriptor(StringHeader header, uint32_t startAddress, uint32_t size) { strlcpy(data, header.getString(), sizeof(data)); - Utility::itoa((int32_t)startAddress, &data[strlen(header.getString())], 16); + Bootloader::Utility::itoa((int32_t)startAddress, &data[strlen(header.getString())], 16); data[strlen(header.getString()) + 8] = '/'; data[strlen(header.getString()) + 8 + 1] = '0'; data[strlen(header.getString()) + 8 + 2] = '1'; data[strlen(header.getString()) + 8 + 3] = '*'; data[strlen(header.getString()) + 8 + 4] = '0'; - Utility::itoa((int32_t)size/1024, &data[strlen(header.getString()) + 8 + 5], 10); + Bootloader::Utility::itoa((int32_t)size/1024, &data[strlen(header.getString()) + 8 + 5], 10); data[strlen(header.getString()) + 8 + 5 + 2] = 'K'; data[strlen(header.getString()) + 8 + 5 + 2 + 1] = 'g'; data[strlen(header.getString()) + 8 + 5 + 2 + 2] = '\0'; diff --git a/bootloader/utility.cpp b/bootloader/utility.cpp index d2bdc44a040..1329351e5f3 100644 --- a/bootloader/utility.cpp +++ b/bootloader/utility.cpp @@ -1,7 +1,7 @@ #include #include -int Utility::versionSum(const char * version, int length) { +int Bootloader::Utility::versionSum(const char * version, int length) { int sum = 0; for (int i = 0; i < length; i++) { sum += version[i] * (strlen(version) * 100 - i * 10); diff --git a/bootloader/utility.h b/bootloader/utility.h index 1d24f989a87..bc8c03ca143 100644 --- a/bootloader/utility.h +++ b/bootloader/utility.h @@ -1,9 +1,11 @@ #ifndef _BOOTLOADER_ITOA_H_ #define _BOOTLOADER_ITOA_H_ -namespace Utility { - static char * itoa(int value, char * result, int base); - static int versionSum(const char * version, int length); +namespace Bootloader { + namespace Utility { + static char * itoa(int value, char * result, int base); + static int versionSum(const char * version, int length); + } } #endif From add333b9205ac337e4f5ef3362c5cbde9bfa761d Mon Sep 17 00:00:00 2001 From: devdl11 Date: Sat, 9 Apr 2022 11:08:02 +0200 Subject: [PATCH 227/355] [bootloade] Fix freezing --- bootloader/interface/menus/home/home.cpp | 2 +- bootloader/utility.h | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bootloader/interface/menus/home/home.cpp b/bootloader/interface/menus/home/home.cpp index ef367c8eb62..2529547e5c3 100644 --- a/bootloader/interface/menus/home/home.cpp +++ b/bootloader/interface/menus/home/home.cpp @@ -33,7 +33,7 @@ bool slotB_submenu() { } bool installer_submenu() { - + return false; } bool about_submenu() { diff --git a/bootloader/utility.h b/bootloader/utility.h index bc8c03ca143..12c6ce3297f 100644 --- a/bootloader/utility.h +++ b/bootloader/utility.h @@ -2,10 +2,11 @@ #define _BOOTLOADER_ITOA_H_ namespace Bootloader { - namespace Utility { + class Utility { + public: static char * itoa(int value, char * result, int base); static int versionSum(const char * version, int length); - } + }; } #endif From 2bfc243e5a5950a34433d132afc165812600ffe1 Mon Sep 17 00:00:00 2001 From: LeMoustachu Date: Sat, 9 Apr 2022 19:08:04 +0200 Subject: [PATCH 228/355] update atomic submodule (#208) --- apps/atomic | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/atomic b/apps/atomic index 69f7a06ba53..acefa4fa3c0 160000 --- a/apps/atomic +++ b/apps/atomic @@ -1 +1 @@ -Subproject commit 69f7a06ba53eaae9ba2ab8f0c0318e2f77455f4d +Subproject commit acefa4fa3c0b562cbc0cbd64c35d718e17a913e2 From 4ca7d9208c210d5b9f564f4d3453841a1aa3bc0c Mon Sep 17 00:00:00 2001 From: Lauryy06 <80424145+Lauryy06@users.noreply.github.com> Date: Sun, 10 Apr 2022 22:17:52 +0200 Subject: [PATCH 229/355] [escher] Changed PopUpTopMargin in metrics (#201) --- escher/include/escher/metric.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/escher/include/escher/metric.h b/escher/include/escher/metric.h index f514cb68c0a..0f19f221eda 100644 --- a/escher/include/escher/metric.h +++ b/escher/include/escher/metric.h @@ -23,7 +23,7 @@ class Metric { constexpr static KDCoordinate ScrollStep = 10; constexpr static KDCoordinate PopUpLeftMargin = 27; constexpr static KDCoordinate PopUpRightMargin = 27; - constexpr static KDCoordinate PopUpTopMargin = 50; + constexpr static KDCoordinate PopUpTopMargin = 15; constexpr static KDCoordinate ExamPopUpTopMargin = 27; constexpr static KDCoordinate ExamPopUpBottomMargin = 55; constexpr static KDCoordinate StoreRowHeight = 50; From f174fbc3f5fca984b5437d770ce4efff7fda4dfc Mon Sep 17 00:00:00 2001 From: "Yaya.Cout" <67095734+Yaya-Cout@users.noreply.github.com> Date: Fri, 15 Apr 2022 16:53:19 +0200 Subject: [PATCH 230/355] [apps/code] Fix Key selector in auto completion Scenario: Go in the Python editor and type "from ion import *", then type "K" and see what happens. This fix UpsilonNumworks#198 --- apps/code/catalog.de.i18n | 46 +++++++++++++ apps/code/catalog.en.i18n | 46 +++++++++++++ apps/code/catalog.es.i18n | 46 +++++++++++++ apps/code/catalog.fr.i18n | 46 +++++++++++++ apps/code/catalog.hu.i18n | 46 +++++++++++++ apps/code/catalog.it.i18n | 46 +++++++++++++ apps/code/catalog.nl.i18n | 46 +++++++++++++ apps/code/catalog.pt.i18n | 46 +++++++++++++ apps/code/catalog.universal.i18n | 46 +++++++++++++ apps/code/python_toolbox.cpp | 79 +++++++++++++++++++++-- apps/code/python_toolbox.h | 3 +- apps/code/test/toolbox_ion_keys_dummy.cpp | 10 +-- apps/code/toolbox.de.i18n | 3 + apps/code/toolbox.en.i18n | 3 + apps/code/toolbox.es.i18n | 3 + apps/code/toolbox.fr.i18n | 3 + apps/code/toolbox.hu.i18n | 3 + apps/code/toolbox.it.i18n | 3 + apps/code/toolbox.nl.i18n | 3 + apps/code/toolbox.pt.i18n | 3 + apps/code/toolbox.universal.i18n | 2 - apps/code/toolbox_ion_keys.cpp | 63 ++++++++++-------- apps/code/toolbox_ion_keys.h | 4 +- 23 files changed, 554 insertions(+), 45 deletions(-) diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index bb4c1994748..799ed941337 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -109,6 +109,52 @@ PythonBatteryIscharging = "Gibt zurück, ob die Batterie geladen wird" PythonSetBrightness = "Helligkeitsstufe festlegen" PythonGetBrightness = "Helligkeitsstufe abrufen" PythonKandinskyFunction = "Kandinsky-Modul Funktionspräfix" +PythonKeyLeft = "LEFT ARROW key" +PythonKeyUp = "UP ARROW key" +PythonKeyDown = "DOWN ARROW key" +PythonKeyRight = "RIGHT ARROW key" +PythonKeyOk = "OK key" +PythonKeyBack = "BACK key" +PythonKeyHome = "HOME key" +PythonKeyOnOff = "ON/OFF key" +PythonKeyShift = "SHIFT key" +PythonKeyAlpha = "ALPHA key" +PythonKeyXnt = "X,N,T key" +PythonKeyVar = "VAR key" +PythonKeyToolbox = "TOOLBOX key" +PythonKeyBackspace = "BACKSPACE key" +PythonKeyExp = "EXPONENTIAL key" +PythonKeyLn = "NATURAL LOGARITHM key" +PythonKeyLog = "DECIMAL LOGARITHM key" +PythonKeyImaginary = "IMAGINARY I key" +PythonKeyComma = "COMMA key" +PythonKeyPower = "POWER key" +PythonKeySine = "SINE key" +PythonKeyCosine = "COSINE key" +PythonKeyTangent = "TANGENT key" +PythonKeyPi = "PI key" +PythonKeySqrt = "SQUARE ROOT key" +PythonKeySquare = "SQUARE key" +PythonKeySeven = "7 key" +PythonKeyEight = "8 key" +PythonKeyNine = "9 key" +PythonKeyLeftParenthesis = "LEFT PARENTHESIS key" +PythonKeyRightParenthesis = "RIGHT PARENTHESIS key" +PythonKeyFour = "4 key" +PythonKeyFive = "5 key" +PythonKeySix = "6 key" +PythonKeyMultiplication = "MULTIPLICATION key" +PythonKeyDivision = "DIVISION key" +PythonKeyOne = "1 key" +PythonKeyTwo = "2 key" +PythonKeyThree = "3 key" +PythonKeyPlus = "PLUS key" +PythonKeyMinus = "MINUS key" +PythonKeyZero = "0 key" +PythonKeyDot = "DOT key" +PythonKeyEe = "10 POWER X key" +PythonKeyAns = "ANS key" +PythonKeyExe = "EXE key" PythonLdexp = "Liefert x*(2**i), Inverse von frexp" PythonLength = "Länge eines Objekts" PythonLgamma = "Log-Gamma-Funktion" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index 7241ff9b142..af439aeb8f0 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -95,6 +95,52 @@ PythonSetBrightness = "Set brightness level" PythonGetBrightness = "Get brightness level" PythonIsNaN = "Check if x is a NaN" PythonKandinskyFunction = "kandinsky module function prefix" +PythonKeyLeft = "LEFT ARROW key" +PythonKeyUp = "UP ARROW key" +PythonKeyDown = "DOWN ARROW key" +PythonKeyRight = "RIGHT ARROW key" +PythonKeyOk = "OK key" +PythonKeyBack = "BACK key" +PythonKeyHome = "HOME key" +PythonKeyOnOff = "ON/OFF key" +PythonKeyShift = "SHIFT key" +PythonKeyAlpha = "ALPHA key" +PythonKeyXnt = "X,N,T key" +PythonKeyVar = "VAR key" +PythonKeyToolbox = "TOOLBOX key" +PythonKeyBackspace = "BACKSPACE key" +PythonKeyExp = "EXPONENTIAL key" +PythonKeyLn = "NATURAL LOGARITHM key" +PythonKeyLog = "DECIMAL LOGARITHM key" +PythonKeyImaginary = "IMAGINARY I key" +PythonKeyComma = "COMMA key" +PythonKeyPower = "POWER key" +PythonKeySine = "SINE key" +PythonKeyCosine = "COSINE key" +PythonKeyTangent = "TANGENT key" +PythonKeyPi = "PI key" +PythonKeySqrt = "SQUARE ROOT key" +PythonKeySquare = "SQUARE key" +PythonKeySeven = "7 key" +PythonKeyEight = "8 key" +PythonKeyNine = "9 key" +PythonKeyLeftParenthesis = "LEFT PARENTHESIS key" +PythonKeyRightParenthesis = "RIGHT PARENTHESIS key" +PythonKeyFour = "4 key" +PythonKeyFive = "5 key" +PythonKeySix = "6 key" +PythonKeyMultiplication = "MULTIPLICATION key" +PythonKeyDivision = "DIVISION key" +PythonKeyOne = "1 key" +PythonKeyTwo = "2 key" +PythonKeyThree = "3 key" +PythonKeyPlus = "PLUS key" +PythonKeyMinus = "MINUS key" +PythonKeyZero = "0 key" +PythonKeyDot = "DOT key" +PythonKeyEe = "10 POWER X key" +PythonKeyAns = "ANS key" +PythonKeyExe = "EXE key" PythonLdexp = "Return x*(2**i), inverse of frexp" PythonLength = "Length of an object" PythonLgamma = "Log-gamma function" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index 4bc9eb2dc6a..c7d94d3f447 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -95,6 +95,52 @@ PythonSetBrightness = "Establecer nivel de brillo" PythonGetBrightness = "Obtener nivel de brillo" PythonIsNaN = "Check if x is a NaN" PythonKandinskyFunction = "kandinsky module function prefix" +PythonKeyLeft = "LEFT ARROW key" +PythonKeyUp = "UP ARROW key" +PythonKeyDown = "DOWN ARROW key" +PythonKeyRight = "RIGHT ARROW key" +PythonKeyOk = "OK key" +PythonKeyBack = "BACK key" +PythonKeyHome = "HOME key" +PythonKeyOnOff = "ON/OFF key" +PythonKeyShift = "SHIFT key" +PythonKeyAlpha = "ALPHA key" +PythonKeyXnt = "X,N,T key" +PythonKeyVar = "VAR key" +PythonKeyToolbox = "TOOLBOX key" +PythonKeyBackspace = "BACKSPACE key" +PythonKeyExp = "EXPONENTIAL key" +PythonKeyLn = "NATURAL LOGARITHM key" +PythonKeyLog = "DECIMAL LOGARITHM key" +PythonKeyImaginary = "IMAGINARY I key" +PythonKeyComma = "COMMA key" +PythonKeyPower = "POWER key" +PythonKeySine = "SINE key" +PythonKeyCosine = "COSINE key" +PythonKeyTangent = "TANGENT key" +PythonKeyPi = "PI key" +PythonKeySqrt = "SQUARE ROOT key" +PythonKeySquare = "SQUARE key" +PythonKeySeven = "7 key" +PythonKeyEight = "8 key" +PythonKeyNine = "9 key" +PythonKeyLeftParenthesis = "LEFT PARENTHESIS key" +PythonKeyRightParenthesis = "RIGHT PARENTHESIS key" +PythonKeyFour = "4 key" +PythonKeyFive = "5 key" +PythonKeySix = "6 key" +PythonKeyMultiplication = "MULTIPLICATION key" +PythonKeyDivision = "DIVISION key" +PythonKeyOne = "1 key" +PythonKeyTwo = "2 key" +PythonKeyThree = "3 key" +PythonKeyPlus = "PLUS key" +PythonKeyMinus = "MINUS key" +PythonKeyZero = "0 key" +PythonKeyDot = "DOT key" +PythonKeyEe = "10 POWER X key" +PythonKeyAns = "ANS key" +PythonKeyExe = "EXE key" PythonLdexp = "Return x*(2**i), inverse of frexp" PythonLength = "Length of an object" PythonLgamma = "Log-gamma function" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index c43c9a2c22a..aae8345d317 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -95,6 +95,52 @@ PythonSetBrightness = "Définir le niveau de luminosité" PythonGetBrightness = "Obtenir le niveau de luminosité" PythonIsNaN = "Teste si x est NaN" PythonKandinskyFunction = "Préfixe fonction module kandinsky" +PythonKeyLeft = "Touche FLECHE GAUCHE" +PythonKeyUp = "Touche FLECHE HAUT" +PythonKeyDown = "Touche FLECHE BAS" +PythonKeyRight = "Touche FLECHE DROITE" +PythonKeyOk = "Touche OK" +PythonKeyBack = "Touche RETOUR" +PythonKeyHome = "Touche HOME" +PythonKeyOnOff = "Touche ON/OFF" +PythonKeyShift = "Touche SHIFT" +PythonKeyAlpha = "Touche ALPHA" +PythonKeyXnt = "Touche X,N,T" +PythonKeyVar = "Touche VAR" +PythonKeyToolbox = "Touche BOITE A OUTILS" +PythonKeyBackspace = "Touche EFFACER" +PythonKeyExp = "Touche EXPONENTIELLE" +PythonKeyLn = "Touche LOGARITHME NEPERIEN" +PythonKeyLog = "Touche LOGARITHME DECIMAL" +PythonKeyImaginary = "Touche I IMAGINAIRE" +PythonKeyComma = "Touche VIRGULE" +PythonKeyPower = "Touche PUISSANCE" +PythonKeySine = "Touche SINUS" +PythonKeyCosine = "Touche COSINUS" +PythonKeyTangent = "Touche TANGENTE" +PythonKeyPi = "Touche PI" +PythonKeySqrt = "Touche RACINE CARREE" +PythonKeySquare = "Touche CARRE" +PythonKeySeven = "Touche 7" +PythonKeyEight = "Touche 8" +PythonKeyNine = "Touche 9" +PythonKeyLeftParenthesis = "Touche PARENTHESE GAUCHE" +PythonKeyRightParenthesis = "Touche PARENTHESE DROITE" +PythonKeyFour = "Touche 4" +PythonKeyFive = "Touche 5" +PythonKeySix = "Touche 6" +PythonKeyMultiplication = "Touche MULTIPLICATION" +PythonKeyDivision = "Touche DIVISION" +PythonKeyOne = "Touche 1" +PythonKeyTwo = "Touche 2" +PythonKeyThree = "Touche 3" +PythonKeyPlus = "Touche PLUS" +PythonKeyMinus = "Touche MOINS" +PythonKeyZero = "Touche 0" +PythonKeyDot = "Touche POINT" +PythonKeyEe = "Touche 10 PUISSANCE X" +PythonKeyAns = "Touche ANS" +PythonKeyExe = "Touche EXE" PythonLdexp = "Inverse de frexp : x*(2**i)" PythonLength = "Longueur d'un objet" PythonLgamma = "Logarithme de la fonction gamma" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index ad7eb410f74..3dac494700e 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -208,3 +208,49 @@ PythonSysImplementation = "Information about Python" PythonSysModules = "Dictionary of loaded modules" PythonSysVersion = "Python language version (string)" PythonSysVersioninfo = "Python language version (tuple)" +PythonKeyLeft = "BALRA NYÍL billentyű" +PythonKeyUp = "FEL NYÍL billentyű" +PythonKeyDown = "LE NYÍL billentyű" +PythonKeyRight = "JOBBRA NYÍL billentyű" +PythonKeyOk = "OK gomb" +PythonKeyBack = "VISSZA gomb" +PythonKeyHome = "Lakáskulcs" +PythonKeyOnOff = "BE/KI gomb" +PythonKeyShift = "SHIFT billentyű" +PythonKeyAlpha = "ALPHA kulcs" +PythonKeyXnt = "X,N,T gomb" +PythonKeyVar = "VAR gomb" +PythonKeyToolbox = "TOOLBOX gomb" +PythonKeyBackspace = "BACKSPACE billentyű" +PythonKeyExp = "EXPONENTIÁLIS kulcs" +PythonKeyLn = "TERMÉSZETES LOGARITMUS kulcs" +PythonKeyLog = "DECIMÁLIS LOGARITMUS billentyű" +PythonKeyImaginary = "KÉPZELETES I kulcs" +PythonKeyComma = "VESZSŰ gomb" +PythonKeyPower = "POWER gomb" +PythonKeySine = "SINE gomb" +PythonKeyCosine = "KOSINUS kulcs" +PythonKeyTangent = "ÉRINTŐ gomb" +PythonKeyPi = "PI kulcs" +PythonKeySqrt = "NÉGYGYÖK kulcs" +PythonKeySquare = "SZÖGZET billentyű" +PythonKeySeven = "7 kulcs" +PythonKeyEight = "8 kulcs" +PythonKeyNine = "9 kulcs" +PythonKeyLeftParenthesis = "BAL ZÁRÓZELŐ gomb" +PythonKeyRightParenthesis = "JOBB ZÁRÓZELŐ billentyű" +PythonKeyFour = "4 kulcs" +PythonKeyFive = "5 kulcs" +PythonKeySix = "6 kulcs" +PythonKeyMultiplication = "SZORZAT gomb" +PythonKeyDivision = "OSZTÁS kulcs" +PythonKeyOne = "1 kulcs" +PythonKeyTwo = "2 kulcs" +PythonKeyThree = "3 kulcs" +PythonKeyPlus = "PLUSZ kulcs" +PythonKeyMinus = "MÍNUS gomb" +PythonKeyZero = "0 kulcs" +PythonKeyDot = "DOT gomb" +PythonKeyEe = "10 POWER X gomb" +PythonKeyAns = "ANS kulcs" +PythonKeyExe = "EXE kulcs" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index 9a1bae232d5..7e797a39932 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -109,6 +109,52 @@ PythonSetBrightness = "Imposta livello di luminosità" PythonGetBrightness = "Ottieni livello di luminosità" PythonIsNaN = "Testa se x è NaN" PythonKandinskyFunction = "Prefisso funzione modulo kandinsky" +PythonKeyLeft = "Tasto FRECCIA SINISTRA" +PythonKeyUp = "Tasto FRECCIA ALTO" +PythonKeyDown = "Tasto FRECCIA BASSO" +PythonKeyRight = "Tasto FRECCIA DESTRA" +PythonKeyOk = "Tasto OK" +PythonKeyBack = "Tasto INDIETRO" +PythonKeyHome = "Tasto CASA" +PythonKeyOnOff = "Tasto ON/OFF" +PythonKeyShift = "Tasto SHIFT" +PythonKeyAlpha = "Tasto ALPHA" +PythonKeyXnt = "Tasto X,N,T" +PythonKeyVar = "Tasto VAR" +PythonKeyToolbox = "Tasto TOOLBOX" +PythonKeyBackspace = "Tasto CANCELLA" +PythonKeyExp = "Tasto ESPONENZIALE" +PythonKeyLn = "Tasto LOGARITMO NEPERIANO" +PythonKeyLog = "Tasto LOGARITMO DECIMALE" +PythonKeyImaginary = "Tasto I IMMAGINE" +PythonKeyComma = "Tasto VIRGOLA" +PythonKeyPower = "Tasto POTENZA" +PythonKeySine = "Tasto SENO" +PythonKeyCosine = "Tasto COSENO" +PythonKeyTangent = "Tasto TANGENTE" +PythonKeyPi = "Tasto PI" +PythonKeySqrt = "Tasto RADICE QUADRATA" +PythonKeySquare = "Tasto QUADRATO" +PythonKeySeven = "Tasto 7" +PythonKeyEight = "Tasto 8" +PythonKeyNine = "Tasto 9" +PythonKeyLeftParenthesis = "Tasto PARENTESI SINISTRA" +PythonKeyRightParenthesis = "Tasto PARENTESI DESTRA" +PythonKeyFour = "Tasto 4" +PythonKeyFive = "Tasto 5" +PythonKeySix = "Tasto 6" +PythonKeyMultiplication = "Tasto MOLTIPLICAZIONE" +PythonKeyDivision = "Tasto DIVISIONE" +PythonKeyOne = "Tasto 1" +PythonKeyTwo = "Tasto 2" +PythonKeyThree = "Tasto 3" +PythonKeyPlus = "Tasto PIÙ" +PythonKeyMinus = "Tasto MENO" +PythonKeyZero = "Tasto 0" +PythonKeyDot = "Tasto PUNTO" +PythonKeyEe = "Tasto 10 POTENZA X" +PythonKeyAns = "Tasto ANS" +PythonKeyExe = "Tasto EXE" PythonLdexp = "Inversa di frexp : x*(2**i)" PythonLength = "Longhezza di un oggetto" PythonLgamma = "Logaritmo della funzione gamma" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index 8fe6b81e0e7..9d98d756e3f 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -110,6 +110,52 @@ PythonSetBrightness = "Set brightness level" PythonGetBrightness = "Get brightness level" PythonIsNaN = "Controleer of x geen getal is" PythonKandinskyFunction = "kandinsky module voorvoegsel" +PythonKeyLeft = "PIJL NAAR LINKS toets" +PythonKeyUp = "PIJL OMHOOG toets" +PythonKeyDown = "PIJL OMLAAG toets" +PythonKeyRight = "PIJL NAAR RECHTS toets" +PythonKeyOk = "OK toets" +PythonKeyBack = "TERUG toets" +PythonKeyHome = "HOME toets" +PythonKeyOnOff = "AAN/UIT toets" +PythonKeyShift = "SHIFT toets" +PythonKeyAlpha = "ALPHA toets" +PythonKeyXnt = "X,N,T toets" +PythonKeyVar = "VAR toets" +PythonKeyToolbox = "TOOLBOX toets" +PythonKeyBackspace = "BACKSPACE toets" +PythonKeyExp = "EXPONENTIEEL toets" +PythonKeyLn = "NATUURLIJKE LOGARITME toets" +PythonKeyLog = "BRIGGSE LOGARITME toets" +PythonKeyImaginary = "IMAGINAIRE I toets" +PythonKeyComma = "KOMMA toets" +PythonKeyPower = "MACHT toets" +PythonKeySine = "SINUS toets" +PythonKeyCosine = "COSINUS toets" +PythonKeyTangent = "TANGENS toets" +PythonKeyPi = "PI toets" +PythonKeySqrt = "VIERKANTSWORTEL toets" +PythonKeySquare = "KWADRAAT toets" +PythonKeySeven = "7 toets" +PythonKeyEight = "8 toets" +PythonKeyNine = "9 toets" +PythonKeyLeftParenthesis = "HAAKJE OPENEN toets" +PythonKeyRightParenthesis = "HAAKJE SLUITEN toets" +PythonKeyFour = "4 toets" +PythonKeyFive = "5 toets" +PythonKeySix = "6 toets" +PythonKeyMultiplication = "VERMENIGVULDIGEN toets" +PythonKeyDivision = "DELEN toets" +PythonKeyOne = "1 toets" +PythonKeyTwo = "2 toets" +PythonKeyThree = "3 toets" +PythonKeyPlus = "PLUS toets" +PythonKeyMinus = "MIN toets" +PythonKeyZero = "0 toets" +PythonKeyDot = "PUNT toets" +PythonKeyEe = "10 TOT DE MACHT X toets" +PythonKeyAns = "ANS toets" +PythonKeyExe = "EXE toets" PythonLdexp = "Geeft x*(2**i), inversie van frexp" PythonLength = "Lengte van een object" PythonLgamma = "Log-gammafunctie" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index 1a894c32273..d3a49aa559b 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -95,6 +95,52 @@ PythonSetBrightness = "Definir nível de brilho" PythonGetBrightness = "Obter nível de brilho" PythonIsNaN = "Verificar se x é um NaN" PythonKandinskyFunction = "Prefixo da função do módulo kandinsky" +PythonKeyLeft = "tecla SETA ESQUERDA" +PythonKeyUp = "tecla SETA CIMA " +PythonKeyDown = "tecla SETA BAIXO" +PythonKeyRight = "tecla SETA DIREITA" +PythonKeyOk = "tecla OK" +PythonKeyBack = "tecla VOLTAR" +PythonKeyHome = "tecla HOME" +PythonKeyOnOff = "tecla ON/OFF" +PythonKeyShift = "tecla SHIFT" +PythonKeyAlpha = "tecla ALPHA" +PythonKeyXnt = "tecla X,N,T" +PythonKeyVar = "tecla VAR" +PythonKeyToolbox = "tecla CAIXA DE FERRAMENTAS" +PythonKeyBackspace = "tecla APAGAR" +PythonKeyExp = "tecla EXPONENCIAL" +PythonKeyLn = "tecla LOGARITMO NATURAL" +PythonKeyLog = "tecla LOGARITMO DECIMAL" +PythonKeyImaginary = "tecla I IMAGINÁRIO" +PythonKeyComma = "tecla VÍRGULA" +PythonKeyPower = "tecla EXPOENTE" +PythonKeySine = "tecla SENO" +PythonKeyCosine = "tecla COSSENO" +PythonKeyTangent = "tecla TANGENTE" +PythonKeyPi = "tecla PI" +PythonKeySqrt = "tecla RAIZ QUADRADA" +PythonKeySquare = "tecla AO QUADRADO" +PythonKeySeven = "tecla 7" +PythonKeyEight = "tecla 8" +PythonKeyNine = "tecla 9" +PythonKeyLeftParenthesis = "tecla PARÊNTESE ESQUERDO" +PythonKeyRightParenthesis = "tecla PARÊNTESE DIREITO" +PythonKeyFour = "tecla 4" +PythonKeyFive = "tecla 5" +PythonKeySix = "tecla 6" +PythonKeyMultiplication = "tecla MULTIPLICAÇÃO" +PythonKeyDivision = "tecla DIVISÃO" +PythonKeyOne = "tecla 1" +PythonKeyTwo = "tecla 2" +PythonKeyThree = "tecla 3" +PythonKeyPlus = "tecla MAIS" +PythonKeyMinus = "tecla MENOS" +PythonKeyZero = "tecla 0" +PythonKeyDot = "tecla PONTO" +PythonKeyEe = "tecla 10 expoente X" +PythonKeyAns = "tecla ANS" +PythonKeyExe = "tecla EXE" PythonLdexp = "Devolve x*(2**i), inverso de frexp" PythonLength = "Comprimento de um objeto" PythonLgamma = "Logaritmo da função gama" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index 7da352806d6..05a79de125b 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -114,6 +114,52 @@ PythonCommandIsInfinite = "isinf(x)" PythonCommandIsNaN = "isnan(x)" PythonCommandKandinskyFunction = "kandinsky.function" PythonCommandKandinskyFunctionWithoutArg = "kandinsky.\x11" +PythonCommandKeyLeft = "KEY_LEFT" +PythonCommandKeyUp = "KEY_UP" +PythonCommandKeyDown = "KEY_DOWN" +PythonCommandKeyRight = "KEY_RIGHT" +PythonCommandKeyOk = "KEY_OK" +PythonCommandKeyBack = "KEY_BACK" +PythonCommandKeyHome = "KEY_HOME" +PythonCommandKeyOnOff = "KEY_ONOFF" +PythonCommandKeyShift = "KEY_SHIFT" +PythonCommandKeyAlpha = "KEY_ALPHA" +PythonCommandKeyXnt = "KEY_XNT" +PythonCommandKeyVar = "KEY_VAR" +PythonCommandKeyToolbox = "KEY_TOOLBOX" +PythonCommandKeyBackspace = "KEY_BACKSPACE" +PythonCommandKeyExp = "KEY_EXP" +PythonCommandKeyLn = "KEY_LN" +PythonCommandKeyLog = "KEY_LOG" +PythonCommandKeyImaginary = "KEY_IMAGINARY" +PythonCommandKeyComma = "KEY_COMMA" +PythonCommandKeyPower = "KEY_POWER" +PythonCommandKeySine = "KEY_SINE" +PythonCommandKeyCosine = "KEY_COSINE" +PythonCommandKeyTangent = "KEY_TANGENT" +PythonCommandKeyPi = "KEY_PI" +PythonCommandKeySqrt = "KEY_SQRT" +PythonCommandKeySquare = "KEY_SQUARE" +PythonCommandKeySeven = "KEY_SEVEN" +PythonCommandKeyEight = "KEY_EIGHT" +PythonCommandKeyNine = "KEY_NINE" +PythonCommandKeyLeftParenthesis = "KEY_LEFTPARENTHESIS" +PythonCommandKeyRightParenthesis = "KEY_RIGHTPARENTHESIS" +PythonCommandKeyFour = "KEY_FOUR" +PythonCommandKeyFive = "KEY_FIVE" +PythonCommandKeySix = "KEY_SIX" +PythonCommandKeyMultiplication = "KEY_MULTIPLICATION" +PythonCommandKeyDivision = "KEY_DIVISION" +PythonCommandKeyOne = "KEY_ONE" +PythonCommandKeyTwo = "KEY_TWO" +PythonCommandKeyThree = "KEY_THREE" +PythonCommandKeyPlus = "KEY_PLUS" +PythonCommandKeyMinus = "KEY_MINUS" +PythonCommandKeyZero = "KEY_ZERO" +PythonCommandKeyDot = "KEY_DOT" +PythonCommandKeyEe = "KEY_EE" +PythonCommandKeyAns = "KEY_ANS" +PythonCommandKeyExe = "KEY_EXE" PythonCommandIsKeyDown = "keydown(k)" PythonCommandBattery = "battery()" PythonCommandBatteryLevel = "battery_level()" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 35a453b22ad..d601db5f14c 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -366,7 +366,57 @@ const ToolboxMessageTree KandinskyModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillRect, I18n::Message::PythonFillRect), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillCircle, I18n::Message::PythonFillCircle), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillPolygon, I18n::Message::PythonFillPolygon), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetPalette, I18n::Message::PythonGetPalette)}; + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetPalette, I18n::Message::PythonGetPalette) +}; + +const ToolboxMessageTree IonKeyModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyLeft, I18n::Message::PythonKeyLeft, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyUp, I18n::Message::PythonKeyUp, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyDown, I18n::Message::PythonKeyDown, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyRight, I18n::Message::PythonKeyRight, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyOk, I18n::Message::PythonKeyOk, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyBack, I18n::Message::PythonKeyBack, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyHome, I18n::Message::PythonKeyHome, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyOnOff, I18n::Message::PythonKeyOnOff, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyShift, I18n::Message::PythonKeyShift, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyAlpha, I18n::Message::PythonKeyAlpha, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyXnt, I18n::Message::PythonKeyXnt, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyVar, I18n::Message::PythonKeyVar, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyToolbox, I18n::Message::PythonKeyToolbox, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyBackspace, I18n::Message::PythonKeyBackspace, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyExp, I18n::Message::PythonKeyExp, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyLn, I18n::Message::PythonKeyLn, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyLog, I18n::Message::PythonKeyLog, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyImaginary, I18n::Message::PythonKeyImaginary, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyComma, I18n::Message::PythonKeyComma, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyPower, I18n::Message::PythonKeyPower, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeySine, I18n::Message::PythonKeySine, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyCosine, I18n::Message::PythonKeyCosine, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyTangent, I18n::Message::PythonKeyTangent, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyPi, I18n::Message::PythonKeyPi, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeySqrt, I18n::Message::PythonKeySqrt, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeySquare, I18n::Message::PythonKeySquare, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeySeven, I18n::Message::PythonKeySeven, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyEight, I18n::Message::PythonKeyEight, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyNine, I18n::Message::PythonKeyNine, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyLeftParenthesis, I18n::Message::PythonKeyLeftParenthesis, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyRightParenthesis, I18n::Message::PythonKeyRightParenthesis, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyFour, I18n::Message::PythonKeyFour, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyFive, I18n::Message::PythonKeyFive, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeySix, I18n::Message::PythonKeySix, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyMultiplication, I18n::Message::PythonKeyMultiplication, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyDivision, I18n::Message::PythonKeyDivision, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyOne, I18n::Message::PythonKeyOne, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyTwo, I18n::Message::PythonKeyTwo, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyThree, I18n::Message::PythonKeyThree, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyPlus, I18n::Message::PythonKeyPlus, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyMinus, I18n::Message::PythonKeyMinus, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyZero, I18n::Message::PythonKeyZero, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyDot, I18n::Message::PythonKeyDot, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyEe, I18n::Message::PythonKeyEe, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyAns, I18n::Message::PythonKeyAns, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyExe, I18n::Message::PythonKeyExe, false) +}; const ToolboxMessageTree IonModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportIon, I18n::Message::PythonImportIon, false), @@ -379,7 +429,9 @@ const ToolboxMessageTree IonModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBatteryIscharging, I18n::Message::PythonBatteryIscharging), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSetBrightness, I18n::Message::PythonSetBrightness), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetBrightness, I18n::Message::PythonGetBrightness), - ToolboxMessageTree::Leaf(I18n::Message::IonSelector, I18n::Message::IonSelector) + // This is a special case, because it is handled separately, so the sub-tree is unused. + ToolboxMessageTree::Node(I18n::Message::IonSelector, IonKeyModuleChildren), + ToolboxMessageTree::Node(I18n::Message::IonKeyList, IonKeyModuleChildren) }; const ToolboxMessageTree TimeModuleChildren[] = { @@ -706,11 +758,6 @@ bool PythonToolbox::selectLeaf(int selectedRow, bool quitToolbox) { return true; } #endif - if(node->insertedText() == I18n::Message::IonSelector){ - m_ionKeys.setSender(sender()); - Container::activeApp()->displayModalViewController(static_cast(&m_ionKeys), 0.f, 0.f, Metric::PopUpTopMargin, Metric::PopUpLeftMargin, 0, Metric::PopUpRightMargin); - return true; - } const char * editedText = I18n::translate(node->insertedText()); // strippedEditedText array needs to be in the same scope as editedText char strippedEditedText[k_maxMessageSize]; @@ -728,6 +775,24 @@ bool PythonToolbox::selectLeaf(int selectedRow, bool quitToolbox) { return true; } +// This is the same function as in the Toolbox class, but we need to override it because we need to handle the Key selector differently. +bool PythonToolbox::selectSubMenu(int selectedRow) { + // If the selected row is a is the Key selector, we display the IonKeySelectorViewController + if (m_messageTreeModel->childAtIndex(selectedRow)->label() == I18n::Message::IonSelector) { + m_ionKeys.setSender(sender()); + Container::activeApp()->displayModalViewController(static_cast(&m_ionKeys), 0.f, 0.f, Metric::PopUpTopMargin, Metric::PopUpLeftMargin, 0, Metric::PopUpRightMargin); + return true; + } + m_selectableTableView.deselectTable(); + m_messageTreeModel = static_cast(m_messageTreeModel->childAtIndex(selectedRow)); + if (m_messageTreeModel->isFork()) { + assert(m_messageTreeModel->numberOfChildren() < 0); + m_messageTreeModel = static_cast(m_messageTreeModel->childAtIndex(indexAfterFork())); + } + return NestedMenuController::selectSubMenu(selectedRow); +} + + const ToolboxMessageTree * PythonToolbox::rootModel() const { return &toolboxModel; } diff --git a/apps/code/python_toolbox.h b/apps/code/python_toolbox.h index 9be9f4aa9c5..4252b1f16b0 100644 --- a/apps/code/python_toolbox.h +++ b/apps/code/python_toolbox.h @@ -21,6 +21,7 @@ class PythonToolbox : public Toolbox { protected: KDCoordinate rowHeight(int j) override; bool selectLeaf(int selectedRow, bool quitToolbox) override; + bool selectSubMenu(int selectedRow) override; MessageTableCellWithMessage * leafCellAtIndex(int index) override; MessageTableCellWithChevron* nodeCellAtIndex(int index) override; int maxNumberOfDisplayedRows() override; @@ -34,7 +35,7 @@ class PythonToolbox : public Toolbox { void scrollToAndSelectChild(int i); MessageTableCellWithMessage m_leafCells[k_maxNumberOfDisplayedRows]; MessageTableCellWithChevron m_nodeCells[k_maxNumberOfDisplayedRows]; - toolboxIonKeys m_ionKeys; + ToolboxIonKeys m_ionKeys; }; } diff --git a/apps/code/test/toolbox_ion_keys_dummy.cpp b/apps/code/test/toolbox_ion_keys_dummy.cpp index 9f38bb541e4..969b20e3c42 100644 --- a/apps/code/test/toolbox_ion_keys_dummy.cpp +++ b/apps/code/test/toolbox_ion_keys_dummy.cpp @@ -1,26 +1,26 @@ #include "../toolbox_ion_keys.h" namespace Code { - toolboxIonKeys::toolboxIonKeys() : + ToolboxIonKeys::ToolboxIonKeys() : ViewController(nullptr), m_view() { } - bool toolboxIonKeys::handleEvent(Ion::Events::Event e) { + bool ToolboxIonKeys::handleEvent(Ion::Events::Event e) { return false; } - toolboxIonKeys::toolboxIonView::toolboxIonView(): + ToolboxIonKeys::toolboxIonView::toolboxIonView(): View() { } - void toolboxIonKeys::toolboxIonView::drawRect(KDContext * ctx, KDRect rect) const { + void ToolboxIonKeys::toolboxIonView::drawRect(KDContext * ctx, KDRect rect) const { return; } - View * toolboxIonKeys::view(){ + View * ToolboxIonKeys::view(){ return &m_view; } diff --git a/apps/code/toolbox.de.i18n b/apps/code/toolbox.de.i18n index 82dd2b142c1..c71afcc5254 100644 --- a/apps/code/toolbox.de.i18n +++ b/apps/code/toolbox.de.i18n @@ -5,3 +5,6 @@ LoopsAndTests = "Schleifen und Tests" Files = "Dateien" Exceptions = "Ausnahmen" UlabDocumentation = "Dokumentation" +IonSelector = "Schlüsselauswahl" +PressAKey = "drücke eine Taste" +IonKeyList = "Liste der Schlüssel" diff --git a/apps/code/toolbox.en.i18n b/apps/code/toolbox.en.i18n index 59eadf40323..820680d2f55 100644 --- a/apps/code/toolbox.en.i18n +++ b/apps/code/toolbox.en.i18n @@ -5,3 +5,6 @@ LoopsAndTests = "Loops and tests" Files = "Files" Exceptions = "Exceptions" UlabDocumentation = "Documentation" +IonSelector = "Key selector" +PressAKey = "Press a key" +IonKeyList = "List of keys" diff --git a/apps/code/toolbox.es.i18n b/apps/code/toolbox.es.i18n index 905e28a8e9d..8744a2872f5 100644 --- a/apps/code/toolbox.es.i18n +++ b/apps/code/toolbox.es.i18n @@ -5,3 +5,6 @@ LoopsAndTests = "Loops and tests" Files = "Files" Exceptions = "Exceptions" UlabDocumentation = "Documentación" +IonSelector = "Selector de llave" +PressAKey = "presione una tecla" +IonKeyList = "Lista de llaves" diff --git a/apps/code/toolbox.fr.i18n b/apps/code/toolbox.fr.i18n index 077e4550a45..b2c2dfe97cf 100644 --- a/apps/code/toolbox.fr.i18n +++ b/apps/code/toolbox.fr.i18n @@ -5,3 +5,6 @@ LoopsAndTests = "Boucles et tests" Files = "Fichiers" Exceptions = "Exceptions" UlabDocumentation = "Documentation" +IonSelector = "Sélecteur de touche" +PressAKey = "Appuyez sur une touche" +IonKeyList = "Liste des touches" diff --git a/apps/code/toolbox.hu.i18n b/apps/code/toolbox.hu.i18n index fbdff6f74f6..da82c67b22f 100644 --- a/apps/code/toolbox.hu.i18n +++ b/apps/code/toolbox.hu.i18n @@ -5,3 +5,6 @@ LoopsAndTests = "Hurkok és tesztek" Files = "Fájlok" Exceptions = "Kivételek" UlabDocumentation = "Dokumentáció" +IonSelector = "Kulcsválasztó" +PressAKey = "Nyomj meg egy gombot" +IonKeyList = "A kulcsok listája" diff --git a/apps/code/toolbox.it.i18n b/apps/code/toolbox.it.i18n index 7069ccb4e7e..7ec36994c9a 100644 --- a/apps/code/toolbox.it.i18n +++ b/apps/code/toolbox.it.i18n @@ -5,3 +5,6 @@ LoopsAndTests = "Cicli e test" Files = "Files" Exceptions = "Exceptions" UlabDocumentation = "Documentazione" +IonSelector = "Selettore a chiave" +PressAKey = "Premi un tasto" +IonKeyList = "Elenco delle chiavi" diff --git a/apps/code/toolbox.nl.i18n b/apps/code/toolbox.nl.i18n index 4df9ed1a753..137144df572 100644 --- a/apps/code/toolbox.nl.i18n +++ b/apps/code/toolbox.nl.i18n @@ -5,3 +5,6 @@ LoopsAndTests = "Herhalingen en testen" Files = "Files" Exceptions = "Exceptions" UlabDocumentation = "Documentatie" +IonSelector = "Toetsenkiezer" +PressAKey = "druk op een knop" +IonKeyList = "Lijst met sleutels" diff --git a/apps/code/toolbox.pt.i18n b/apps/code/toolbox.pt.i18n index 4ea2d75fd1e..9884dfdc075 100644 --- a/apps/code/toolbox.pt.i18n +++ b/apps/code/toolbox.pt.i18n @@ -5,3 +5,6 @@ LoopsAndTests = "Laços e testes" Files = "Files" Exceptions = "Exceptions" UlabDocumentation = "Documentação" +IonSelector = "Seletor de chave" +PressAKey = "Pressione uma tecla" +IonKeyList = "Lista de chaves" diff --git a/apps/code/toolbox.universal.i18n b/apps/code/toolbox.universal.i18n index e7664f96cb1..1e757f43a35 100644 --- a/apps/code/toolbox.universal.i18n +++ b/apps/code/toolbox.universal.i18n @@ -68,6 +68,4 @@ PythonCommandDef = "def \x11():\n " PythonCommandDefWithArg = "def function(x):" PythonCommandReturn = "return " RandomModule = "random" -IonSelector = "Key selector" -PressAKey = "Press a key" UlabDocumentationLink = "micropython-ulab.readthedocs.io" diff --git a/apps/code/toolbox_ion_keys.cpp b/apps/code/toolbox_ion_keys.cpp index fe0a40d5b6a..b5167d60c2f 100644 --- a/apps/code/toolbox_ion_keys.cpp +++ b/apps/code/toolbox_ion_keys.cpp @@ -9,42 +9,49 @@ extern "C" { extern "C" const mp_rom_map_elem_t modion_module_globals_table[52]; namespace Code { - toolboxIonKeys::toolboxIonKeys() : +ToolboxIonKeys::ToolboxIonKeys() : ViewController(nullptr), m_view() - { - } +{ +} - bool toolboxIonKeys::handleEvent(Ion::Events::Event e) { - Ion::Keyboard::State state = Ion::Keyboard::scan(); - for(uint16_t i = 0; i < sizeof(modion_module_globals_table)/sizeof(_mp_rom_map_elem_t); i++){ - _mp_rom_map_elem_t element = modion_module_globals_table[i]; - if(mp_obj_is_small_int(element.value)){ - int key = mp_obj_get_int(element.value); - if(state.keyDown(static_cast(key))){ - m_sender->handleEventWithText(qstr_str(MP_OBJ_QSTR_VALUE(element.key)), true); - } +bool ToolboxIonKeys::handleEvent(Ion::Events::Event e) { + // FIXME: Use event data to get the pressed key and fix the EXE key. + Ion::Keyboard::State state = Ion::Keyboard::scan(); + for (uint16_t i = 0; i < sizeof(modion_module_globals_table) / sizeof(_mp_rom_map_elem_t); i++) { + _mp_rom_map_elem_t element = modion_module_globals_table[i]; + if (mp_obj_is_small_int(element.value)) { + int key = mp_obj_get_int(element.value); + if (state.keyDown(static_cast(key))) { + m_sender->handleEventWithText(qstr_str(MP_OBJ_QSTR_VALUE(element.key)), true); } } - Container::activeApp()->dismissModalViewController(); - AppsContainer::sharedAppsContainer()->redrawWindow(); - return true; } + Container::activeApp()->dismissModalViewController(); + AppsContainer::sharedAppsContainer()->redrawWindow(); + return true; +} - toolboxIonKeys::toolboxIonView::toolboxIonView(): - View() - { - } +ToolboxIonKeys::toolboxIonView::toolboxIonView() : + View() +{ +} - void toolboxIonKeys::toolboxIonView::drawRect(KDContext * ctx, KDRect rect) const { - ctx->fillRect(rect, Palette::WallScreen); - ctx->strokeRect(rect, Palette::ListCellBorder); - ctx->drawString(I18n::translate(I18n::Message::PressAKey),KDPoint(rect.left()+80, rect.top()+20),KDFont::LargeFont,Palette::PrimaryText,Palette::WallScreen); - - } +void ToolboxIonKeys::toolboxIonView::drawRect(KDContext * ctx, KDRect rect) const { + ctx->fillRect(rect, Palette::WallScreen); + ctx->strokeRect(rect, Palette::ListCellBorder); + KDSize fontSize = KDFont::LargeFont->glyphSize(); + const char * message = I18n::translate(I18n::Message::PressAKey); + // Get the starting position of the text to center it. + KDPoint textPosition = KDPoint(rect.size().width() / 2 - strlen(message) * fontSize.width() / 2, + rect.size().height() / 2 - fontSize.height() / 2); - View * toolboxIonKeys::view(){ - return &m_view; - } + ctx->drawString(message, textPosition, KDFont::LargeFont, Palette::PrimaryText, Palette::WallScreen); + +} + +View * ToolboxIonKeys::view() { + return &m_view; +} } diff --git a/apps/code/toolbox_ion_keys.h b/apps/code/toolbox_ion_keys.h index 4efe2036d9e..d6706ea3498 100644 --- a/apps/code/toolbox_ion_keys.h +++ b/apps/code/toolbox_ion_keys.h @@ -2,9 +2,9 @@ namespace Code { - class toolboxIonKeys : public ViewController { + class ToolboxIonKeys : public ViewController { public : - toolboxIonKeys(); + ToolboxIonKeys(); View * view() override; bool handleEvent(Ion::Events::Event e) override; void setSender(InputEventHandler * sender) { m_sender = sender; } From 0e24823511c7eabb428afd69443f191e9567c9eb Mon Sep 17 00:00:00 2001 From: Nithin <10778861+darthnithin@users.noreply.github.com> Date: Fri, 15 Apr 2022 09:50:17 -0700 Subject: [PATCH 231/355] Fix Readme Links (#210) --- README.fr.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.fr.md b/README.fr.md index 8177ddfb590..31850ab0691 100644 --- a/README.fr.md +++ b/README.fr.md @@ -26,7 +26,7 @@ Upsilon est un fork d'Omega, un fork d'Epsilon, l'OS de Numworks tournant sur le ## Installation ### Site web -Rendez-vous sur le [site d'Upsilon](https://lolocomotive.github.io/Upsilon-website) à la section "Installer". +Rendez-vous sur le [site d'Upsilon](https://getupsilon.web.app/) à la section "Installer". Si votre calculatrice est reconnue, qu'elle contient une version d'Epsilon inférieure à 16 et que votre navigateur accepte WebUSB, la page vous proposera d'installer Upsilon. Ne débranchez votre calculatrice qu'une fois l'installation terminée. diff --git a/README.md b/README.md index 592dbdbeee9..5cc9ed4a581 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Upsilon is a fork of Omega, an user-made OS that runs on the Numworks calculator ### Installer -Go to the [Upsilon website](https://lolocomotive.github.io/Upsilon-website) to the "Install" section. +Go to the [Upsilon website](https://getupsilon.web.app/) to the "Install" section. If your calculator is recognized, contains a version of Epsilon lower than 16 and your browser accepts WebUSB, the page will suggest you to install Upsilon. Do not disconnect your calculator until the installation is complete. From 22ba19054225f5ccf6dc85657ec030da418516f0 Mon Sep 17 00:00:00 2001 From: Laury Date: Wed, 23 Mar 2022 22:09:26 +0100 Subject: [PATCH 232/355] [calculation] Fix bug in calculation store --- apps/calculation/calculation_store.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/calculation/calculation_store.cpp b/apps/calculation/calculation_store.cpp index 72bc57d343a..f1bb4b940c8 100644 --- a/apps/calculation/calculation_store.cpp +++ b/apps/calculation/calculation_store.cpp @@ -173,7 +173,7 @@ void CalculationStore::realDeleteCalculationAtIndex(int i) { // Delete the oldest calculation in the store and returns the amount of space freed by the operation size_t CalculationStore::deleteOldestCalculation() { char * oldBufferEnd = (char *) m_calculationAreaEnd; - deleteCalculationAtIndex(numberOfCalculations()-1); + realDeleteCalculationAtIndex(numberOfCalculations()-1); char * newBufferEnd = (char *) m_calculationAreaEnd; return oldBufferEnd - newBufferEnd; } From 2bf6de50443bd99df56a11d76a409dbc51a83c57 Mon Sep 17 00:00:00 2001 From: Laury Date: Fri, 8 Apr 2022 19:03:13 +0200 Subject: [PATCH 233/355] [reader] Fixed bug when a word is too big --- apps/reader/TexParser.md | 2 +- apps/reader/base.de.i18n | 4 +- apps/reader/base.en.i18n | 4 +- apps/reader/base.es.i18n | 4 +- apps/reader/base.fr.i18n | 4 +- apps/reader/base.hu.i18n | 4 +- apps/reader/base.it.i18n | 4 +- apps/reader/base.nl.i18n | 4 +- apps/reader/base.pt.i18n | 4 +- apps/reader/read_book_controller.cpp | 15 ++- apps/reader/read_book_controller.h | 3 +- apps/reader/utility.cpp | 7 ++ apps/reader/word_wrap_view.cpp | 158 +++++++++++++++++++------ apps/reader/word_wrap_view.h | 5 +- build/utilities/translate.py | 56 ++++----- ion/include/ion/unicode/utf8_helper.h | 1 + ion/src/shared/unicode/utf8_helper.cpp | 14 +++ 17 files changed, 218 insertions(+), 75 deletions(-) diff --git a/apps/reader/TexParser.md b/apps/reader/TexParser.md index b530498cd35..2ff9e9865ad 100644 --- a/apps/reader/TexParser.md +++ b/apps/reader/TexParser.md @@ -1,6 +1,6 @@ # LaTeX Parser -In the reader app, you can read a txt file. You can also read a txt file with LaTeX expression inside of it. +In the reader app, you can read a txt file. You can also read a .urt file with LaTeX expression inside of it. All the symbols you can use are listed here : diff --git a/apps/reader/base.de.i18n b/apps/reader/base.de.i18n index 3dcf262d7a2..9cbb398970f 100644 --- a/apps/reader/base.de.i18n +++ b/apps/reader/base.de.i18n @@ -1,3 +1,5 @@ ReaderApp = "Leser" ReaderAppCapital = "LESER" -NoFileToDisplay = "Keine Dateien zum Anzeigen" \ No newline at end of file +NoFileToDisplay = "Keine Dateien zum Anzeigen" +FileError1 = "Fehler beim Lesen der Datei" +FileError2 = "Bitte überprüfen Sie die Syntax" diff --git a/apps/reader/base.en.i18n b/apps/reader/base.en.i18n index 0b0a2de2aef..2cb016526a4 100644 --- a/apps/reader/base.en.i18n +++ b/apps/reader/base.en.i18n @@ -1,3 +1,5 @@ ReaderApp = "Reader" ReaderAppCapital = "READER" -NoFileToDisplay = "No file to display" \ No newline at end of file +NoFileToDisplay = "No file to display" +FileError1 = "Error while reading file" +FileError2 = "Please check its syntax" diff --git a/apps/reader/base.es.i18n b/apps/reader/base.es.i18n index bcbe8f0cdea..76962dca3c4 100644 --- a/apps/reader/base.es.i18n +++ b/apps/reader/base.es.i18n @@ -1,3 +1,5 @@ ReaderApp = "Lector" ReaderAppCapital = "LECTOR" -NoFileToDisplay ="No hay archivos para mostrar" \ No newline at end of file +NoFileToDisplay ="No hay archivos para mostrar" +FileError1 = "Error al leer el archivo" +FileError2 = "Por favor revise su sintaxis" diff --git a/apps/reader/base.fr.i18n b/apps/reader/base.fr.i18n index f8b97e0364e..b74e6add0f5 100644 --- a/apps/reader/base.fr.i18n +++ b/apps/reader/base.fr.i18n @@ -1,3 +1,5 @@ ReaderApp = "Liseuse" ReaderAppCapital = "LISEUSE" -NoFileToDisplay = "Aucun fichier à afficher" \ No newline at end of file +NoFileToDisplay = "Aucun fichier à afficher" +FileError1 = "Erreur durant la lecture du fichier" +FileError2 = "Veuillez vérifier sa syntaxe" \ No newline at end of file diff --git a/apps/reader/base.hu.i18n b/apps/reader/base.hu.i18n index 09357ced763..9580dc40add 100644 --- a/apps/reader/base.hu.i18n +++ b/apps/reader/base.hu.i18n @@ -1,3 +1,5 @@ ReaderApp = "Olvasó" ReaderAppCapital = "OLVASÓ" -NoFileToDisplay = "Nincs megjeleníthető fájl" \ No newline at end of file +NoFileToDisplay = "Nincs megjeleníthető fájl" +FileError1 = "Hiba a fájl olvasása közben" +FileError2 = "Kérjük, ellenőrizze a szintaxisát" diff --git a/apps/reader/base.it.i18n b/apps/reader/base.it.i18n index c553356d9a6..abfa80eda75 100644 --- a/apps/reader/base.it.i18n +++ b/apps/reader/base.it.i18n @@ -1,3 +1,5 @@ ReaderApp = "Lettore" ReaderAppCapital = "LETTORE" -NoFileToDisplay = "essun file da visualizzare" \ No newline at end of file +NoFileToDisplay = "essun file da visualizzare" +FileError1 = "Errore durante la lettura del file" +FileError2 = "Si prega di controllare la sua sintassi" diff --git a/apps/reader/base.nl.i18n b/apps/reader/base.nl.i18n index 57675267376..9f8c2304f40 100644 --- a/apps/reader/base.nl.i18n +++ b/apps/reader/base.nl.i18n @@ -1,3 +1,5 @@ ReaderApp = "Lezer" ReaderAppCapital = "LEZER" -NoFileToDisplay = "Geen bestanden om weer te geven" \ No newline at end of file +NoFileToDisplay = "Geen bestanden om weer te geven" +FileError1 = "Fout tijdens het lezen van bestand" +FileError2 = "Controleer de syntaxis ervan" diff --git a/apps/reader/base.pt.i18n b/apps/reader/base.pt.i18n index 2aa88319b9c..0c2281287ca 100644 --- a/apps/reader/base.pt.i18n +++ b/apps/reader/base.pt.i18n @@ -1,3 +1,5 @@ ReaderApp = "Leitor" ReaderAppCapital = "LEITOR" -NoFileToDisplay = "Nenhum arquivo para exibir" \ No newline at end of file +NoFileToDisplay = "Nenhum arquivo para exibir" +FileError1 = "Erro ao ler o arquivo" +FileError2 = "Verifique sua sintaxe" diff --git a/apps/reader/read_book_controller.cpp b/apps/reader/read_book_controller.cpp index b80e6cdae7a..85ef0b77c23 100644 --- a/apps/reader/read_book_controller.cpp +++ b/apps/reader/read_book_controller.cpp @@ -1,10 +1,12 @@ #include "read_book_controller.h" +#include -namespace Reader +namespace Reader { ReadBookController::ReadBookController(Responder * parentResponder) : - ViewController(parentResponder) + ViewController(parentResponder), + m_readerView(this) { } @@ -18,7 +20,7 @@ void ReadBookController::setBook(const External::Archive::File& file, bool isRic m_readerView.setText(reinterpret_cast(file.data), file.dataLength, isRichTextFile); } -bool ReadBookController::handleEvent(Ion::Events::Event event) { +bool ReadBookController::handleEvent(Ion::Events::Event event) { if(event == Ion::Events::Down) { m_readerView.nextPage(); return true; @@ -34,6 +36,13 @@ void ReadBookController::viewDidDisappear() { savePosition(); } +void ReadBookController::throwError() { + static_cast(parentResponder())->pop(); + Container::activeApp()->displayWarning(I18n::Message::SyntaxError); + // As the error is thrown when we are drawing, me must redraw the whole screen + AppsContainer::sharedAppsContainer()->redrawWindow(); +} + void ReadBookController::savePosition() const { BookSave save = m_readerView.getBookSave(); diff --git a/apps/reader/read_book_controller.h b/apps/reader/read_book_controller.h index 642269d99ee..7fb42b16587 100644 --- a/apps/reader/read_book_controller.h +++ b/apps/reader/read_book_controller.h @@ -16,6 +16,7 @@ class ReadBookController : public ViewController { void viewDidDisappear() override; void savePosition() const; void loadPosition(); + void throwError(); private: WordWrapTextView m_readerView; const External::Archive::File* m_file; @@ -23,4 +24,4 @@ class ReadBookController : public ViewController { } -#endif \ No newline at end of file +#endif diff --git a/apps/reader/utility.cpp b/apps/reader/utility.cpp index 6ffc5820a21..d0f9e172c1c 100644 --- a/apps/reader/utility.cpp +++ b/apps/reader/utility.cpp @@ -129,6 +129,13 @@ const char * StartOfPrintableWord(const char * word, const char * start) { if (word == start) { return word; } + // Go to start of code point with some code points dark magic + if (!(*word & 0x80 == 0)) { + word--; + while (!(*word & 0x80 && *word & 0x40)) { + word--; + } + } UTF8Decoder decoder(start, word); CodePoint codePoint = decoder.previousCodePoint(); const char * result = word; diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index 367e2b03991..032b039ec08 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -5,17 +5,19 @@ #include #include "../shared/poincare_helpers.h" #include +#include "read_book_controller.h" namespace Reader { -WordWrapTextView::WordWrapTextView() : +WordWrapTextView::WordWrapTextView(ReadBookController * readBookController) : PointerTextView(GlobalPreferences::sharedGlobalPreferences()->font()), m_pageOffset(0), m_nextPageOffset(0), m_length(0), m_isRichTextFile(false), // Value isn't important, it will change when the file is loaded - m_lastPagesOffsetsIndex(0) + m_lastPagesOffsetsIndex(0), + m_readBookController(readBookController) { for (int i = 0; i < k_lastOffsetsBufferSize; i++) { m_lastPagesOffsets[i] = -1; // -1 Means : no informations @@ -96,7 +98,8 @@ void WordWrapTextView::richTextPreviousPage() { endOfWord = startOfWord - 1; // Update next endOfWord continue; } else { - // TODO: print error + m_readBookController->throwError(); + return; } } @@ -110,7 +113,7 @@ void WordWrapTextView::richTextPreviousPage() { if (expressionStart < text()) { break; // File isn't rightly formated } - expressionStart ++; + expressionStart --; } TexParser parser = TexParser(expressionStart, endOfWord); @@ -175,13 +178,16 @@ void WordWrapTextView::plainTextPreviousPage() { KDPoint textEndPosition(m_frame.width() - k_margin, m_frame.height() - k_margin); - while(startOfWord>=text()) { - startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); - endOfWord = UTF8Helper::EndOfWord(startOfWord); + while(endOfWord>text()) { KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); KDPoint textStartPosition = KDPoint(textEndPosition.x()-textSize.width(), textEndPosition.y()); if (textStartPosition.x() < k_margin) { + // We check if the word is too long to be displayed entirely on just one line + if(textSize.width() > m_frame.width() - 2 * k_margin) { + startOfWord = endOfWord - (textEndPosition.x() - k_margin) / charWidth; + continue; + } textEndPosition = KDPoint(m_frame.width() - k_margin, textEndPosition.y() - charHeight); textStartPosition = KDPoint(textEndPosition.x() - textSize.width(), textEndPosition.y()); } @@ -203,16 +209,23 @@ void WordWrapTextView::plainTextPreviousPage() { break; } - if (textStartPosition.y() != textEndPosition.y()) { // If line changed, x is at start of line + if (textStartPosition.y() != textEndPosition.y()) { // If line changed, x is at start of line textStartPosition = KDPoint(m_frame.width() - k_margin, textStartPosition.y()); } - if (textStartPosition.x() < k_margin) { // Go to line if left overflow + if (textStartPosition.x() <= k_margin) { // Go to line if left overflow textStartPosition = KDPoint(m_frame.width() - k_margin, textStartPosition.y() - charHeight); } textEndPosition = textStartPosition; endOfWord = startOfWord + 1; + startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); + } + + while (endOfWord < text() + m_length && (*endOfWord == ' ' || *endOfWord == '\n')) { + endOfWord++; } + + m_pageOffset = endOfWord - text(); } void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { @@ -242,11 +255,12 @@ void WordWrapTextView::richTextDrawRect(KDContext * ctx, KDRect rect) const { const int wordMaxLength = (m_frame.width() - 2*k_margin ) / charWidth; char word[wordMaxLength]; - Layout layout; + Layout tooBigLayout = EmptyLayout::Builder(); // To store layouts that are too big to be displayed entirely on one line + KDCoordinate tooBigLayoutAlreadyWroteWidth = 0; // We store here the part of the layout that has already been written KDPoint textPosition = KDPoint(k_margin, k_margin); - while (!endOfPage && startOfWord < endOfFile) { + while (!endOfPage && (startOfWord < endOfFile || !tooBigLayout.isEmpty())) { // We process line by line const char * firstReadIndex = startOfWord; @@ -256,8 +270,19 @@ void WordWrapTextView::richTextDrawRect(KDContext * ctx, KDRect rect) const { KDCoordinate baseline = charHeight / 2; while (firstReadIndex < endOfFile) { + // 1.0. We check if we are drawing a too big layout + if (!tooBigLayout.isEmpty()) { + lineSize = tooBigLayout.layoutSize(); + baseline = tooBigLayout.baseline(); + + if (tooBigLayout.layoutSize().width() - tooBigLayoutAlreadyWroteWidth > m_frame.width() - 2 * k_margin) { + // Remaining part of the layout don't fit on the line + break; + } + } KDSize textSize = KDSizeZero; + KDCoordinate updatedBaseline = 0; // 0 if it's not a layout // 1.1. And we check if we are at the end of the line if(*firstReadIndex == '\n') { @@ -265,12 +290,20 @@ void WordWrapTextView::richTextDrawRect(KDContext * ctx, KDRect rect) const { } // 1.2. Check if we are in a color change - if (*firstReadIndex == '%') { // We assume each '%' non-escaped is announcing a color change // TODO : check file is rightly formated + if (*firstReadIndex == '%') { // We assume each '%' non-escaped is announcing a color change // We go to the end of the color change + 1 + const char * startIndex = firstReadIndex; + do { firstReadIndex ++; - } while (*firstReadIndex != '%'); + } while (*firstReadIndex != '%' && firstReadIndex < endOfFile && startIndex - firstReadIndex < 5); firstReadIndex ++; + + if (firstReadIndex - startIndex > 5) { + m_readBookController->throwError(); + return; + } + continue; } @@ -286,16 +319,17 @@ void WordWrapTextView::richTextDrawRect(KDContext * ctx, KDRect rect) const { } TexParser parser = TexParser(expressionStart, firstReadIndex); - Layout layout = parser.getLayout(); + Layout firstReadLayout = parser.getLayout(); - KDCoordinate layoutBaseline = layout.baseline(); - // We check if we must change baseline + KDCoordinate layoutBaseline = firstReadLayout.baseline(); + + updatedBaseline = baseline; // We really update baseline after, if the layout fit on the line if (layoutBaseline > baseline) { - baseline = layoutBaseline; + updatedBaseline = layoutBaseline; } - KDSize layoutSize = layout.layoutSize(); - textSize = KDSize(layoutSize.width(), layoutSize.height() + baseline - layoutBaseline); + KDSize layoutSize = firstReadLayout.layoutSize(); + textSize = KDSize(layoutSize.width(), layoutSize.height() + updatedBaseline - layoutBaseline); firstReadIndex ++; } @@ -315,11 +349,16 @@ void WordWrapTextView::richTextDrawRect(KDContext * ctx, KDRect rect) const { // 1.5. We update size int newWidth = lineSize.width() + textSize.width(); - // We check if the new text fit on the line - if (newWidth > m_frame.width() - 2 * k_margin) { + // We check if the new text fit on the line. + // If not, we look if only the word cannot fit on one line. If so, we do not go to the line + if (newWidth > m_frame.width() - 2 * k_margin && !(textSize.width() > m_frame.width() - 2 * k_margin)) { break; } + if (updatedBaseline) { // Now we update baseline + baseline = updatedBaseline; + } + int newHeight; if (lineSize.height() > textSize.height()) { newHeight = lineSize.height(); @@ -345,7 +384,30 @@ void WordWrapTextView::richTextDrawRect(KDContext * ctx, KDRect rect) const { } // 2. And now... we read the line again to draw it ! - while (startOfWord < endOfFile) { + while (startOfWord < endOfFile || !tooBigLayout.isEmpty()) { + // 2.0 Before all, we check if we were drawing a layout that was too big to fit on one line. + // In this case, we do all the routine here. + if (!tooBigLayout.isEmpty()) { + KDPoint position = KDPoint(textPosition.x() - tooBigLayoutAlreadyWroteWidth, textPosition.y()); + tooBigLayout.draw(ctx, position, m_textColor, m_backgroundColor); + // We fill the left margin + ctx->fillRect(KDRect(0, textPosition.y(), k_margin, tooBigLayout.layoutSize().height()), m_backgroundColor); + + KDCoordinate drawnWidth = tooBigLayout.layoutSize().width() - tooBigLayoutAlreadyWroteWidth; + tooBigLayoutAlreadyWroteWidth += drawnWidth; + + if (drawnWidth > m_frame.width() - 2 * k_margin) { + // We have to fill the margin with the background color + ctx->fillRect(KDRect(textPosition.x() + drawnWidth, textPosition.y(), k_margin, lineSize.height()), m_backgroundColor); + textPosition = KDPoint(k_margin, textPosition.y() + lineSize.height()); + break; + } else { + tooBigLayout = EmptyLayout::Builder(); // We have finished drawing the tooBigLayout + } + continue; + } + + Layout layout; //2.1. We check if we are at the end of the line if (*startOfWord == '\n') { @@ -355,7 +417,6 @@ void WordWrapTextView::richTextDrawRect(KDContext * ctx, KDRect rect) const { // We aren't supposed to be at the end of the page, else the loop on top would have stopped drawing } - const char * endOfWord; // 2.2. Check if we are in a color change @@ -369,7 +430,8 @@ void WordWrapTextView::richTextDrawRect(KDContext * ctx, KDRect rect) const { continue; } else { - // TODO: Print exception + m_readBookController->throwError(); + return; } } @@ -412,14 +474,34 @@ void WordWrapTextView::richTextDrawRect(KDContext * ctx, KDRect rect) const { // 2.4.1. Check if we need to go to the next line if(endTextPosition.x() > m_frame.width() - k_margin) { - textPosition = KDPoint(k_margin, textPosition.y() + lineSize.height()); - break; + + // We check if the word is too long to be displayed entirely on just one line + if(textSize.width() > m_frame.width() - 2 * k_margin) { + if (toDraw == ToDraw::Text) { + startOfWord = endOfWord - (endTextPosition.x() - k_margin) / charWidth; + continue; + } else { + assert(toDraw == ToDraw::Expression); + tooBigLayout = layout; + tooBigLayoutAlreadyWroteWidth += m_frame.width() - k_margin - textPosition.x(); + endTextPosition = KDPoint(k_margin, textPosition.y() + lineSize.height()); // We jump line now, because this will be the next value of textPosition + } + } else { + textPosition = KDPoint(k_margin, textPosition.y() + lineSize.height()); + // endTextPosition will be updated later + break; + } } // 2.5. Now we draw ! if (toDraw == ToDraw::Expression) { KDPoint position = KDPoint(textPosition.x(), textPosition.y() + baseline - layout.baseline()); layout.draw(ctx, position, m_textColor, m_backgroundColor); + + if (!tooBigLayout.isEmpty()) { + // We fill the margin + ctx->fillRect(KDRect(m_frame.width() - k_margin, textPosition.y(), k_margin, lineSize.height()), m_backgroundColor); + } } else { KDPoint position = KDPoint(textPosition.x(), textPosition.y() + baseline - charHeight / 2); @@ -435,6 +517,11 @@ void WordWrapTextView::richTextDrawRect(KDContext * ctx, KDRect rect) const { textPosition = KDPoint(textPosition.x() + charWidth, textPosition.y()); } startOfWord = endOfWord; + + // 2.8. We exit now if we are in the "too big layout" case + if (!tooBigLayout.isEmpty()) { + break; + } } } m_nextPageOffset = startOfWord - text(); @@ -446,7 +533,7 @@ void WordWrapTextView::plainTextDrawRect(KDContext * ctx, KDRect rect) const { const char * endOfFile = text() + m_length; const char * startOfWord = text() + m_pageOffset; - const char * endOfWord = UTF8Helper::EndOfWord(startOfWord); + const char * endOfWord = UTF8Helper::EndOfWord(startOfWord, endOfFile); KDPoint textPosition(k_margin, k_margin); const int wordMaxLength = 128; @@ -458,8 +545,13 @@ void WordWrapTextView::plainTextDrawRect(KDContext * ctx, KDRect rect) const { while(startOfWord < endOfFile) { KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); KDPoint nextTextPosition = KDPoint(textPosition.x()+textSize.width(), textPosition.y()); - + if(nextTextPosition.x() > m_frame.width() - k_margin) { // Right overflow + // We check if the word is too long to be displayed entirely on just one line + if(textSize.width() > m_frame.width() - 2 * k_margin) { + endOfWord = startOfWord + (m_frame.width() - k_margin - textPosition.x()) / charWidth; + continue; + } textPosition = KDPoint(k_margin, textPosition.y() + textSize.height()); nextTextPosition = KDPoint(k_margin + textSize.width(), textPosition.y()); } @@ -488,15 +580,15 @@ void WordWrapTextView::plainTextDrawRect(KDContext * ctx, KDRect rect) const { if(nextTextPosition.y() + textSize.height() > m_frame.height() - k_margin) { // If out of page, quit break; } - if(nextTextPosition.y() != textPosition.y()) { // If line changed, x is at start of line + if(nextTextPosition.y() != textPosition.y()) { // If line changed, x is at start of line nextTextPosition = KDPoint(k_margin, nextTextPosition.y()); } - if(nextTextPosition.x() > m_frame.width() - k_margin) { // Go to line if right overflow + if(nextTextPosition.x() >= m_frame.width() - k_margin) { // Go to line if right overflow nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + textSize.height()); } textPosition = nextTextPosition; - endOfWord = UTF8Helper::EndOfWord(startOfWord); + endOfWord = UTF8Helper::EndOfWord(startOfWord, endOfFile); } m_nextPageOffset = startOfWord - text(); @@ -523,7 +615,7 @@ bool WordWrapTextView::updateTextColorForward(const char * colorStart) const { int keySize = 1; KDColor lastColor = m_textColor; - + switch (*(colorStart+1)) { case 'r': @@ -666,6 +758,6 @@ bool WordWrapTextView::updateTextColorBackward(const char * colorStart) const { } return true; -} +} } diff --git a/apps/reader/word_wrap_view.h b/apps/reader/word_wrap_view.h index 714f1a5f5f5..c50dba12674 100644 --- a/apps/reader/word_wrap_view.h +++ b/apps/reader/word_wrap_view.h @@ -12,9 +12,11 @@ struct BookSave { KDColor color; }; +class ReadBookController; + class WordWrapTextView : public PointerTextView { public: - WordWrapTextView(); + WordWrapTextView(ReadBookController * readBookController); void drawRect(KDContext * ctx, KDRect rect) const override; void setText(const char*, int length, bool isRichTextFile); void nextPage(); @@ -42,6 +44,7 @@ class WordWrapTextView : public PointerTextView { */ int m_lastPagesOffsets[k_lastOffsetsBufferSize]; int m_lastPagesOffsetsIndex; + ReadBookController * m_readBookController; }; } diff --git a/build/utilities/translate.py b/build/utilities/translate.py index 72f851e454c..1bfc533e62b 100755 --- a/build/utilities/translate.py +++ b/build/utilities/translate.py @@ -5,7 +5,7 @@ import re import sys import argparse - +from typing import Dict, List # Deep translator import try: from deep_translator import GoogleTranslator @@ -64,14 +64,14 @@ def translate(text: str, output_language: str, input_language: str = 'auto')\ return translator.translate(text) -def get_all_files_in_directory(directory: str) -> list[str]: +def get_all_files_in_directory(directory: str) -> List[str]: """Get all files in the given directory recursively. Args: directory (str): The directory Returns: - List[str]: The list of files in the directory + Liststr]: The list of files in the directory """ # Initialize the list of files @@ -92,7 +92,7 @@ def get_all_files_in_directory(directory: str) -> list[str]: return files -def get_i18n_files(directory: str = '.') -> dict[str, list[str]]: +def get_i18n_files(directory: str = '.') -> Dict[str, List[str]]: """Get the list of i18n files in the given directory. Args: @@ -100,13 +100,13 @@ def get_i18n_files(directory: str = '.') -> dict[str, list[str]]: Defaults to '.'. Returns: - dict[list[str]]: The list of i18n files in a dictionary of languages. + Dict[List[str]]: The list of i18n files in a dictionary of languages. """ # Get all files in the directory recursively files = get_all_files_in_directory(directory) # Initialize the dictionary - i18n_files_language: dict[str, list[str]] = {} + i18n_files_language: Dict[str, List[str]] = {} # Iterate over all files in the directory for i in files: # If the file is an i18n file @@ -125,21 +125,21 @@ def get_i18n_files(directory: str = '.') -> dict[str, list[str]]: return i18n_files_language -def need_to_be_translated(keys: dict[str, list[list[str]]])\ - -> dict[str, list[list[str]]]: +def need_to_be_translated(keys: Dict[str, List[List[str]]])\ + -> Dict[str, List[List[str]]]: """Return the key that needs to be translated by locale. Args: - keys (dict[str, list[str]]): The keys of the i18n files + keys (Dict[str, List[str]]): The keys of the i18n files Returns: - dict[str, list[str]]: The keys that needs to be translated, + Dict[str, List[str]]: The keys that needs to be translated, sorted by locale """ # Initialize the list of keys - keys_list: list[list[str]] = [] - keys_to_translate: dict[str, list[list[str]]] = {} + keys_list: List[List[str]] = [] + keys_to_translate: Dict[str, List[List[str]]] = {} # Iterate over all locales for value_ in keys.values(): # Iterate on keys of the locale @@ -151,14 +151,14 @@ def need_to_be_translated(keys: dict[str, list[list[str]]])\ keys_list.append(key) for locale, value in keys.items(): # Initialize the list of keys in the locale - keys_in_locale: list[str] = [i[0] for i in value] + keys_in_locale: List[str] = [i[0] for i in value] # Get the keys of keys that need to be translated - keys_to_translate_in_locale: list[list[str]] = [ + keys_to_translate_in_locale: List[List[str]] = [ key for key in keys_list if key[0] not in keys_in_locale ] # Remove duplicates from the list # Initialize the deduplicated list - keys_to_translate_in_locale_deduplicated: list[list[str]] = [] + keys_to_translate_in_locale_deduplicated: List[List[str]] = [] # Iterate over the duplicated list for item in keys_to_translate_in_locale: # If the key is not in the deduplicated list, add it @@ -170,14 +170,14 @@ def need_to_be_translated(keys: dict[str, list[list[str]]])\ return keys_to_translate -def get_keys_in_file(filename: str) -> list[list[str]]: +def get_keys_in_file(filename: str) -> List[List[str]]: """Return a list of keys in the file. Args: filename (str): The name of the file to read Returns: - list[str]: The keys in the file + List[str]: The keys in the file """ # Initialize the list of keys in the file @@ -211,19 +211,19 @@ def get_keys_in_file(filename: str) -> list[list[str]]: return keys -def list_keys(i18n_files: dict[str, list[str]]) -> dict[str, list[list[str]]]: +def list_keys(i18n_files: Dict[str, List[str]]) -> Dict[str, List[List[str]]]: """List all keys in the i18n files. Args: - i18n_files (dict[str, list[str]]): I18n files list + i18n_files (Dict[str, List[str]]): I18n files list Returns: - dict[str, list[str]]: The dictionnary of keys in the i18n files by + Dict[str, List[str]]: The dictionnary of keys in the i18n files by locale. """ # Initialize the list of keys in the i18n files - keys_dict: dict[str, list[list[str]]] = {} + keys_dict: Dict[str, List[List[str]]] = {} # Iterate on the locales for locale in i18n_files: # Initialize the dictionary for the locale @@ -294,22 +294,22 @@ def get_value_from_key(key_to_find: str, filename_generic: str, locale: str)\ return "" -def translate_missing_keys(keys_to_translate: dict[str, list[list[str]]], - input_language: str) -> dict[str, list[list[str]]]: +def translate_missing_keys(keys_to_translate: Dict[str, List[List[str]]], + input_language: str) -> Dict[str, List[List[str]]]: """Get a dictionary of file with the keys and translations to add. Args: - keys_to_translate (dict[str, list[list[str]]]): The list of keys to + keys_to_translate (Dict[str, List[List[str]]]): The list of keys to translate input_language (str): The language to get the text that will be translated Returns: - dict[str, list[str]]: The dictionary of files with translations + Dict[str, List[str]]: The dictionary of files with translations """ # Initialize the dictionary of translations - output: dict[str, list[list[str]]] = {} + output: Dict[str, List[List[str]]] = {} # Initialize the variable to store the number of translated keys keys_translated: int = 0 # Iterate over the locales of the dictionary of untranslated keys @@ -373,11 +373,11 @@ def translate_missing_keys(keys_to_translate: dict[str, list[list[str]]], return output -def save_translations(missing_keys_translation: dict[str, list[list[str]]]): +def save_translations(missing_keys_translation: Dict[str, List[List[str]]]): """Save the translations. Args: - missing_keys_translation (dict[str, list[list[str]]]): The dictionary + missing_keys_translation (Dict[str, List[List[str]]]): The dictionary of translations """ diff --git a/ion/include/ion/unicode/utf8_helper.h b/ion/include/ion/unicode/utf8_helper.h index ba00c195039..a3ce96df81f 100644 --- a/ion/include/ion/unicode/utf8_helper.h +++ b/ion/include/ion/unicode/utf8_helper.h @@ -120,6 +120,7 @@ size_t StringGlyphLength(const char * s, int maxSize = -1); const char * BeginningOfWord(const char * text, const char * word); // Returns the position of the first following char ' ', '\n' or 0 const char * EndOfWord(const char * word); +const char * EndOfWord(const char * word, const char * end); // On a line, count number of glyphs before and after locations void countGlyphsInLine(const char * text, int * before, int * after, const char * beforeLocation, const char *afterLocation = nullptr); diff --git a/ion/src/shared/unicode/utf8_helper.cpp b/ion/src/shared/unicode/utf8_helper.cpp index 9393069a6d2..0481df94134 100644 --- a/ion/src/shared/unicode/utf8_helper.cpp +++ b/ion/src/shared/unicode/utf8_helper.cpp @@ -475,6 +475,20 @@ const char * EndOfWord(const char * word) { return result; } +const char * EndOfWord(const char * word, const char * end) { + UTF8Decoder decoder(word); + CodePoint codePoint = decoder.nextCodePoint(); + const char * result = word; + while (!CodePointIsEndOfWord(codePoint)) { + result = decoder.stringPosition(); + if (result >= end) { + break; + } + codePoint = decoder.nextCodePoint(); + } + return result; +} + void countGlyphsInLine(const char * text, int * before, int * after, const char * beforeLocation, const char *afterLocation) { UTF8Helper::CodePointAction countGlyph = [](int, void * glyphCount, int, int) { int * castedCount = (int *) glyphCount; From d42e4269a9f8bf160aa421ec764b8ac3abf38b0d Mon Sep 17 00:00:00 2001 From: "Yaya.Cout" <67095734+Yaya-Cout@users.noreply.github.com> Date: Wed, 30 Mar 2022 21:34:54 +0200 Subject: [PATCH 234/355] [apps] Add Shift + Ans shortcut to go to the last application --- apps/apps_container.cpp | 22 ++++++++++++++++++++++ apps/apps_container.h | 3 +++ apps/apps_container_storage.cpp | 15 +++++++++++++++ apps/apps_container_storage.h | 1 + ion/include/ion/events.h | 1 + 5 files changed, 42 insertions(+) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index abb0c0a8825..2e8143c3d78 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -220,6 +220,7 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { return didProcessEvent || alphaLockWantsRedraw; } +// List of keys that are used to switch between apps, in order of app to go (eg. 0 : First App, 1 : Second App, 2 : Third App, ...) static constexpr Ion::Events::Event switch_events[] = { Ion::Events::ShiftSeven, Ion::Events::ShiftEight, Ion::Events::ShiftNine, Ion::Events::ShiftFour, Ion::Events::ShiftFive, Ion::Events::ShiftSix, @@ -231,14 +232,17 @@ bool AppsContainer::processEvent(Ion::Events::Event event) { // Warning: if the window is dirtied, you need to call window()->redraw() if (event == Ion::Events::USBPlug) { if (Ion::USB::isPlugged()) { + // If the exam mode is enabled, we ask to disable it, else, we enable USB if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { displayExamModePopUp(GlobalPreferences::ExamMode::Off); window()->redraw(); } else { Ion::USB::enable(); } + // Update brightness when USB is plugged Ion::Backlight::setBrightness(GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()); } else { + // If the USB isn't plugged in USBPlug event, we disable USB Ion::USB::disable(); } return true; @@ -269,20 +273,38 @@ bool AppsContainer::processEvent(Ion::Events::Event event) { return true; } + // Add Shift + Ans shortcut to go to the previous app + if (event == Ion::Events::ShiftAns) { + switchTo(appSnapshotAtIndex(m_lastAppIndex)); + return true; + } + + // If the event is the OnOff key, we suspend the calculator. if (event == Ion::Events::OnOff) { suspend(true); return true; } + // If the event is a brightness event, we update the brightness according to the event. if (event == Ion::Events::BrightnessPlus || event == Ion::Events::BrightnessMinus) { int delta = Ion::Backlight::MaxBrightness/GlobalPreferences::NumberOfBrightnessStates; int NumberOfStepsPerShortcut = GlobalPreferences::sharedGlobalPreferences()->brightnessShortcut(); int direction = (event == Ion::Events::BrightnessPlus) ? NumberOfStepsPerShortcut*delta : -delta*NumberOfStepsPerShortcut; GlobalPreferences::sharedGlobalPreferences()->setBrightnessLevel(GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()+direction); } + // Else, the event was not processed. return false; } bool AppsContainer::switchTo(App::Snapshot * snapshot) { + // Get app index of the snapshot + int m_appIndexToSwitch = appIndexFromSnapshot(snapshot); + // If the app is home, skip app index saving + if (m_appIndexToSwitch != 0) { + // Save last app index + m_lastAppIndex = m_currentAppIndex; + // Save current app index + m_currentAppIndex = m_appIndexToSwitch; + } if (s_activeApp && snapshot != s_activeApp->snapshot()) { resetShiftAlphaStatus(); } diff --git a/apps/apps_container.h b/apps/apps_container.h index f52d50b6c50..07a642aee14 100644 --- a/apps/apps_container.h +++ b/apps/apps_container.h @@ -28,6 +28,7 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag static bool poincareCircuitBreaker(); virtual int numberOfApps() = 0; virtual App::Snapshot * appSnapshotAtIndex(int index) = 0; + virtual int appIndexFromSnapshot(App::Snapshot * snapshot) = 0; App::Snapshot * initialAppSnapshot(); App::Snapshot * hardwareTestAppSnapshot(); App::Snapshot * onBoardingAppSnapshot(); @@ -69,6 +70,8 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag static KDColor k_promptFGColors[]; static KDColor k_promptBGColors[]; static int k_promptNumberOfMessages; + int m_currentAppIndex; // Home isn't included after the second app switching + int m_lastAppIndex; AppsWindow m_window; EmptyBatteryWindow m_emptyBatteryWindow; Shared::GlobalContext m_globalContext; diff --git a/apps/apps_container_storage.cpp b/apps/apps_container_storage.cpp index 2ef60aafa5c..1a883d4844d 100644 --- a/apps/apps_container_storage.cpp +++ b/apps/apps_container_storage.cpp @@ -36,3 +36,18 @@ App::Snapshot * AppsContainerStorage::appSnapshotAtIndex(int index) { assert(index >= 0 && index < k_numberOfCommonApps); return snapshots[index]; } + +// Get the index of the app from its snapshot +int AppsContainerStorage::appIndexFromSnapshot(App::Snapshot * snapshot) { + App::Snapshot * snapshots[] = { + homeAppSnapshot() + APPS_CONTAINER_SNAPSHOT_LIST + }; + assert(sizeof(snapshots)/sizeof(snapshots[0]) == k_numberOfCommonApps); + for (int i = 0; i < k_numberOfCommonApps; i++) { + if (snapshots[i] == snapshot) { + return i; + } + } + return NULL; +} \ No newline at end of file diff --git a/apps/apps_container_storage.h b/apps/apps_container_storage.h index 9abd3c27ad4..c3191a751da 100644 --- a/apps/apps_container_storage.h +++ b/apps/apps_container_storage.h @@ -12,6 +12,7 @@ class AppsContainerStorage : public AppsContainer { AppsContainerStorage(); int numberOfApps() override; App::Snapshot * appSnapshotAtIndex(int index) override; + int appIndexFromSnapshot(App::Snapshot * snapshot) override; void * currentAppBuffer() override { return &m_apps; }; private: union Apps { diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index 51c8da397b7..e7ae2feb2b1 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -185,6 +185,7 @@ constexpr Event ShiftSeven = Event::ShiftKey(Keyboard::Key::Seven); constexpr Event ShiftEight = Event::ShiftKey(Keyboard::Key::Eight); constexpr Event ShiftNine = Event::ShiftKey(Keyboard::Key::Nine); +constexpr Event ShiftAns = Event::ShiftKey(Keyboard::Key::Ans); constexpr Event ShiftEXE = Event::ShiftKey(Keyboard::Key::EXE); // Alpha From c54a97a03dc21eb35e762a0bcf5dd1fe575c19aa Mon Sep 17 00:00:00 2001 From: "Yaya.Cout" <67095734+Yaya-Cout@users.noreply.github.com> Date: Thu, 31 Mar 2022 16:21:23 +0200 Subject: [PATCH 235/355] [apps/apps_container_storage.cpp] Add newline --- apps/apps_container_storage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/apps_container_storage.cpp b/apps/apps_container_storage.cpp index 1a883d4844d..25953f269bc 100644 --- a/apps/apps_container_storage.cpp +++ b/apps/apps_container_storage.cpp @@ -50,4 +50,4 @@ int AppsContainerStorage::appIndexFromSnapshot(App::Snapshot * snapshot) { } } return NULL; -} \ No newline at end of file +} From 4b7631e78a0984ba4da09fdf903747d7b05f1f7b Mon Sep 17 00:00:00 2001 From: "Yaya.Cout" <67095734+Yaya-Cout@users.noreply.github.com> Date: Thu, 31 Mar 2022 16:23:35 +0200 Subject: [PATCH 236/355] [apps/apps_container_storage.cpp] Add an assert --- apps/apps_container_storage.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/apps_container_storage.cpp b/apps/apps_container_storage.cpp index 25953f269bc..ad2dec6197c 100644 --- a/apps/apps_container_storage.cpp +++ b/apps/apps_container_storage.cpp @@ -49,5 +49,7 @@ int AppsContainerStorage::appIndexFromSnapshot(App::Snapshot * snapshot) { return i; } } + // Achievement unlock : how did you get here ? + assert(false); return NULL; } From 38796253cb4dcf0b72e8cf423fb813fa06b631fc Mon Sep 17 00:00:00 2001 From: Laury Date: Fri, 15 Apr 2022 22:30:35 +0200 Subject: [PATCH 237/355] [external] First attempt to merge Khi external API --- apps/external/archive.cpp | 6 +- apps/external/archive.h | 1 + apps/external/extapp_api.cpp | 142 +++++++++++++++++++++++------------ apps/external/extapp_api.h | 4 + ion/include/ion.h | 1 + ion/include/ion/flash.h | 22 ++++++ 6 files changed, 126 insertions(+), 50 deletions(-) create mode 100644 ion/include/ion/flash.h diff --git a/apps/external/archive.cpp b/apps/external/archive.cpp index 8f3d000bf7c..1fdceae2ed4 100644 --- a/apps/external/archive.cpp +++ b/apps/external/archive.cpp @@ -74,6 +74,8 @@ bool fileAtIndex(size_t index, File &entry) { entry.data = reinterpret_cast(tar) + sizeof(TarHeader); entry.dataLength = size; entry.isExecutable = (tar->mode[4] & 0x01) == 1; + // TODO: Handle the trash + entry.readable = true; return true; } else { @@ -115,7 +117,7 @@ int indexFromName(const char *name) { File entry; for (int i = 0; fileAtIndex(i, entry); i++) { - if (strcmp(name, entry.name) == 0) { + if (entry.readable && strcmp(name, entry.name) == 0) { return i; } } @@ -144,6 +146,7 @@ bool executableAtIndex(size_t index, File &entry) { entry.data = dummy.data; entry.dataLength = dummy.dataLength; entry.isExecutable = dummy.isExecutable; + entry.readable = dummy.readable; return true; } final_count++; @@ -177,6 +180,7 @@ bool fileAtIndex(size_t index, File &entry) { entry.data = NULL; entry.dataLength = 0; entry.isExecutable = true; + entry.readable = true; return true; } diff --git a/apps/external/archive.h b/apps/external/archive.h index 670bd138f42..de96e6d204f 100644 --- a/apps/external/archive.h +++ b/apps/external/archive.h @@ -14,6 +14,7 @@ struct File { const uint8_t *data; size_t dataLength; bool isExecutable; + bool readable; }; bool fileAtIndex(size_t index, File &entry); diff --git a/apps/external/extapp_api.cpp b/apps/external/extapp_api.cpp index eefd19f5810..fd408ab51b1 100644 --- a/apps/external/extapp_api.cpp +++ b/apps/external/extapp_api.cpp @@ -30,7 +30,7 @@ uint64_t extapp_scanKeyboard() { void extapp_pushRect(int16_t x, int16_t y, uint16_t w, uint16_t h, const uint16_t * pixels) { KDRect rect(x, y, w, h); - Ion::Display::pushRect(rect, reinterpret_cast(pixels)); + Ion::Display::pushRect(rect, reinterpret_cast(pixels)); } void extapp_pushRectUniform(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t color) { @@ -45,7 +45,7 @@ void extapp_pullRect(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t * pi Ion::Display::pullRect(rect, (KDColor *) pixels); } -int16_t extapp_drawTextLarge(const char *text, int16_t x, int16_t y, uint16_t fg, uint16_t bg, bool fake) { +int16_t extapp_drawTextLarge(const char * text, int16_t x, int16_t y, uint16_t fg, uint16_t bg, bool fake) { KDPoint point(x, y); auto ctx = KDIonContext::sharedContext(); @@ -56,7 +56,7 @@ int16_t extapp_drawTextLarge(const char *text, int16_t x, int16_t y, uint16_t fg return point.x(); } -int16_t extapp_drawTextSmall(const char *text, int16_t x, int16_t y, uint16_t fg, uint16_t bg, bool fake) { +int16_t extapp_drawTextSmall(const char * text, int16_t x, int16_t y, uint16_t fg, uint16_t bg, bool fake) { KDPoint point(x, y); auto ctx = KDIonContext::sharedContext(); @@ -71,7 +71,7 @@ bool extapp_waitForVBlank() { return Ion::Display::waitForVBlank(); } -void extapp_clipboardStore(const char *text) { +void extapp_clipboardStore(const char * text) { Clipboard::sharedClipboard()->store(text); } @@ -79,87 +79,93 @@ const char * extapp_clipboardText() { return Clipboard::sharedClipboard()->storedText(); } +bool match(const char * filename, const char * extension) { + return strcmp(filename + strlen(filename) - strlen(extension), extension) == 0; +} + int extapp_fileListWithExtension(const char ** filenames, int maxrecords, const char * extension, int storage) { - if(storage == EXTAPP_RAM_FILE_SYSTEM) { + int numberOfFilesWithExtension = 0; + if (storage == EXTAPP_RAM_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { int n = Ion::Storage::sharedStorage()->numberOfRecordsWithExtension(extension); if (n > maxrecords) { n = maxrecords; } - for(int i = 0; i < n; i++) { - filenames[i] = Ion::Storage::sharedStorage()->recordWithExtensionAtIndex(extension, i).fullName(); + for (; numberOfFilesWithExtension < n; numberOfFilesWithExtension++) { + filenames[numberOfFilesWithExtension] = Ion::Storage::sharedStorage()->recordWithExtensionAtIndex(extension, numberOfFilesWithExtension).fullName(); } - return n; - } else if(storage == EXTAPP_FLASH_FILE_SYSTEM) { - // TODO: filter by extension - int n = External::Archive::numberOfFiles(); - if (n > maxrecords) { - n = maxrecords; + if (numberOfFilesWithExtension == maxrecords) { + return numberOfFilesWithExtension; } - for(int i = 0; i < n; i++) { + } + // Don't read external files the exam mode is enabled + if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) return numberOfFilesWithExtension; + if (storage == EXTAPP_FLASH_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { + // Filter by extension + int n = External::Archive::numberOfFiles(); + for (int i = 0; i < n && numberOfFilesWithExtension < maxrecords; i++) { External::Archive::File entry; - External::Archive::fileAtIndex(i, entry); - filenames[i] = entry.name; + if (External::Archive::fileAtIndex(i, entry) && match(entry.name, extension)) { + filenames[numberOfFilesWithExtension] = entry.name; + ++numberOfFilesWithExtension; + } } - return n; - } else { - return 0; } + return numberOfFilesWithExtension; } bool extapp_fileExists(const char * filename, int storage) { - if(storage == EXTAPP_RAM_FILE_SYSTEM) { - return !Ion::Storage::sharedStorage()->recordNamed(filename).isNull(); - } else if(storage == EXTAPP_FLASH_FILE_SYSTEM) { + if (storage == EXTAPP_RAM_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { + if (!Ion::Storage::sharedStorage()->recordNamed(filename).isNull()) { + return true; + } + } + if (storage == EXTAPP_FLASH_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { return External::Archive::indexFromName(filename) >= 0; - } else { - return false; } + return false; } bool extapp_fileErase(const char * filename, int storage) { - if(storage == EXTAPP_RAM_FILE_SYSTEM) { + if (storage == EXTAPP_RAM_FILE_SYSTEM) { Ion::Storage::Record record = Ion::Storage::sharedStorage()->recordNamed(filename); - if(record.isNull()) { + if (record.isNull()) { return false; } else { record.destroy(); return true; } - } else { - return false; } + return false; } -const char * extapp_fileRead(const char * filename, size_t *len, int storage) { - if(storage == EXTAPP_RAM_FILE_SYSTEM) { +const char * extapp_fileRead(const char * filename, size_t * len, int storage) { + if (storage == EXTAPP_RAM_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { const Ion::Storage::Record record = Ion::Storage::sharedStorage()->recordNamed(filename); - if (record.isNull()) { - return NULL; - } else { - if(len) { + if (!record.isNull()) { + if (len) { *len = record.value().size; } return (const char *) record.value().buffer; } - } else if(storage == EXTAPP_FLASH_FILE_SYSTEM) { + } + // Don't read external files the exam mode is enabled + if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) return NULL; + if (storage == EXTAPP_FLASH_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { int index = External::Archive::indexFromName(filename); if (index >= 0) { External::Archive::File entry; External::Archive::fileAtIndex(index, entry); - if(len) { + if (len) { *len = entry.dataLength; } - return (const char *)entry.data; - } else { - return NULL; + return (const char *) entry.data; } - } else { - return NULL; } + return NULL; } bool extapp_fileWrite(const char * filename, const char * content, size_t len, int storage) { - if(storage == EXTAPP_RAM_FILE_SYSTEM) { + if (storage == EXTAPP_RAM_FILE_SYSTEM) { Ion::Storage::Record::ErrorStatus status = Ion::Storage::sharedStorage()->createRecordWithFullName(filename, content, len); if (status == Ion::Storage::Record::ErrorStatus::NameTaken) { Ion::Storage::Record::Data data; @@ -168,12 +174,9 @@ bool extapp_fileWrite(const char * filename, const char * content, size_t len, i return Ion::Storage::sharedStorage()->recordNamed(filename).setValue(data) == Ion::Storage::Record::ErrorStatus::None; } else if (status == Ion::Storage::Record::ErrorStatus::None) { return true; - } else { - return false; } - } else { - return false; } + return false; } static void reloadTitleBar() { @@ -238,18 +241,18 @@ const int16_t translated_keys[] = '?','!',KEY_CHAR_EXPN10,KEY_CHAR_ANS,KEY_CTRL_EXE,-1, }; -#ifdef SIMULATOR +#ifdef ION_SIMULATOR_FILES #define TICKS_PER_MINUTE 60000 #else #define TICKS_PER_MINUTE 11862 #endif -int extapp_getKey(bool allowSuspend, bool *alphaWasActive) { +int extapp_getKey(bool allowSuspend, bool * alphaWasActive) { int key = -1; size_t t1 = Ion::Timing::millis(); for (;;) { int timeout = 10000; - if(alphaWasActive) { + if (alphaWasActive) { *alphaWasActive = Ion::Events::isAlphaActive(); } Ion::Events::Event event = Ion::Events::getEvent(&timeout); @@ -285,6 +288,43 @@ int extapp_getKey(bool allowSuspend, bool *alphaWasActive) { return translated_keys[key]; } +int extapp_restoreBackup(int mode) { + return 0; +} + +bool isKeydown(int k) { + Ion::Keyboard::State scan = Ion::Keyboard::scan(); + return scan.keyDown(Ion::Keyboard::Key(k)); +} + +bool extapp_eraseSector(void * ptr) { +#ifdef DEVICE + int i = Ion::Device::Flash::SectorAtAddress((size_t) ptr); + if (i < 0) + return false; + Ion::Device::Flash::EraseSector(i); + return true; +#else + return false; +#endif +} + +bool extapp_writeMemory(unsigned char * dest, const unsigned char * data, size_t length) { +#ifdef DEVICE + int n = Ion::Device::Flash::SectorAtAddress((uint64_t) dest); + if (n < 0) + return false; + Ion::Device::Flash::WriteMemory(dest, (unsigned char *) data, length); + return true; +#else + return false; +#endif +} + +bool extapp_inExamMode() { + return GlobalPreferences::sharedGlobalPreferences()->isInExamMode(); +} + extern "C" void (* const apiPointers[])(void) = { (void (*)(void)) extapp_millis, (void (*)(void)) extapp_msleep, @@ -305,5 +345,9 @@ extern "C" void (* const apiPointers[])(void) = { (void (*)(void)) extapp_lockAlpha, (void (*)(void)) extapp_resetKeyboard, (void (*)(void)) extapp_getKey, + (void (*)(void)) extapp_restoreBackup, + (void (*)(void)) extapp_eraseSector, + (void (*)(void)) extapp_writeMemory, + (void (*)(void)) extapp_inExamMode, (void (*)(void)) nullptr, }; diff --git a/apps/external/extapp_api.h b/apps/external/extapp_api.h index b17b8643673..cc950fd7a94 100644 --- a/apps/external/extapp_api.h +++ b/apps/external/extapp_api.h @@ -18,6 +18,7 @@ #define EXTAPP_RAM_FILE_SYSTEM 0 #define EXTAPP_FLASH_FILE_SYSTEM 1 +#define EXTAPP_BOTH_FILE_SYSTEM 2 #define SCANCODE_Left ((uint64_t)1 << 0) #define SCANCODE_Up ((uint64_t)1 << 1) @@ -252,5 +253,8 @@ EXTERNC bool extapp_fileWrite(const char * filename, const char * content, size_ EXTERNC void extapp_lockAlpha(); EXTERNC void extapp_resetKeyboard(); EXTERNC int extapp_getKey(bool allowSuspend, bool *alphaWasActive); +EXTERNC int extapp_restoreBackup(int mode); // Keep for compatibility with KhiCAS on Khi, but it do nothing +EXTERNC bool extapp_eraseSector(void * ptr); +EXTERNC bool extapp_writeMemory(unsigned char * dest,const unsigned char * data,size_t length); #endif diff --git a/ion/include/ion.h b/ion/include/ion.h index e6072db2686..af4c55a3489 100644 --- a/ion/include/ion.h +++ b/ion/include/ion.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/ion/include/ion/flash.h b/ion/include/ion/flash.h new file mode 100644 index 00000000000..4e61b1fa912 --- /dev/null +++ b/ion/include/ion/flash.h @@ -0,0 +1,22 @@ +#ifndef ION_DEVICE_SHARED_FLASH_H +#define ION_DEVICE_SHARED_FLASH_H + +#include +#include + +namespace Ion { +namespace Device { +namespace Flash { + +int TotalNumberOfSectors(); +int SectorAtAddress(uint32_t address); + +void MassErase(); +void EraseSector(int i); +void WriteMemory(uint8_t * destination, uint8_t * source, size_t length); + +} +} +} + +#endif From 77352e34b52c391fe50774e81b4e3862d79f4cfa Mon Sep 17 00:00:00 2001 From: Laury Date: Fri, 15 Apr 2022 22:30:53 +0200 Subject: [PATCH 238/355] [storage] Possibility to store metadata with records (cursor in scripts) --- apps/code/editor_controller.cpp | 8 +- apps/code/script.cpp | 15 +++ apps/code/script.h | 2 + ion/include/ion/internal_storage.h | 24 ++++- ion/include/ion/storage.h | 1 + ion/src/shared/internal_storage.cpp | 152 ++++++++++++++++++++++++++-- ion/src/shared/storage.cpp | 2 +- 7 files changed, 195 insertions(+), 9 deletions(-) diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index be09c9662b8..d286b92cf2f 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -65,13 +65,19 @@ void EditorController::viewWillAppear() { ViewController::viewWillAppear(); m_editorView.loadSyntaxHighlighter(); if(GlobalPreferences::sharedGlobalPreferences()->cursorSaving()) { - m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); + int offset = m_script.cursorOffset(); + if (offset != -1) { + m_editorView.setCursorLocation(m_editorView.text() + offset); + } else { + m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); + } } else { m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); } } void EditorController::viewDidDisappear() { + m_script.setCursorOffset(m_editorView.cursorLocation() - m_script.content()); m_editorView.resetSelection(); m_menuController->scriptContentEditionDidFinish(); } diff --git a/apps/code/script.cpp b/apps/code/script.cpp index 4b39b345281..3b73d54bd7e 100644 --- a/apps/code/script.cpp +++ b/apps/code/script.cpp @@ -65,6 +65,21 @@ bool Script::nameCompliant(const char * name) { return false; } +uint16_t Script::cursorOffset() { + assert(!isNull()); + Ion::Storage::MetadataRowHeader * metadataForRecord = Ion::Storage::sharedStorage()->metadataForRecord(*this); + if (metadataForRecord != nullptr) { + assert(metadataForRecord->metadataSize == 2); + return *((uint16_t*) metadataForRecord->data()); + } + + return -1; +} +void Script::setCursorOffset(uint16_t position) { + assert(!isNull()); + Ion::Storage::sharedStorage()->setMetadataForRecord(*this, sizeof(uint16_t), &position); +} + uint8_t * StatusFromData(Script::Data d) { return const_cast(static_cast(d.buffer)); } diff --git a/apps/code/script.h b/apps/code/script.h index 6f7df9cdae7..ffa8038bc14 100644 --- a/apps/code/script.h +++ b/apps/code/script.h @@ -50,6 +50,8 @@ class Script : public Ion::Storage::Record { void toggleAutoimportationStatus(); const char * content() const; size_t contentSize() { return value().size - k_statusSize; } + void setCursorOffset(uint16_t position); // -1 if no metadata + uint16_t cursorOffset(); /* Fetched status */ bool fetchedFromConsole() const; diff --git a/ion/include/ion/internal_storage.h b/ion/include/ion/internal_storage.h index 07287cf4090..933877edb82 100644 --- a/ion/include/ion/internal_storage.h +++ b/ion/include/ion/internal_storage.h @@ -64,6 +64,7 @@ class InternalStorage { return m_fullNameCRC32 == 0; } const char * fullName() const; + uint32_t fullNameCRC32() const { return m_fullNameCRC32; } ErrorStatus setBaseNameWithExtension(const char * baseName, const char * extension); ErrorStatus setName(const char * fullName); Data value() const; @@ -74,11 +75,19 @@ class InternalStorage { uint32_t m_fullNameCRC32; }; + struct MetadataRowHeader { // In fact, it's a struct with a method to get data + public: + char * data() const { return (char *) this + sizeof(MetadataRowHeader); } + uint32_t size() const { return sizeof(MetadataRowHeader) + metadataSize; } + uint32_t fullNameCRC32; + uint32_t metadataSize; // To fullfill alignment + MetadataRowHeader * nextRow; + }; #if ION_STORAGE_LOG void log(); #endif - size_t availableSize(); + size_t availableSize(char ** endBufferReturn = nullptr); size_t putAvailableSpaceAtEndOfRecord(Record r); void getAvailableSpaceFromEndOfRecord(Record r, size_t recordAvailableSpace); uint32_t checksum(); @@ -114,6 +123,11 @@ class InternalStorage { // Used by Python OS module int numberOfRecords(); Record recordAtIndex(int index); + + // Metadata + MetadataRowHeader * metadataForRecord(Record r); + bool setMetadataForRecord(Record r, int size, const void * metadata); + void removeMetadataForRecord(Record r); protected: InternalStorage(); /* Getters on address in buffer */ @@ -123,6 +137,13 @@ class InternalStorage { const void * valueOfRecordStarting(char * start) const; void destroyRecord(const Record record); + struct MetadataMapHeader { + char * startOfMetadataMap() { return (char *) this - metadataMapSize + sizeof(MetadataMapHeader); } + uint8_t metadataMapSize; + uint8_t numberOfRows; + MetadataRowHeader * firstRow; + }; + class RecordIterator { public: RecordIterator(char * start) : m_recordStart(start) {} @@ -142,6 +163,7 @@ class InternalStorage { mutable Record m_lastRecordRetrieved; mutable char * m_lastRecordRetrievedPointer; + MetadataMapHeader * m_metadataMapHeader; private: constexpr static uint32_t Magic = 0xEE0BDDBA; constexpr static size_t k_maxRecordSize = (1 << sizeof(record_size_t)*8); diff --git a/ion/include/ion/storage.h b/ion/include/ion/storage.h index 5385f4608d3..300e77d39e5 100644 --- a/ion/include/ion/storage.h +++ b/ion/include/ion/storage.h @@ -8,6 +8,7 @@ namespace Ion { class Storage : public InternalStorage { public: using InternalStorage::Record; + using InternalStorage::MetadataRowHeader; using InternalStorage::expExtension; using InternalStorage::funcExtension; diff --git a/ion/src/shared/internal_storage.cpp b/ion/src/shared/internal_storage.cpp index 5fc4a90d10a..eade3a611b8 100644 --- a/ion/src/shared/internal_storage.cpp +++ b/ion/src/shared/internal_storage.cpp @@ -112,11 +112,138 @@ void InternalStorage::log() { } #endif -size_t InternalStorage::availableSize() { - /* TODO maybe do: availableSize(char ** endBuffer) to get the endBuffer if it - * is needed after calling availableSize */ - assert(k_storageSize >= (endBuffer() - m_buffer) + sizeof(record_size_t)); - return k_storageSize-(endBuffer()-m_buffer)-sizeof(record_size_t); +InternalStorage::MetadataRowHeader * InternalStorage::metadataForRecord(Record r) { + MetadataRowHeader * header = m_metadataMapHeader->firstRow; + for (int i = 0; i < m_metadataMapHeader->numberOfRows; i++) { + if (header->fullNameCRC32 == r.fullNameCRC32()) { + return header; + } + header = header->nextRow; + } + + return nullptr; +} + +void InternalStorage::removeMetadataForRecord(Record r) { + if (r.isNull()) { + return; + } + + MetadataRowHeader ** rowPointer = &m_metadataMapHeader->firstRow; + MetadataRowHeader * headerToRemove = nullptr; + size_t headerToRemoveSize = 0; // We compute it now as it will be more difficult later + for (int i = 0; i < m_metadataMapHeader->numberOfRows; i++) { + if ((*rowPointer)->fullNameCRC32 == r.fullNameCRC32()) { + headerToRemove = *rowPointer; + headerToRemoveSize = headerToRemove->size(); + if ((*rowPointer)->nextRow != nullptr) { + *rowPointer = (MetadataRowHeader *) ((char *) (*rowPointer)->nextRow + headerToRemove->size()); + } else { + *rowPointer = nullptr; + } + break; + } + + rowPointer = &(*rowPointer)->nextRow; + } + + if (headerToRemove == nullptr) { + return; + } + + MetadataRowHeader * header = headerToRemove->nextRow; + if (header != nullptr) { + while (header->nextRow) { + MetadataRowHeader * nextRow = header->nextRow; + header->nextRow = (MetadataRowHeader *) ((char *) header->nextRow +headerToRemoveSize); + header = nextRow; + } + } + + char * startOfMetadataMap = m_metadataMapHeader->startOfMetadataMap(); + uint8_t sizeToMove = (char *) headerToRemove - startOfMetadataMap; + + memmove(startOfMetadataMap + headerToRemoveSize, startOfMetadataMap, sizeToMove); + m_metadataMapHeader->numberOfRows--; + m_metadataMapHeader->metadataMapSize -= headerToRemoveSize; +} + +bool InternalStorage::setMetadataForRecord(Record r, int size, const void * metadata) { + int neededSize = 0; + char * endBufferPointer = nullptr; + MetadataRowHeader * headerToUpdate = nullptr; + MetadataRowHeader ** headerToUpdatePointer = nullptr; + int headerToUpdateIndex = -1; + + // We find the metadata row header for this record + MetadataRowHeader ** headerPointer = &m_metadataMapHeader->firstRow; + for (int i = 0; i < m_metadataMapHeader->numberOfRows; i++) { + if ((*headerPointer)->fullNameCRC32 == r.fullNameCRC32()) { + neededSize = size - (*headerPointer)->metadataSize; + headerToUpdate = (*headerPointer); + headerToUpdatePointer = headerPointer; + headerToUpdateIndex = i; + if (neededSize > 0 && neededSize > availableSize(&endBufferPointer)) { + return false; + } + break; + } + + headerPointer = &((*headerPointer)->nextRow); + } + + char * startOfMetadataMap = m_metadataMapHeader->startOfMetadataMap(); // Me must compute it now because it may change + + if (headerToUpdate == nullptr) { // If we didn't find a header, we need to create one + if (size != 0) { + uint8_t newRowSize = sizeof(MetadataRowHeader) + size; + + if (endBufferPointer < m_buffer + k_storageSize - m_metadataMapHeader->metadataMapSize - newRowSize) { + m_metadataMapHeader->numberOfRows++; + m_metadataMapHeader->metadataMapSize += newRowSize; + headerToUpdate = (MetadataRowHeader *) ((char *) startOfMetadataMap - newRowSize); + headerToUpdate->fullNameCRC32 = r.fullNameCRC32(); + headerToUpdate->nextRow = nullptr; + + if (m_metadataMapHeader->numberOfRows == 0) { + m_metadataMapHeader->firstRow = headerToUpdate; + } else { + ((MetadataRowHeader *) startOfMetadataMap)->nextRow = headerToUpdate; + } + + } else { + return false; + } + } else { + return true; + } + } + + if (neededSize != 0) { // If we must move some data to make or fill empty space + m_metadataMapHeader->metadataMapSize += neededSize; + memmove(startOfMetadataMap - neededSize, startOfMetadataMap, (char *) headerToUpdate + sizeof(MetadataRowHeader) - startOfMetadataMap); + + headerToUpdate = (MetadataRowHeader *) ((char *) headerToUpdate - neededSize); + MetadataRowHeader ** headerAfterPointer = headerToUpdatePointer; // Now we update each header below the one we just updated + for (int i = headerToUpdateIndex; i < m_metadataMapHeader->numberOfRows; i++) { + (*headerAfterPointer) = (MetadataRowHeader *) ((char *) (*headerAfterPointer) - neededSize); + headerAfterPointer = &((*headerAfterPointer)->nextRow); + } + } + + headerToUpdate->metadataSize = size; + memcpy(headerToUpdate->data(), metadata, size); + + return true; +} + +size_t InternalStorage::availableSize(char ** endBufferReturn) { + char * endBufferPointer = endBuffer(); + if (endBufferReturn) { + *endBufferReturn = endBufferPointer; + } + assert(k_storageSize >= (endBufferPointer - m_buffer) + sizeof(record_size_t) + m_metadataMapHeader->metadataMapSize); + return k_storageSize-(endBufferPointer-m_buffer)-sizeof(record_size_t) - m_metadataMapHeader->metadataMapSize; } size_t InternalStorage::putAvailableSpaceAtEndOfRecord(InternalStorage::Record r) { @@ -126,7 +253,7 @@ size_t InternalStorage::putAvailableSpaceAtEndOfRecord(InternalStorage::Record r char * nextRecord = p + previousRecordSize; memmove(nextRecord + availableStorageSize, nextRecord, - (m_buffer + k_storageSize - availableStorageSize) - nextRecord); + (m_buffer + k_storageSize - m_metadataMapHeader->metadataMapSize - availableStorageSize) - nextRecord); size_t newRecordSize = previousRecordSize + availableStorageSize; overrideSizeAtPosition(p, (record_size_t)newRecordSize); return newRecordSize; @@ -348,6 +475,12 @@ InternalStorage::InternalStorage() : assert(m_magicFooter == Magic); // Set the size of the first record to 0 overrideSizeAtPosition(m_buffer, 0); + + // Set the metadata map header at the end of the buffer + m_metadataMapHeader = (MetadataMapHeader*) (m_buffer + k_storageSize - sizeof(MetadataMapHeader)); + m_metadataMapHeader->numberOfRows = 0; + m_metadataMapHeader->firstRow = nullptr; + m_metadataMapHeader->metadataMapSize = sizeof(MetadataMapHeader); } // PRIVATE @@ -381,6 +514,11 @@ InternalStorage::Record::ErrorStatus InternalStorage::setFullNameOfRecord(const notifyChangeToDelegate(record); m_lastRecordRetrieved = record; m_lastRecordRetrievedPointer = p; + // Update metadata map + MetadataRowHeader * row = metadataForRecord(record); + if (row != nullptr) { + row->fullNameCRC32 = Record(fullName).fullNameCRC32(); + } return Record::ErrorStatus::None; } return Record::ErrorStatus::RecordDoesNotExist; @@ -452,6 +590,8 @@ void InternalStorage::destroyRecord(Record record) { } char * p = pointerOfRecord(record); if (p != nullptr) { + // Erase metadata + InternalStorage::removeMetadataForRecord(record); record_size_t previousRecordSize = sizeOfRecordStarting(p); slideBuffer(p+previousRecordSize, -previousRecordSize); notifyChangeToDelegate(); diff --git a/ion/src/shared/storage.cpp b/ion/src/shared/storage.cpp index 50a8a4c7007..3a90f51adb9 100644 --- a/ion/src/shared/storage.cpp +++ b/ion/src/shared/storage.cpp @@ -21,7 +21,7 @@ size_t Storage::availableSize() { bufferSize += sizeOfRecordStarting(p); } } - return k_storageSize-bufferSize-sizeof(record_size_t); + return k_storageSize-bufferSize-sizeof(record_size_t)-InternalStorage::m_metadataMapHeader->metadataMapSize; } else { return InternalStorage::availableSize(); } From cec75bfaedae82fd2746eb1d37a62140b87d292a Mon Sep 17 00:00:00 2001 From: Laury Date: Sun, 17 Apr 2022 18:05:58 +0200 Subject: [PATCH 239/355] [rpn] Updated submodules --- apps/rpn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/rpn b/apps/rpn index b51172c32fd..df2a10a9a03 160000 --- a/apps/rpn +++ b/apps/rpn @@ -1 +1 @@ -Subproject commit b51172c32fdfb5a36c7526799965d6f3da613d69 +Subproject commit df2a10a9a03fba3556840f33da5d3b68343bbc97 From d10d1cd65d4dc0809193554f0b7576db349d2d12 Mon Sep 17 00:00:00 2001 From: Laury Date: Sun, 17 Apr 2022 21:57:47 +0200 Subject: [PATCH 240/355] [rpn] Updated submodule to fix dirty line --- apps/rpn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/rpn b/apps/rpn index df2a10a9a03..908ab20719d 160000 --- a/apps/rpn +++ b/apps/rpn @@ -1 +1 @@ -Subproject commit df2a10a9a03fba3556840f33da5d3b68343bbc97 +Subproject commit 908ab20719de2d8f30d9fc2da5220f502ef014fb From 3c53421ffaca329139a6dfc4d40dd4d50607c6c3 Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 23 Apr 2022 23:10:58 +0200 Subject: [PATCH 241/355] [poincare] Added simplification of nested radicals --- poincare/src/square_root.cpp | 41 +++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/poincare/src/square_root.cpp b/poincare/src/square_root.cpp index cdefee1a520..29094e9529e 100644 --- a/poincare/src/square_root.cpp +++ b/poincare/src/square_root.cpp @@ -44,11 +44,46 @@ Expression SquareRoot::shallowReduce(ExpressionNode::ReductionContext reductionC return e; } } - Expression c = childAtIndex(0); - if (c.deepIsMatrix(reductionContext.context())) { + Expression child = childAtIndex(0); + if (child.deepIsMatrix(reductionContext.context())) { return replaceWithUndefinedInPlace(); } - Power p = Power::Builder(c, Rational::Builder(1, 2)); + + // Test if we are in a case like sqrt(a +- sqrt(b)) where a and b are rational + if (child.type() == ExpressionNode::Type::Addition && child.numberOfChildren() == 2) { + bool subtraction = child.numberOfChildren() == 2 && child.childAtIndex(1).type() == ExpressionNode::Type::Multiplication && child.childAtIndex(1).numberOfChildren() == 2 && child.childAtIndex(1).childAtIndex(0).type() == ExpressionNode::Type::Rational && child.childAtIndex(1).childAtIndex(0).sign(reductionContext.context()) == ExpressionNode::Sign::Negative; + Expression a = child.childAtIndex(0); + Expression b = subtraction && child.childAtIndex(1).childAtIndex(0).convert().isMinusOne() ? child.childAtIndex(1).childAtIndex(1) : child.childAtIndex(1); + // If root have been simplified in x * sqrt(y), we say that b is sqrt(x^2 * y) + if (b.type() == ExpressionNode::Type::Multiplication && b.numberOfChildren() == 2 && b.childAtIndex(0).type() == ExpressionNode::Type::Rational && b.childAtIndex(0).type() == ExpressionNode::Type::Rational && b.childAtIndex(1).type() == ExpressionNode::Type::Power && b.childAtIndex(1).childAtIndex(0).type() == ExpressionNode::Type::Rational && b.childAtIndex(1).childAtIndex(1).type() == ExpressionNode::Type::Rational && b.childAtIndex(1).childAtIndex(1).convert().isHalf()) { + b = Power::Builder(Multiplication::Builder(Power::Builder(b.childAtIndex(0).clone(), Rational::Builder(2, 1)).shallowReduce(reductionContext), b.childAtIndex(1).childAtIndex(0).clone()).shallowReduce(reductionContext), Rational::Builder(1, 2)); + } + if (a.type() == ExpressionNode::Type::Rational && b.type() == ExpressionNode::Type::Power && b.childAtIndex(0).type() == ExpressionNode::Type::Rational && b.childAtIndex(1).type() == ExpressionNode::Type::Rational && b.childAtIndex(1).convert().isHalf()) { + Expression a0 = a.clone(); + Expression a1 = b.childAtIndex(0).clone(); + // There is a simplification (wikipedia) + Expression rr = Subtraction::Builder(Power::Builder(a0, Rational::Builder(2, 1)).shallowReduce(reductionContext), a1).shallowReduce(reductionContext); + if (a0.sign(reductionContext.context()) != ExpressionNode::Sign::Negative && rr.sign(reductionContext.context()) != ExpressionNode::Sign::Negative) { + Expression r = Power::Builder(rr, Rational::Builder(1, 2)).shallowReduce(reductionContext); + if (r.type() == ExpressionNode::Type::Rational) { + Expression c = Division::Builder(Addition::Builder(a0.clone(), r.clone()).shallowReduce(reductionContext), Rational::Builder(2, 1)).shallowReduce(reductionContext); + Expression d = Division::Builder(Subtraction::Builder(a0.clone(), r.clone()).shallowReduce(reductionContext), Rational::Builder(2, 1)).shallowReduce(reductionContext); + + Expression result; + if (subtraction) { + result = Subtraction::Builder(Power::Builder(c, Rational::Builder(1, 2)).shallowReduce(reductionContext), Power::Builder(d, Rational::Builder(1, 2)).shallowReduce(reductionContext)); + } else { + result = Addition::Builder(Power::Builder(c, Rational::Builder(1, 2)).shallowReduce(reductionContext), Power::Builder(d, Rational::Builder(1, 2)).shallowReduce(reductionContext)); + } + + replaceWithInPlace(result); + return result.shallowReduce(reductionContext); + } + } + } + } + + Power p = Power::Builder(child, Rational::Builder(1, 2)); replaceWithInPlace(p); return p.shallowReduce(reductionContext); } From 5ed8aef90716e06956f463f5aa91ba08298ec7bf Mon Sep 17 00:00:00 2001 From: devdl11 Date: Mon, 25 Apr 2022 18:22:19 +0200 Subject: [PATCH 242/355] Bootloader pre-release --- bootloader/Makefile | 41 ++- bootloader/boot.cpp | 178 +++++----- bootloader/boot.h | 36 +- bootloader/boot/rt0.cpp | 3 +- bootloader/drivers/board.cpp | 18 + bootloader/drivers/stm32_drivers.cpp | 56 +++ bootloader/drivers/stm32_drivers.h | 153 ++++++++ bootloader/interface.cpp | 328 ------------------ bootloader/interface.h | 28 -- bootloader/interface/menus/about.cpp | 20 ++ .../interface/menus/{about => }/about.h | 6 +- bootloader/interface/menus/about/about.cpp | 13 - bootloader/interface/menus/crash.cpp | 22 ++ bootloader/interface/menus/crash.h | 19 + bootloader/interface/menus/dfu.cpp | 37 ++ bootloader/interface/menus/dfu.h | 21 ++ bootloader/interface/menus/home.cpp | 144 ++++++++ bootloader/interface/menus/home.h | 36 ++ bootloader/interface/menus/home/home.cpp | 62 ---- bootloader/interface/menus/home/home.h | 23 -- bootloader/interface/menus/installer.cpp | 41 +++ bootloader/interface/menus/installer.h | 20 ++ .../interface/menus/installer/installer.h | 15 - bootloader/interface/menus/slot_recovery.cpp | 39 +++ bootloader/interface/menus/slot_recovery.h | 19 + bootloader/interface/menus/warning.cpp | 35 ++ bootloader/interface/menus/warning.h | 17 + bootloader/interface/src/menu.cpp | 69 +++- bootloader/interface/src/menu.h | 84 +++-- bootloader/interface/static/interface.cpp | 68 ++++ bootloader/interface/static/interface.h | 19 + bootloader/interface/static/messages.h | 93 +++++ bootloader/main.cpp | 26 +- bootloader/messages.h | 60 ---- bootloader/recovery.cpp | 61 +--- bootloader/recovery.h | 1 - bootloader/{ => slots}/kernel_header.cpp | 2 +- bootloader/{ => slots}/kernel_header.h | 4 +- bootloader/{ => slots}/slot.cpp | 4 +- bootloader/{ => slots}/slot.h | 5 +- bootloader/{ => slots}/slot_exam_mode.cpp | 45 ++- bootloader/{ => slots}/slot_exam_mode.h | 10 +- bootloader/{ => slots}/userland_header.cpp | 2 +- bootloader/{ => slots}/userland_header.h | 4 +- bootloader/usb_data.cpp | 14 +- bootloader/usb_data.h | 6 +- bootloader/usb_desc.cpp | 12 - bootloader/utility.cpp | 12 +- bootloader/utility.h | 8 +- ion/src/device/bootloader/internal_flash.ld | 8 + .../device/bootloader/usb/dfu_interface.cpp | 37 +- ion/src/device/bootloader/usb/dfu_interface.h | 8 +- ion/src/device/shared/drivers/board.h | 1 + ion/src/device/shared/drivers/flash.cpp | 8 + ion/src/device/shared/drivers/flash.h | 2 + .../device/shared/drivers/internal_flash.cpp | 19 + .../device/shared/drivers/internal_flash.h | 2 + ion/src/device/shared/regs/flash.h | 3 + ion/src/device/shared/regs/rcc.h | 6 + ion/src/device/shared/regs/syscfg.h | 2 + liba/Makefile | 1 + liba/include/stdlib.h | 1 + bootloader/itoa.cpp => liba/src/itoa.c | 3 +- .../local/upsilon_light/bootloader/cable.png | Bin 0 -> 179 bytes .../upsilon_light/bootloader/computer.xcf | Bin 0 -> 5072 bytes 65 files changed, 1329 insertions(+), 811 deletions(-) create mode 100644 bootloader/drivers/stm32_drivers.cpp create mode 100644 bootloader/drivers/stm32_drivers.h delete mode 100644 bootloader/interface.cpp delete mode 100644 bootloader/interface.h create mode 100644 bootloader/interface/menus/about.cpp rename bootloader/interface/menus/{about => }/about.h (61%) delete mode 100644 bootloader/interface/menus/about/about.cpp create mode 100644 bootloader/interface/menus/crash.cpp create mode 100644 bootloader/interface/menus/crash.h create mode 100644 bootloader/interface/menus/dfu.cpp create mode 100644 bootloader/interface/menus/dfu.h create mode 100644 bootloader/interface/menus/home.cpp create mode 100644 bootloader/interface/menus/home.h delete mode 100644 bootloader/interface/menus/home/home.cpp delete mode 100644 bootloader/interface/menus/home/home.h create mode 100644 bootloader/interface/menus/installer.cpp create mode 100644 bootloader/interface/menus/installer.h delete mode 100644 bootloader/interface/menus/installer/installer.h create mode 100644 bootloader/interface/menus/slot_recovery.cpp create mode 100644 bootloader/interface/menus/slot_recovery.h create mode 100644 bootloader/interface/menus/warning.cpp create mode 100644 bootloader/interface/menus/warning.h create mode 100644 bootloader/interface/static/interface.cpp create mode 100644 bootloader/interface/static/interface.h create mode 100644 bootloader/interface/static/messages.h delete mode 100644 bootloader/messages.h rename bootloader/{ => slots}/kernel_header.cpp (93%) rename bootloader/{ => slots}/kernel_header.h (88%) rename bootloader/{ => slots}/slot.cpp (95%) rename bootloader/{ => slots}/slot.h (86%) rename bootloader/{ => slots}/slot_exam_mode.cpp (77%) rename bootloader/{ => slots}/slot_exam_mode.h (78%) rename bootloader/{ => slots}/userland_header.cpp (95%) rename bootloader/{ => slots}/userland_header.h (94%) delete mode 100644 bootloader/usb_desc.cpp rename bootloader/itoa.cpp => liba/src/itoa.c (92%) create mode 100644 themes/themes/local/upsilon_light/bootloader/cable.png create mode 100644 themes/themes/local/upsilon_light/bootloader/computer.xcf diff --git a/bootloader/Makefile b/bootloader/Makefile index ff7a5599a31..89308cccd3d 100644 --- a/bootloader/Makefile +++ b/bootloader/Makefile @@ -1,38 +1,49 @@ bootloader_src += $(addprefix bootloader/,\ - slot_exam_mode.cpp \ + utility.cpp \ boot.cpp \ main.cpp \ - kernel_header.cpp \ - userland_header.cpp \ - slot.cpp \ - interface.cpp \ jump_to_firmware.s \ trampoline.cpp \ recovery.cpp \ usb_data.cpp \ - itoa.cpp \ - utility.cpp \ ) -bootloader_images = $(addprefix bootloader/, \ - computer.png \ +bootloader_src += $(addprefix bootloader/slots/, \ + slot_exam_mode.cpp \ + slot.cpp \ + userland_header.cpp \ + kernel_header.cpp \ +) + +bootloader_src += $(addprefix bootloader/drivers/, \ + stm32_drivers.cpp \ +) + +bootloader_src += $(addprefix bootloader/interface/static/, \ + interface.cpp \ ) bootloader_src += $(addprefix bootloader/interface/src/,\ menu.cpp \ ) -bootloader_src += $(addprefix bootloader/interface/menus/about/,\ - about.cpp \ -) -bootloader_src += $(addprefix bootloader/interface/menus/home/,\ +bootloader_src += $(addprefix bootloader/interface/menus/, \ + about.cpp \ home.cpp \ + dfu.cpp \ + installer.cpp \ + warning.cpp \ + slot_recovery.cpp \ + crash.cpp \ ) +bootloader_images = $(addprefix bootloader/, \ + computer.png \ +) bootloader_src += $(ion_src) $(simple_kandinsky_src) $(liba_src) $(libaxx_src) $(bootloader_images) -$(eval $(call depends_on_image,bootloader/interface.cpp,$(bootloader_images))) -$(eval $(call depends_on_image,bootloader/interface/menus/home/home.cpp,$(bootloader_images))) +$(eval $(call depends_on_image,bootloader/interface/static/interface.cpp,$(bootloader_images))) +$(eval $(call depends_on_image,bootloader/interface/src/menu.cpp,$(bootloader_images))) diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index 765368b39ae..405b54d7f75 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -1,19 +1,33 @@ #include -#include -#include -#include -#include -#include +#include +#include #include #include -#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include +using namespace Utility; + +extern "C" { + extern char _fake_isr_function_start; +} + namespace Bootloader { +BootConfig * Boot::config() { + static BootConfig * bootcfg = new BootConfig(); + return bootcfg; +} + BootMode Boot::mode() { return BootMode::SlotA; } @@ -22,36 +36,86 @@ void Boot::setMode(BootMode mode) { // We dont use the exam mode driver as storage for the boot mode because we need the 16k of storage x) } +void Boot::busErr() { + if (config()->isBooting()) { + config()->slot()->boot(); + } + Bootloader::Recovery::crash_handler("BusFault"); +} + +bool Boot::isKernelPatched(const Slot & s) { + if (s.userlandHeader()->isOmega()) { + // we don't need to patch the kernel + return true; + } + + // It's an epsilon kernel, so we need to check if it's patched + + uint32_t origin_isr = s.address() + sizeof(Bootloader::KernelHeader) - sizeof(uint32_t) * 3; + + if (*(uint32_t *)(origin_isr + sizeof(uint32_t) * 12) == (uint32_t)0x0) { + // fake epsilon + return true; + } + + return *(uint32_t *)(origin_isr + sizeof(uint32_t) * 21) == (uint32_t)&_fake_isr_function_start; +} + +__attribute((section(".fake_isr_function"))) __attribute__((used)) void Boot::flsh_intr() { + // a simple function + while (1) {} +} + +void Boot::patchKernel(const Slot & s) { + uint32_t origin_isr = s.address() + sizeof(Bootloader::KernelHeader) - sizeof(uint32_t) * 3 - 0x90000000; + // we allocate a big buffer to store the first sector + uint8_t data[1024*4]; + memcpy(data, (void*)0x90000000, 1024*4); + uint32_t dummy_address = (uint32_t)&_fake_isr_function_start; + uint8_t * ptr = (uint8_t *)&dummy_address; + data[origin_isr + sizeof(uint32_t) * 21] = ptr[0]; + data[origin_isr + sizeof(uint32_t) * 21 + 1] = ptr[1]; + data[origin_isr + sizeof(uint32_t) * 21 + 2] = ptr[2]; + data[origin_isr + sizeof(uint32_t) * 21 + 3] = ptr[3]; + Ion::Device::ExternalFlash::EraseSector(0); + Ion::Device::ExternalFlash::WriteMemory((uint8_t*)0x90000000, data, 1024*4); +} + void Boot::bootSlot(Bootloader::Slot s) { + config()->setSlot(&s); if (!s.userlandHeader()->isOmega() && !s.userlandHeader()->isUpsilon()) { // We are trying to boot epsilon, so we check the version and show an advertisement if needed const char * version = s.userlandHeader()->version(); - const char * min = "18.2.4"; + const char * min = "18.2.0"; int vsum = Utility::versionSum(version, strlen(version)); int minsum = Utility::versionSum(min, strlen(min)); if (vsum >= minsum) { - Interface::drawEpsilonAdvertisement(); - uint64_t scan = 0; - while (scan != Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { - scan = Ion::Keyboard::scan(); - if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::EXE) || scan == Ion::Keyboard::State(Ion::Keyboard::Key::OK)) { - scan = Ion::Keyboard::State(Ion::Keyboard::Key::Back); - s.boot(); - } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { - Ion::Power::standby(); // Force a core reset to exit - } - } - Interface::drawMenu(); + WarningMenu menu = WarningMenu(); + menu.open(); return; } } - s.boot(); - Interface::drawMenu(); + bootSelectedSlot(); +} + +void Boot::bootSelectedSlot() { + lockInternal(); + enableFlashIntr(); + config()->setBooting(true); + Ion::Device::Flash::EnableInternalSessionLock(); } __attribute__((noreturn)) void Boot::boot() { assert(mode() != BootMode::Unknown); + Boot::config()->clearSlot(); + Boot::config()->setBooting(false); + + if (!Boot::isKernelPatched(Slot::A())) { + Boot::patchKernel(Slot::A()); + Ion::LED::setColor(KDColorRed); + } + while (true) { HomeMenu menu = HomeMenu(); menu.open(true); @@ -61,67 +125,6 @@ __attribute__((noreturn)) void Boot::boot() { bootloader(); } -void Boot::installerMenu() { - Interface::drawInstallerSelection(); - uint64_t scan = 0; - while (scan != Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { - scan = Ion::Keyboard::scan(); - if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::One)) { - scan = Ion::Keyboard::State(Ion::Keyboard::Key::Back); - bootloader(); - continue; - } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Two)) { - scan = Ion::Keyboard::State(Ion::Keyboard::Key::Back); - bootloaderUpdate(); - continue; - } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { - Ion::Power::standby(); // Force a core reset to exit - } - } - - Interface::drawMenu(); -} - -void Boot::aboutMenu() { - // Draw the about menu - Interface::drawAbout(); - // Wait for the user to press OK, EXE or Back button - while (true) { - uint64_t scan = Ion::Keyboard::scan(); - if ((scan == Ion::Keyboard::State(Ion::Keyboard::Key::OK)) || - (scan == Ion::Keyboard::State(Ion::Keyboard::Key::EXE)) || - (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Back))) { - // Redraw the menu and return - Interface::drawMenu(); - return; - } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { - Ion::Power::standby(); // Force a core reset to exit - } - } - -} - -void Boot::bootloaderUpdate() { - USBData data = USBData::BOOTLOADER_UPDATE(); - - for (;;) { - Bootloader::Interface::drawBootloaderUpdate(); - Ion::USB::enable(); - do { - uint64_t scan = Ion::Keyboard::scan(); - if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { - Ion::USB::disable(); - Interface::drawMenu(); - return; - } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { - Ion::Power::standby(); - } - } while (!Ion::USB::isEnumerated()); - - Ion::USB::DFU(true, &data); - } -} - void Boot::bootloader() { USBData data = USBData::DEFAULT(); for(;;) { @@ -138,7 +141,6 @@ void Boot::bootloader() { if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { // Disable USB, redraw the menu and return Ion::USB::disable(); - Interface::drawMenu(); return; } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { Ion::Power::standby(); // Force a core reset to exit @@ -150,6 +152,10 @@ void Boot::bootloader() { } } +void Boot::jumpToInternalBootloader() { + Ion::Device::Board::jumpToInternalBootloader(); +} + void Boot::lockInternal() { Ion::Device::Flash::DisableInternalProtection(); Ion::Device::Flash::SetInternalSectorProtection(0, true); @@ -159,4 +165,8 @@ void Boot::lockInternal() { Ion::Device::Flash::EnableInternalProtection(); } +void Boot::enableFlashIntr() { + Ion::Device::Flash::EnableInternalFlashInterrupt(); +} + } diff --git a/bootloader/boot.h b/bootloader/boot.h index 0414ed22d69..2b7d66d47f0 100644 --- a/bootloader/boot.h +++ b/bootloader/boot.h @@ -2,10 +2,25 @@ #define BOOTLOADER_BOOT_H #include -#include +#include namespace Bootloader { +class BootConfig { + public: + BootConfig() : m_slot(nullptr), m_booting(false) {}; + + void setSlot(Slot * slot) { m_slot = slot; } + Slot * slot() const { return m_slot; } + void clearSlot() { m_slot = nullptr; } + + void setBooting(bool booting) { m_booting = booting; } + bool isBooting() const { return m_booting; } + private: + Bootloader::Slot * m_slot; + bool m_booting; +}; + enum BootMode: uint8_t { SlotA = 0, SlotB = 1, @@ -20,15 +35,26 @@ class Boot { public: static BootMode mode(); static void setMode(BootMode mode); + static BootConfig * config(); + + static bool isKernelPatched(const Slot & slot); + static void patchKernel(const Slot & slot); + + static void busErr(); + __attribute__ ((noreturn)) static void boot(); - static void bootloader(); - static void aboutMenu(); - static void installerMenu(); - static void bootloaderUpdate(); + static void bootSlot(Bootloader::Slot slot); + static void bootSelectedSlot(); + __attribute__ ((noreturn)) static void jumpToInternalBootloader(); + __attribute((section(".fake_isr_function"))) __attribute__((used)) static void flsh_intr(); + + static void bootloader(); static void lockInternal(); + static void enableFlashIntr(); }; + } #endif \ No newline at end of file diff --git a/bootloader/boot/rt0.cpp b/bootloader/boot/rt0.cpp index 59751ca5fcd..65eedf9859f 100644 --- a/bootloader/boot/rt0.cpp +++ b/bootloader/boot/rt0.cpp @@ -7,6 +7,7 @@ #include #include #include +#include typedef void (*cxx_constructor)(); @@ -49,7 +50,7 @@ void __attribute__((noinline)) usage_fault_handler() { } void __attribute__((noinline)) bus_fault_handler() { - Bootloader::Recovery::crash_handler("BusFault"); + Bootloader::Boot::busErr(); } /* In order to ensure that this method is execute from the external flash, we diff --git a/bootloader/drivers/board.cpp b/bootloader/drivers/board.cpp index 2ee8fe666d9..0a80a89ec98 100644 --- a/bootloader/drivers/board.cpp +++ b/bootloader/drivers/board.cpp @@ -4,9 +4,13 @@ #include #include #include +#include #include #include +#include + +using namespace STM32; typedef void(*ISR)(void); extern ISR InitialisationVector[]; @@ -439,6 +443,20 @@ bool pcbVersionIsLocked() { return *reinterpret_cast(InternalFlash::Config::OTPLockAddress(k_pcbVersionOTPIndex)) == 0; } +void jumpToInternalBootloader() { + asm volatile ("cpsie i" : : : "memory"); + + STM32::rcc_deinit(); + STM32::hal_deinit(); + STM32::systick_deinit(); + + const uint32_t p = (*((uint32_t *) 0x1FF00000)); + asm volatile ("MSR msp, %0" : : "r" (p) : ); + void (*SysMemBootJump)(void); + SysMemBootJump = (void (*)(void)) (*((uint32_t *) 0x1FF00004)); + SysMemBootJump(); +} + } } } diff --git a/bootloader/drivers/stm32_drivers.cpp b/bootloader/drivers/stm32_drivers.cpp new file mode 100644 index 00000000000..6fd018aab03 --- /dev/null +++ b/bootloader/drivers/stm32_drivers.cpp @@ -0,0 +1,56 @@ +#include "stm32_drivers.h" + +void STM32::rcc_deinit() { + SET_BIT(STM_32_RCC->CR, (0x1UL << (0U))); + while (READ_BIT(STM_32_RCC->CR, (0x1UL << (1U))) == 0) {} + SET_BIT(STM_32_RCC->CR, (0x10UL << (3U))); + CLEAR_REG(STM_32_RCC->CFGR); + while (READ_BIT(STM_32_RCC->CFGR, (0x3UL << (2U))) != 0) {} + CLEAR_BIT(STM_32_RCC->CR, (0x1UL << (16U)) | (0x1UL << (18U)) | (0x1UL << (19U))); + while (READ_BIT(STM_32_RCC->CR, (0x1UL << (17U))) != 0) {} + CLEAR_BIT(STM_32_RCC->CR, (0x1UL << (24U))); + while (READ_BIT(STM_32_RCC->CR, (0x1UL << (25U))) != 0) {} + CLEAR_BIT(STM_32_RCC->CR, (0x1UL << (26U))); + while (READ_BIT(STM_32_RCC->CR, (0x1UL << (27U))) != 0) {} + CLEAR_BIT(STM_32_RCC->CR, (0x1UL << (28U))); + while (READ_BIT(STM_32_RCC->CR, (0x1UL << (29U))) != 0) {} + STM_32_RCC->PLLCFGR = ((0x10UL << (0x0U)) | (0x040UL << (6U)) | (0x080UL << (6U)) | (0x4UL << (24U)) | 0x20000000U); + STM_32_RCC->PLLI2SCFGR = ((0x040UL << (6U)) | (0x080UL << (6U)) | (0x4UL << (24U)) | (0x2UL << (28U))); + STM_32_RCC->PLLSAICFGR = ((0x040UL << (6U)) | (0x080UL << (6U)) | (0x4UL << (24U)) | 0x20000000U); + CLEAR_BIT(STM_32_RCC->CIR, ((0x1UL << (8U)) | (0x1UL << (9U)) | (0x1UL << (10U)) | (0x1UL << (11U)) | (0x1UL << (12U)) | (0x1UL << (13U)) | (0x1UL << (14U)))); + SET_BIT(STM_32_RCC->CIR, ((0x1UL << (16U)) | (0x1UL << (17U)) | (0x1UL << (18U)) | (0x1UL << (19U)) | (0x1UL << (20U)) | (0x1UL << (21U)) | (0x1UL << (22U)) | (0x1UL << (23U)))); + CLEAR_BIT(STM_32_RCC->CSR, ((0x1UL << (0U)))); + SET_BIT(STM_32_RCC->CSR, ((0x1UL << (24U)))); + uint32_t sysclock = ((uint32_t)16000000U); + uint32_t a = ((sysclock / 1000U)); + uint32_t b = 15U; + STM_32_SysTick->LOAD = (uint32_t)(a - 1UL); + STM_32_SCB->SHPR[(((uint32_t)(-1))&0xFUL)-4UL] = (uint8_t)((((1UL << 4U)-1UL) << (8U - 4UL)) & (uint32_t)0xFFUL); + STM_32_SysTick->VAL = 0U; + STM_32_SysTick->CTRL = (1UL << 2U) | (1UL << 1U) | (1UL); + uint32_t c = ((uint32_t)((STM_32_SCB->AIRCR & (7UL << 8U)) >> 8U)); + uint32_t d = (c & (uint32_t)0x07UL); + uint32_t e; + uint32_t f; + e = ((7UL - d) > (uint32_t)(4UL)) ? (uint32_t)(4UL) : (7UL - d); + f = ((d + (uint32_t)(4UL)) < (uint32_t)(7UL)) ? (uint32_t)(0UL) : (uint32_t)((d - 7UL) + (uint32_t)(4UL)); + uint32_t g = (((b & (uint32_t)((1UL << (e)) - 1UL)) << f) | ((0UL & (uint32_t)((1UL << (f)) - 1UL)))); + STM_32_SCB->SHPR[(((uint32_t)(-1))&0xFUL)-4UL] = (uint8_t)((g << (8U - 4UL)) & (uint32_t)0xFFUL); +} + +void STM32::hal_deinit() { + STM_32_RCC->APB1RSTR = 0xFFFFFFFFU; + STM_32_RCC->APB1RSTR = 0x00U; + STM_32_RCC->APB2RSTR = 0xFFFFFFFFU; + STM_32_RCC->APB2RSTR = 0x00U; + STM_32_RCC->AHB1RSTR = 0xFFFFFFFFU; + STM_32_RCC->AHB1RSTR = 0x00U; + STM_32_RCC->AHB2RSTR = 0xFFFFFFFFU; + STM_32_RCC->AHB2RSTR = 0x00U; + STM_32_RCC->AHB3RSTR = 0xFFFFFFFFU; + STM_32_RCC->AHB3RSTR = 0x00U; +} + +void STM32::systick_deinit() { + STM_32_SysTick->CTRL = STM_32_SysTick->LOAD = STM_32_SysTick->VAL = 0; +} diff --git a/bootloader/drivers/stm32_drivers.h b/bootloader/drivers/stm32_drivers.h new file mode 100644 index 00000000000..bba55c74b1d --- /dev/null +++ b/bootloader/drivers/stm32_drivers.h @@ -0,0 +1,153 @@ +#include + +/* + Here we implement a very little part of the code from the default stm32 libs because we only need the unload function. + Now we include the license of the original code as required. +*/ + +/* +This software component is provided to you as part of a software package and +applicable license terms are in the Package_license file. If you received this +software component outside of a package or without applicable license terms, +the terms of the BSD-3-Clause license shall apply. +You may obtain a copy of the BSD-3-Clause at: +https://opensource.org/licenses/BSD-3-Clause +*/ + +namespace STM32 { + + typedef struct + { + volatile uint32_t CR; + volatile uint32_t PLLCFGR; + volatile uint32_t CFGR; + volatile uint32_t CIR; + volatile uint32_t AHB1RSTR; + volatile uint32_t AHB2RSTR; + volatile uint32_t AHB3RSTR; + uint32_t RESERVED0; + volatile uint32_t APB1RSTR; + volatile uint32_t APB2RSTR; + uint32_t RESERVED1[2]; + volatile uint32_t AHB1ENR; + volatile uint32_t AHB2ENR; + volatile uint32_t AHB3ENR; + uint32_t RESERVED2; + volatile uint32_t APB1ENR; + volatile uint32_t APB2ENR; + uint32_t RESERVED3[2]; + volatile uint32_t AHB1LPENR; + volatile uint32_t AHB2LPENR; + volatile uint32_t AHB3LPENR; + uint32_t RESERVED4; + volatile uint32_t APB1LPENR; + volatile uint32_t APB2LPENR; + uint32_t RESERVED5[2]; + volatile uint32_t BDCR; + volatile uint32_t CSR; + uint32_t RESERVED6[2]; + volatile uint32_t SSCGR; + volatile uint32_t PLLI2SCFGR; + volatile uint32_t PLLSAICFGR; + volatile uint32_t DCKCFGR1; + volatile uint32_t DCKCFGR2; + } STM32_RCC_TypeDef; + + typedef struct + { + volatile uint32_t CTRL; + volatile uint32_t LOAD; + volatile uint32_t VAL; + volatile const uint32_t CALIB; + } STM32_SysTick_Type; + + typedef struct + { + volatile uint32_t ISER[8U]; + uint32_t RESERVED0[24U]; + volatile uint32_t ICER[8U]; + uint32_t RSERVED1[24U]; + volatile uint32_t ISPR[8U]; + uint32_t RESERVED2[24U]; + volatile uint32_t ICPR[8U]; + uint32_t RESERVED3[24U]; + volatile uint32_t IABR[8U]; + uint32_t RESERVED4[56U]; + volatile uint8_t IP[240U]; + uint32_t RESERVED5[644U]; + volatile uint32_t STIR; + } STM32_NVIC_Type; + + typedef struct { + volatile const uint32_t CPUID; + volatile uint32_t ICSR; + volatile uint32_t VTOR; + volatile uint32_t AIRCR; + volatile uint32_t SCR; + volatile uint32_t CCR; + volatile uint8_t SHPR[12U]; + volatile uint32_t SHCSR; + volatile uint32_t CFSR; + volatile uint32_t HFSR; + volatile uint32_t DFSR; + volatile uint32_t MMFAR; + volatile uint32_t BFAR; + volatile uint32_t AFSR; + volatile const uint32_t ID_PFR[2U]; + volatile const uint32_t ID_DFR; + volatile const uint32_t ID_AFR; + volatile const uint32_t ID_MFR[4U]; + volatile const uint32_t ID_ISAR[5U]; + uint32_t RESERVED0[1U]; + volatile const uint32_t CLIDR; + volatile const uint32_t CTR; + volatile const uint32_t CCSIDR; + volatile uint32_t CSSELR; + volatile uint32_t CPACR; + uint32_t RESERVED3[93U]; + volatile uint32_t STIR; + uint32_t RESERVED4[15U]; + volatile const uint32_t MVFR0; + volatile const uint32_t MVFR1; + volatile const uint32_t MVFR2; + uint32_t RESERVED5[1U]; + volatile uint32_t ICIALLU; + uint32_t RESERVED6[1U]; + volatile uint32_t ICIMVAU; + volatile uint32_t DCIMVAC; + volatile uint32_t DCISW; + volatile uint32_t DCCMVAU; + volatile uint32_t DCCMVAC; + volatile uint32_t DCCSW; + volatile uint32_t DCCIMVAC; + volatile uint32_t DCCISW; + uint32_t RESERVED7[6U]; + volatile uint32_t ITCMCR; + volatile uint32_t DTCMCR; + volatile uint32_t AHBPCR; + volatile uint32_t CACR; + volatile uint32_t AHBSCR; + uint32_t RESERVED8[1U]; + volatile uint32_t ABFSR; + } STM32_SCB_Type; + + #define RCC_BASE 0x40023800UL + #define SysTick_BASE 0xE000E010UL + #define NVIC_BASE 0xE000E100UL + #define SCB_BASE 0xE000ED00UL + + #define SET_BIT(REG, BIT) ((REG) |= (BIT)) + #define CLEAR_BIT(REG, BIT) ((REG) &= ~(BIT)) + #define READ_BIT(REG, BIT) ((REG) & (BIT)) + #define CLEAR_REG(REG) ((REG) = (0x0)) + #define READ_REG(REG) ((REG)) + + #define STM_32_RCC ((STM32::STM32_RCC_TypeDef *) RCC_BASE) + #define STM_32_SysTick ((STM32::STM32_SysTick_Type *) SysTick_BASE) + #define STM_32_NVIC ((STM32::STM32_NVIC_Type *) NVIC_BASE) + #define STM_32_SCB ((STM32_SCB_Type *) SCB_BASE) + + extern void rcc_deinit(); + extern void hal_deinit(); + extern void systick_deinit(); +} \ No newline at end of file diff --git a/bootloader/interface.cpp b/bootloader/interface.cpp deleted file mode 100644 index 7d54da274a5..00000000000 --- a/bootloader/interface.cpp +++ /dev/null @@ -1,328 +0,0 @@ - -#include -#include -#include - -#include -#include -#include -#include - -#include "computer.h" - -namespace Bootloader { - -void Interface::drawImage(KDContext * ctx, const Image * image, int offset) { - const uint8_t * data; - size_t size; - size_t pixelBufferSize; - - if (image != nullptr) { - data = image->compressedPixelData(); - size = image->compressedPixelDataSize(); - pixelBufferSize = image->width() * image->height(); - } else { - return; - } - - KDColor pixelBuffer[4000]; - assert(pixelBufferSize <= 4000); - assert(Ion::stackSafe()); // That's a VERY big buffer we're allocating on the stack - - Ion::decompress( - data, - reinterpret_cast(pixelBuffer), - size, - pixelBufferSize * sizeof(KDColor) - ); - - KDRect bounds((320 - image->width()) / 2, offset, image->width(), image->height()); - - ctx->fillRectWithPixels(bounds, pixelBuffer, nullptr); -} - -void Interface::drawHeader() { - KDContext * ctx = KDIonContext::sharedContext(); - ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); - drawImage(ctx, ImageStore::Computer, 25); - KDSize fontSize = KDFont::LargeFont->glyphSize(); - int initPos = (320 - fontSize.width() * strlen(Messages::mainTitle)) / 2; - ctx->drawString(Messages::mainTitle, KDPoint(initPos, ImageStore::Computer->height() + fontSize.height() + 10), KDFont::LargeFont, KDColorBlack, KDColorWhite); -} - -void Interface::drawMenu() { - // Get the context - KDContext * ctx = KDIonContext::sharedContext(); - // Clear the screen - ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); - // Draw the image - drawImage(ctx, ImageStore::Computer, 25); - // Get the font size - KDSize largeSize = KDFont::LargeFont->glyphSize(); - KDSize smallSize = KDFont::SmallFont->glyphSize(); - // Draw the title - int initPos = (320 - largeSize.width() * strlen(Messages::mainMenuTitle)) / 2; - ctx->drawString(Messages::mainMenuTitle, KDPoint(initPos, ImageStore::Computer->height() + largeSize.height() + 10), KDFont::LargeFont, KDColorBlack, KDColorWhite); - - // Initialize the slot list - Slot slots[3] = {Slot::A(), Slot::Khi(), Slot::B()}; - char indexes[3] = {'1', '2', '3'}; - - // Set the khi slot index, improve this when Khi will have a dedicated header - const uint8_t khiIndex = 2 - 1; - - // Get the start y position - int y = ImageStore::Computer->height() + (largeSize.height() + 10) + (smallSize.height() + 10); - - // Iterate over the slot list - for (uint8_t i = 0; i < sizeof(indexes) / sizeof(indexes[0]); i++) { - // Get the slot from the list - Slot slot = slots[i]; - // Get the "X - " string - char converted[] = {indexes[i], ' ', '-', ' ', '\0'}; - // Setup the margin - int x = 10; - // If the slot is valid, draw the slot - if (slot.kernelHeader()->isValid() && slot.userlandHeader()->isValid()) { - // Draw the slot number - ctx->drawString(converted, KDPoint(x, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - // Increment the x position - x += strlen(converted) * smallSize.width(); - // Draw the slot version - ctx->drawString(slot.userlandHeader()->version(), KDPoint(x, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - - // Get if the commit (patchLevel) isn't empty - if (slot.kernelHeader()->patchLevel()[0] != '\0') { - // Increment the x position - x += strlen(slot.userlandHeader()->version()) * smallSize.width() + smallSize.width() * 2; - // Draw the slot commit - ctx->drawString(slot.kernelHeader()->patchLevel(), KDPoint(x, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - // Increment the x position - x += strlen(slot.kernelHeader()->patchLevel()) * smallSize.width() + smallSize.width(); - } else { - // Increment the x position - x += strlen(slot.userlandHeader()->version()) * smallSize.width() + smallSize.width(); - } - - const char * OSName = ""; - const char * OSVersion = ""; - // If the slot is Upsilon, draw the slot name - if (slot.userlandHeader()->isOmega() && slot.userlandHeader()->isUpsilon()) { - // Set the OS name - OSName = Messages::upsilon; - // Set the OS version - OSVersion = slot.userlandHeader()->upsilonVersion(); - } else if (slot.userlandHeader()->isOmega()) { - // Get if slot is Khi - bool isKhi = (i == khiIndex); - // If the slot is Khi, draw the slot name (Khi) - if (isKhi) { - // Set the OS name - OSName = Messages::khi; - } else { - // Set the OS name - OSName = Messages::omega; - } - // Set the OS version - OSVersion = slot.userlandHeader()->omegaVersion(); - } else { - // Set the OS name - OSName = Messages::epsilon; - } - // Draw the OS name - ctx->drawString(OSName, KDPoint(x, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - // Increment the x position - x += strlen(OSName) * smallSize.width() + smallSize.width(); - // Draw the OS version - ctx->drawString(OSVersion, KDPoint(x, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - // Else, the slot is invalid - } else { - ctx->drawString(Messages::invalidSlot, KDPoint(10, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - } - // Increment the y position - y += smallSize.height() + 10; - } - - // Draw the DFU message - ctx->drawString(Messages::dfuText, KDPoint(10, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - - // Increment the y position - y += smallSize.height() + 10; - // Draw the about message - ctx->drawString(Messages::aboutText, KDPoint(10, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - - // Reboot is disabled for now - // y += smallSize.height() + 10; - // ctx->drawString(Messages::rebootText, KDPoint(10, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - - // Draw the footer - initPos = (320 - smallSize.width() * strlen(Messages::mainTitle)) / 2; - ctx->drawString(Messages::mainTitle, KDPoint(initPos, 240 - smallSize.height() - 10), KDFont::SmallFont, KDColorBlack, KDColorWhite); -} - -void Interface::drawFlasher() { - Interface::drawHeader(); - KDContext * ctx = KDIonContext::sharedContext(); - int y = ImageStore::Computer->height() + (KDFont::LargeFont->glyphSize().height() + 10) + (KDFont::SmallFont->glyphSize().height() + 10); - int initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::dfuSubtitle)) / 2; - ctx->drawString(Messages::dfuSubtitle, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); -} - -void Interface::drawLoading() { - KDContext * ctx = KDIonContext::sharedContext(); - ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); - drawImage(ctx, ImageStore::Computer, 25); - Ion::Timing::msleep(250); - KDSize fontSize = KDFont::LargeFont->glyphSize(); - int initPos = (320 - fontSize.width() * strlen(Messages::mainTitle)) / 2; - for (uint8_t i = 0; i < strlen(Messages::mainTitle); i++) { - char tmp[2] = {Messages::mainTitle[i], '\0'}; - ctx->drawString(tmp, KDPoint(initPos + i * (fontSize.width()), ImageStore::Computer->height() + fontSize.height() + 10), KDFont::LargeFont, KDColorBlack, KDColorWhite); - Ion::Timing::msleep(50); - } - Ion::Timing::msleep(500); -} - -void Interface::drawAbout() { - drawHeader(); - // Create the list of about messages - // TODO: Move it to messages.h - char aboutMessages[][38] = { - "This is the bootloader of", - "the Upsilon Calculator.", - "It is used to install", - "and select the OS", - "to boot." - }; - // Get the context - KDContext * ctx = KDIonContext::sharedContext(); - // Get the start Y position - KDSize largeSize = KDFont::LargeFont->glyphSize(); - KDSize smallSize = KDFont::SmallFont->glyphSize(); - int y = ImageStore::Computer->height() + (largeSize.height() + 10) + (smallSize.height() + 10); - // Iterate over the list and draw each message - for (uint8_t i = 0; i < sizeof(aboutMessages) / sizeof(aboutMessages[0]); i++) { - // Get the message - char * actualMessage = aboutMessages[i]; - // Get the start X position - int initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(actualMessage)) / 2; - // Draw the message - ctx->drawString(actualMessage, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - // Increment the Y position - y += smallSize.height() + 10; - } - - ctx->drawString(Messages::bootloaderVersion, KDPoint((320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::bootloaderVersion)) / 2, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - -} - -void Interface::drawCrash(const char * error) { - KDContext * ctx = KDIonContext::sharedContext(); - ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); - drawImage(ctx, ImageStore::Computer, 25); - KDSize fontSize = KDFont::LargeFont->glyphSize(); - int initPos = (320 - fontSize.width() * strlen(Messages::crashTitle)) / 2; - ctx->drawString(Messages::crashTitle, KDPoint(initPos, ImageStore::Computer->height() + fontSize.height() + 10), KDFont::LargeFont, KDColorBlack, KDColorWhite); - initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(error)) / 2; - ctx->drawString(error, KDPoint(initPos, ImageStore::Computer->height() + fontSize.height() + 10 + KDFont::SmallFont->glyphSize().height() + 10), KDFont::SmallFont, KDColorBlack, KDColorWhite); - initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::crashText1)) / 2; - ctx->drawString(Messages::crashText1, KDPoint(initPos, ImageStore::Computer->height() + fontSize.height() + 10 + KDFont::SmallFont->glyphSize().height() + 10 + KDFont::SmallFont->glyphSize().height() + 10 + 20), KDFont::SmallFont, KDColorBlack, KDColorWhite); - initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::crashText2)) / 2; - ctx->drawString(Messages::crashText2, KDPoint(initPos, ImageStore::Computer->height() + fontSize.height() + 10 + KDFont::SmallFont->glyphSize().height() + 10 + KDFont::SmallFont->glyphSize().height() + 10 + 20 + KDFont::SmallFont->glyphSize().height() + 10), KDFont::SmallFont, KDColorBlack, KDColorWhite); -} - -void Interface::drawRecovery() { - KDContext * ctx = KDIonContext::sharedContext(); - ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); - drawImage(ctx, ImageStore::Computer, 25); - KDSize fontSize = KDFont::LargeFont->glyphSize(); - int initPos = (320 - fontSize.width() * strlen(Messages::recoveryTitle)) / 2; - int y = ImageStore::Computer->height() + fontSize.height() + 5; - ctx->drawString(Messages::recoveryTitle, KDPoint(initPos, y), KDFont::LargeFont, KDColorBlack, KDColorWhite); - initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::recoveryText1)) / 2; - y += fontSize.height() + 5; - ctx->drawString(Messages::recoveryText1, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::recoveryText2)) / 2; - y += fontSize.height() + 5; - ctx->drawString(Messages::recoveryText2, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::recoveryText3)) / 2; - y += fontSize.height() + 5; - ctx->drawString(Messages::recoveryText3, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::recoveryText4)) / 2; - y += fontSize.height() + 5; - ctx->drawString(Messages::recoveryText4, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::recoveryText5)) / 2; - y += fontSize.height() + 5; - ctx->drawString(Messages::recoveryText5, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); -} - -void Interface::drawInstallerSelection() { - // Get the context - KDContext * ctx = KDIonContext::sharedContext(); - // Clear the screen - ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); - // Draw the image - drawImage(ctx, ImageStore::Computer, 25); - // Get the font size - KDSize largeSize = KDFont::LargeFont->glyphSize(); - KDSize smallSize = KDFont::SmallFont->glyphSize(); - // Get the start x position - int initPos = (320 - largeSize.width() * strlen(Messages::installerSelectionTitle)) / 2; - // Get the start y position - int y = ImageStore::Computer->height() + largeSize.height() + 10; - // Draw the title - ctx->drawString(Messages::installerSelectionTitle, KDPoint(initPos, y), KDFont::LargeFont, KDColorBlack, KDColorWhite); - // Increment the y position - y += largeSize.height() + 5; - // Get the y position of the subtitle - initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::installerText1)) / 2; - // Draw the subtitle - ctx->drawString(Messages::installerText1, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - // Increment the y position - y += smallSize.height() + 10; - // Set the start x position - initPos = 10; - // Draw the first button (slot flash) - ctx->drawString(Messages::installerText2, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); - // Increment the y position - y += smallSize.height() + 10; - // Draw the second button (bootloader flash) - ctx->drawString(Messages::installerText3, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); -} - -void Interface::drawBootloaderUpdate() { - Interface::drawHeader(); - KDContext * ctx = KDIonContext::sharedContext(); - int y = ImageStore::Computer->height() + (KDFont::LargeFont->glyphSize().height() + 10) + (KDFont::SmallFont->glyphSize().height() + 10); - int initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::bootloaderSubtitle)) / 2; - ctx->drawString(Messages::bootloaderSubtitle, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); -} - -void Interface::drawEpsilonAdvertisement() { - KDContext * ctx = KDIonContext::sharedContext(); - ctx->fillRect(KDRect(0, 0, 320, 240), KDColorRed); - drawImage(ctx, ImageStore::Computer, 25); - KDSize fontSize = KDFont::LargeFont->glyphSize(); - int initPos = (320 - fontSize.width() * strlen(Messages::epsilonWarningTitle)) / 2; - int y = ImageStore::Computer->height() + fontSize.height() + 15; - ctx->drawString(Messages::epsilonWarningTitle, KDPoint(initPos, y), KDFont::LargeFont, KDColorWhite, KDColorRed); - initPos = (320 - fontSize.width() * strlen(Messages::epsilonWarningText1)) / 2; - y += fontSize.height() + 5; - ctx->drawString(Messages::epsilonWarningText1, KDPoint(initPos, y), KDFont::LargeFont, KDColorWhite, KDColorRed); - initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::epsilonWarningText2)) / 2; - y += fontSize.height() + 2; - ctx->drawString(Messages::epsilonWarningText2, KDPoint(initPos, y), KDFont::SmallFont, KDColorWhite, KDColorRed); - initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::epsilonWarningText3)) / 2; - y += KDFont::SmallFont->glyphSize().height() + 5; - ctx->drawString(Messages::epsilonWarningText3, KDPoint(initPos, y), KDFont::SmallFont, KDColorWhite, KDColorRed); - initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::epsilonWarningText4)) / 2; - y += KDFont::SmallFont->glyphSize().height() + 10; - ctx->drawString(Messages::epsilonWarningText4, KDPoint(initPos, y), KDFont::SmallFont, KDColorWhite, KDColorRed); - y += KDFont::SmallFont->glyphSize().height() + 10; - initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::epsilonWarningText5)) / 2; - ctx->drawString(Messages::epsilonWarningText5, KDPoint(initPos, y), KDFont::SmallFont, KDColorWhite, KDColorRed); - y += KDFont::SmallFont->glyphSize().height() + 5; - ctx->drawString(Messages::epsilonWarningText6, KDPoint(initPos, y), KDFont::SmallFont, KDColorWhite, KDColorRed); -} - -} diff --git a/bootloader/interface.h b/bootloader/interface.h deleted file mode 100644 index b89c3ce2513..00000000000 --- a/bootloader/interface.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef BOOTLOADER_INTERFACE -#define BOOTLOADER_INTERFACE - -#include -#include -#include - -namespace Bootloader { -class Interface { - -public: - static void drawImage(KDContext * ctx, const Image * image, int offset); - static void drawLoading(); - static void drawHeader(); - static void drawMenu(); - static void drawFlasher(); - static void drawAbout(); - static void drawCrash(const char * error); - static void drawRecovery(); - static void drawInstallerSelection(); - static void drawBootloaderUpdate(); - static void drawEpsilonAdvertisement(); - -}; - -} - -#endif \ No newline at end of file diff --git a/bootloader/interface/menus/about.cpp b/bootloader/interface/menus/about.cpp new file mode 100644 index 00000000000..7e1d176bb44 --- /dev/null +++ b/bootloader/interface/menus/about.cpp @@ -0,0 +1,20 @@ +#include "about.h" +#include + +Bootloader::AboutMenu::AboutMenu() : Menu(KDColorBlack, KDColorWhite, Messages::aboutMenuTitle, Messages::bootloaderVersion) { + setup(); +} + +void Bootloader::AboutMenu::setup() { + m_default_columns[0] = Column(Messages::aboutMessage1, k_small_font, 0, true); + m_default_columns[1] = Column(Messages::aboutMessage2, k_small_font, 0, true); + m_default_columns[2] = Column(Messages::aboutMessage3, k_small_font, 0, true); + m_default_columns[3] = Column(Messages::aboutMessage4, k_small_font, 0, true); + m_default_columns[4] = Column(Messages::aboutMessage5, k_small_font, 0, true); + + m_columns[0] = ColumnBinder(&m_default_columns[0]); + m_columns[1] = ColumnBinder(&m_default_columns[1]); + m_columns[2] = ColumnBinder(&m_default_columns[2]); + m_columns[3] = ColumnBinder(&m_default_columns[3]); + m_columns[4] = ColumnBinder(&m_default_columns[4]); +} \ No newline at end of file diff --git a/bootloader/interface/menus/about/about.h b/bootloader/interface/menus/about.h similarity index 61% rename from bootloader/interface/menus/about/about.h rename to bootloader/interface/menus/about.h index 96dc86c111a..4f3df1c91d0 100644 --- a/bootloader/interface/menus/about/about.h +++ b/bootloader/interface/menus/about.h @@ -1,5 +1,5 @@ -#ifndef _BOOTLOADER_INTERFACE_ABOUT_ABOUT_H_ -#define _BOOTLOADER_INTERFACE_ABOUT_ABOUT_H_ +#ifndef _BOOTLOADER_INTERFACE_ABOUT_H_ +#define _BOOTLOADER_INTERFACE_ABOUT_H_ #include @@ -9,7 +9,7 @@ namespace Bootloader { AboutMenu(); void setup() override; - + void post_open() override {}; }; } diff --git a/bootloader/interface/menus/about/about.cpp b/bootloader/interface/menus/about/about.cpp deleted file mode 100644 index 4c73b95bc9e..00000000000 --- a/bootloader/interface/menus/about/about.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "about.h" - -Bootloader::AboutMenu::AboutMenu() : Menu(KDColorBlack, KDColorWhite, Messages::aboutMenuTitle, Messages::bootloaderVersion) { - setup(); -} - -void Bootloader::AboutMenu::setup() { - m_colomns[0] = Colomn("This is the bootloader of", k_small_font, 0, true); - m_colomns[1] = Colomn("the Upsilon Calculator.", k_small_font, 0, true); - m_colomns[2] = Colomn("It is used to install", k_small_font, 0, true); - m_colomns[3] = Colomn("and select the OS", k_small_font, 0, true); - m_colomns[4] = Colomn("to boot.", k_small_font, 0, true); -} \ No newline at end of file diff --git a/bootloader/interface/menus/crash.cpp b/bootloader/interface/menus/crash.cpp new file mode 100644 index 00000000000..d7ed3a19eed --- /dev/null +++ b/bootloader/interface/menus/crash.cpp @@ -0,0 +1,22 @@ +#include "crash.h" + +Bootloader::CrashMenu::CrashMenu(const char * err) : Menu(KDColorBlack, KDColorWhite, Bootloader::Messages::bootloaderCrashTitle, Bootloader::Messages::mainTitle), m_error(err) { + setup(); +} + +void Bootloader::CrashMenu::setup() { + m_default_columns[0] = Column(m_error, k_large_font, 0, true); + m_default_columns[1] = Column(Bootloader::Messages::bootloaderCrashMessage1, k_small_font, 0, true); + m_default_columns[2] = Column(Bootloader::Messages::bootloaderCrashMessage2, k_small_font, 0, true); + + m_columns[0] = ColumnBinder(&m_default_columns[0]); + m_columns[1] = ColumnBinder(&m_default_columns[1]); + m_columns[2] = ColumnBinder(&m_default_columns[2]); +} + +void Bootloader::CrashMenu::post_open() { + // We override the open method + for (;;) { + // Infinite loop + } +} \ No newline at end of file diff --git a/bootloader/interface/menus/crash.h b/bootloader/interface/menus/crash.h new file mode 100644 index 00000000000..c73c3dbd6cd --- /dev/null +++ b/bootloader/interface/menus/crash.h @@ -0,0 +1,19 @@ +#ifndef _BOOTLOADER_INTERFACE_MENUS_CRASH_H_ +#define _BOOTLOADER_INTERFACE_MENUS_CRASH_H_ + +#include + +namespace Bootloader { + class CrashMenu : public Menu { + public: + CrashMenu(const char * error); + + void setup() override; + void post_open() override; + + private: + const char * m_error; + }; +} + +#endif \ No newline at end of file diff --git a/bootloader/interface/menus/dfu.cpp b/bootloader/interface/menus/dfu.cpp new file mode 100644 index 00000000000..537108f3cae --- /dev/null +++ b/bootloader/interface/menus/dfu.cpp @@ -0,0 +1,37 @@ +#include "dfu.h" +#include +#include + +Bootloader::DfuMenu::DfuMenu(const char * text, const USBData * data) : Menu(KDColorBlack, KDColorWhite, Messages::dfuTitle, Messages::mainTitle), m_submenu_text(text), m_data(data) { + setup(); +} + +void Bootloader::DfuMenu::setup() { + m_default_columns[0] = Column(m_submenu_text, k_small_font, 0, true); + + m_columns[0] = ColumnBinder(&m_default_columns[0]); +} + +void Bootloader::DfuMenu::post_open() { + // We override the open method + if (!m_data->getData().isProtectedInternal() && m_data->getData().isProtectedExternal()) { + // Because we want to flash the internal, we will jump into the stm32 bootloader + Bootloader::Boot::jumpToInternalBootloader(); + return; // We never reach this point + } + for (;;) { + Ion::USB::enable(); + do { + uint64_t scan = Ion::Keyboard::scan(); + if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { + Ion::USB::disable(); + forceExit(); + return; + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { + Ion::Power::standby(); + return; + } + } while (!Ion::USB::isEnumerated()); + Ion::USB::DFU(true, (void *)m_data); + } +} \ No newline at end of file diff --git a/bootloader/interface/menus/dfu.h b/bootloader/interface/menus/dfu.h new file mode 100644 index 00000000000..ddb957fcb33 --- /dev/null +++ b/bootloader/interface/menus/dfu.h @@ -0,0 +1,21 @@ +#ifndef _BOOTLOADER_INTERFACE_MENUS_DFU_H_ +#define _BOOTLOADER_INTERFACE_MENUS_DFU_H_ + +#include +#include + +namespace Bootloader { + class DfuMenu : public Menu { + public: + DfuMenu(const char * submenu, const USBData * usbData); + + void setup() override; + void post_open() override; + + private: + const char * m_submenu_text; + const USBData * m_data; + }; +} + +#endif \ No newline at end of file diff --git a/bootloader/interface/menus/home.cpp b/bootloader/interface/menus/home.cpp new file mode 100644 index 00000000000..7805d799608 --- /dev/null +++ b/bootloader/interface/menus/home.cpp @@ -0,0 +1,144 @@ +#include "home.h" +#include +#include +#include +#include + +Bootloader::AboutMenu * Bootloader::HomeMenu::aboutMenu() { + static AboutMenu * aboutMenu = new AboutMenu(); + return aboutMenu; +} + +Bootloader::InstallerMenu * Bootloader::HomeMenu::installerMenu() { + static InstallerMenu * installerMenu = new InstallerMenu(); + return installerMenu; +} + +Bootloader::HomeMenu::HomeMenu() : Menu(KDColorBlack, KDColorWhite, Messages::homeTitle, Messages::mainTitle) { + setup(); +} + +bool slotA_submenu() { + if (Bootloader::Slot::isFullyValid(Bootloader::Slot::A())) { + Bootloader::Boot::bootSlot(Bootloader::Slot::A()); + return true; + } + return false; +} + +bool slotKhi_submenu() { + if (Bootloader::Slot::isFullyValid(Bootloader::Slot::Khi())) { + Bootloader::Boot::bootSlot(Bootloader::Slot::Khi()); + return true; + } + return false; +} + +bool slotB_submenu() { + if (Bootloader::Slot::isFullyValid(Bootloader::Slot::B())) { + Bootloader::Boot::bootSlot(Bootloader::Slot::B()); + return true; + } + return false; +} + +bool installer_submenu() { + Bootloader::HomeMenu::installerMenu()->open(); + return true; +} + +bool about_submenu() { + Bootloader::HomeMenu::aboutMenu()->open(); + return true; +} + +const char * Bootloader::HomeMenu::slotA_text() { + return Slot::isFullyValid(Slot::A()) ? Messages::homeSlotASubmenu : Messages::invalidSlot; +} + +const char * Bootloader::HomeMenu::slotA_kernel_text() { + return Slot::isFullyValid(Slot::A()) ? Slot::A().kernelHeader()->patchLevel() : nullptr; +} + +const char * Bootloader::HomeMenu::slotA_version_text() { + return Slot::isFullyValid(Slot::A()) ? Slot::A().userlandHeader()->isOmega() && Slot::A().userlandHeader()->isUpsilon() ? Slot::A().userlandHeader()->upsilonVersion() : Slot::A().userlandHeader()->isOmega() ? Slot::A().userlandHeader()->omegaVersion() : Slot::A().kernelHeader()->version() : nullptr; +} + +const char * Bootloader::HomeMenu::slotA_os_text() { + if (Slot::isFullyValid(Slot::A())) { + if (Slot::A().userlandHeader()->isOmega() && Slot::A().userlandHeader()->isUpsilon()) { + return Messages::upsilonSlot; + } else if (Slot::A().userlandHeader()->isOmega()) { + return Messages::omegaSlot; + } else { + return Messages::epsilonSlot; + } + } + return nullptr; +} + +const char * Bootloader::HomeMenu::slotKhi_text() { + return Slot::isFullyValid(Slot::Khi()) ? Messages:: homeSlotKhiSubmenu : Messages::invalidSlot; +} + +const char * Bootloader::HomeMenu::slotKhi_kernel_text() { + return Slot::isFullyValid(Slot::Khi()) ? Slot::Khi().kernelHeader()->patchLevel() : nullptr; +} + +const char * Bootloader::HomeMenu::slotKhi_os_text() { + if (Slot::isFullyValid(Slot::Khi())) { + if (Slot::Khi().userlandHeader()->isOmega() && Slot::Khi().userlandHeader()->isUpsilon()) { + return Messages::upsilonSlot; + } else if (Slot::Khi().userlandHeader()->isOmega()) { + return Messages::omegaSlot; + } else { + return Messages::epsilonSlot; + } + } + return nullptr; +} + +const char * Bootloader::HomeMenu::slotKhi_version_text() { + return Slot::isFullyValid(Slot::Khi()) ? Slot::Khi().userlandHeader()->isOmega() && Slot::Khi().userlandHeader()->isUpsilon() ? Slot::Khi().userlandHeader()->upsilonVersion() : Slot::Khi().userlandHeader()->isOmega() ? Slot::Khi().userlandHeader()->omegaVersion() : Slot::Khi().kernelHeader()->version() : nullptr; +} + +const char * Bootloader::HomeMenu::slotB_text() { + return Slot::isFullyValid(Slot::B()) ? Messages::homeSlotBSubmenu : Messages::invalidSlot; +} + +const char * Bootloader::HomeMenu::slotB_kernel_text() { + return Slot::isFullyValid(Slot::B()) ? Slot::B().kernelHeader()->patchLevel() : nullptr; +} + +const char * Bootloader::HomeMenu::slotB_os_text() { + if (Slot::isFullyValid(Slot::B())) { + if (Slot::B().userlandHeader()->isOmega() && Slot::B().userlandHeader()->isUpsilon()) { + return Messages::upsilonSlot; + } else if (Slot::B().userlandHeader()->isOmega()) { + return Messages::omegaSlot; + } else { + return Messages::epsilonSlot; + } + } + return nullptr; +} + +const char * Bootloader::HomeMenu::slotB_version_text() { + return Slot::isFullyValid(Slot::B()) ? Slot::B().userlandHeader()->isOmega() && Slot::B().userlandHeader()->isUpsilon() ? Slot::B().userlandHeader()->upsilonVersion() : Slot::B().userlandHeader()->isOmega() ? Slot::B().userlandHeader()->omegaVersion() : Slot::B().kernelHeader()->version() : nullptr; +} + +void Bootloader::HomeMenu::setup() { + m_slot_columns[0] = SlotColumn(slotA_text(), slotA_kernel_text(), slotA_os_text(), slotA_version_text(), Ion::Keyboard::Key::One, k_small_font, 10, false, &slotA_submenu); + m_slot_columns[1] = SlotColumn(slotKhi_text(), slotKhi_kernel_text(), slotKhi_os_text(), slotKhi_version_text(), Ion::Keyboard::Key::Two, k_small_font, 10, false, &slotKhi_submenu); + m_slot_columns[2] = SlotColumn(slotB_text(), slotB_kernel_text(), slotB_os_text(), slotB_version_text(), Ion::Keyboard::Key::Three, k_small_font, 10, false, &slotB_submenu); + m_default_columns[0] = Column(Messages::homeInstallerSubmenu, Ion::Keyboard::Key::Four, k_small_font, 10, false, &installer_submenu); + m_default_columns[1] = Column(Messages::homeAboutSubmenu, Ion::Keyboard::Key::Five, k_small_font, 10, false, &about_submenu); + + + m_columns[0] = ColumnBinder(&m_slot_columns[0]); + m_columns[1] = ColumnBinder(&m_slot_columns[1]); + m_columns[2] = ColumnBinder(&m_slot_columns[2]); + m_columns[3] = ColumnBinder(&m_default_columns[0]); + m_columns[4] = ColumnBinder(&m_default_columns[1]); + +} \ No newline at end of file diff --git a/bootloader/interface/menus/home.h b/bootloader/interface/menus/home.h new file mode 100644 index 00000000000..00de3edc3ba --- /dev/null +++ b/bootloader/interface/menus/home.h @@ -0,0 +1,36 @@ +#ifndef _BOOTLOADER_INTERFACE_MENUS_HOME_H_ +#define _BOOTLOADER_INTERFACE_MENUS_HOME_H_ + +#include +#include +#include + +namespace Bootloader { + class HomeMenu : public Menu { + public: + HomeMenu(); + + void setup() override; + void post_open() override {}; + + static AboutMenu * aboutMenu(); + static InstallerMenu * installerMenu(); + + private: + const char * slotA_text(); + const char * slotA_kernel_text(); + const char * slotA_os_text(); + const char * slotA_version_text(); + const char * slotKhi_text(); + const char * slotKhi_kernel_text(); + const char * slotKhi_os_text(); + const char * slotKhi_version_text(); + const char * slotB_text(); + const char * slotB_kernel_text(); + const char * slotB_os_text(); + const char * slotB_version_text(); + + }; +} + +#endif diff --git a/bootloader/interface/menus/home/home.cpp b/bootloader/interface/menus/home/home.cpp deleted file mode 100644 index 2529547e5c3..00000000000 --- a/bootloader/interface/menus/home/home.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "home.h" -#include -#include - -Bootloader::AboutMenu * Bootloader::HomeMenu::aboutMenu() { - static AboutMenu * aboutMenu = new AboutMenu(); - return aboutMenu; -} - -Bootloader::HomeMenu::HomeMenu() : Menu(KDColorBlack, KDColorWhite, Messages::mainMenuTitle, Messages::mainTitle) { - setup(); -} - -bool slotA_submenu() { - if (Bootloader::Slot::isFullyValid(Bootloader::Slot::A())) { - - } - return false; -} - -bool slotKhi_submenu() { - if (Bootloader::Slot::isFullyValid(Bootloader::Slot::Khi())) { - - } - return false; -} - -bool slotB_submenu() { - if (Bootloader::Slot::isFullyValid(Bootloader::Slot::B())) { - - } - return false; -} - -bool installer_submenu() { - return false; -} - -bool about_submenu() { - Bootloader::HomeMenu::aboutMenu()->open(); - return true; -} - -const char * Bootloader::HomeMenu::slotA_text() { - return Slot::isFullyValid(Slot::A()) ? "1 - Slot A" : Messages::invalidSlot; -} - -const char * Bootloader::HomeMenu::slotKhi_text() { - return Slot::isFullyValid(Slot::Khi()) ? "2 - Slot Khi" : Messages::invalidSlot; -} - -const char * Bootloader::HomeMenu::slotB_text() { - return Slot::isFullyValid(Slot::B()) ? "3 - Slot B" : Messages::invalidSlot; -} - -void Bootloader::HomeMenu::setup() { - m_colomns[0] = Colomn(slotA_text(), Ion::Keyboard::Key::One, k_small_font, 30, false, &slotA_submenu); - m_colomns[1] = Colomn(slotKhi_text(), Ion::Keyboard::Key::Two, k_small_font, 30, false, &slotKhi_submenu); - m_colomns[2] = Colomn(slotB_text(), Ion::Keyboard::Key::Three, k_small_font, 30, false, &slotB_submenu); - m_colomns[3] = Colomn("4- installer", Ion::Keyboard::Key::Four, k_small_font, 30, false, &installer_submenu); - m_colomns[4] = Colomn("5- about", Ion::Keyboard::Key::Five, k_small_font, 30, false, &about_submenu); -} \ No newline at end of file diff --git a/bootloader/interface/menus/home/home.h b/bootloader/interface/menus/home/home.h deleted file mode 100644 index 6f6c4303779..00000000000 --- a/bootloader/interface/menus/home/home.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef _BOOTLOADER_INTERFACE_HOME_HOME_H_ -#define _BOOTLOADER_INTERFACE_HOME_HOME_H_ - -#include -#include - -namespace Bootloader { - class HomeMenu : public Menu { - public: - HomeMenu(); - - void setup() override; - static AboutMenu * aboutMenu(); - - private: - const char * slotA_text(); - const char * slotKhi_text(); - const char * slotB_text(); - - }; -} - -#endif diff --git a/bootloader/interface/menus/installer.cpp b/bootloader/interface/menus/installer.cpp new file mode 100644 index 00000000000..a82b4aebefc --- /dev/null +++ b/bootloader/interface/menus/installer.cpp @@ -0,0 +1,41 @@ +#include "installer.h" +#include +#include + +#include + +Bootloader::DfuMenu * Bootloader::InstallerMenu::SlotsDFU() { + USBData data = USBData::DEFAULT(); + static DfuMenu * slotsDfu = new DfuMenu(Messages::dfuSlotsUpdate, &data); + return slotsDfu; +} + +Bootloader::DfuMenu * Bootloader::InstallerMenu::BootloaderDFU() { + USBData data = USBData::BOOTLOADER_UPDATE(); + static DfuMenu * bootloaderDfu = new DfuMenu(Messages::dfuBootloaderUpdate, &data); + return bootloaderDfu; +} + +Bootloader::InstallerMenu::InstallerMenu() : Menu(KDColorBlack, KDColorWhite, Messages::installerTitle, Messages::mainTitle) { + setup(); +} + +bool slots_submenu() { + Bootloader::InstallerMenu::SlotsDFU()->open(); + return true; +} + +bool bootloader_submenu() { + Bootloader::InstallerMenu::BootloaderDFU()->open(); + return true; +} + +void Bootloader::InstallerMenu::setup() { + m_default_columns[0] = Column(Messages::installerText1, k_large_font, 0, true); + m_default_columns[1] = Column(Messages::installerSlotsSubmenu, Ion::Keyboard::Key::One, k_small_font, 30, false, &slots_submenu); + m_default_columns[2] = Column(Messages::installerBootloaderSubmenu, Ion::Keyboard::Key::Two, k_small_font, 30, false, &bootloader_submenu); + + m_columns[0] = ColumnBinder(&m_default_columns[0]); + m_columns[1] = ColumnBinder(&m_default_columns[1]); + m_columns[2] = ColumnBinder(&m_default_columns[2]); +} \ No newline at end of file diff --git a/bootloader/interface/menus/installer.h b/bootloader/interface/menus/installer.h new file mode 100644 index 00000000000..b68a49aab25 --- /dev/null +++ b/bootloader/interface/menus/installer.h @@ -0,0 +1,20 @@ +#ifndef _BOOTLOADER_INTERFACE_MENUS_INSTALLER_H_ +#define _BOOTLOADER_INTERFACE_MENUS_INSTALLER_H_ + +#include +#include + +namespace Bootloader { + class InstallerMenu : public Menu { + public: + InstallerMenu(); + + void setup() override; + void post_open() override {}; + + static DfuMenu * SlotsDFU(); + static DfuMenu * BootloaderDFU(); + }; +} + +#endif \ No newline at end of file diff --git a/bootloader/interface/menus/installer/installer.h b/bootloader/interface/menus/installer/installer.h deleted file mode 100644 index be9f49fb415..00000000000 --- a/bootloader/interface/menus/installer/installer.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef _BOOTLOADER_INTERFACE_MENUS_INSTALLER_INSTALLER_H_ -#define _BOOTLOADER_INTERFACE_MENUS_INSTALLER_INSTALLER_H_ - -#include - -namespace Bootloader { - class InstallerMenu : public Menu { - public: - InstallerMenu(); - - void setup() override; - }; -} - -#endif \ No newline at end of file diff --git a/bootloader/interface/menus/slot_recovery.cpp b/bootloader/interface/menus/slot_recovery.cpp new file mode 100644 index 00000000000..d026f2be33b --- /dev/null +++ b/bootloader/interface/menus/slot_recovery.cpp @@ -0,0 +1,39 @@ +#include "slot_recovery.h" +#include + +Bootloader::SlotRecoveryMenu::SlotRecoveryMenu(USBData * usb) : Menu(KDColorBlack, KDColorWhite, Messages::recoveryTitle, Messages::mainTitle), m_data(usb) { + setup(); +} + +void Bootloader::SlotRecoveryMenu::setup() { + m_default_columns[0] = Column(Messages::recoveryMessage1, k_small_font, 0, true); + m_default_columns[1] = Column(Messages::recoveryMessage2, k_small_font, 0, true); + m_default_columns[2] = Column(Messages::recoveryMessage3, k_small_font, 0, true); + m_default_columns[3] = Column(Messages::recoveryMessage4, k_small_font, 0, true); + m_default_columns[4] = Column(Messages::recoveryMessage5, k_small_font, 0, true); + + m_columns[0] = ColumnBinder(&m_default_columns[0]); + m_columns[1] = ColumnBinder(&m_default_columns[1]); + m_columns[2] = ColumnBinder(&m_default_columns[2]); + m_columns[3] = ColumnBinder(&m_default_columns[3]); + m_columns[4] = ColumnBinder(&m_default_columns[4]); +} + +void Bootloader::SlotRecoveryMenu::post_open() { + // We override the open method + for (;;) { + Ion::USB::enable(); + do { + uint64_t scan = Ion::Keyboard::scan(); + if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { + Ion::USB::disable(); + forceExit(); + return; + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { + Ion::Power::standby(); + return; + } + } while (!Ion::USB::isEnumerated()); + Ion::USB::DFU(true, (void *)m_data); + } +} \ No newline at end of file diff --git a/bootloader/interface/menus/slot_recovery.h b/bootloader/interface/menus/slot_recovery.h new file mode 100644 index 00000000000..452868dee18 --- /dev/null +++ b/bootloader/interface/menus/slot_recovery.h @@ -0,0 +1,19 @@ +#ifndef _BOOTLOADER_INTERFACE_MENU_SLOT_RECOVERY_H +#define _BOOTLOADER_INTERFACE_MENU_SLOT_RECOVERY_H + +#include +#include + +namespace Bootloader { + class SlotRecoveryMenu : public Menu { + public: + SlotRecoveryMenu(USBData * usbData); + + void setup() override; + void post_open() override; + private: + const USBData * m_data; + }; +} + +#endif \ No newline at end of file diff --git a/bootloader/interface/menus/warning.cpp b/bootloader/interface/menus/warning.cpp new file mode 100644 index 00000000000..316dfde69d0 --- /dev/null +++ b/bootloader/interface/menus/warning.cpp @@ -0,0 +1,35 @@ +#include "warning.h" +#include +#include + +Bootloader::WarningMenu::WarningMenu() : Menu(KDColorWhite, KDColorRed, Messages::epsilonWarningTitle, Messages::mainTitle, false, 3) { + setup(); +} + +bool proceed() { + Bootloader::Boot::bootSelectedSlot(); + return true; +} + +bool backoff() { + if (Bootloader::Boot::config()->slot() != nullptr) { + Bootloader::Boot::config()->clearSlot(); + } + return true; +} + +void Bootloader::WarningMenu::setup() { + m_default_columns[0] = Column(Messages::epsilonWarningMessage1, k_small_font, 0, true); + m_default_columns[1] = Column(Messages::epsilonWarningMessage2, k_small_font, 0, true); + m_default_columns[2] = Column(Messages::epsilonWarningMessage3, k_small_font, 0, true); + m_default_columns[3] = Column(Messages::epsilonWarningMessage4, k_small_font, 0, true); + m_default_columns[4] = Column(Messages::epsilonWarningMessage5, Ion::Keyboard::Key::EXE, k_small_font, 0, true, &proceed); + m_default_columns[5] = Column(Messages::epsilonWarningMessage6, Ion::Keyboard::Key::Back, k_small_font, 0, true, &backoff); + + m_columns[0] = ColumnBinder(&m_default_columns[0]); + m_columns[1] = ColumnBinder(&m_default_columns[1]); + m_columns[2] = ColumnBinder(&m_default_columns[2]); + m_columns[3] = ColumnBinder(&m_default_columns[3]); + m_columns[4] = ColumnBinder(&m_default_columns[4]); + m_columns[5] = ColumnBinder(&m_default_columns[5]); +} \ No newline at end of file diff --git a/bootloader/interface/menus/warning.h b/bootloader/interface/menus/warning.h new file mode 100644 index 00000000000..bc98a5640fa --- /dev/null +++ b/bootloader/interface/menus/warning.h @@ -0,0 +1,17 @@ +#ifndef _BOOTLOADER_INTERFACE_MENUS_WARNING_H_ +#define _BOOTLOADER_INTERFACE_MENUS_WARNING_H_ + +#include +#include + +namespace Bootloader { + class WarningMenu : public Menu { + public: + WarningMenu(); + + void setup() override; + void post_open() override {}; + }; +} + +#endif \ No newline at end of file diff --git a/bootloader/interface/src/menu.cpp b/bootloader/interface/src/menu.cpp index 6b197e4d121..1d81287ae30 100644 --- a/bootloader/interface/src/menu.cpp +++ b/bootloader/interface/src/menu.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -9,7 +9,7 @@ const Ion::Keyboard::Key Bootloader::Menu::k_breaking_keys[]; void Bootloader::Menu::setup() { - // Here we add the colomns to the menu. + // Here we add the columns to the menu. } void Bootloader::Menu::open(bool noreturn) { @@ -17,8 +17,10 @@ void Bootloader::Menu::open(bool noreturn) { uint64_t scan = 0; bool exit = false; + + post_open(); - while(!exit) { + while(!exit && !m_forced_exit) { scan = Ion::Keyboard::scan(); exit = !handleKey(scan); if (noreturn) { @@ -40,13 +42,17 @@ void Bootloader::Menu::showMenu() { ctx->drawString(m_title, KDPoint(x, y), k_large_font, m_foreground, m_background); y += largeFontHeight() + 10; - //TODO: center the colomns if m_centerY is true + //TODO: center the columns if m_centerY is true - for (Colomn & colomn : m_colomns) { - if (colomn.isNull()) { - break; + for (ColumnBinder column : m_columns) { + if (column.isNull()) { + continue; + } + if (column.type() == ColumnType::SLOT) { + y += ((SlotColumn *)column.getColumn())->draw(ctx, y, m_background, m_foreground) + m_margin; + } else if (column.type() == ColumnType::DEFAULT) { + y += ((Column *)column.getColumn())->draw(ctx, y, m_background, m_foreground) + m_margin; } - y += colomn.draw(ctx, y, m_background, m_foreground) + k_colomns_margin; } if (m_bottom != nullptr) { @@ -66,26 +72,32 @@ bool Bootloader::Menu::handleKey(uint64_t key) { Ion::Power::standby(); return false; } - for (Colomn & colomn : this->m_colomns) { - if (colomn.isNull() || !colomn.isClickable()) { + for (ColumnBinder column : m_columns) { + if (column.isNull()) { continue; } else { - if (colomn.didHandledEvent(key)) { - redraw(); + if (column.type() == ColumnType::SLOT) { + if (((SlotColumn *)column.getColumn())->didHandledEvent(key)) { + redraw(); + } + } else if (column.type() == ColumnType::DEFAULT) { + if (((Column *)column.getColumn())->didHandledEvent(key)) { + redraw(); + } } } } return true; } -bool Bootloader::Menu::Colomn::didHandledEvent(uint64_t key) { +bool Bootloader::Menu::Column::didHandledEvent(uint64_t key) { if (isMyKey(key) && isClickable()) { return m_callback(); } return false; } -int Bootloader::Menu::Colomn::draw(KDContext * ctx, int y, KDColor background, KDColor foreground) { +int Bootloader::Menu::Column::draw(KDContext * ctx, int y, KDColor background, KDColor foreground) { int x = m_extraX; if (m_center) { x += Bootloader::Menu::calculateCenterX(m_text, m_font->glyphSize().width()); @@ -93,3 +105,32 @@ int Bootloader::Menu::Colomn::draw(KDContext * ctx, int y, KDColor background, K ctx->drawString(m_text, KDPoint(x, y), m_font, foreground, background); return m_font->glyphSize().height(); } + +int Bootloader::Menu::SlotColumn::draw(KDContext * ctx, int y, KDColor background, KDColor foreground) { + int x = m_extraX; + + int width = strlen(m_text); + if (m_kernalPatch != nullptr) { + width += strlen(m_kernalPatch) + m_font->glyphSize().width(); + } + if (m_osType != nullptr) { + width += strlen(m_osType) + m_font->glyphSize().width(); + } + if (m_center) { + x += Bootloader::Menu::getScreen().width() - width * m_font->glyphSize().width(); + } + ctx->drawString(m_text, KDPoint(x, y), m_font, foreground, background); + x += strlen(m_text) * m_font->glyphSize().width() + m_font->glyphSize().width(); + if (m_kernalPatch != nullptr) { + ctx->drawString(m_kernalPatch, KDPoint(x, y), m_font, foreground, background); + } + x += strlen(m_kernalPatch) * m_font->glyphSize().width() + m_font->glyphSize().width(); + if (m_osType != nullptr) { + ctx->drawString(m_osType, KDPoint(x, y), m_font, foreground, background); + } + x += strlen(m_osType) * m_font->glyphSize().width() + m_font->glyphSize().width(); + if (m_kernelVersion != nullptr) { + ctx->drawString(m_kernelVersion, KDPoint(x, y), m_font, foreground, background); + } + return m_font->glyphSize().height(); +} diff --git a/bootloader/interface/src/menu.h b/bootloader/interface/src/menu.h index 46cb5601741..da591a02265 100644 --- a/bootloader/interface/src/menu.h +++ b/bootloader/interface/src/menu.h @@ -2,48 +2,82 @@ #define _BOOTLOADER_MENU_H_ #include -#include +#include #include namespace Bootloader { class Menu { public: - Menu() : m_colomns(), m_background(KDColorWhite), m_title(Messages::mainTitle), m_foreground(KDColorBlack), m_bottom(nullptr), m_centerY(false) { - setup(); - }; - Menu(KDColor forground, KDColor background, const char * title) : m_colomns(), m_background(background), m_title(title), m_foreground(forground), m_bottom(nullptr), m_centerY(false) { - setup(); - }; - Menu(KDColor forground, KDColor background, const char * title, const char * bottom) : m_colomns(), m_background(background), m_title(title), m_foreground(forground), m_bottom(bottom), m_centerY(false) { - setup(); - }; - Menu(KDColor forground, KDColor background, const char * title, const char * bottom, bool centerY) : m_colomns(), m_background(background), m_title(title), m_foreground(forground), m_bottom(bottom), m_centerY(centerY) { + Menu() : Menu(KDColorBlack, KDColorWhite, Messages::mainTitle) { }; + Menu(KDColor forground, KDColor background, const char * title) : Menu(forground, background, title, nullptr) {}; + Menu(KDColor forground, KDColor background, const char * title, const char * bottom) : Menu(forground, background, title, bottom, false) {}; + Menu(KDColor forground, KDColor background, const char * title, const char * bottom, bool centerY) : Menu(forground, background, title, bottom, centerY, k_columns_margin) {}; + Menu(KDColor forground, KDColor background, const char * title, const char * bottom, bool centerY, int margin) : m_columns(), m_default_columns(), m_slot_columns(), m_background(background), m_title(title), m_foreground(forground), m_bottom(bottom), m_centerY(centerY), m_forced_exit(false), m_margin(margin) { setup(); } + static const int k_columns_margin = 5; virtual void setup() = 0; + virtual void post_open() = 0; + + enum ColumnType { + DEFAULT, + SLOT + }; - class Colomn { + class Column { public: - Colomn() : m_text(nullptr), m_key(Ion::Keyboard::Key::None), m_font(KDFont::SmallFont), m_extraX(0), m_center(false), m_callback(nullptr) {}; - Colomn(const char * t, Ion::Keyboard::Key k, const KDFont * font, int extraX, bool center, bool(*pointer)()) : m_text(t), m_key(k), m_font(font), m_extraX(extraX), m_center(center), m_callback(pointer), m_clickable(true) {}; - Colomn(const char * t, const KDFont * font, int extraX, bool center) : m_text(t), m_key(Ion::Keyboard::Key::None), m_font(font), m_extraX(extraX), m_center(center), m_callback(nullptr), m_clickable(false) {}; + Column() : m_text(nullptr), m_key(Ion::Keyboard::Key::None), m_font(KDFont::SmallFont), m_extraX(0), m_center(false), m_callback(nullptr), m_clickable(false) {}; + + Column(const char * t, Ion::Keyboard::Key k, const KDFont * font, int extraX, bool center, bool(*pointer)()) : m_text(t), m_key(k), m_font(font), m_extraX(extraX), m_center(center), m_callback(pointer), m_clickable(true) {}; + Column(const char * t, const KDFont * font, int extraX, bool center) : m_text(t), m_key(Ion::Keyboard::Key::None), m_font(font), m_extraX(extraX), m_center(center), m_callback(nullptr), m_clickable(false) {}; bool isNull() const { return m_text == nullptr; }; bool isClickable() const { return m_clickable; }; bool didHandledEvent(uint64_t key); - int draw(KDContext * ctx, int y, KDColor background, KDColor foreground); + virtual int draw(KDContext * ctx, int y, KDColor background, KDColor foreground); + virtual int columnType() { return ColumnType::DEFAULT; }; private: bool isMyKey(uint64_t key) const { return Ion::Keyboard::State(m_key) == key; }; - + protected: const char * m_text; Ion::Keyboard::Key m_key; const KDFont * m_font; int m_extraX; bool m_center; - bool m_clickable; bool (*m_callback)(); + bool m_clickable; + }; + + class SlotColumn : public Column { + public: + SlotColumn() : Column(), m_kernalPatch(nullptr), m_osType(nullptr), m_kernelVersion(nullptr) {}; + + SlotColumn(const char * t, Ion::Keyboard::Key k, const KDFont * font, int extraX, bool center, bool(*pointer)()) : Column(t, k, font, extraX, center, pointer), m_kernalPatch(nullptr), m_osType(nullptr), m_kernelVersion(nullptr) {}; + SlotColumn(const char * t, const char * k, const char * o, const char * kernelV, Ion::Keyboard::Key key, const KDFont * font, int extraX, bool center, bool(*pointer)()) : Column(t, key, font, extraX, center, pointer), m_kernalPatch(k), m_osType(o), m_kernelVersion(kernelV) {}; + + int draw(KDContext * ctx, int y, KDColor background, KDColor foreground) override; + virtual int columnType() { return ColumnType::SLOT; }; + + private: + const char * m_kernalPatch; + const char * m_osType; + const char * m_kernelVersion; + }; + + class ColumnBinder { + public: + ColumnBinder() : m_pointer(nullptr), m_type(ColumnType::DEFAULT) {}; + ColumnBinder(Column * pointer) : m_pointer(pointer), m_type(ColumnType::DEFAULT) {}; + ColumnBinder(SlotColumn * pointer) : m_pointer(pointer), m_type(ColumnType::SLOT) {}; + + bool isNull() const { return m_pointer == nullptr; }; + void * getColumn() const { return m_pointer; }; + ColumnType type() const { return m_type; }; + private: + void * m_pointer; + ColumnType m_type; }; void open(bool noreturn = false); @@ -56,9 +90,11 @@ namespace Bootloader { static const KDRect getScreen() { return KDRect(0, 0, 320, 240); }; + protected: + void forceExit() { m_forced_exit = true; }; + private: - static const int k_max_colomns = 5; - static const int k_colomns_margin = 5; + static const int k_max_columns = 6; static constexpr Ion::Keyboard::Key k_breaking_keys[] = {Ion::Keyboard::Key::Back, Ion::Keyboard::Key::Home}; @@ -72,12 +108,18 @@ namespace Bootloader { void showMenu(); protected: - Colomn m_colomns[k_max_colomns]; + ColumnBinder m_columns[k_max_columns]; + // Columns Storage + Column m_default_columns[k_max_columns]; + SlotColumn m_slot_columns[k_max_columns]; KDColor m_background; KDColor m_foreground; const char * m_title; const char * m_bottom; bool m_centerY; + int m_margin; + private: + bool m_forced_exit; }; } diff --git a/bootloader/interface/static/interface.cpp b/bootloader/interface/static/interface.cpp new file mode 100644 index 00000000000..817b6f2c087 --- /dev/null +++ b/bootloader/interface/static/interface.cpp @@ -0,0 +1,68 @@ + +#include +#include +#include +#include +#include + +#include + +namespace Bootloader { + +void Interface::drawImage(KDContext * ctx, const Image * image, int offset) { + const uint8_t * data; + size_t size; + size_t pixelBufferSize; + + if (image != nullptr) { + data = image->compressedPixelData(); + size = image->compressedPixelDataSize(); + pixelBufferSize = image->width() * image->height(); + } else { + return; + } + + KDColor pixelBuffer[4000]; + assert(pixelBufferSize <= 4000); + assert(Ion::stackSafe()); // That's a VERY big buffer we're allocating on the stack + + Ion::decompress( + data, + reinterpret_cast(pixelBuffer), + size, + pixelBufferSize * sizeof(KDColor) + ); + + KDRect bounds((320 - image->width()) / 2, offset, image->width(), image->height()); + + ctx->fillRectWithPixels(bounds, pixelBuffer, nullptr); +} + +void Interface::drawFlasher() { + KDContext * ctx = KDIonContext::sharedContext(); + ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); + drawImage(ctx, ImageStore::Computer, 25); + KDSize fontSize = KDFont::LargeFont->glyphSize(); + int initPos = (320 - fontSize.width() * strlen(Messages::mainTitle)) / 2; + ctx->drawString(Messages::mainTitle, KDPoint(initPos, ImageStore::Computer->height() + fontSize.height() + 10), KDFont::LargeFont, KDColorBlack, KDColorWhite); + int y = ImageStore::Computer->height() + (KDFont::LargeFont->glyphSize().height() + 10) + (KDFont::SmallFont->glyphSize().height() + 10); + initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::dfuSlotsUpdate)) / 2; + ctx->drawString(Messages::dfuSlotsUpdate, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); +} + +void Interface::drawLoading() { + KDContext * ctx = KDIonContext::sharedContext(); + ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); + drawImage(ctx, ImageStore::Computer, 25); + Ion::Timing::msleep(250); + KDSize fontSize = KDFont::LargeFont->glyphSize(); + int initPos = (320 - fontSize.width() * strlen(Messages::mainTitle)) / 2; + for (uint8_t i = 0; i < strlen(Messages::mainTitle); i++) { + char tmp[2] = {Messages::mainTitle[i], '\0'}; + ctx->drawString(tmp, KDPoint(initPos + i * (fontSize.width()), ImageStore::Computer->height() + fontSize.height() + 10), KDFont::LargeFont, KDColorBlack, KDColorWhite); + Ion::Timing::msleep(50); + } + Ion::Timing::msleep(500); +} + +} diff --git a/bootloader/interface/static/interface.h b/bootloader/interface/static/interface.h new file mode 100644 index 00000000000..8c5234fc4da --- /dev/null +++ b/bootloader/interface/static/interface.h @@ -0,0 +1,19 @@ +#ifndef BOOTLOADER_INTERFACE_STATIC_INTERFACE_H +#define BOOTLOADER_INTERFACE_STATIC_INTERFACE_H + +#include +#include +#include + +namespace Bootloader { +class Interface { + +public: + static void drawImage(KDContext * ctx, const Image * image, int offset); + static void drawLoading(); + static void drawFlasher(); +}; + +} + +#endif \ No newline at end of file diff --git a/bootloader/interface/static/messages.h b/bootloader/interface/static/messages.h new file mode 100644 index 00000000000..de63bf3d3cd --- /dev/null +++ b/bootloader/interface/static/messages.h @@ -0,0 +1,93 @@ +#ifndef BOOTLOADER_INTERFACE_STATIC_MESSAGES_H +#define BOOTLOADER_INTERFACE_STATIC_MESSAGES_H + +namespace Bootloader { + +class Messages { +public: + // TODO: Remove it when this fork will be updated + #ifdef UPSILON_VERSION + constexpr static const char * mainTitle = "Upsilon Calculator"; + #elif defined OMEGA_VERSION + constexpr static const char * mainTitle = "Omega Calculator"; + #else + constexpr static const char * mainTitle = "NumWorks Calculator"; + #endif + + // home menu + constexpr static const char * homeTitle = "Select a slot"; + + // Slots OS Type + constexpr static const char * upsilonSlot = "-- Upsilon "; + constexpr static const char * khiSlot = "-- Khi "; + constexpr static const char * omegaSlot = "-- Omega "; + constexpr static const char * epsilonSlot = "-- Epsilon "; + constexpr static const char * invalidSlot = "X - Invalid slot"; + + // Home Submenu + constexpr static const char * homeSlotASubmenu = "1 - Slot A"; + constexpr static const char * homeSlotKhiSubmenu = "2 - Slot Khi"; + constexpr static const char * homeSlotBSubmenu = "3 - Slot B"; + constexpr static const char * homeInstallerSubmenu = "4 - Installer Mode"; + constexpr static const char * homeAboutSubmenu = "5 - About"; + constexpr static const char * homeRebootSubmenu = "6 - Reboot"; + + // DFU menu + constexpr static const char * dfuTitle = "Installer"; + + constexpr static const char * dfuSlotsUpdate = "Waiting for Slots..."; + constexpr static const char * dfuBootloaderUpdate = "Waiting for Bootloader..."; + + // Installer menu + constexpr static const char * installerTitle = "Installer mode"; + + constexpr static const char * installerText1 = "Please select a mode:"; + constexpr static const char * installerSlotsSubmenu = "1 - Flash Slots"; + constexpr static const char * installerBootloaderSubmenu = "2 - Flash Bootloader"; + + // Bootloader Crash Handler + constexpr static const char * bootloaderCrashTitle = "BOOTLOADER CRASH"; + + constexpr static const char * bootloaderCrashMessage1 = "The bootloader has crashed."; + constexpr static const char * bootloaderCrashMessage2 = "Please restart the calculator."; + + // Recovery menu + constexpr static const char * recoveryTitle = "Recovery mode"; + + constexpr static const char * recoveryMessage1 = "The bootloader has detected a crash."; + constexpr static const char * recoveryMessage2 = "Plug the calculator to a device capable of"; + constexpr static const char * recoveryMessage3 = "accessing the internal storage."; + constexpr static const char * recoveryMessage4 = "Press Back to continue."; + constexpr static const char * recoveryMessage5 = "(you will not be able to recover your data !)"; + + // Warning menu + constexpr static const char * epsilonWarningTitle = "Epsilon Slot"; + + constexpr static const char * epsilonWarningMessage1 = "!! WARNING !! "; + constexpr static const char * epsilonWarningMessage2 = "This version of epsilon"; + constexpr static const char * epsilonWarningMessage3 = "can lock the calculator."; + constexpr static const char * epsilonWarningMessage4 = "Proceed the boot ?"; + constexpr static const char * epsilonWarningMessage5 = "EXE - Yes"; + constexpr static const char * epsilonWarningMessage6 = "BACK - No"; + + // About menu + constexpr static const char * aboutMenuTitle = "About"; + + constexpr static const char * aboutMessage1 = "This is the bootloader of"; + constexpr static const char * aboutMessage2 = "the Upsilon Calculator."; + constexpr static const char * aboutMessage3 = "It is used to install"; + constexpr static const char * aboutMessage4 = "and select the OS"; + constexpr static const char * aboutMessage5 = "to boot."; + + constexpr static const char * bootloaderVersion = "Version 1.0.0 - FREED0M"; + + //USB NAMES + constexpr static const char * usbUpsilonBootloader = "Upsilon Bootloader"; + constexpr static const char * usbUpsilonRecovery = "Upsilon Recovery"; + constexpr static const char * usbBootloaderUpdate = "Bootloader Update"; + +}; + +}; + +#endif diff --git a/bootloader/main.cpp b/bootloader/main.cpp index c847ad88223..7902fc7d039 100644 --- a/bootloader/main.cpp +++ b/bootloader/main.cpp @@ -3,9 +3,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include #include @@ -17,20 +17,20 @@ __attribute__ ((noreturn)) void ion_main(int argc, const char * const argv[]) { // We check if there is a slot in exam_mode - bool isSlotA = Bootloader::Slot::A().kernelHeader()->isValid(); + bool isSlotA = Bootloader::Slot::isFullyValid(Bootloader::Slot::A()); if (isSlotA) { - Bootloader::ExamMode::ExamMode SlotAExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotAExamMode(Bootloader::Slot::A().kernelHeader()->isAboveVersion16()); + Bootloader::ExamMode::ExamMode SlotAExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotAExamMode(!Bootloader::Slot::A().userlandHeader()->isOmega()); if (SlotAExamMode != Bootloader::ExamMode::ExamMode::Off && SlotAExamMode != Bootloader::ExamMode::ExamMode::Unknown) { // We boot the slot in exam_mode Bootloader::Slot::A().boot(); } } - bool isSlotB = Bootloader::Slot::B().kernelHeader()->isValid(); + bool isSlotB = Bootloader::Slot::isFullyValid(Bootloader::Slot::B()); if (isSlotB) { - Bootloader::ExamMode::ExamMode SlotBExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotBExamMode(Bootloader::Slot::B().kernelHeader()->isAboveVersion16()); + Bootloader::ExamMode::ExamMode SlotBExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotBExamMode(!Bootloader::Slot::B().userlandHeader()->isOmega()); if (SlotBExamMode != Bootloader::ExamMode::ExamMode::Off && SlotBExamMode != Bootloader::ExamMode::ExamMode::Unknown && isSlotB) { // We boot the slot in exam_mode Bootloader::Slot::B().boot(); @@ -38,6 +38,18 @@ __attribute__ ((noreturn)) void ion_main(int argc, const char * const argv[]) { } + // I have no idea if this will work, but if Pariss did a good job, it should + + bool isKhiSlot = Bootloader::Slot::isFullyValid(Bootloader::Slot::Khi()); + + if (isKhiSlot) { + Bootloader::ExamMode::ExamMode KhiExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotKhiExamMode(); + if (KhiExamMode != Bootloader::ExamMode::ExamMode::Off && KhiExamMode != Bootloader::ExamMode::ExamMode::Unknown && isKhiSlot) { + // We boot the slot in exam_mode + Bootloader::Slot::Khi().boot(); + } + } + if (Bootloader::Recovery::has_crashed()) { Bootloader::Recovery::recover_data(); } diff --git a/bootloader/messages.h b/bootloader/messages.h deleted file mode 100644 index 6d8e90c3905..00000000000 --- a/bootloader/messages.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef BOOTLOADER_INTERFACE_MESSAGES_H -#define BOOTLOADER_INTERFACE_MESSAGES_H - -namespace Bootloader { - -class Messages { -public: - // TODO: Remove it when this fork will be updated - #ifdef UPSILON_VERSION - constexpr static const char * mainTitle = "Upsilon Calculator"; - #elif defined OMEGA_VERSION - constexpr static const char * mainTitle = "Omega Calculator"; - #else - constexpr static const char * mainTitle = "NumWorks Calculator"; - #endif - constexpr static const char * mainMenuTitle = "Select a slot"; - constexpr static const char * upsilon = "-- Upsilon "; - constexpr static const char * khi = "-- Khi "; - constexpr static const char * omega = "-- Omega "; - constexpr static const char * epsilon = "-- Epsilon "; - constexpr static const char * invalidSlot = "X - Invalid slot"; - constexpr static const char * dfuText = "4 - Installer Mode"; - constexpr static const char * aboutText = "5 - About"; - constexpr static const char * rebootText = "6 - Reboot"; - constexpr static const char * dfuSubtitle = "Waiting for Slots..."; - constexpr static const char * crashTitle = "BOOTLOADER CRASH"; - constexpr static const char * crashText1 = "The bootloader has crashed."; - constexpr static const char * crashText2 = "Please restart the calculator."; - constexpr static const char * recoveryTitle = "Recovery mode"; - constexpr static const char * recoveryText1 = "The bootloader has detected a crash."; - constexpr static const char * recoveryText2 = "Plug the calculator to a device capable of"; - constexpr static const char * recoveryText3 = "accessing the internal storage."; - constexpr static const char * recoveryText4 = "Press Back to continue."; - constexpr static const char * recoveryText5 = "(you will not be able to recover your data !)"; - constexpr static const char * installerSelectionTitle = "Installer Mode"; - constexpr static const char * installerText1 = "Please select a mode:"; - constexpr static const char * installerText2 = "1. Flash slots"; - constexpr static const char * installerText3 = "2. Flash the bootloader"; - constexpr static const char * bootloaderSubtitle = "Waiting for the bootloader..."; - constexpr static const char * epsilonWarningTitle = "Epsilon Slot"; - constexpr static const char * epsilonWarningText1 = "!! WARNING !! "; - constexpr static const char * epsilonWarningText2 = "This version of epsilon"; - constexpr static const char * epsilonWarningText3 = "can lock the calculator."; - constexpr static const char * epsilonWarningText4 = "Proceed the boot ?"; - constexpr static const char * epsilonWarningText5 = "EXE - Yes"; - constexpr static const char * epsilonWarningText6 = "BACK - No"; - constexpr static const char * bootloaderVersion = "Version 1.0.0 - FREEDOM"; - constexpr static const char * aboutMenuTitle = "About"; - - //USB NAMES - - constexpr static const char * upsilonBootloader = "Upsilon Bootloader"; - constexpr static const char * upsilonRecovery = "Upsilon Recovery"; - constexpr static const char * bootloaderUpdate = "Bootloader Update"; - -}; - -}; - -#endif diff --git a/bootloader/recovery.cpp b/bootloader/recovery.cpp index eb99bfbbcb9..5d357de305b 100644 --- a/bootloader/recovery.cpp +++ b/bootloader/recovery.cpp @@ -5,9 +5,11 @@ #include #include -#include -#include +#include +#include #include +#include +#include constexpr static uint32_t MagicStorage = 0xEE0BDDBA; @@ -19,11 +21,8 @@ void Bootloader::Recovery::crash_handler(const char *error) { Ion::Backlight::setBrightness(180); Ion::Display::pushRectUniform(KDRect(0,0,320,240), KDColorWhite); - Interface::drawCrash(error); - - while (true) { - - } + CrashMenu menu(error); + menu.open(true); } bool Bootloader::Recovery::has_crashed() { @@ -76,55 +75,13 @@ void Bootloader::Recovery::recover_data() { Ion::Device::Board::initPeripherals(false); Ion::Display::pushRectUniform(KDRect(0,0,320,240), KDColorWhite); Ion::Backlight::init(); - Interface::drawRecovery(); - - uint64_t scan = 0; USBData udata = USBData::Recovery((uint32_t)getSlotConcerned().getStorageAddress(), (uint32_t)getSlotConcerned().getStorageSize()); - - while (scan != Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { - Ion::USB::enable(); - do { - scan = Ion::Keyboard::scan(); - if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { - Ion::USB::disable(); - } - } while (!Ion::USB::isEnumerated() && scan != Ion::Keyboard::State(Ion::Keyboard::Key::Back)); - - if (scan != Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { - Ion::USB::DFU(true, &udata); - } - } + + SlotRecoveryMenu menu = SlotRecoveryMenu(&udata); + menu.open(); // Invalidate storage header *(uint32_t *)(getSlotConcerned().getStorageAddress()) = (uint32_t)0x0; } - -void Bootloader::Recovery::debug() { - bool isA = Bootloader::Slot::A().kernelHeader()->isValid() && Bootloader::Slot::A().userlandHeader()->isValid(); - bool isB = Bootloader::Slot::B().kernelHeader()->isValid() && Bootloader::Slot::B().userlandHeader()->isValid(); - - if (isA) { - const uint32_t * storage = (uint32_t *)Bootloader::Slot::A().userlandHeader()->storageAddress(); - *(uint32_t *)(0x20000070) = *storage; - if (*storage == MagicStorage) { - *(uint32_t *)(0x20000070 + (sizeof(uint32_t))) = 0xDEADBEEF; - } - } - - if (isB) { - const uint32_t * storage = (uint32_t *)Bootloader::Slot::B().userlandHeader()->storageAddress(); - *(uint32_t *)(0x20000070 + 2*(sizeof(uint32_t))) = *storage; - if (*storage == MagicStorage) { - *(uint32_t *)(0x20000070 + 3*(sizeof(uint32_t))) = 0xDEADBEEF; - } - } - - if (has_crashed()) { - *(uint32_t *)(0x20000070 + 4*(sizeof(uint32_t))) = 0xDEADBEEF; - } else { - *(uint32_t *)(0x20000070 + 4*(sizeof(uint32_t))) = 0xBEEFDEAD; - } - -} diff --git a/bootloader/recovery.h b/bootloader/recovery.h index e9273bbfd1e..f5327c914ab 100644 --- a/bootloader/recovery.h +++ b/bootloader/recovery.h @@ -24,7 +24,6 @@ class Recovery { static void crash_handler(const char * error); static void recover_data(); static bool has_crashed(); - static void debug(); }; }; diff --git a/bootloader/kernel_header.cpp b/bootloader/slots/kernel_header.cpp similarity index 93% rename from bootloader/kernel_header.cpp rename to bootloader/slots/kernel_header.cpp index 53e02cfaf5f..c323046d9af 100644 --- a/bootloader/kernel_header.cpp +++ b/bootloader/slots/kernel_header.cpp @@ -1,4 +1,4 @@ -#include +#include #include namespace Bootloader { diff --git a/bootloader/kernel_header.h b/bootloader/slots/kernel_header.h similarity index 88% rename from bootloader/kernel_header.h rename to bootloader/slots/kernel_header.h index ad7fbaf4b71..835aa9338a9 100644 --- a/bootloader/kernel_header.h +++ b/bootloader/slots/kernel_header.h @@ -1,5 +1,5 @@ -#ifndef BOOTLOADER_KERNEL_HEADER_H -#define BOOTLOADER_KERNEL_HEADER_H +#ifndef BOOTLOADER_SLOTS_KERNEL_HEADER_H +#define BOOTLOADER_SLOTS_KERNEL_HEADER_H #include #include diff --git a/bootloader/slot.cpp b/bootloader/slots/slot.cpp similarity index 95% rename from bootloader/slot.cpp rename to bootloader/slots/slot.cpp index a6ffe0e1ddb..d8ce933985f 100644 --- a/bootloader/slot.cpp +++ b/bootloader/slots/slot.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -37,8 +37,6 @@ const UserlandHeader* Slot::userlandHeader() const { Ion::Device::Flash::LockSlotA(); } - Boot::lockInternal(); - // Configure the MPU for the booted firmware Ion::Device::Board::bootloaderMPU(); diff --git a/bootloader/slot.h b/bootloader/slots/slot.h similarity index 86% rename from bootloader/slot.h rename to bootloader/slots/slot.h index 0550b02225c..8aa97b4d388 100644 --- a/bootloader/slot.h +++ b/bootloader/slots/slot.h @@ -1,5 +1,5 @@ -#ifndef BOOTLOADER_SLOT_H -#define BOOTLOADER_SLOT_H +#ifndef BOOTLOADER_SLOTS_SLOT_H +#define BOOTLOADER_SLOTS_SLOT_H #include @@ -19,6 +19,7 @@ class Slot { const KernelHeader* kernelHeader() const; const UserlandHeader* userlandHeader() const; [[ noreturn ]] void boot() const; + const uint32_t address() const { return m_address; } static const Slot A(); static const Slot B(); diff --git a/bootloader/slot_exam_mode.cpp b/bootloader/slots/slot_exam_mode.cpp similarity index 77% rename from bootloader/slot_exam_mode.cpp rename to bootloader/slots/slot_exam_mode.cpp index 983a51fa847..45b3123239e 100644 --- a/bootloader/slot_exam_mode.cpp +++ b/bootloader/slots/slot_exam_mode.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -118,6 +118,32 @@ uint8_t * SignificantSlotBExamModeAddress(bool newVersion) { } +uint8_t * SignificantSlotKhiExamModeAddress() { + uint32_t * persitence_start_32 = (uint32_t *)SlotsExamMode::getSlotKhiStartExamAddress(); + uint32_t * persitence_end_32 = (uint32_t *)SlotsExamMode::getSlotKhiEndExamAddress(); + + assert((persitence_end_32 - persitence_start_32) % 4 == 0); + while (persitence_start_32 < persitence_end_32 && *persitence_start_32 == 0x0) { + // Scan by groups of 32 bits to reach first non-zero bit + persitence_start_32++; + } + uint8_t * persitence_start_8 = (uint8_t *)persitence_start_32; + uint8_t * persitence_end_8 = (uint8_t *)persitence_end_32; + while (persitence_start_8 < persitence_end_8 && *persitence_start_8 == 0x0) { + // Scan by groups of 8 bits to reach first non-zero bit + persitence_start_8++; + } + if (persitence_start_8 == persitence_end_8 + // we can't toggle from 0[3] to 2[3] when there is only one 1 bit in the whole sector + || (persitence_start_8 + 1 == persitence_end_8 && *persitence_start_8 == 1)) { + assert(Ion::Device::Flash::SectorAtAddress(SlotsExamMode::getSlotKhiStartExamAddress()) >= 0); + Ion::Device::Flash::EraseSector(Ion::Device::Flash::SectorAtAddress(SlotsExamMode::getSlotKhiStartExamAddress())); + return (uint8_t *)SlotsExamMode::getSlotKhiStartExamAddress(); + } + + return persitence_start_8; +} + uint8_t SlotsExamMode::FetchSlotAExamMode(bool newVersion) { uint8_t * readingAddress = SignificantSlotAExamModeAddress(newVersion); if (!newVersion) { @@ -146,6 +172,15 @@ uint8_t SlotsExamMode::FetchSlotBExamMode(bool newVersion) { } +uint8_t SlotsExamMode::FetchSlotKhiExamMode() { + uint8_t * readingAddress = SignificantSlotKhiExamModeAddress(); + // Count the number of 0[3] before reading address + uint32_t nbOfZerosBefore = ((readingAddress - (uint8_t *)getSlotKhiStartExamAddress()) * numberOfBitsInByte) % 4; + // Count the number of 0[3] at reading address + size_t numberOfLeading0 = (numberOfBitsInByte - numberOfBitsAfterLeadingZeroes(*readingAddress)) % 4; + return (nbOfZerosBefore + numberOfLeading0) % 4; +} + uint32_t SlotsExamMode::getSlotAStartExamAddress(bool newVersion) { return newVersion ? SlotAExamModeBufferStartNewVersions : SlotAExamModeBufferStartOldVersions; } @@ -162,5 +197,13 @@ uint32_t SlotsExamMode::getSlotBEndExamAddress(bool newVersion) { return newVersion ? SlotBExamModeBufferEndNewVersions : SlotBExamModeBufferEndOldVersions; } +uint32_t SlotsExamMode::getSlotKhiStartExamAddress() { + return SlotKhiExamModeBufferStart; +} + +uint32_t SlotsExamMode::getSlotKhiEndExamAddress() { + return SlotKhiExamModeBufferEnd; +} + } } diff --git a/bootloader/slot_exam_mode.h b/bootloader/slots/slot_exam_mode.h similarity index 78% rename from bootloader/slot_exam_mode.h rename to bootloader/slots/slot_exam_mode.h index 7c93eed7215..d9e380e9a31 100644 --- a/bootloader/slot_exam_mode.h +++ b/bootloader/slots/slot_exam_mode.h @@ -1,5 +1,5 @@ -#ifndef BOOTLOADER_EXAM_MODE_H -#define BOOTLOADER_EXAM_MODE_H +#ifndef BOOTLOADER_SLOTS_EXAM_MODE_H +#define BOOTLOADER_SLOTS_EXAM_MODE_H extern "C" { #include @@ -20,15 +20,21 @@ static const uint32_t SlotBExamModeBufferEndOldVersions = 0x90403000; static const uint32_t SlotBExamModeBufferStartNewVersions = 0x907f0000; static const uint32_t SlotBExamModeBufferEndNewVersions = 0x90800000; +static const uint32_t SlotKhiExamModeBufferStart = 0x90181000; +static const uint32_t SlotKhiExamModeBufferEnd = 0x90183000; + class SlotsExamMode{ public: static uint8_t FetchSlotAExamMode(bool newVersion); static uint8_t FetchSlotBExamMode(bool newVerion); + static uint8_t FetchSlotKhiExamMode(); static uint32_t getSlotAStartExamAddress(bool newVersion); static uint32_t getSlotAEndExamAddress(bool newVersion); static uint32_t getSlotBStartExamAddress(bool newVersion); static uint32_t getSlotBEndExamAddress(bool newVersion); + static uint32_t getSlotKhiStartExamAddress(); + static uint32_t getSlotKhiEndExamAddress(); }; diff --git a/bootloader/userland_header.cpp b/bootloader/slots/userland_header.cpp similarity index 95% rename from bootloader/userland_header.cpp rename to bootloader/slots/userland_header.cpp index ee1f8b15d65..3c8a9190872 100644 --- a/bootloader/userland_header.cpp +++ b/bootloader/slots/userland_header.cpp @@ -1,4 +1,4 @@ -#include +#include namespace Bootloader { diff --git a/bootloader/userland_header.h b/bootloader/slots/userland_header.h similarity index 94% rename from bootloader/userland_header.h rename to bootloader/slots/userland_header.h index 39356f3d3ca..382e409b071 100644 --- a/bootloader/userland_header.h +++ b/bootloader/slots/userland_header.h @@ -1,5 +1,5 @@ -#ifndef BOOTLOADER_USERLAND_HEADER_H -#define BOOTLOADER_USERLAND_HEADER_H +#ifndef BOOTLOADER_SLOTS_USERLAND_HEADER_H +#define BOOTLOADER_SLOTS_USERLAND_HEADER_H #include #include diff --git a/bootloader/usb_data.cpp b/bootloader/usb_data.cpp index b161df94c80..bd88437918c 100644 --- a/bootloader/usb_data.cpp +++ b/bootloader/usb_data.cpp @@ -1,10 +1,10 @@ #include +#include + #include #include -#include #include #include -#include extern "C" { } @@ -13,13 +13,13 @@ static char data[255]; const char * Bootloader::USBData::buildStringDescriptor(StringHeader header, uint32_t startAddress, uint32_t size) { strlcpy(data, header.getString(), sizeof(data)); - Bootloader::Utility::itoa((int32_t)startAddress, &data[strlen(header.getString())], 16); + itoa((int32_t)startAddress, &data[strlen(header.getString())], 16); data[strlen(header.getString()) + 8] = '/'; data[strlen(header.getString()) + 8 + 1] = '0'; data[strlen(header.getString()) + 8 + 2] = '1'; data[strlen(header.getString()) + 8 + 3] = '*'; data[strlen(header.getString()) + 8 + 4] = '0'; - Bootloader::Utility::itoa((int32_t)size/1024, &data[strlen(header.getString()) + 8 + 5], 10); + itoa((int32_t)size/1024, &data[strlen(header.getString()) + 8 + 5], 10); data[strlen(header.getString()) + 8 + 5 + 2] = 'K'; data[strlen(header.getString()) + 8 + 5 + 2 + 1] = 'g'; data[strlen(header.getString()) + 8 + 5 + 2 + 2] = '\0'; @@ -27,13 +27,13 @@ const char * Bootloader::USBData::buildStringDescriptor(StringHeader header, uin } const Bootloader::USBData Bootloader::USBData::DEFAULT() { - return USBData("@Flash/0x90000000/08*004Kg,01*032Kg,63*064Kg,64*064Kg", Messages::upsilonBootloader, ProtectionState()); + return USBData("@Flash/0x90000000/08*004Kg,01*032Kg,63*064Kg,64*064Kg", Messages::usbUpsilonBootloader, ProtectionState(false, true)); } const Bootloader::USBData Bootloader::USBData::BOOTLOADER_UPDATE() { - return USBData("@Flash/0x08000000/04*016Kg", Messages::bootloaderUpdate, ProtectionState(true, false)); + return USBData("@Flash/0x08000000/04*016Kg", Messages::usbBootloaderUpdate, ProtectionState(true, false)); } Bootloader::USBData Bootloader::USBData::Recovery(uint32_t startAddress, uint32_t size) { - return USBData(buildStringDescriptor(StringHeader::SRAM(), startAddress, size), Messages::upsilonRecovery, ProtectionState(false, false)); + return USBData(buildStringDescriptor(StringHeader::SRAM(), startAddress, size), Messages::usbUpsilonRecovery, ProtectionState(false, false)); } diff --git a/bootloader/usb_data.h b/bootloader/usb_data.h index 33398595d43..a0477edbd78 100644 --- a/bootloader/usb_data.h +++ b/bootloader/usb_data.h @@ -33,11 +33,11 @@ class USBData { const char * m_string; }; - USBData(const char * desc, const char * name, ProtectionState data = ProtectionState()) : m_stringDescriptor(desc), m_name(name), m_data(&data) {}; + USBData(const char * desc, const char * name, ProtectionState data = ProtectionState()) : m_stringDescriptor(desc), m_name(name), m_data(data) {}; const char * stringDescriptor() const { return m_stringDescriptor; } const char * getName() const { return m_name; } - ProtectionState * getData() const { return m_data; } + ProtectionState getData() const { return m_data; } static const char * buildStringDescriptor(StringHeader header, uint32_t startAddress, uint32_t size); @@ -48,7 +48,7 @@ class USBData { private: const char * m_stringDescriptor; const char * m_name; - ProtectionState * m_data; + ProtectionState m_data; }; } diff --git a/bootloader/usb_desc.cpp b/bootloader/usb_desc.cpp deleted file mode 100644 index be49306000b..00000000000 --- a/bootloader/usb_desc.cpp +++ /dev/null @@ -1,12 +0,0 @@ - -namespace Ion { -namespace Device { -namespace USB { - -const char* stringDescriptor() { - return "@Flash/0x90000000/08*004Kg,01*032Kg,63*064Kg,64*064Kg"; -} - -} -} -} diff --git a/bootloader/utility.cpp b/bootloader/utility.cpp index 1329351e5f3..fb6d676ac5b 100644 --- a/bootloader/utility.cpp +++ b/bootloader/utility.cpp @@ -1,10 +1,10 @@ #include #include -int Bootloader::Utility::versionSum(const char * version, int length) { - int sum = 0; - for (int i = 0; i < length; i++) { - sum += version[i] * (strlen(version) * 100 - i * 10); - } - return sum; +int Utility::versionSum(const char * version, int length) { + int sum = 0; + for (int i = 0; i < length; i++) { + sum += version[i] * (strlen(version) * 100 - i * 10); + } + return sum; } diff --git a/bootloader/utility.h b/bootloader/utility.h index 12c6ce3297f..6a2c0d59014 100644 --- a/bootloader/utility.h +++ b/bootloader/utility.h @@ -1,12 +1,8 @@ #ifndef _BOOTLOADER_ITOA_H_ #define _BOOTLOADER_ITOA_H_ -namespace Bootloader { - class Utility { - public: - static char * itoa(int value, char * result, int base); - static int versionSum(const char * version, int length); - }; +namespace Utility { + extern int versionSum(const char * version, int length); } #endif diff --git a/ion/src/device/bootloader/internal_flash.ld b/ion/src/device/bootloader/internal_flash.ld index ebbf98a5198..e441c6527eb 100644 --- a/ion/src/device/bootloader/internal_flash.ld +++ b/ion/src/device/bootloader/internal_flash.ld @@ -45,6 +45,14 @@ SECTIONS { _exam_mode_buffer_end = .; } >INTERNAL_FLASH + .fake_isr_function : { + . = ALIGN(4); + _fake_isr_function_start = .; + KEEP(*(.fake_isr_function)) + KEEP(*(.fake_isr_function.*)) + _fake_isr_function_end = .; + } + .text : { . = ALIGN(4); *(.text) diff --git a/ion/src/device/bootloader/usb/dfu_interface.cpp b/ion/src/device/bootloader/usb/dfu_interface.cpp index 0b6abb61178..05e86f4edd2 100644 --- a/ion/src/device/bootloader/usb/dfu_interface.cpp +++ b/ion/src/device/bootloader/usb/dfu_interface.cpp @@ -211,23 +211,14 @@ void DFUInterface::eraseMemoryIfNeeded() { willErase(); - Bootloader::ProtectionState * config = getDfuConfig(); - - if (config != nullptr) { - // More simple to read - if ((0x08000000 <= m_eraseAddress && m_eraseAddress <= 0x08010000)&& !m_dfuData.isProtectedInternal()) { - Flash::EraseSector(m_erasePage); - } else if ((0x90000000 <= m_eraseAddress && m_eraseAddress <= 0x90800000)&& !m_dfuData.isProtectedExternal()) { - Flash::EraseSector(m_erasePage); - } - } else { - if (m_erasePage == Flash::TotalNumberOfSectors()) { - Flash::MassErase(); - } else { - Flash::EraseSector(m_erasePage); - } - } + Bootloader::ProtectionState config = getDfuConfig(); + // More simple to read + if ((0x08000000 <= m_eraseAddress && m_eraseAddress <= 0x08010000)&& !m_dfuData.isProtectedInternal()) { + Flash::EraseSector(m_erasePage); + } else if ((0x90000000 <= m_eraseAddress && m_eraseAddress <= 0x90800000)&& !m_dfuData.isProtectedExternal()) { + Flash::EraseSector(m_erasePage); + } /* Put an out of range value in m_erasePage to indicate that no erase is * waiting. */ m_erasePage = -1; @@ -242,15 +233,11 @@ void DFUInterface::writeOnMemory() { memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); } else if (Flash::SectorAtAddress(m_writeAddress) >= 0) { - Bootloader::ProtectionState * config = getDfuConfig(); + Bootloader::ProtectionState config = getDfuConfig(); - if (config != nullptr) { - if (m_writeAddress >= 0x08000000 && m_writeAddress <= 0x08010000 && !m_dfuData.isProtectedInternal()) { - Flash::WriteMemory(reinterpret_cast(m_writeAddress), m_largeBuffer, m_largeBufferLength); - } else if (m_writeAddress >= 0x90000000 && m_writeAddress <= 0x90800000 && !m_dfuData.isProtectedExternal()) { - Flash::WriteMemory(reinterpret_cast(m_writeAddress), m_largeBuffer, m_largeBufferLength); - } - } else { + if (m_writeAddress >= 0x08000000 && m_writeAddress <= 0x08010000 && !m_dfuData.isProtectedInternal()) { + Flash::WriteMemory(reinterpret_cast(m_writeAddress), m_largeBuffer, m_largeBufferLength); + } else if (m_writeAddress >= 0x90000000 && m_writeAddress <= 0x90800000 && !m_dfuData.isProtectedExternal()) { Flash::WriteMemory(reinterpret_cast(m_writeAddress), m_largeBuffer, m_largeBufferLength); } @@ -306,7 +293,7 @@ void DFUInterface::leaveDFUAndReset() { } void DFUInterface::copyDfuData() { - m_dfuData = Bootloader::ProtectionState(!m_dfuConfig->isProtectedInternal(), !m_dfuConfig->isProtectedExternal()); + m_dfuData = Bootloader::ProtectionState(!m_dfuConfig.isProtectedInternal(), !m_dfuConfig.isProtectedExternal()); } } diff --git a/ion/src/device/bootloader/usb/dfu_interface.h b/ion/src/device/bootloader/usb/dfu_interface.h index 2cb2eaeee95..4fa319bd990 100644 --- a/ion/src/device/bootloader/usb/dfu_interface.h +++ b/ion/src/device/bootloader/usb/dfu_interface.h @@ -30,7 +30,7 @@ class DFUInterface : public Interface { m_writeAddress(0), m_bInterfaceAlternateSetting(bInterfaceAlternateSetting), m_isErasingAndWriting(false), - m_dfuConfig(nullptr), + m_dfuConfig(), m_eraseAddress(0), m_dfuData() { @@ -40,8 +40,8 @@ class DFUInterface : public Interface { void wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) override; bool isErasingAndWriting() const { return m_isErasingAndWriting; } - void setDfuConfig(Bootloader::ProtectionState * data) { m_dfuConfig = data; copyDfuData(); } - Bootloader::ProtectionState * getDfuConfig() const { return m_dfuConfig; } + void setDfuConfig(Bootloader::ProtectionState data) { m_dfuConfig = data; copyDfuData(); } + Bootloader::ProtectionState getDfuConfig() const { return m_dfuConfig; } protected: void setActiveInterfaceAlternative(uint8_t interfaceAlternativeIndex) override { @@ -180,7 +180,7 @@ class DFUInterface : public Interface { uint32_t m_writeAddress; uint8_t m_bInterfaceAlternateSetting; bool m_isErasingAndWriting; - Bootloader::ProtectionState * m_dfuConfig; + Bootloader::ProtectionState m_dfuConfig; uint32_t m_eraseAddress; Bootloader::ProtectionState m_dfuData; }; diff --git a/ion/src/device/shared/drivers/board.h b/ion/src/device/shared/drivers/board.h index 8d1d1bbb6fa..ff6e88ac8b5 100644 --- a/ion/src/device/shared/drivers/board.h +++ b/ion/src/device/shared/drivers/board.h @@ -40,6 +40,7 @@ void writePCBVersion(PCBVersion version); void lockPCBVersion(); bool pcbVersionIsLocked(); +void jumpToInternalBootloader(); } } } diff --git a/ion/src/device/shared/drivers/flash.cpp b/ion/src/device/shared/drivers/flash.cpp index 0d2fb959ff4..2860dd593af 100644 --- a/ion/src/device/shared/drivers/flash.cpp +++ b/ion/src/device/shared/drivers/flash.cpp @@ -62,6 +62,14 @@ void SetInternalSectorProtection(int i, bool protect) { InternalFlash::SetSectorProtection(i, protect); } +void EnableInternalSessionLock() { + InternalFlash::EnableSessionLock(); +} + +void EnableInternalFlashInterrupt() { + InternalFlash::EnableFlashInterrupt(); +} + void LockSlotA() { ExternalFlash::LockSlotA(); } diff --git a/ion/src/device/shared/drivers/flash.h b/ion/src/device/shared/drivers/flash.h index d385add1f20..91f942a2011 100644 --- a/ion/src/device/shared/drivers/flash.h +++ b/ion/src/device/shared/drivers/flash.h @@ -18,6 +18,8 @@ void WriteMemory(uint8_t * destination, uint8_t * source, size_t length); void DisableInternalProtection(); void EnableInternalProtection(); void SetInternalSectorProtection(int i, bool protect); +void EnableInternalSessionLock(); // Will cause BUSERR when enabled +void EnableInternalFlashInterrupt(); void LockSlotA(); void LockSlotB(); diff --git a/ion/src/device/shared/drivers/internal_flash.cpp b/ion/src/device/shared/drivers/internal_flash.cpp index f71d8f7ac76..91292c0afc0 100644 --- a/ion/src/device/shared/drivers/internal_flash.cpp +++ b/ion/src/device/shared/drivers/internal_flash.cpp @@ -344,6 +344,25 @@ void SetSectorProtection(int i, bool protect) { } } +void EnableSessionLock() { + if (FLASH.OPTCR()->getLOCK()) { + // writing bullshit to the lock register to lock it until next core reset + FLASH.OPTKEYR()->set(0x00000000); + FLASH.OPTKEYR()->set(0xFFFFFFFF); + } +} + +void EnableFlashInterrupt() { + open(); + FLASH.CR()->setERRIE(true); + wait(); + FLASH.CR()->setEOPIE(true); + wait(); + FLASH.CR()->setRDERRIE(true); + wait(); + close(); +} + } } } diff --git a/ion/src/device/shared/drivers/internal_flash.h b/ion/src/device/shared/drivers/internal_flash.h index 3961884f2c4..cf351a1b6d2 100644 --- a/ion/src/device/shared/drivers/internal_flash.h +++ b/ion/src/device/shared/drivers/internal_flash.h @@ -18,6 +18,8 @@ void WriteMemory(uint8_t * destination, uint8_t * source, size_t length); void EnableProtection(); void DisableProtection(); void SetSectorProtection(int i, bool protect); +void EnableSessionLock(); +void EnableFlashInterrupt(); /* The Device is powered by a 2.8V LDO. This allows us to perform writes to the * Flash 32 bits at once. */ diff --git a/ion/src/device/shared/regs/flash.h b/ion/src/device/shared/regs/flash.h index b289d022784..4896e2b42c1 100644 --- a/ion/src/device/shared/regs/flash.h +++ b/ion/src/device/shared/regs/flash.h @@ -45,6 +45,9 @@ class FLASH { REGS_FIELD(SNB, uint8_t, 6, 3); REGS_TYPE_FIELD(PSIZE, 9, 8); REGS_BOOL_FIELD(STRT, 16); + REGS_BOOL_FIELD(EOPIE, 24); + REGS_BOOL_FIELD(ERRIE, 25); + REGS_BOOL_FIELD(RDERRIE, 26) REGS_BOOL_FIELD(LOCK, 31); }; diff --git a/ion/src/device/shared/regs/rcc.h b/ion/src/device/shared/regs/rcc.h index 1355085741f..0505126328c 100644 --- a/ion/src/device/shared/regs/rcc.h +++ b/ion/src/device/shared/regs/rcc.h @@ -14,6 +14,12 @@ class RCC { public: REGS_BOOL_FIELD(HSION, 0); REGS_BOOL_FIELD(HSIRDY, 1); + REGS_BOOL_FIELD(HSITRIM1, 3); + REGS_BOOL_FIELD(HSITRIM2, 4); + REGS_BOOL_FIELD(HSITRIM3, 5); + REGS_BOOL_FIELD(HSITRIM4, 6); + REGS_BOOL_FIELD(HSITRIM5, 7); + REGS_BOOL_FIELD(HSICAL, 8); REGS_BOOL_FIELD(HSEON, 16); REGS_BOOL_FIELD_R(HSERDY, 17); REGS_BOOL_FIELD(PLLON, 24); diff --git a/ion/src/device/shared/regs/syscfg.h b/ion/src/device/shared/regs/syscfg.h index 82dfddda605..e044a65f630 100644 --- a/ion/src/device/shared/regs/syscfg.h +++ b/ion/src/device/shared/regs/syscfg.h @@ -5,6 +5,8 @@ #include #include "gpio.h" +#define REGS_SYSCFG_CONFIG_F412 1 + namespace Ion { namespace Device { namespace Regs { diff --git a/liba/Makefile b/liba/Makefile index 639bf196db7..98cf5384830 100644 --- a/liba/Makefile +++ b/liba/Makefile @@ -24,6 +24,7 @@ liba_src += $(addprefix liba/src/, \ strlcpy.c \ strlen.c \ external/sqlite/mem5.c \ + itoa.c \ ) liba_src += $(addprefix liba/src/external/openbsd/, \ diff --git a/liba/include/stdlib.h b/liba/include/stdlib.h index 6c27b3d6e1e..7bac0e2fa7a 100644 --- a/liba/include/stdlib.h +++ b/liba/include/stdlib.h @@ -10,6 +10,7 @@ void free(void *ptr); void * malloc(size_t size); void * realloc(void *ptr, size_t size); void * calloc(size_t count, size_t size); +char * itoa(int value, char *str, int base); void abort(void) __attribute__((noreturn)); diff --git a/bootloader/itoa.cpp b/liba/src/itoa.c similarity index 92% rename from bootloader/itoa.cpp rename to liba/src/itoa.c index 8235e7b32a6..25c5a61bf79 100644 --- a/bootloader/itoa.cpp +++ b/liba/src/itoa.c @@ -1,6 +1,5 @@ #include #include -#include // https://www.techiedelight.com/implement-itoa-function-in-c/ @@ -19,7 +18,7 @@ char* reverse(char *buffer, int i, int j) { } // Iterative function to implement `itoa()` function in C -char* Bootloader::Utility::itoa(int value, char* buffer, int base) { +char* itoa(int value, char* buffer, int base) { // invalid input if (base < 2 || base > 32) { return buffer; diff --git a/themes/themes/local/upsilon_light/bootloader/cable.png b/themes/themes/local/upsilon_light/bootloader/cable.png new file mode 100644 index 0000000000000000000000000000000000000000..ac0996ca74685d68c185ee0bbc4cc3a05cdb6aec GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^Y(VV7!2~3A?JidUsVYww$B>FS$tej5D^{!s2nb9_ zNT?`y?&$8$F3il#+`^x3V3@$52{5#SsAJA*S1~lk!zb;$B{EN4o7dwGqi2xG= YtES2ht{+eQfv#ZiboFyt=akR{0N5@#iU0rr literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/bootloader/computer.xcf b/themes/themes/local/upsilon_light/bootloader/computer.xcf new file mode 100644 index 0000000000000000000000000000000000000000..4ad99c7c0b1c1f4f75a18ac1b2f1c9cdbf41e044 GIT binary patch literal 5072 zcmd^C&u<$=6i(K5oWyY&E9J)ls>Ohk2(7cbw&S%)T(xOSBefzTDHH*bCi}x#$@Ut1 zlcon;a^TL16DOYlS>k{Y5-L)qwihHUD1QYAvgQxK%lFoH>NZZ@f>fb)tT*%R%$u3_ zzIktUoiht1epfa49Z`~oP}U&x1W-PML?FY%sHP#Kr$6<7P6B)!;_t|_ot8uU%+OF! zL>nEUUp?dU`GtAc4%>uM#QI<2JoRO2fq-4ZoCN2(`OFOT#Tr?}C2dR4YUC@g;dn=kv zqy{YPX}@D?q^!gTD8E@Y^`gyekOxZ!D8Hp6WVT875)L$)9klqGW!P6(8T7oda&eWH zW0Dvf$jjxsyF|0eh19$}h@N*JVaDo~Wm~ajiuX0dEllg{^yNY+MmEPzt*+p3V|`6H zP+HA1+px-oX!e3x+}`cS;(;!O%bJZC!_Etdq#`P*xU`T;r;~{X&U?EZ;bhE_EYc>L z8aoG57>>uw{P|KbchthRX&AA=5kSlCLYB)qv#s*FW#xNL^r*!#5nE#pB+QMFAmofeXYwkafZi3v(VN(yl)ozl`;JgxN)-;O<4{k&O$L08|C zKA5lAXLsxjQ`?bTVLQ6^V9O58Rkv6%r)$yd{3A{@ixry{Rh@h!o=i9xPZ^3Vi;@u6 znIa_QxG1FKDib7WQ!|n{__%UpOtqOiWM8F|O{a3;69+)=7YYV`){DBiN3{CNmX4IC zdrJ^yK~AoB?k(vxQObw|j9cmBa!N`kQc7AEG?n2vWf{k*rWSc z`(Yx~)7gd30lWyIcjV+U%YV47^Qu)cZ+=M{BM^EkyO5U%pZpj#-rNxCr&(hijAkg> zgJ0;u<2{(~!DJDwLuCTe#o-=&44C#KD}x;&JzD=&NO#UFgk2Q0FZbZyFAH>c%jmaV zgtp5x=KX>+TQZRrf33b*}1jLJ+RXIqqK}s+%5eJ}&Hc#<0J; z&sE)~-w9R&j;}fb382qkb@xY{*6;g4Cr~|xL3e)w>Mf@^=1f$B&M|awPHW$p0`MQI z4~F`HrZ+2qHCx}@^xYn>x!?J2b2YF5;c6ZmR5v}`3_T_cHn2aus(EbGz(x&hpoN1q zcR%3#-TZ^-sDTbVgN|C8jvDAdN842Z&~aaVFmwnwoJR+C3D#P-e&+79e*I~z;r{5m z!!^JNgliBNA+PS@yS@Jv(HGp5N1Z?ezT*kLH`=~8 z;5(+#k)j`--&eOCM(6z?09D$^9rH)r8)L3Na*g|hvatK{Pq#!^wF;$eTQA4(Zl)}v zk9#Wrrz@syQTv+tH&;y8r_MUz=erA{?UK>CV#15f zH@)F^hc}e!lT)9-Ej9g+{$w^>H@h+HE^YYS*Ehh(9vImJ7kf(W$*E7^4w-&Pe=>Vu zWDkt&fss8hQU@b-a8XwpPfmRT7tHiS`je@HkvbTugOU2R& Date: Mon, 25 Apr 2022 18:35:16 +0200 Subject: [PATCH 243/355] Fix CLI bootloader --- ion/src/device/bootloader/drivers/board.cpp | 3 +++ ion/src/device/n0110/drivers/board.cpp | 2 ++ ion/src/device/shared/drivers/board.cpp | 2 ++ 3 files changed, 7 insertions(+) diff --git a/ion/src/device/bootloader/drivers/board.cpp b/ion/src/device/bootloader/drivers/board.cpp index 03e47bc2cdb..a10d766c1df 100644 --- a/ion/src/device/bootloader/drivers/board.cpp +++ b/ion/src/device/bootloader/drivers/board.cpp @@ -418,6 +418,9 @@ bool pcbVersionIsLocked() { return *reinterpret_cast(InternalFlash::Config::OTPLockAddress(k_pcbVersionOTPIndex)) == 0; } + +void jumpToInternalBootloader() {} + } } } diff --git a/ion/src/device/n0110/drivers/board.cpp b/ion/src/device/n0110/drivers/board.cpp index 8e93e4d3c5f..7060da3e5f5 100644 --- a/ion/src/device/n0110/drivers/board.cpp +++ b/ion/src/device/n0110/drivers/board.cpp @@ -434,6 +434,8 @@ bool pcbVersionIsLocked() { return *reinterpret_cast(InternalFlash::Config::OTPLockAddress(k_pcbVersionOTPIndex)) == 0; } +void jumpToInternalBootloader() {} + } } } diff --git a/ion/src/device/shared/drivers/board.cpp b/ion/src/device/shared/drivers/board.cpp index 2164d0d6b94..768647f41e4 100644 --- a/ion/src/device/shared/drivers/board.cpp +++ b/ion/src/device/shared/drivers/board.cpp @@ -91,6 +91,8 @@ void setClockFrequency(Frequency f) { } } +void jumpToInternalBootloader() {} + } } } From 42368ecd6e145a9c321d81867ba36e686728319e Mon Sep 17 00:00:00 2001 From: devdl11 Date: Mon, 25 Apr 2022 18:40:55 +0200 Subject: [PATCH 244/355] second patch --- ion/src/device/shared/drivers/board.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/ion/src/device/shared/drivers/board.cpp b/ion/src/device/shared/drivers/board.cpp index 768647f41e4..2164d0d6b94 100644 --- a/ion/src/device/shared/drivers/board.cpp +++ b/ion/src/device/shared/drivers/board.cpp @@ -91,8 +91,6 @@ void setClockFrequency(Frequency f) { } } -void jumpToInternalBootloader() {} - } } } From 18ff283580818d3b48f4d7cc973fa4b75ba710f3 Mon Sep 17 00:00:00 2001 From: devdl11 Date: Mon, 25 Apr 2022 19:52:58 +0200 Subject: [PATCH 245/355] Boot fix --- bootloader/boot.cpp | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index 405b54d7f75..85412aa3952 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -58,12 +58,12 @@ bool Boot::isKernelPatched(const Slot & s) { return true; } - return *(uint32_t *)(origin_isr + sizeof(uint32_t) * 21) == (uint32_t)&_fake_isr_function_start; + return *(uint32_t *)(origin_isr + sizeof(uint32_t) * 4) == (uint32_t)&_fake_isr_function_start && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 5) == (uint32_t)&_fake_isr_function_start && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 6) == (uint32_t)&_fake_isr_function_start && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 7) == (uint32_t)&_fake_isr_function_start; } __attribute((section(".fake_isr_function"))) __attribute__((used)) void Boot::flsh_intr() { // a simple function - while (1) {} + } void Boot::patchKernel(const Slot & s) { @@ -73,10 +73,26 @@ void Boot::patchKernel(const Slot & s) { memcpy(data, (void*)0x90000000, 1024*4); uint32_t dummy_address = (uint32_t)&_fake_isr_function_start; uint8_t * ptr = (uint8_t *)&dummy_address; - data[origin_isr + sizeof(uint32_t) * 21] = ptr[0]; - data[origin_isr + sizeof(uint32_t) * 21 + 1] = ptr[1]; - data[origin_isr + sizeof(uint32_t) * 21 + 2] = ptr[2]; - data[origin_isr + sizeof(uint32_t) * 21 + 3] = ptr[3]; + data[origin_isr + sizeof(uint32_t) * 6] = ptr[0]; + data[origin_isr + sizeof(uint32_t) * 6 + 1] = ptr[1]; + data[origin_isr + sizeof(uint32_t) * 6 + 2] = ptr[2]; + data[origin_isr + sizeof(uint32_t) * 6 + 3] = ptr[3]; + + data[origin_isr + sizeof(uint32_t) * 5] = ptr[0]; + data[origin_isr + sizeof(uint32_t) * 5 + 1] = ptr[1]; + data[origin_isr + sizeof(uint32_t) * 5 + 2] = ptr[2]; + data[origin_isr + sizeof(uint32_t) * 5 + 3] = ptr[3]; + + data[origin_isr + sizeof(uint32_t) * 7] = ptr[0]; + data[origin_isr + sizeof(uint32_t) * 7 + 1] = ptr[1]; + data[origin_isr + sizeof(uint32_t) * 7 + 2] = ptr[2]; + data[origin_isr + sizeof(uint32_t) * 7 + 3] = ptr[3]; + + data[origin_isr + sizeof(uint32_t) * 4] = ptr[0]; + data[origin_isr + sizeof(uint32_t) * 4 + 1] = ptr[1]; + data[origin_isr + sizeof(uint32_t) * 4 + 2] = ptr[2]; + data[origin_isr + sizeof(uint32_t) * 4 + 3] = ptr[3]; + Ion::Device::ExternalFlash::EraseSector(0); Ion::Device::ExternalFlash::WriteMemory((uint8_t*)0x90000000, data, 1024*4); } @@ -86,7 +102,7 @@ void Boot::bootSlot(Bootloader::Slot s) { if (!s.userlandHeader()->isOmega() && !s.userlandHeader()->isUpsilon()) { // We are trying to boot epsilon, so we check the version and show an advertisement if needed const char * version = s.userlandHeader()->version(); - const char * min = "18.2.0"; + const char * min = "18.2.4"; int vsum = Utility::versionSum(version, strlen(version)); int minsum = Utility::versionSum(min, strlen(min)); if (vsum >= minsum) { @@ -100,8 +116,9 @@ void Boot::bootSlot(Bootloader::Slot s) { void Boot::bootSelectedSlot() { lockInternal(); - enableFlashIntr(); + // enableFlashIntr(); config()->setBooting(true); + config()->slot()->boot(); Ion::Device::Flash::EnableInternalSessionLock(); } @@ -111,11 +128,6 @@ __attribute__((noreturn)) void Boot::boot() { Boot::config()->clearSlot(); Boot::config()->setBooting(false); - if (!Boot::isKernelPatched(Slot::A())) { - Boot::patchKernel(Slot::A()); - Ion::LED::setColor(KDColorRed); - } - while (true) { HomeMenu menu = HomeMenu(); menu.open(true); From 0e49d23faab0a618f935c9591ede4918fe396342 Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Tue, 26 Apr 2022 15:26:34 +0200 Subject: [PATCH 246/355] Apply suggestions from code review Co-authored-by: Yaya-Cout --- bootloader/boot.h | 2 +- bootloader/drivers/stm32_drivers.h | 2 +- bootloader/interface/menus/about.cpp | 2 +- bootloader/interface/menus/about.h | 2 +- bootloader/interface/menus/crash.cpp | 2 +- bootloader/interface/menus/crash.h | 2 +- bootloader/interface/menus/dfu.cpp | 2 +- bootloader/interface/menus/home.cpp | 2 +- bootloader/interface/menus/installer.cpp | 2 +- bootloader/interface/menus/installer.h | 2 +- bootloader/interface/menus/slot_recovery.cpp | 2 +- bootloader/interface/menus/slot_recovery.h | 2 +- bootloader/interface/menus/warning.cpp | 2 +- bootloader/interface/menus/warning.h | 2 +- bootloader/interface/src/menu.h | 2 +- bootloader/interface/static/interface.cpp | 1 - bootloader/interface/static/interface.h | 2 +- bootloader/interface/static/messages.h | 7 ------- bootloader/main.cpp | 2 +- 19 files changed, 17 insertions(+), 25 deletions(-) diff --git a/bootloader/boot.h b/bootloader/boot.h index 2b7d66d47f0..50800f3d52a 100644 --- a/bootloader/boot.h +++ b/bootloader/boot.h @@ -57,4 +57,4 @@ class Boot { } -#endif \ No newline at end of file +#endif diff --git a/bootloader/drivers/stm32_drivers.h b/bootloader/drivers/stm32_drivers.h index bba55c74b1d..50f723ba6d0 100644 --- a/bootloader/drivers/stm32_drivers.h +++ b/bootloader/drivers/stm32_drivers.h @@ -150,4 +150,4 @@ namespace STM32 { extern void rcc_deinit(); extern void hal_deinit(); extern void systick_deinit(); -} \ No newline at end of file +} diff --git a/bootloader/interface/menus/about.cpp b/bootloader/interface/menus/about.cpp index 7e1d176bb44..661c6a20f3d 100644 --- a/bootloader/interface/menus/about.cpp +++ b/bootloader/interface/menus/about.cpp @@ -17,4 +17,4 @@ void Bootloader::AboutMenu::setup() { m_columns[2] = ColumnBinder(&m_default_columns[2]); m_columns[3] = ColumnBinder(&m_default_columns[3]); m_columns[4] = ColumnBinder(&m_default_columns[4]); -} \ No newline at end of file +} diff --git a/bootloader/interface/menus/about.h b/bootloader/interface/menus/about.h index 4f3df1c91d0..b4c43d57c0f 100644 --- a/bootloader/interface/menus/about.h +++ b/bootloader/interface/menus/about.h @@ -14,4 +14,4 @@ namespace Bootloader { } -#endif \ No newline at end of file +#endif diff --git a/bootloader/interface/menus/crash.cpp b/bootloader/interface/menus/crash.cpp index d7ed3a19eed..2124eafb3d4 100644 --- a/bootloader/interface/menus/crash.cpp +++ b/bootloader/interface/menus/crash.cpp @@ -19,4 +19,4 @@ void Bootloader::CrashMenu::post_open() { for (;;) { // Infinite loop } -} \ No newline at end of file +} diff --git a/bootloader/interface/menus/crash.h b/bootloader/interface/menus/crash.h index c73c3dbd6cd..50a66d4868c 100644 --- a/bootloader/interface/menus/crash.h +++ b/bootloader/interface/menus/crash.h @@ -16,4 +16,4 @@ namespace Bootloader { }; } -#endif \ No newline at end of file +#endif diff --git a/bootloader/interface/menus/dfu.cpp b/bootloader/interface/menus/dfu.cpp index 537108f3cae..e09598cb605 100644 --- a/bootloader/interface/menus/dfu.cpp +++ b/bootloader/interface/menus/dfu.cpp @@ -34,4 +34,4 @@ void Bootloader::DfuMenu::post_open() { } while (!Ion::USB::isEnumerated()); Ion::USB::DFU(true, (void *)m_data); } -} \ No newline at end of file +} diff --git a/bootloader/interface/menus/home.cpp b/bootloader/interface/menus/home.cpp index 7805d799608..c5f9bbed269 100644 --- a/bootloader/interface/menus/home.cpp +++ b/bootloader/interface/menus/home.cpp @@ -141,4 +141,4 @@ void Bootloader::HomeMenu::setup() { m_columns[3] = ColumnBinder(&m_default_columns[0]); m_columns[4] = ColumnBinder(&m_default_columns[1]); -} \ No newline at end of file +} diff --git a/bootloader/interface/menus/installer.cpp b/bootloader/interface/menus/installer.cpp index a82b4aebefc..5f9ad8ebf0a 100644 --- a/bootloader/interface/menus/installer.cpp +++ b/bootloader/interface/menus/installer.cpp @@ -38,4 +38,4 @@ void Bootloader::InstallerMenu::setup() { m_columns[0] = ColumnBinder(&m_default_columns[0]); m_columns[1] = ColumnBinder(&m_default_columns[1]); m_columns[2] = ColumnBinder(&m_default_columns[2]); -} \ No newline at end of file +} diff --git a/bootloader/interface/menus/installer.h b/bootloader/interface/menus/installer.h index b68a49aab25..c26c27cbc34 100644 --- a/bootloader/interface/menus/installer.h +++ b/bootloader/interface/menus/installer.h @@ -17,4 +17,4 @@ namespace Bootloader { }; } -#endif \ No newline at end of file +#endif diff --git a/bootloader/interface/menus/slot_recovery.cpp b/bootloader/interface/menus/slot_recovery.cpp index d026f2be33b..2f142be0acd 100644 --- a/bootloader/interface/menus/slot_recovery.cpp +++ b/bootloader/interface/menus/slot_recovery.cpp @@ -36,4 +36,4 @@ void Bootloader::SlotRecoveryMenu::post_open() { } while (!Ion::USB::isEnumerated()); Ion::USB::DFU(true, (void *)m_data); } -} \ No newline at end of file +} diff --git a/bootloader/interface/menus/slot_recovery.h b/bootloader/interface/menus/slot_recovery.h index 452868dee18..08676645d07 100644 --- a/bootloader/interface/menus/slot_recovery.h +++ b/bootloader/interface/menus/slot_recovery.h @@ -16,4 +16,4 @@ namespace Bootloader { }; } -#endif \ No newline at end of file +#endif diff --git a/bootloader/interface/menus/warning.cpp b/bootloader/interface/menus/warning.cpp index 316dfde69d0..fca936c14cc 100644 --- a/bootloader/interface/menus/warning.cpp +++ b/bootloader/interface/menus/warning.cpp @@ -32,4 +32,4 @@ void Bootloader::WarningMenu::setup() { m_columns[3] = ColumnBinder(&m_default_columns[3]); m_columns[4] = ColumnBinder(&m_default_columns[4]); m_columns[5] = ColumnBinder(&m_default_columns[5]); -} \ No newline at end of file +} diff --git a/bootloader/interface/menus/warning.h b/bootloader/interface/menus/warning.h index bc98a5640fa..d2f8a649b77 100644 --- a/bootloader/interface/menus/warning.h +++ b/bootloader/interface/menus/warning.h @@ -14,4 +14,4 @@ namespace Bootloader { }; } -#endif \ No newline at end of file +#endif diff --git a/bootloader/interface/src/menu.h b/bootloader/interface/src/menu.h index da591a02265..a9faccea832 100644 --- a/bootloader/interface/src/menu.h +++ b/bootloader/interface/src/menu.h @@ -123,4 +123,4 @@ namespace Bootloader { }; } -#endif // _BOOTLOADER_MENU_H_ \ No newline at end of file +#endif // _BOOTLOADER_MENU_H_ diff --git a/bootloader/interface/static/interface.cpp b/bootloader/interface/static/interface.cpp index 817b6f2c087..4244a227b5f 100644 --- a/bootloader/interface/static/interface.cpp +++ b/bootloader/interface/static/interface.cpp @@ -1,4 +1,3 @@ - #include #include #include diff --git a/bootloader/interface/static/interface.h b/bootloader/interface/static/interface.h index 8c5234fc4da..3741b5ec17e 100644 --- a/bootloader/interface/static/interface.h +++ b/bootloader/interface/static/interface.h @@ -16,4 +16,4 @@ class Interface { } -#endif \ No newline at end of file +#endif diff --git a/bootloader/interface/static/messages.h b/bootloader/interface/static/messages.h index de63bf3d3cd..4db53869e77 100644 --- a/bootloader/interface/static/messages.h +++ b/bootloader/interface/static/messages.h @@ -5,14 +5,7 @@ namespace Bootloader { class Messages { public: - // TODO: Remove it when this fork will be updated - #ifdef UPSILON_VERSION constexpr static const char * mainTitle = "Upsilon Calculator"; - #elif defined OMEGA_VERSION - constexpr static const char * mainTitle = "Omega Calculator"; - #else - constexpr static const char * mainTitle = "NumWorks Calculator"; - #endif // home menu constexpr static const char * homeTitle = "Select a slot"; diff --git a/bootloader/main.cpp b/bootloader/main.cpp index 7902fc7d039..d5161456b31 100644 --- a/bootloader/main.cpp +++ b/bootloader/main.cpp @@ -11,7 +11,7 @@ __attribute__ ((noreturn)) void ion_main(int argc, const char * const argv[]) { // Clear the screen - Ion::Display::pushRectUniform(KDRect(0,0,320,240), KDColorWhite); + Ion::Display::pushRectUniform(KDRect(0,0,320,240), KDColorBlack); // Initialize the backlight Ion::Backlight::init(); From 04ae526a39e7c7b9d5cb37bf5132de4885308232 Mon Sep 17 00:00:00 2001 From: devdl11 Date: Tue, 26 Apr 2022 18:10:34 +0200 Subject: [PATCH 247/355] Removing useless file --- .../local/upsilon_light/bootloader/computer.xcf | Bin 5072 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 themes/themes/local/upsilon_light/bootloader/computer.xcf diff --git a/themes/themes/local/upsilon_light/bootloader/computer.xcf b/themes/themes/local/upsilon_light/bootloader/computer.xcf deleted file mode 100644 index 4ad99c7c0b1c1f4f75a18ac1b2f1c9cdbf41e044..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5072 zcmd^C&u<$=6i(K5oWyY&E9J)ls>Ohk2(7cbw&S%)T(xOSBefzTDHH*bCi}x#$@Ut1 zlcon;a^TL16DOYlS>k{Y5-L)qwihHUD1QYAvgQxK%lFoH>NZZ@f>fb)tT*%R%$u3_ zzIktUoiht1epfa49Z`~oP}U&x1W-PML?FY%sHP#Kr$6<7P6B)!;_t|_ot8uU%+OF! zL>nEUUp?dU`GtAc4%>uM#QI<2JoRO2fq-4ZoCN2(`OFOT#Tr?}C2dR4YUC@g;dn=kv zqy{YPX}@D?q^!gTD8E@Y^`gyekOxZ!D8Hp6WVT875)L$)9klqGW!P6(8T7oda&eWH zW0Dvf$jjxsyF|0eh19$}h@N*JVaDo~Wm~ajiuX0dEllg{^yNY+MmEPzt*+p3V|`6H zP+HA1+px-oX!e3x+}`cS;(;!O%bJZC!_Etdq#`P*xU`T;r;~{X&U?EZ;bhE_EYc>L z8aoG57>>uw{P|KbchthRX&AA=5kSlCLYB)qv#s*FW#xNL^r*!#5nE#pB+QMFAmofeXYwkafZi3v(VN(yl)ozl`;JgxN)-;O<4{k&O$L08|C zKA5lAXLsxjQ`?bTVLQ6^V9O58Rkv6%r)$yd{3A{@ixry{Rh@h!o=i9xPZ^3Vi;@u6 znIa_QxG1FKDib7WQ!|n{__%UpOtqOiWM8F|O{a3;69+)=7YYV`){DBiN3{CNmX4IC zdrJ^yK~AoB?k(vxQObw|j9cmBa!N`kQc7AEG?n2vWf{k*rWSc z`(Yx~)7gd30lWyIcjV+U%YV47^Qu)cZ+=M{BM^EkyO5U%pZpj#-rNxCr&(hijAkg> zgJ0;u<2{(~!DJDwLuCTe#o-=&44C#KD}x;&JzD=&NO#UFgk2Q0FZbZyFAH>c%jmaV zgtp5x=KX>+TQZRrf33b*}1jLJ+RXIqqK}s+%5eJ}&Hc#<0J; z&sE)~-w9R&j;}fb382qkb@xY{*6;g4Cr~|xL3e)w>Mf@^=1f$B&M|awPHW$p0`MQI z4~F`HrZ+2qHCx}@^xYn>x!?J2b2YF5;c6ZmR5v}`3_T_cHn2aus(EbGz(x&hpoN1q zcR%3#-TZ^-sDTbVgN|C8jvDAdN842Z&~aaVFmwnwoJR+C3D#P-e&+79e*I~z;r{5m z!!^JNgliBNA+PS@yS@Jv(HGp5N1Z?ezT*kLH`=~8 z;5(+#k)j`--&eOCM(6z?09D$^9rH)r8)L3Na*g|hvatK{Pq#!^wF;$eTQA4(Zl)}v zk9#Wrrz@syQTv+tH&;y8r_MUz=erA{?UK>CV#15f zH@)F^hc}e!lT)9-Ej9g+{$w^>H@h+HE^YYS*Ehh(9vImJ7kf(W$*E7^4w-&Pe=>Vu zWDkt&fss8hQU@b-a8XwpPfmRT7tHiS`je@HkvbTugOU2R& Date: Wed, 27 Apr 2022 14:06:02 +0200 Subject: [PATCH 248/355] [apps/python] Use super implementation in the select sub menu function of the toolbox --- apps/code/python_toolbox.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index d601db5f14c..d6cc65dbaec 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -783,13 +783,7 @@ bool PythonToolbox::selectSubMenu(int selectedRow) { Container::activeApp()->displayModalViewController(static_cast(&m_ionKeys), 0.f, 0.f, Metric::PopUpTopMargin, Metric::PopUpLeftMargin, 0, Metric::PopUpRightMargin); return true; } - m_selectableTableView.deselectTable(); - m_messageTreeModel = static_cast(m_messageTreeModel->childAtIndex(selectedRow)); - if (m_messageTreeModel->isFork()) { - assert(m_messageTreeModel->numberOfChildren() < 0); - m_messageTreeModel = static_cast(m_messageTreeModel->childAtIndex(indexAfterFork())); - } - return NestedMenuController::selectSubMenu(selectedRow); + return Toolbox::selectSubMenu(selectedRow); } From 0332f43c5c24afab24e786b17ee47d27ebec3f93 Mon Sep 17 00:00:00 2001 From: devdl11 Date: Wed, 27 Apr 2022 15:44:42 +0200 Subject: [PATCH 249/355] Reviews --- bootloader/boot.cpp | 10 +- bootloader/boot.h | 4 +- bootloader/boot/rt0.cpp | 2 +- bootloader/drivers/stm32_drivers.cpp | 15 +++ bootloader/drivers/stm32_drivers.h | 6 ++ bootloader/interface/menus/about.h | 2 +- bootloader/interface/menus/crash.cpp | 2 +- bootloader/interface/menus/crash.h | 2 +- bootloader/interface/menus/dfu.cpp | 6 +- bootloader/interface/menus/dfu.h | 4 +- bootloader/interface/menus/home.cpp | 105 +++++++------------ bootloader/interface/menus/home.h | 19 ++-- bootloader/interface/menus/installer.cpp | 8 +- bootloader/interface/menus/installer.h | 2 +- bootloader/interface/menus/slot_recovery.cpp | 2 +- bootloader/interface/menus/slot_recovery.h | 2 +- bootloader/interface/menus/warning.h | 2 +- bootloader/interface/src/menu.cpp | 2 +- bootloader/interface/src/menu.h | 2 +- bootloader/main.cpp | 4 +- bootloader/recovery.cpp | 4 +- bootloader/recovery.h | 4 +- 22 files changed, 94 insertions(+), 115 deletions(-) diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index 85412aa3952..7b108fe0945 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -36,7 +36,7 @@ void Boot::setMode(BootMode mode) { // We dont use the exam mode driver as storage for the boot mode because we need the 16k of storage x) } -void Boot::busErr() { +void Boot::busError() { if (config()->isBooting()) { config()->slot()->boot(); } @@ -61,7 +61,7 @@ bool Boot::isKernelPatched(const Slot & s) { return *(uint32_t *)(origin_isr + sizeof(uint32_t) * 4) == (uint32_t)&_fake_isr_function_start && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 5) == (uint32_t)&_fake_isr_function_start && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 6) == (uint32_t)&_fake_isr_function_start && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 7) == (uint32_t)&_fake_isr_function_start; } -__attribute((section(".fake_isr_function"))) __attribute__((used)) void Boot::flsh_intr() { +__attribute((section(".fake_isr_function"))) __attribute__((used)) void Boot::flash_interrupt() { // a simple function } @@ -103,9 +103,9 @@ void Boot::bootSlot(Bootloader::Slot s) { // We are trying to boot epsilon, so we check the version and show an advertisement if needed const char * version = s.userlandHeader()->version(); const char * min = "18.2.4"; - int vsum = Utility::versionSum(version, strlen(version)); - int minsum = Utility::versionSum(min, strlen(min)); - if (vsum >= minsum) { + int versionSum = Utility::versionSum(version, strlen(version)); + int minimalVersionTrigger = Utility::versionSum(min, strlen(min)); + if (versionSum >= minimalVersionTrigger) { WarningMenu menu = WarningMenu(); menu.open(); return; diff --git a/bootloader/boot.h b/bootloader/boot.h index 50800f3d52a..934d60d66a5 100644 --- a/bootloader/boot.h +++ b/bootloader/boot.h @@ -40,14 +40,14 @@ class Boot { static bool isKernelPatched(const Slot & slot); static void patchKernel(const Slot & slot); - static void busErr(); + static void busError(); __attribute__ ((noreturn)) static void boot(); static void bootSlot(Bootloader::Slot slot); static void bootSelectedSlot(); __attribute__ ((noreturn)) static void jumpToInternalBootloader(); - __attribute((section(".fake_isr_function"))) __attribute__((used)) static void flsh_intr(); + __attribute((section(".fake_isr_function"))) __attribute__((used)) static void flash_interrupt(); static void bootloader(); static void lockInternal(); diff --git a/bootloader/boot/rt0.cpp b/bootloader/boot/rt0.cpp index 65eedf9859f..06221b25274 100644 --- a/bootloader/boot/rt0.cpp +++ b/bootloader/boot/rt0.cpp @@ -50,7 +50,7 @@ void __attribute__((noinline)) usage_fault_handler() { } void __attribute__((noinline)) bus_fault_handler() { - Bootloader::Boot::busErr(); + Bootloader::Boot::busError(); } /* In order to ensure that this method is execute from the external flash, we diff --git a/bootloader/drivers/stm32_drivers.cpp b/bootloader/drivers/stm32_drivers.cpp index 6fd018aab03..ac5d097529b 100644 --- a/bootloader/drivers/stm32_drivers.cpp +++ b/bootloader/drivers/stm32_drivers.cpp @@ -1,5 +1,20 @@ #include "stm32_drivers.h" +/** + * THIS CODE COMES FROM THE STM32_HAL LIBRARY (LICENSE ABOVE) AND HAVE BEEN MODIFIED + * WE USE ONLY THE HAL_deinit, RCC_deinit and systick_deninit FUNCTIONS AND ONLY COPIED THE CODE NEEDED. + * WE NEEDED THIS CODE TO BE ABLE TO BOOT THE STM32 BOOTLOADER + */ + +/* +This software component is provided to you as part of a software package and +applicable license terms are in the Package_license file. If you received this +software component outside of a package or without applicable license terms, +the terms of the BSD-3-Clause license shall apply. +You may obtain a copy of the BSD-3-Clause at: +https://opensource.org/licenses/BSD-3-Clause +*/ + void STM32::rcc_deinit() { SET_BIT(STM_32_RCC->CR, (0x1UL << (0U))); while (READ_BIT(STM_32_RCC->CR, (0x1UL << (1U))) == 0) {} diff --git a/bootloader/drivers/stm32_drivers.h b/bootloader/drivers/stm32_drivers.h index 50f723ba6d0..9efa636dd96 100644 --- a/bootloader/drivers/stm32_drivers.h +++ b/bootloader/drivers/stm32_drivers.h @@ -5,6 +5,12 @@ Now we include the license of the original code as required. */ +/** + * THIS CODE COMES FROM THE STM32_HAL LIBRARY (LICENSE ABOVE) AND HAVE BEEN MODIFIED + * WE USE ONLY THE HAL_deinit, RCC_deinit and systick_deninit FUNCTIONS AND ONLY COPIED THE CODE NEEDED. + * WE NEEDED THIS CODE TO BE ABLE TO BOOT THE STM32 BOOTLOADER + */ + /* This software component is provided to you as part of a software package and applicable license terms are in the Package_license file. If you received this diff --git a/bootloader/interface/menus/about.h b/bootloader/interface/menus/about.h index b4c43d57c0f..00f48270540 100644 --- a/bootloader/interface/menus/about.h +++ b/bootloader/interface/menus/about.h @@ -9,7 +9,7 @@ namespace Bootloader { AboutMenu(); void setup() override; - void post_open() override {}; + void postOpen() override {}; }; } diff --git a/bootloader/interface/menus/crash.cpp b/bootloader/interface/menus/crash.cpp index 2124eafb3d4..242299bc2cc 100644 --- a/bootloader/interface/menus/crash.cpp +++ b/bootloader/interface/menus/crash.cpp @@ -14,7 +14,7 @@ void Bootloader::CrashMenu::setup() { m_columns[2] = ColumnBinder(&m_default_columns[2]); } -void Bootloader::CrashMenu::post_open() { +void Bootloader::CrashMenu::postOpen() { // We override the open method for (;;) { // Infinite loop diff --git a/bootloader/interface/menus/crash.h b/bootloader/interface/menus/crash.h index 50a66d4868c..bcb74ec81e7 100644 --- a/bootloader/interface/menus/crash.h +++ b/bootloader/interface/menus/crash.h @@ -9,7 +9,7 @@ namespace Bootloader { CrashMenu(const char * error); void setup() override; - void post_open() override; + void postOpen() override; private: const char * m_error; diff --git a/bootloader/interface/menus/dfu.cpp b/bootloader/interface/menus/dfu.cpp index e09598cb605..88e705e52a2 100644 --- a/bootloader/interface/menus/dfu.cpp +++ b/bootloader/interface/menus/dfu.cpp @@ -2,17 +2,17 @@ #include #include -Bootloader::DfuMenu::DfuMenu(const char * text, const USBData * data) : Menu(KDColorBlack, KDColorWhite, Messages::dfuTitle, Messages::mainTitle), m_submenu_text(text), m_data(data) { +Bootloader::DfuMenu::DfuMenu(const char * text, const USBData * data) : Menu(KDColorBlack, KDColorWhite, Messages::dfuTitle, Messages::mainTitle), m_submenuText(text), m_data(data) { setup(); } void Bootloader::DfuMenu::setup() { - m_default_columns[0] = Column(m_submenu_text, k_small_font, 0, true); + m_default_columns[0] = Column(m_submenuText, k_small_font, 0, true); m_columns[0] = ColumnBinder(&m_default_columns[0]); } -void Bootloader::DfuMenu::post_open() { +void Bootloader::DfuMenu::postOpen() { // We override the open method if (!m_data->getData().isProtectedInternal() && m_data->getData().isProtectedExternal()) { // Because we want to flash the internal, we will jump into the stm32 bootloader diff --git a/bootloader/interface/menus/dfu.h b/bootloader/interface/menus/dfu.h index ddb957fcb33..b817393826d 100644 --- a/bootloader/interface/menus/dfu.h +++ b/bootloader/interface/menus/dfu.h @@ -10,10 +10,10 @@ namespace Bootloader { DfuMenu(const char * submenu, const USBData * usbData); void setup() override; - void post_open() override; + void postOpen() override; private: - const char * m_submenu_text; + const char * m_submenuText; const USBData * m_data; }; } diff --git a/bootloader/interface/menus/home.cpp b/bootloader/interface/menus/home.cpp index c5f9bbed269..61c69f95a4f 100644 --- a/bootloader/interface/menus/home.cpp +++ b/bootloader/interface/menus/home.cpp @@ -52,24 +52,14 @@ bool about_submenu() { return true; } -const char * Bootloader::HomeMenu::slotA_text() { - return Slot::isFullyValid(Slot::A()) ? Messages::homeSlotASubmenu : Messages::invalidSlot; -} - -const char * Bootloader::HomeMenu::slotA_kernel_text() { - return Slot::isFullyValid(Slot::A()) ? Slot::A().kernelHeader()->patchLevel() : nullptr; -} - -const char * Bootloader::HomeMenu::slotA_version_text() { - return Slot::isFullyValid(Slot::A()) ? Slot::A().userlandHeader()->isOmega() && Slot::A().userlandHeader()->isUpsilon() ? Slot::A().userlandHeader()->upsilonVersion() : Slot::A().userlandHeader()->isOmega() ? Slot::A().userlandHeader()->omegaVersion() : Slot::A().kernelHeader()->version() : nullptr; -} - -const char * Bootloader::HomeMenu::slotA_os_text() { - if (Slot::isFullyValid(Slot::A())) { - if (Slot::A().userlandHeader()->isOmega() && Slot::A().userlandHeader()->isUpsilon()) { +const char * Bootloader::HomeMenu::getSlotOsText(Slot slot) { + if (Slot::isFullyValid(slot)) { + if (slot.userlandHeader()->isOmega() && slot.userlandHeader()->isUpsilon()) { return Messages::upsilonSlot; - } else if (Slot::A().userlandHeader()->isOmega()) { + } else if (slot.userlandHeader()->isOmega() && slot.kernelHeader()->patchLevel()[0] != '\0') { return Messages::omegaSlot; + } else if (slot.userlandHeader()->isOmega()) { + return Messages::khiSlot; } else { return Messages::epsilonSlot; } @@ -77,68 +67,43 @@ const char * Bootloader::HomeMenu::slotA_os_text() { return nullptr; } -const char * Bootloader::HomeMenu::slotKhi_text() { - return Slot::isFullyValid(Slot::Khi()) ? Messages:: homeSlotKhiSubmenu : Messages::invalidSlot; -} - -const char * Bootloader::HomeMenu::slotKhi_kernel_text() { - return Slot::isFullyValid(Slot::Khi()) ? Slot::Khi().kernelHeader()->patchLevel() : nullptr; -} - -const char * Bootloader::HomeMenu::slotKhi_os_text() { - if (Slot::isFullyValid(Slot::Khi())) { - if (Slot::Khi().userlandHeader()->isOmega() && Slot::Khi().userlandHeader()->isUpsilon()) { - return Messages::upsilonSlot; - } else if (Slot::Khi().userlandHeader()->isOmega()) { - return Messages::omegaSlot; - } else { - return Messages::epsilonSlot; +const char * Bootloader::HomeMenu::getSlotText(Slot slot) { + if(Slot::isFullyValid(slot)) { + if (slot.address() == Slot::A().address()) { + return Messages::homeSlotASubmenu; + } else if (slot.address() == Slot::Khi().address()) { + return Messages::homeSlotKhiSubmenu; + } else if (slot.address() == Slot::B().address()) { + return Messages::homeSlotBSubmenu; } } - return nullptr; -} - -const char * Bootloader::HomeMenu::slotKhi_version_text() { - return Slot::isFullyValid(Slot::Khi()) ? Slot::Khi().userlandHeader()->isOmega() && Slot::Khi().userlandHeader()->isUpsilon() ? Slot::Khi().userlandHeader()->upsilonVersion() : Slot::Khi().userlandHeader()->isOmega() ? Slot::Khi().userlandHeader()->omegaVersion() : Slot::Khi().kernelHeader()->version() : nullptr; -} - -const char * Bootloader::HomeMenu::slotB_text() { - return Slot::isFullyValid(Slot::B()) ? Messages::homeSlotBSubmenu : Messages::invalidSlot; -} - -const char * Bootloader::HomeMenu::slotB_kernel_text() { - return Slot::isFullyValid(Slot::B()) ? Slot::B().kernelHeader()->patchLevel() : nullptr; + return Messages::invalidSlot; } -const char * Bootloader::HomeMenu::slotB_os_text() { - if (Slot::isFullyValid(Slot::B())) { - if (Slot::B().userlandHeader()->isOmega() && Slot::B().userlandHeader()->isUpsilon()) { - return Messages::upsilonSlot; - } else if (Slot::B().userlandHeader()->isOmega()) { - return Messages::omegaSlot; - } else { - return Messages::epsilonSlot; - } - } - return nullptr; +const char * Bootloader::HomeMenu::getKernelText(Slot slot) { + return Slot::isFullyValid(slot) ? slot.kernelHeader()->patchLevel() : nullptr; } -const char * Bootloader::HomeMenu::slotB_version_text() { - return Slot::isFullyValid(Slot::B()) ? Slot::B().userlandHeader()->isOmega() && Slot::B().userlandHeader()->isUpsilon() ? Slot::B().userlandHeader()->upsilonVersion() : Slot::B().userlandHeader()->isOmega() ? Slot::B().userlandHeader()->omegaVersion() : Slot::B().kernelHeader()->version() : nullptr; +const char * Bootloader::HomeMenu::getVersionText(Slot slot) { + return Slot::isFullyValid(slot) ? slot.userlandHeader()->isOmega() && slot.userlandHeader()->isUpsilon() ? slot.userlandHeader()->upsilonVersion() : slot.userlandHeader()->isOmega() ? slot.userlandHeader()->omegaVersion() : slot.kernelHeader()->version() : nullptr; } void Bootloader::HomeMenu::setup() { - m_slot_columns[0] = SlotColumn(slotA_text(), slotA_kernel_text(), slotA_os_text(), slotA_version_text(), Ion::Keyboard::Key::One, k_small_font, 10, false, &slotA_submenu); - m_slot_columns[1] = SlotColumn(slotKhi_text(), slotKhi_kernel_text(), slotKhi_os_text(), slotKhi_version_text(), Ion::Keyboard::Key::Two, k_small_font, 10, false, &slotKhi_submenu); - m_slot_columns[2] = SlotColumn(slotB_text(), slotB_kernel_text(), slotB_os_text(), slotB_version_text(), Ion::Keyboard::Key::Three, k_small_font, 10, false, &slotB_submenu); - m_default_columns[0] = Column(Messages::homeInstallerSubmenu, Ion::Keyboard::Key::Four, k_small_font, 10, false, &installer_submenu); - m_default_columns[1] = Column(Messages::homeAboutSubmenu, Ion::Keyboard::Key::Five, k_small_font, 10, false, &about_submenu); - - - m_columns[0] = ColumnBinder(&m_slot_columns[0]); - m_columns[1] = ColumnBinder(&m_slot_columns[1]); - m_columns[2] = ColumnBinder(&m_slot_columns[2]); - m_columns[3] = ColumnBinder(&m_default_columns[0]); - m_columns[4] = ColumnBinder(&m_default_columns[1]); + Slot A = Slot::A(); + Slot Khi = Slot::Khi(); + Slot B = Slot::B(); + + m_slot_columns[0] = SlotColumn(getSlotText(A), getKernelText(A), getSlotOsText(A), getVersionText(A), Ion::Keyboard::Key::One, k_small_font, 10, false, &slotA_submenu); + m_slot_columns[1] = SlotColumn(getSlotText(Khi), getKernelText(Khi), getSlotOsText(Khi), getVersionText(Khi), Ion::Keyboard::Key::Two, k_small_font, 10, false, &slotKhi_submenu); + m_slot_columns[2] = SlotColumn(getSlotText(B), getKernelText(B), getSlotOsText(B), getVersionText(B), Ion::Keyboard::Key::Three, k_small_font, 10, false, &slotB_submenu); + m_default_columns[0] = Column(Messages::homeInstallerSubmenu, Ion::Keyboard::Key::Four, k_small_font, 10, false, &installer_submenu); + m_default_columns[1] = Column(Messages::homeAboutSubmenu, Ion::Keyboard::Key::Five, k_small_font, 10, false, &about_submenu); + + + m_columns[0] = ColumnBinder(&m_slot_columns[0]); + m_columns[1] = ColumnBinder(&m_slot_columns[1]); + m_columns[2] = ColumnBinder(&m_slot_columns[2]); + m_columns[3] = ColumnBinder(&m_default_columns[0]); + m_columns[4] = ColumnBinder(&m_default_columns[1]); } diff --git a/bootloader/interface/menus/home.h b/bootloader/interface/menus/home.h index 00de3edc3ba..da6f010b17e 100644 --- a/bootloader/interface/menus/home.h +++ b/bootloader/interface/menus/home.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace Bootloader { class HomeMenu : public Menu { @@ -11,24 +12,16 @@ namespace Bootloader { HomeMenu(); void setup() override; - void post_open() override {}; + void postOpen() override {}; static AboutMenu * aboutMenu(); static InstallerMenu * installerMenu(); private: - const char * slotA_text(); - const char * slotA_kernel_text(); - const char * slotA_os_text(); - const char * slotA_version_text(); - const char * slotKhi_text(); - const char * slotKhi_kernel_text(); - const char * slotKhi_os_text(); - const char * slotKhi_version_text(); - const char * slotB_text(); - const char * slotB_kernel_text(); - const char * slotB_os_text(); - const char * slotB_version_text(); + const char * getSlotOsText(Slot slot); + const char * getSlotText(Slot slot); + const char * getKernelText(Slot slot); + const char * getVersionText(Slot slot); }; } diff --git a/bootloader/interface/menus/installer.cpp b/bootloader/interface/menus/installer.cpp index 5f9ad8ebf0a..b6acf2c2e9e 100644 --- a/bootloader/interface/menus/installer.cpp +++ b/bootloader/interface/menus/installer.cpp @@ -20,20 +20,20 @@ Bootloader::InstallerMenu::InstallerMenu() : Menu(KDColorBlack, KDColorWhite, Me setup(); } -bool slots_submenu() { +bool slotsSubmenu() { Bootloader::InstallerMenu::SlotsDFU()->open(); return true; } -bool bootloader_submenu() { +bool bootloaderSubmenu() { Bootloader::InstallerMenu::BootloaderDFU()->open(); return true; } void Bootloader::InstallerMenu::setup() { m_default_columns[0] = Column(Messages::installerText1, k_large_font, 0, true); - m_default_columns[1] = Column(Messages::installerSlotsSubmenu, Ion::Keyboard::Key::One, k_small_font, 30, false, &slots_submenu); - m_default_columns[2] = Column(Messages::installerBootloaderSubmenu, Ion::Keyboard::Key::Two, k_small_font, 30, false, &bootloader_submenu); + m_default_columns[1] = Column(Messages::installerSlotsSubmenu, Ion::Keyboard::Key::One, k_small_font, 30, false, &slotsSubmenu); + m_default_columns[2] = Column(Messages::installerBootloaderSubmenu, Ion::Keyboard::Key::Two, k_small_font, 30, false, &bootloaderSubmenu); m_columns[0] = ColumnBinder(&m_default_columns[0]); m_columns[1] = ColumnBinder(&m_default_columns[1]); diff --git a/bootloader/interface/menus/installer.h b/bootloader/interface/menus/installer.h index c26c27cbc34..dd98e4a98b0 100644 --- a/bootloader/interface/menus/installer.h +++ b/bootloader/interface/menus/installer.h @@ -10,7 +10,7 @@ namespace Bootloader { InstallerMenu(); void setup() override; - void post_open() override {}; + void postOpen() override {}; static DfuMenu * SlotsDFU(); static DfuMenu * BootloaderDFU(); diff --git a/bootloader/interface/menus/slot_recovery.cpp b/bootloader/interface/menus/slot_recovery.cpp index 2f142be0acd..f052d6be57e 100644 --- a/bootloader/interface/menus/slot_recovery.cpp +++ b/bootloader/interface/menus/slot_recovery.cpp @@ -19,7 +19,7 @@ void Bootloader::SlotRecoveryMenu::setup() { m_columns[4] = ColumnBinder(&m_default_columns[4]); } -void Bootloader::SlotRecoveryMenu::post_open() { +void Bootloader::SlotRecoveryMenu::postOpen() { // We override the open method for (;;) { Ion::USB::enable(); diff --git a/bootloader/interface/menus/slot_recovery.h b/bootloader/interface/menus/slot_recovery.h index 08676645d07..4eba9284463 100644 --- a/bootloader/interface/menus/slot_recovery.h +++ b/bootloader/interface/menus/slot_recovery.h @@ -10,7 +10,7 @@ namespace Bootloader { SlotRecoveryMenu(USBData * usbData); void setup() override; - void post_open() override; + void postOpen() override; private: const USBData * m_data; }; diff --git a/bootloader/interface/menus/warning.h b/bootloader/interface/menus/warning.h index d2f8a649b77..f2763f203c5 100644 --- a/bootloader/interface/menus/warning.h +++ b/bootloader/interface/menus/warning.h @@ -10,7 +10,7 @@ namespace Bootloader { WarningMenu(); void setup() override; - void post_open() override {}; + void postOpen() override {}; }; } diff --git a/bootloader/interface/src/menu.cpp b/bootloader/interface/src/menu.cpp index 1d81287ae30..d45fb846621 100644 --- a/bootloader/interface/src/menu.cpp +++ b/bootloader/interface/src/menu.cpp @@ -18,7 +18,7 @@ void Bootloader::Menu::open(bool noreturn) { uint64_t scan = 0; bool exit = false; - post_open(); + postOpen(); while(!exit && !m_forced_exit) { scan = Ion::Keyboard::scan(); diff --git a/bootloader/interface/src/menu.h b/bootloader/interface/src/menu.h index a9faccea832..f623710c244 100644 --- a/bootloader/interface/src/menu.h +++ b/bootloader/interface/src/menu.h @@ -18,7 +18,7 @@ namespace Bootloader { static const int k_columns_margin = 5; virtual void setup() = 0; - virtual void post_open() = 0; + virtual void postOpen() = 0; enum ColumnType { DEFAULT, diff --git a/bootloader/main.cpp b/bootloader/main.cpp index d5161456b31..c0f0995db11 100644 --- a/bootloader/main.cpp +++ b/bootloader/main.cpp @@ -50,8 +50,8 @@ __attribute__ ((noreturn)) void ion_main(int argc, const char * const argv[]) { } } - if (Bootloader::Recovery::has_crashed()) { - Bootloader::Recovery::recover_data(); + if (Bootloader::Recovery::hasCrashed()) { + Bootloader::Recovery::recoverData(); } Bootloader::Interface::drawLoading(); diff --git a/bootloader/recovery.cpp b/bootloader/recovery.cpp index 5d357de305b..8c849f781b7 100644 --- a/bootloader/recovery.cpp +++ b/bootloader/recovery.cpp @@ -25,7 +25,7 @@ void Bootloader::Recovery::crash_handler(const char *error) { menu.open(true); } -bool Bootloader::Recovery::has_crashed() { +bool Bootloader::Recovery::hasCrashed() { bool isA = Bootloader::Slot::A().kernelHeader()->isValid() && Bootloader::Slot::A().userlandHeader()->isValid(); bool isB = Bootloader::Slot::B().kernelHeader()->isValid() && Bootloader::Slot::B().userlandHeader()->isValid(); @@ -71,7 +71,7 @@ Bootloader::Recovery::CrashedSlot Bootloader::Recovery::getSlotConcerned() { } } -void Bootloader::Recovery::recover_data() { +void Bootloader::Recovery::recoverData() { Ion::Device::Board::initPeripherals(false); Ion::Display::pushRectUniform(KDRect(0,0,320,240), KDColorWhite); Ion::Backlight::init(); diff --git a/bootloader/recovery.h b/bootloader/recovery.h index f5327c914ab..001f8eaab22 100644 --- a/bootloader/recovery.h +++ b/bootloader/recovery.h @@ -22,8 +22,8 @@ class Recovery { static CrashedSlot getSlotConcerned(); static void crash_handler(const char * error); - static void recover_data(); - static bool has_crashed(); + static void recoverData(); + static bool hasCrashed(); }; }; From 45b4dd337c2695b545175393a120e716ec53f78b Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Wed, 27 Apr 2022 15:46:31 +0200 Subject: [PATCH 250/355] Update bootloader/interface/static/messages.h Co-authored-by: Yaya-Cout --- bootloader/interface/static/messages.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootloader/interface/static/messages.h b/bootloader/interface/static/messages.h index 4db53869e77..e47764ac87e 100644 --- a/bootloader/interface/static/messages.h +++ b/bootloader/interface/static/messages.h @@ -7,7 +7,7 @@ class Messages { public: constexpr static const char * mainTitle = "Upsilon Calculator"; - // home menu + // Home menu constexpr static const char * homeTitle = "Select a slot"; // Slots OS Type From 47558446c4d23675f31ba3825554337cd8f628af Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Wed, 27 Apr 2022 18:56:06 +0200 Subject: [PATCH 251/355] Update bootloader/interface/menus/dfu.h Co-authored-by: Yaya-Cout --- bootloader/interface/menus/dfu.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootloader/interface/menus/dfu.h b/bootloader/interface/menus/dfu.h index b817393826d..17a4873b2d3 100644 --- a/bootloader/interface/menus/dfu.h +++ b/bootloader/interface/menus/dfu.h @@ -18,4 +18,4 @@ namespace Bootloader { }; } -#endif \ No newline at end of file +#endif From c73b13bd416352dbfc546e2fa2e4ecbfb3e677bd Mon Sep 17 00:00:00 2001 From: devdl11 Date: Wed, 27 Apr 2022 19:24:59 +0200 Subject: [PATCH 252/355] Some code --- bootloader/boot.cpp | 32 +++++++++++-------- build/targets.device.bootloader.mak | 2 -- ion/src/device/shared/drivers/flash.cpp | 4 +++ ion/src/device/shared/drivers/flash.h | 1 + .../device/shared/drivers/internal_flash.cpp | 11 +++++++ .../device/shared/drivers/internal_flash.h | 1 + 6 files changed, 35 insertions(+), 16 deletions(-) diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index 7b108fe0945..dc574c4f881 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -58,12 +58,16 @@ bool Boot::isKernelPatched(const Slot & s) { return true; } - return *(uint32_t *)(origin_isr + sizeof(uint32_t) * 4) == (uint32_t)&_fake_isr_function_start && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 5) == (uint32_t)&_fake_isr_function_start && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 6) == (uint32_t)&_fake_isr_function_start && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 7) == (uint32_t)&_fake_isr_function_start; + // return *(uint32_t *)(origin_isr + sizeof(uint32_t) * 4) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 5) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 6) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 7) == ((uint32_t)&_fake_isr_function_start) + 1;*(uint32_t *)(origin_isr + sizeof(uint32_t) * 4) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 5) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 6) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 7) == ((uint32_t)&_fake_isr_function_start) + 1;*(uint32_t *)(origin_isr + sizeof(uint32_t) * 4) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 5) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 6) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 7) == ((uint32_t)&_fake_isr_function_start) + 1; + return *(uint32_t *)(origin_isr + sizeof(uint32_t) * 7) == ((uint32_t)&_fake_isr_function_start) + 1; } __attribute((section(".fake_isr_function"))) __attribute__((used)) void Boot::flash_interrupt() { // a simple function - + Ion::Device::Flash::ClearInternalFlashErrors(); + asm("bx lr"); + asm("ldr PC, [PC, -0x18]"); + Ion::LED::setColor(KDColorBlue); } void Boot::patchKernel(const Slot & s) { @@ -71,27 +75,27 @@ void Boot::patchKernel(const Slot & s) { // we allocate a big buffer to store the first sector uint8_t data[1024*4]; memcpy(data, (void*)0x90000000, 1024*4); - uint32_t dummy_address = (uint32_t)&_fake_isr_function_start; + uint32_t dummy_address = (uint32_t)&_fake_isr_function_start + 1; uint8_t * ptr = (uint8_t *)&dummy_address; - data[origin_isr + sizeof(uint32_t) * 6] = ptr[0]; + data[origin_isr + sizeof(uint32_t) * 6] = ptr[0]; // BusFault data[origin_isr + sizeof(uint32_t) * 6 + 1] = ptr[1]; data[origin_isr + sizeof(uint32_t) * 6 + 2] = ptr[2]; data[origin_isr + sizeof(uint32_t) * 6 + 3] = ptr[3]; - data[origin_isr + sizeof(uint32_t) * 5] = ptr[0]; - data[origin_isr + sizeof(uint32_t) * 5 + 1] = ptr[1]; - data[origin_isr + sizeof(uint32_t) * 5 + 2] = ptr[2]; - data[origin_isr + sizeof(uint32_t) * 5 + 3] = ptr[3]; + // data[origin_isr + sizeof(uint32_t) * 5] = ptr[0]; // MemManage + // data[origin_isr + sizeof(uint32_t) * 5 + 1] = ptr[1]; + // data[origin_isr + sizeof(uint32_t) * 5 + 2] = ptr[2]; + // data[origin_isr + sizeof(uint32_t) * 5 + 3] = ptr[3]; - data[origin_isr + sizeof(uint32_t) * 7] = ptr[0]; + data[origin_isr + sizeof(uint32_t) * 7] = ptr[0]; // UsageFault data[origin_isr + sizeof(uint32_t) * 7 + 1] = ptr[1]; data[origin_isr + sizeof(uint32_t) * 7 + 2] = ptr[2]; data[origin_isr + sizeof(uint32_t) * 7 + 3] = ptr[3]; - data[origin_isr + sizeof(uint32_t) * 4] = ptr[0]; - data[origin_isr + sizeof(uint32_t) * 4 + 1] = ptr[1]; - data[origin_isr + sizeof(uint32_t) * 4 + 2] = ptr[2]; - data[origin_isr + sizeof(uint32_t) * 4 + 3] = ptr[3]; + // data[origin_isr + sizeof(uint32_t) * 4] = ptr[0];//hardfault + // data[origin_isr + sizeof(uint32_t) * 4 + 1] = ptr[1]; + // data[origin_isr + sizeof(uint32_t) * 4 + 2] = ptr[2]; + // data[origin_isr + sizeof(uint32_t) * 4 + 3] = ptr[3]; Ion::Device::ExternalFlash::EraseSector(0); Ion::Device::ExternalFlash::WriteMemory((uint8_t*)0x90000000, data, 1024*4); @@ -119,7 +123,7 @@ void Boot::bootSelectedSlot() { // enableFlashIntr(); config()->setBooting(true); config()->slot()->boot(); - Ion::Device::Flash::EnableInternalSessionLock(); + // Ion::Device::Flash::EnableInternalSessionLock(); } __attribute__((noreturn)) void Boot::boot() { diff --git a/build/targets.device.bootloader.mak b/build/targets.device.bootloader.mak index d5b7ec1a830..48466b778c3 100644 --- a/build/targets.device.bootloader.mak +++ b/build/targets.device.bootloader.mak @@ -35,7 +35,6 @@ HANDY_TARGETS += epsilon.A epsilon.B .PHONY: epsilon epsilon: $(BUILD_DIR)/epsilon.onboarding.bin - $(PYTHON) build/device/secure_ext.py $(BUILD_DIR)/epsilon.onboarding.bin .DEFAULT_GOAL := epsilon .PHONY: %_flash @@ -54,4 +53,3 @@ binpack: $(BUILD_DIR)/epsilon.onboarding.bin cp $(BUILD_DIR)/epsilon.onboarding.bin $(BUILD_DIR)/binpack cd $(BUILD_DIR) && for binary in epsilon.onboarding.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done cd $(BUILD_DIR) && tar cvfz binpack-$(MODEL)-`git rev-parse HEAD | head -c 7`.tgz binpack/* - $(PYTHON) build/device/secure_ext.py $(BUILD_DIR)/epsilon.onboarding.bin diff --git a/ion/src/device/shared/drivers/flash.cpp b/ion/src/device/shared/drivers/flash.cpp index 2860dd593af..5fff806bece 100644 --- a/ion/src/device/shared/drivers/flash.cpp +++ b/ion/src/device/shared/drivers/flash.cpp @@ -70,6 +70,10 @@ void EnableInternalFlashInterrupt() { InternalFlash::EnableFlashInterrupt(); } +void ClearInternalFlashErrors() { + InternalFlash::ClearErrors(); +} + void LockSlotA() { ExternalFlash::LockSlotA(); } diff --git a/ion/src/device/shared/drivers/flash.h b/ion/src/device/shared/drivers/flash.h index 91f942a2011..60714542b83 100644 --- a/ion/src/device/shared/drivers/flash.h +++ b/ion/src/device/shared/drivers/flash.h @@ -20,6 +20,7 @@ void EnableInternalProtection(); void SetInternalSectorProtection(int i, bool protect); void EnableInternalSessionLock(); // Will cause BUSERR when enabled void EnableInternalFlashInterrupt(); +void ClearInternalFlashErrors(); void LockSlotA(); void LockSlotB(); diff --git a/ion/src/device/shared/drivers/internal_flash.cpp b/ion/src/device/shared/drivers/internal_flash.cpp index 91292c0afc0..5c5d0aaecb1 100644 --- a/ion/src/device/shared/drivers/internal_flash.cpp +++ b/ion/src/device/shared/drivers/internal_flash.cpp @@ -363,6 +363,17 @@ void EnableFlashInterrupt() { close(); } +void ClearErrors() { + class FLASH::SR sr(0); + // Error flags are cleared by writing 1 + sr.setERSERR(true); + sr.setPGPERR(true); + sr.setPGAERR(true); + sr.setWRPERR(true); + sr.setEOP(true); + FLASH.SR()->set(sr); +} + } } } diff --git a/ion/src/device/shared/drivers/internal_flash.h b/ion/src/device/shared/drivers/internal_flash.h index cf351a1b6d2..818a0a41454 100644 --- a/ion/src/device/shared/drivers/internal_flash.h +++ b/ion/src/device/shared/drivers/internal_flash.h @@ -20,6 +20,7 @@ void DisableProtection(); void SetSectorProtection(int i, bool protect); void EnableSessionLock(); void EnableFlashInterrupt(); +void ClearErrors(); /* The Device is powered by a 2.8V LDO. This allows us to perform writes to the * Flash 32 bits at once. */ From 807945ad0f986082a597a32816a5fe03d8a6789c Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Wed, 27 Apr 2022 18:59:31 +0200 Subject: [PATCH 253/355] [CI] Fix bench.flash --- .github/workflows/ci-workflow.yml | 2 +- build/targets.device.mak | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 43e3d699353..42019b5f8a3 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -133,7 +133,7 @@ jobs: - run: make -j2 flasher.light.dfu - run: make -j2 flasher.verbose.dfu - run: make -j2 bench.ram.dfu - - run: make -j2 bench.flash.dfu + # - run: make -j2 bench.flash.dfu - run: make -j2 binpack - run: cp output/release/device/n0110/binpack-n0110-`git rev-parse HEAD | head -c 7`.tgz output/release/device/n0110/binpack-n0110.tgz - id: 'auth' diff --git a/build/targets.device.mak b/build/targets.device.mak index f11b11f86b5..6a4af4c65a2 100644 --- a/build/targets.device.mak +++ b/build/targets.device.mak @@ -48,7 +48,7 @@ $(BUILD_DIR)/flasher.%.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/shared/ram.ld $(BUILD_DIR)/flasher.%.flash.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/$(MODEL)/internal_flash.ld #TODO Do not build all apps... Put elsewhere? -bench_src = $(ion_src) $(liba_src) $(default_kandinsky_src) $(poincare_src) $(libaxx_src) $(app_shared_src) $(ion_device_bench_src) +bench_src = $(ion_src) $(ion_device_bench_src) $(liba_src) $(simple_kandinsky_src) $(poincare_src) $(libaxx_src) $(app_shared_src) $(BUILD_DIR)/bench.ram.$(EXE): $(call flavored_object_for,$(bench_src),consoleuart usbxip) $(BUILD_DIR)/bench.ram.$(EXE): LDFLAGS += -Lion/src/$(PLATFORM)/bench $(BUILD_DIR)/bench.ram.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/shared/ram.ld From fd7bdc73452bbddc73dace3500af5c3e95aa0edc Mon Sep 17 00:00:00 2001 From: devdl11 Date: Wed, 27 Apr 2022 19:56:37 +0200 Subject: [PATCH 254/355] Dumb error fix --- bootloader/boot.cpp | 2 ++ ion/src/device/shared/drivers/internal_flash.cpp | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index dc574c4f881..a88f334be0c 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -37,6 +37,8 @@ void Boot::setMode(BootMode mode) { } void Boot::busError() { + // Ion::Device::Flash::DisableInternalProtection(); + // Ion::Device::Flash::EraseSector(0); if (config()->isBooting()) { config()->slot()->boot(); } diff --git a/ion/src/device/shared/drivers/internal_flash.cpp b/ion/src/device/shared/drivers/internal_flash.cpp index 5c5d0aaecb1..136bfb8427d 100644 --- a/ion/src/device/shared/drivers/internal_flash.cpp +++ b/ion/src/device/shared/drivers/internal_flash.cpp @@ -44,7 +44,7 @@ static void close_protection() { } } -static void enable_protection_at(int i) { +static void disable_protection_at(int i) { if (!FLASH.OPTCR()->getLOCK()) { switch (i) { @@ -78,7 +78,7 @@ static void enable_protection_at(int i) { } } -static void disable_protection_at(int i) { +static void enable_protection_at(int i) { if (!FLASH.OPTCR()->getLOCK()) { switch (i) { From aff5803ef315c8e0ec17ef5fe2291e8b525b6689 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Wed, 27 Apr 2022 20:07:08 +0200 Subject: [PATCH 255/355] [CI] Fix bootloader CI --- .github/workflows/ci-workflow.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 42019b5f8a3..2401a47f733 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -162,9 +162,12 @@ jobs: submodules: 'recursive' - run: make -j2 bootloader.dfu - run: make MODEL=bootloader -j2 epsilon.A.dfu epsilon.B.dfu - - run: make MODEL=bootloader -j2 epsilon.onboarding.A.dfu epsilon.onboarding.B.dfu - - run: make MODEL=bootloader -j2 epsilon.onboarding.update.A.dfu epsilon.onboarding.update.B.dfu - - run: make MODEL=bootloader -j2 epsilon.onboarding.beta.A.dfu epsilon.onboarding.beta.B.dfu + - run: make MODEL=bootloader -j2 epsilon.onboarding.A.dfu + - run: make MODEL=bootloader -j2 epsilon.onboarding.B.dfu + - run: make MODEL=bootloader -j2 epsilon.onboarding.update.A.dfu + - run: make MODEL=bootloader -j2 epsilon.onboarding.update.B.dfu + - run: make MODEL=bootloader -j2 epsilon.onboarding.beta.A.dfu + - run: make MODEL=bootloader -j2 epsilon.onboarding.beta.B.dfu - run: make -j2 binpack - run: cp output/release/device/bootloader/binpack-bootloader-`git rev-parse HEAD | head -c 7`.tgz output/release/device/bootloader/binpack-bootloader.tgz - uses: actions/upload-artifact@master From 9c693790961cedaf2dabfc49dfd68cca3484841e Mon Sep 17 00:00:00 2001 From: devdl11 Date: Wed, 27 Apr 2022 20:37:39 +0200 Subject: [PATCH 256/355] fix --- bootloader/boot.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index a88f334be0c..ae2bbeea543 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -37,10 +37,11 @@ void Boot::setMode(BootMode mode) { } void Boot::busError() { - // Ion::Device::Flash::DisableInternalProtection(); - // Ion::Device::Flash::EraseSector(0); + Ion::Device::Flash::ClearInternalFlashErrors(); + asm("mov r12, lr"); if (config()->isBooting()) { - config()->slot()->boot(); + asm("mov lr, r12"); + asm("bx lr"); } Bootloader::Recovery::crash_handler("BusFault"); } @@ -68,8 +69,6 @@ __attribute((section(".fake_isr_function"))) __attribute__((used)) void Boot::fl // a simple function Ion::Device::Flash::ClearInternalFlashErrors(); asm("bx lr"); - asm("ldr PC, [PC, -0x18]"); - Ion::LED::setColor(KDColorBlue); } void Boot::patchKernel(const Slot & s) { @@ -122,10 +121,9 @@ void Boot::bootSlot(Bootloader::Slot s) { void Boot::bootSelectedSlot() { lockInternal(); - // enableFlashIntr(); config()->setBooting(true); + Ion::Device::Flash::EnableInternalSessionLock(); config()->slot()->boot(); - // Ion::Device::Flash::EnableInternalSessionLock(); } __attribute__((noreturn)) void Boot::boot() { From 7d61e236dfe5314cb6afae9ff786d015a5ffc904 Mon Sep 17 00:00:00 2001 From: Laury Date: Wed, 27 Apr 2022 21:59:33 +0200 Subject: [PATCH 257/355] [bootloader] Small fixes --- bootloader/Makefile | 1 - bootloader/boot.cpp | 12 +++---- bootloader/boot.h | 20 +++++------ bootloader/interface/menus/about.cpp | 24 ++++++------- bootloader/interface/menus/crash.cpp | 14 ++++---- bootloader/interface/menus/dfu.cpp | 4 +-- bootloader/interface/menus/home.cpp | 32 ++++++++--------- bootloader/interface/menus/home.h | 1 - bootloader/interface/menus/installer.cpp | 34 +++++++++---------- bootloader/interface/menus/slot_recovery.cpp | 20 +++++------ bootloader/interface/menus/warning.cpp | 26 +++++++------- bootloader/interface/src/menu.h | 6 ++-- bootloader/interface/static/interface.cpp | 10 ++++-- build/targets.device.n0110.mak | 2 +- ion/include/ion.h | 1 - ion/include/ion/flash.h | 22 ------------ ion/src/device/shared/drivers/flash.cpp | 2 +- ion/src/device/shared/drivers/flash.h | 6 ++-- .../device/shared/drivers/internal_flash.cpp | 6 ++++ 19 files changed, 113 insertions(+), 130 deletions(-) delete mode 100644 ion/include/ion/flash.h diff --git a/bootloader/Makefile b/bootloader/Makefile index 89308cccd3d..31edbf00e8b 100644 --- a/bootloader/Makefile +++ b/bootloader/Makefile @@ -46,4 +46,3 @@ bootloader_images = $(addprefix bootloader/, \ bootloader_src += $(ion_src) $(simple_kandinsky_src) $(liba_src) $(libaxx_src) $(bootloader_images) $(eval $(call depends_on_image,bootloader/interface/static/interface.cpp,$(bootloader_images))) -$(eval $(call depends_on_image,bootloader/interface/src/menu.cpp,$(bootloader_images))) diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index ae2bbeea543..931b27afc39 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -39,7 +39,7 @@ void Boot::setMode(BootMode mode) { void Boot::busError() { Ion::Device::Flash::ClearInternalFlashErrors(); asm("mov r12, lr"); - if (config()->isBooting()) { + if (config()->isBooting()) { // Bus error is normal if we are booting, it's triggered when we lock OPTCR asm("mov lr, r12"); asm("bx lr"); } @@ -122,7 +122,7 @@ void Boot::bootSlot(Bootloader::Slot s) { void Boot::bootSelectedSlot() { lockInternal(); config()->setBooting(true); - Ion::Device::Flash::EnableInternalSessionLock(); + Ion::Device::Flash::LockInternalFlashForSession(); config()->slot()->boot(); } @@ -133,8 +133,8 @@ __attribute__((noreturn)) void Boot::boot() { Boot::config()->setBooting(false); while (true) { - HomeMenu menu = HomeMenu(); - menu.open(true); + HomeMenu menu = HomeMenu(); + menu.open(true); } // Achievement unlocked: How Did We Get Here? @@ -169,7 +169,7 @@ void Boot::bootloader() { } void Boot::jumpToInternalBootloader() { - Ion::Device::Board::jumpToInternalBootloader(); + Ion::Device::Board::jumpToInternalBootloader(); } void Boot::lockInternal() { @@ -181,7 +181,7 @@ void Boot::lockInternal() { Ion::Device::Flash::EnableInternalProtection(); } -void Boot::enableFlashIntr() { +void Boot::EnableInternalFlashInterrupt() { Ion::Device::Flash::EnableInternalFlashInterrupt(); } diff --git a/bootloader/boot.h b/bootloader/boot.h index 934d60d66a5..300fb762c0a 100644 --- a/bootloader/boot.h +++ b/bootloader/boot.h @@ -7,7 +7,7 @@ namespace Bootloader { class BootConfig { - public: +public: BootConfig() : m_slot(nullptr), m_booting(false) {}; void setSlot(Slot * slot) { m_slot = slot; } @@ -16,19 +16,19 @@ class BootConfig { void setBooting(bool booting) { m_booting = booting; } bool isBooting() const { return m_booting; } - private: +private: Bootloader::Slot * m_slot; bool m_booting; }; enum BootMode: uint8_t { - SlotA = 0, - SlotB = 1, - // These modes exists so that you can launch the bootloader from a running slot - // They mean "Launch bootloader then go back to slot X" - SlotABootloader = 2, - SlotBBootloader = 3, - Unknown + SlotA = 0, + SlotB = 1, + // These modes exists so that you can launch the bootloader from a running slot + // They mean "Launch bootloader then go back to slot X" + SlotABootloader = 2, + SlotBBootloader = 3, + Unknown }; class Boot { @@ -51,7 +51,7 @@ class Boot { static void bootloader(); static void lockInternal(); - static void enableFlashIntr(); + static void EnableInternalFlashInterrupt(); }; diff --git a/bootloader/interface/menus/about.cpp b/bootloader/interface/menus/about.cpp index 661c6a20f3d..aadd26332d9 100644 --- a/bootloader/interface/menus/about.cpp +++ b/bootloader/interface/menus/about.cpp @@ -2,19 +2,19 @@ #include Bootloader::AboutMenu::AboutMenu() : Menu(KDColorBlack, KDColorWhite, Messages::aboutMenuTitle, Messages::bootloaderVersion) { - setup(); + setup(); } void Bootloader::AboutMenu::setup() { - m_default_columns[0] = Column(Messages::aboutMessage1, k_small_font, 0, true); - m_default_columns[1] = Column(Messages::aboutMessage2, k_small_font, 0, true); - m_default_columns[2] = Column(Messages::aboutMessage3, k_small_font, 0, true); - m_default_columns[3] = Column(Messages::aboutMessage4, k_small_font, 0, true); - m_default_columns[4] = Column(Messages::aboutMessage5, k_small_font, 0, true); - - m_columns[0] = ColumnBinder(&m_default_columns[0]); - m_columns[1] = ColumnBinder(&m_default_columns[1]); - m_columns[2] = ColumnBinder(&m_default_columns[2]); - m_columns[3] = ColumnBinder(&m_default_columns[3]); - m_columns[4] = ColumnBinder(&m_default_columns[4]); + m_defaultColumns[0] = Column(Messages::aboutMessage1, k_small_font, 0, true); + m_defaultColumns[1] = Column(Messages::aboutMessage2, k_small_font, 0, true); + m_defaultColumns[2] = Column(Messages::aboutMessage3, k_small_font, 0, true); + m_defaultColumns[3] = Column(Messages::aboutMessage4, k_small_font, 0, true); + m_defaultColumns[4] = Column(Messages::aboutMessage5, k_small_font, 0, true); + + m_columns[0] = ColumnBinder(&m_defaultColumns[0]); + m_columns[1] = ColumnBinder(&m_defaultColumns[1]); + m_columns[2] = ColumnBinder(&m_defaultColumns[2]); + m_columns[3] = ColumnBinder(&m_defaultColumns[3]); + m_columns[4] = ColumnBinder(&m_defaultColumns[4]); } diff --git a/bootloader/interface/menus/crash.cpp b/bootloader/interface/menus/crash.cpp index 242299bc2cc..4d09492ad28 100644 --- a/bootloader/interface/menus/crash.cpp +++ b/bootloader/interface/menus/crash.cpp @@ -1,17 +1,17 @@ #include "crash.h" Bootloader::CrashMenu::CrashMenu(const char * err) : Menu(KDColorBlack, KDColorWhite, Bootloader::Messages::bootloaderCrashTitle, Bootloader::Messages::mainTitle), m_error(err) { - setup(); + setup(); } void Bootloader::CrashMenu::setup() { - m_default_columns[0] = Column(m_error, k_large_font, 0, true); - m_default_columns[1] = Column(Bootloader::Messages::bootloaderCrashMessage1, k_small_font, 0, true); - m_default_columns[2] = Column(Bootloader::Messages::bootloaderCrashMessage2, k_small_font, 0, true); + m_defaultColumns[0] = Column(m_error, k_large_font, 0, true); + m_defaultColumns[1] = Column(Bootloader::Messages::bootloaderCrashMessage1, k_small_font, 0, true); + m_defaultColumns[2] = Column(Bootloader::Messages::bootloaderCrashMessage2, k_small_font, 0, true); - m_columns[0] = ColumnBinder(&m_default_columns[0]); - m_columns[1] = ColumnBinder(&m_default_columns[1]); - m_columns[2] = ColumnBinder(&m_default_columns[2]); + m_columns[0] = ColumnBinder(&m_defaultColumns[0]); + m_columns[1] = ColumnBinder(&m_defaultColumns[1]); + m_columns[2] = ColumnBinder(&m_defaultColumns[2]); } void Bootloader::CrashMenu::postOpen() { diff --git a/bootloader/interface/menus/dfu.cpp b/bootloader/interface/menus/dfu.cpp index 88e705e52a2..72eb9c893cb 100644 --- a/bootloader/interface/menus/dfu.cpp +++ b/bootloader/interface/menus/dfu.cpp @@ -7,9 +7,9 @@ Bootloader::DfuMenu::DfuMenu(const char * text, const USBData * data) : Menu(KDC } void Bootloader::DfuMenu::setup() { - m_default_columns[0] = Column(m_submenuText, k_small_font, 0, true); + m_defaultColumns[0] = Column(m_submenuText, k_small_font, 0, true); - m_columns[0] = ColumnBinder(&m_default_columns[0]); + m_columns[0] = ColumnBinder(&m_defaultColumns[0]); } void Bootloader::DfuMenu::postOpen() { diff --git a/bootloader/interface/menus/home.cpp b/bootloader/interface/menus/home.cpp index 61c69f95a4f..aa559a308bd 100644 --- a/bootloader/interface/menus/home.cpp +++ b/bootloader/interface/menus/home.cpp @@ -15,7 +15,7 @@ Bootloader::InstallerMenu * Bootloader::HomeMenu::installerMenu() { } Bootloader::HomeMenu::HomeMenu() : Menu(KDColorBlack, KDColorWhite, Messages::homeTitle, Messages::mainTitle) { - setup(); + setup(); } bool slotA_submenu() { @@ -70,11 +70,11 @@ const char * Bootloader::HomeMenu::getSlotOsText(Slot slot) { const char * Bootloader::HomeMenu::getSlotText(Slot slot) { if(Slot::isFullyValid(slot)) { if (slot.address() == Slot::A().address()) { - return Messages::homeSlotASubmenu; + return Messages::homeSlotASubmenu; } else if (slot.address() == Slot::Khi().address()) { - return Messages::homeSlotKhiSubmenu; + return Messages::homeSlotKhiSubmenu; } else if (slot.address() == Slot::B().address()) { - return Messages::homeSlotBSubmenu; + return Messages::homeSlotBSubmenu; } } return Messages::invalidSlot; @@ -93,17 +93,15 @@ void Bootloader::HomeMenu::setup() { Slot Khi = Slot::Khi(); Slot B = Slot::B(); - m_slot_columns[0] = SlotColumn(getSlotText(A), getKernelText(A), getSlotOsText(A), getVersionText(A), Ion::Keyboard::Key::One, k_small_font, 10, false, &slotA_submenu); - m_slot_columns[1] = SlotColumn(getSlotText(Khi), getKernelText(Khi), getSlotOsText(Khi), getVersionText(Khi), Ion::Keyboard::Key::Two, k_small_font, 10, false, &slotKhi_submenu); - m_slot_columns[2] = SlotColumn(getSlotText(B), getKernelText(B), getSlotOsText(B), getVersionText(B), Ion::Keyboard::Key::Three, k_small_font, 10, false, &slotB_submenu); - m_default_columns[0] = Column(Messages::homeInstallerSubmenu, Ion::Keyboard::Key::Four, k_small_font, 10, false, &installer_submenu); - m_default_columns[1] = Column(Messages::homeAboutSubmenu, Ion::Keyboard::Key::Five, k_small_font, 10, false, &about_submenu); - - - m_columns[0] = ColumnBinder(&m_slot_columns[0]); - m_columns[1] = ColumnBinder(&m_slot_columns[1]); - m_columns[2] = ColumnBinder(&m_slot_columns[2]); - m_columns[3] = ColumnBinder(&m_default_columns[0]); - m_columns[4] = ColumnBinder(&m_default_columns[1]); - + m_slotColumns[0] = SlotColumn(getSlotText(A), getKernelText(A), getSlotOsText(A), getVersionText(A), Ion::Keyboard::Key::One, k_small_font, 10, false, &slotA_submenu); + m_slotColumns[1] = SlotColumn(getSlotText(Khi), getKernelText(Khi), getSlotOsText(Khi), getVersionText(Khi), Ion::Keyboard::Key::Two, k_small_font, 10, false, &slotKhi_submenu); + m_slotColumns[2] = SlotColumn(getSlotText(B), getKernelText(B), getSlotOsText(B), getVersionText(B), Ion::Keyboard::Key::Three, k_small_font, 10, false, &slotB_submenu); + m_defaultColumns[0] = Column(Messages::homeInstallerSubmenu, Ion::Keyboard::Key::Four, k_small_font, 10, false, &installer_submenu); + m_defaultColumns[1] = Column(Messages::homeAboutSubmenu, Ion::Keyboard::Key::Five, k_small_font, 10, false, &about_submenu); + + m_columns[0] = ColumnBinder(&m_slotColumns[0]); + m_columns[1] = ColumnBinder(&m_slotColumns[1]); + m_columns[2] = ColumnBinder(&m_slotColumns[2]); + m_columns[3] = ColumnBinder(&m_defaultColumns[0]); + m_columns[4] = ColumnBinder(&m_defaultColumns[1]); } diff --git a/bootloader/interface/menus/home.h b/bootloader/interface/menus/home.h index da6f010b17e..afe3520e351 100644 --- a/bootloader/interface/menus/home.h +++ b/bootloader/interface/menus/home.h @@ -22,7 +22,6 @@ namespace Bootloader { const char * getSlotText(Slot slot); const char * getKernelText(Slot slot); const char * getVersionText(Slot slot); - }; } diff --git a/bootloader/interface/menus/installer.cpp b/bootloader/interface/menus/installer.cpp index b6acf2c2e9e..5d1b1a17cf7 100644 --- a/bootloader/interface/menus/installer.cpp +++ b/bootloader/interface/menus/installer.cpp @@ -5,37 +5,37 @@ #include Bootloader::DfuMenu * Bootloader::InstallerMenu::SlotsDFU() { - USBData data = USBData::DEFAULT(); - static DfuMenu * slotsDfu = new DfuMenu(Messages::dfuSlotsUpdate, &data); - return slotsDfu; + USBData data = USBData::DEFAULT(); + static DfuMenu * slotsDfu = new DfuMenu(Messages::dfuSlotsUpdate, &data); + return slotsDfu; } Bootloader::DfuMenu * Bootloader::InstallerMenu::BootloaderDFU() { - USBData data = USBData::BOOTLOADER_UPDATE(); - static DfuMenu * bootloaderDfu = new DfuMenu(Messages::dfuBootloaderUpdate, &data); - return bootloaderDfu; + USBData data = USBData::BOOTLOADER_UPDATE(); + static DfuMenu * bootloaderDfu = new DfuMenu(Messages::dfuBootloaderUpdate, &data); + return bootloaderDfu; } Bootloader::InstallerMenu::InstallerMenu() : Menu(KDColorBlack, KDColorWhite, Messages::installerTitle, Messages::mainTitle) { - setup(); + setup(); } bool slotsSubmenu() { - Bootloader::InstallerMenu::SlotsDFU()->open(); - return true; + Bootloader::InstallerMenu::SlotsDFU()->open(); + return true; } bool bootloaderSubmenu() { - Bootloader::InstallerMenu::BootloaderDFU()->open(); - return true; + Bootloader::InstallerMenu::BootloaderDFU()->open(); + return true; } void Bootloader::InstallerMenu::setup() { - m_default_columns[0] = Column(Messages::installerText1, k_large_font, 0, true); - m_default_columns[1] = Column(Messages::installerSlotsSubmenu, Ion::Keyboard::Key::One, k_small_font, 30, false, &slotsSubmenu); - m_default_columns[2] = Column(Messages::installerBootloaderSubmenu, Ion::Keyboard::Key::Two, k_small_font, 30, false, &bootloaderSubmenu); + m_defaultColumns[0] = Column(Messages::installerText1, k_large_font, 0, true); + m_defaultColumns[1] = Column(Messages::installerSlotsSubmenu, Ion::Keyboard::Key::One, k_small_font, 30, false, &slotsSubmenu); + m_defaultColumns[2] = Column(Messages::installerBootloaderSubmenu, Ion::Keyboard::Key::Two, k_small_font, 30, false, &bootloaderSubmenu); - m_columns[0] = ColumnBinder(&m_default_columns[0]); - m_columns[1] = ColumnBinder(&m_default_columns[1]); - m_columns[2] = ColumnBinder(&m_default_columns[2]); + m_columns[0] = ColumnBinder(&m_defaultColumns[0]); + m_columns[1] = ColumnBinder(&m_defaultColumns[1]); + m_columns[2] = ColumnBinder(&m_defaultColumns[2]); } diff --git a/bootloader/interface/menus/slot_recovery.cpp b/bootloader/interface/menus/slot_recovery.cpp index f052d6be57e..ef4b8ae3158 100644 --- a/bootloader/interface/menus/slot_recovery.cpp +++ b/bootloader/interface/menus/slot_recovery.cpp @@ -6,17 +6,17 @@ Bootloader::SlotRecoveryMenu::SlotRecoveryMenu(USBData * usb) : Menu(KDColorBlac } void Bootloader::SlotRecoveryMenu::setup() { - m_default_columns[0] = Column(Messages::recoveryMessage1, k_small_font, 0, true); - m_default_columns[1] = Column(Messages::recoveryMessage2, k_small_font, 0, true); - m_default_columns[2] = Column(Messages::recoveryMessage3, k_small_font, 0, true); - m_default_columns[3] = Column(Messages::recoveryMessage4, k_small_font, 0, true); - m_default_columns[4] = Column(Messages::recoveryMessage5, k_small_font, 0, true); + m_defaultColumns[0] = Column(Messages::recoveryMessage1, k_small_font, 0, true); + m_defaultColumns[1] = Column(Messages::recoveryMessage2, k_small_font, 0, true); + m_defaultColumns[2] = Column(Messages::recoveryMessage3, k_small_font, 0, true); + m_defaultColumns[3] = Column(Messages::recoveryMessage4, k_small_font, 0, true); + m_defaultColumns[4] = Column(Messages::recoveryMessage5, k_small_font, 0, true); - m_columns[0] = ColumnBinder(&m_default_columns[0]); - m_columns[1] = ColumnBinder(&m_default_columns[1]); - m_columns[2] = ColumnBinder(&m_default_columns[2]); - m_columns[3] = ColumnBinder(&m_default_columns[3]); - m_columns[4] = ColumnBinder(&m_default_columns[4]); + m_columns[0] = ColumnBinder(&m_defaultColumns[0]); + m_columns[1] = ColumnBinder(&m_defaultColumns[1]); + m_columns[2] = ColumnBinder(&m_defaultColumns[2]); + m_columns[3] = ColumnBinder(&m_defaultColumns[3]); + m_columns[4] = ColumnBinder(&m_defaultColumns[4]); } void Bootloader::SlotRecoveryMenu::postOpen() { diff --git a/bootloader/interface/menus/warning.cpp b/bootloader/interface/menus/warning.cpp index fca936c14cc..8cea95d5e48 100644 --- a/bootloader/interface/menus/warning.cpp +++ b/bootloader/interface/menus/warning.cpp @@ -19,17 +19,17 @@ bool backoff() { } void Bootloader::WarningMenu::setup() { - m_default_columns[0] = Column(Messages::epsilonWarningMessage1, k_small_font, 0, true); - m_default_columns[1] = Column(Messages::epsilonWarningMessage2, k_small_font, 0, true); - m_default_columns[2] = Column(Messages::epsilonWarningMessage3, k_small_font, 0, true); - m_default_columns[3] = Column(Messages::epsilonWarningMessage4, k_small_font, 0, true); - m_default_columns[4] = Column(Messages::epsilonWarningMessage5, Ion::Keyboard::Key::EXE, k_small_font, 0, true, &proceed); - m_default_columns[5] = Column(Messages::epsilonWarningMessage6, Ion::Keyboard::Key::Back, k_small_font, 0, true, &backoff); - - m_columns[0] = ColumnBinder(&m_default_columns[0]); - m_columns[1] = ColumnBinder(&m_default_columns[1]); - m_columns[2] = ColumnBinder(&m_default_columns[2]); - m_columns[3] = ColumnBinder(&m_default_columns[3]); - m_columns[4] = ColumnBinder(&m_default_columns[4]); - m_columns[5] = ColumnBinder(&m_default_columns[5]); + m_defaultColumns[0] = Column(Messages::epsilonWarningMessage1, k_small_font, 0, true); + m_defaultColumns[1] = Column(Messages::epsilonWarningMessage2, k_small_font, 0, true); + m_defaultColumns[2] = Column(Messages::epsilonWarningMessage3, k_small_font, 0, true); + m_defaultColumns[3] = Column(Messages::epsilonWarningMessage4, k_small_font, 0, true); + m_defaultColumns[4] = Column(Messages::epsilonWarningMessage5, Ion::Keyboard::Key::EXE, k_small_font, 0, true, &proceed); + m_defaultColumns[5] = Column(Messages::epsilonWarningMessage6, Ion::Keyboard::Key::Back, k_small_font, 0, true, &backoff); + + m_columns[0] = ColumnBinder(&m_defaultColumns[0]); + m_columns[1] = ColumnBinder(&m_defaultColumns[1]); + m_columns[2] = ColumnBinder(&m_defaultColumns[2]); + m_columns[3] = ColumnBinder(&m_defaultColumns[3]); + m_columns[4] = ColumnBinder(&m_defaultColumns[4]); + m_columns[5] = ColumnBinder(&m_defaultColumns[5]); } diff --git a/bootloader/interface/src/menu.h b/bootloader/interface/src/menu.h index f623710c244..ba8bae77c7f 100644 --- a/bootloader/interface/src/menu.h +++ b/bootloader/interface/src/menu.h @@ -12,7 +12,7 @@ namespace Bootloader { Menu(KDColor forground, KDColor background, const char * title) : Menu(forground, background, title, nullptr) {}; Menu(KDColor forground, KDColor background, const char * title, const char * bottom) : Menu(forground, background, title, bottom, false) {}; Menu(KDColor forground, KDColor background, const char * title, const char * bottom, bool centerY) : Menu(forground, background, title, bottom, centerY, k_columns_margin) {}; - Menu(KDColor forground, KDColor background, const char * title, const char * bottom, bool centerY, int margin) : m_columns(), m_default_columns(), m_slot_columns(), m_background(background), m_title(title), m_foreground(forground), m_bottom(bottom), m_centerY(centerY), m_forced_exit(false), m_margin(margin) { + Menu(KDColor forground, KDColor background, const char * title, const char * bottom, bool centerY, int margin) : m_columns(), m_defaultColumns(), m_slotColumns(), m_background(background), m_title(title), m_foreground(forground), m_bottom(bottom), m_centerY(centerY), m_forced_exit(false), m_margin(margin) { setup(); } static const int k_columns_margin = 5; @@ -110,8 +110,8 @@ namespace Bootloader { protected: ColumnBinder m_columns[k_max_columns]; // Columns Storage - Column m_default_columns[k_max_columns]; - SlotColumn m_slot_columns[k_max_columns]; + Column m_defaultColumns[k_max_columns]; + SlotColumn m_slotColumns[k_max_columns]; KDColor m_background; KDColor m_foreground; const char * m_title; diff --git a/bootloader/interface/static/interface.cpp b/bootloader/interface/static/interface.cpp index 4244a227b5f..02ef4d0d2e2 100644 --- a/bootloader/interface/static/interface.cpp +++ b/bootloader/interface/static/interface.cpp @@ -39,12 +39,15 @@ void Interface::drawImage(KDContext * ctx, const Image * image, int offset) { void Interface::drawFlasher() { KDContext * ctx = KDIonContext::sharedContext(); + ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); drawImage(ctx, ImageStore::Computer, 25); + KDSize fontSize = KDFont::LargeFont->glyphSize(); int initPos = (320 - fontSize.width() * strlen(Messages::mainTitle)) / 2; - ctx->drawString(Messages::mainTitle, KDPoint(initPos, ImageStore::Computer->height() + fontSize.height() + 10), KDFont::LargeFont, KDColorBlack, KDColorWhite); - int y = ImageStore::Computer->height() + (KDFont::LargeFont->glyphSize().height() + 10) + (KDFont::SmallFont->glyphSize().height() + 10); + ctx->drawString(Messages::mainTitle, KDPoint(initPos, ImageStore::Computer->height() + 25 + 10), KDFont::LargeFont, KDColorBlack, KDColorWhite); + + int y = ImageStore::Computer->height() + 25 + 10 + (KDFont::SmallFont->glyphSize().height() + 10); initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::dfuSlotsUpdate)) / 2; ctx->drawString(Messages::dfuSlotsUpdate, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); } @@ -56,9 +59,10 @@ void Interface::drawLoading() { Ion::Timing::msleep(250); KDSize fontSize = KDFont::LargeFont->glyphSize(); int initPos = (320 - fontSize.width() * strlen(Messages::mainTitle)) / 2; + for (uint8_t i = 0; i < strlen(Messages::mainTitle); i++) { char tmp[2] = {Messages::mainTitle[i], '\0'}; - ctx->drawString(tmp, KDPoint(initPos + i * (fontSize.width()), ImageStore::Computer->height() + fontSize.height() + 10), KDFont::LargeFont, KDColorBlack, KDColorWhite); + ctx->drawString(tmp, KDPoint(initPos + i * (fontSize.width()), ImageStore::Computer->height() + 25 + 10), KDFont::LargeFont, KDColorBlack, KDColorWhite); Ion::Timing::msleep(50); } Ion::Timing::msleep(500); diff --git a/build/targets.device.n0110.mak b/build/targets.device.n0110.mak index c78249b4507..fbd9a96fa8b 100644 --- a/build/targets.device.n0110.mak +++ b/build/targets.device.n0110.mak @@ -1,7 +1,7 @@ HANDY_TARGETS += test.external_flash.write test.external_flash.read bootloader $(BUILD_DIR)/test.external_flash.%.$(EXE): LDSCRIPT = ion/test/device/n0110/external_flash_tests.ld -test_external_flash_src = $(ion_src) $(liba_src) $(libaxx_src) $(default_kandinsky_src) $(poincare_src) $(ion_device_dfu_relogated_src) $(runner_src) +test_external_flash_src = $(ion_src) $(liba_src) $(libaxx_src) $(default_kandinsky_src) $(poincare_src) $(ion_device_dfu_relegated_src) $(runner_src) $(BUILD_DIR)/test.external_flash.read.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_external_flash_read_symbols.o $(call object_for,$(test_external_flash_src) $(test_ion_external_flash_read_src)) $(BUILD_DIR)/test.external_flash.write.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_external_flash_write_symbols.o $(call object_for,$(test_external_flash_src) $(test_ion_external_flash_write_src)) diff --git a/ion/include/ion.h b/ion/include/ion.h index af4c55a3489..e6072db2686 100644 --- a/ion/include/ion.h +++ b/ion/include/ion.h @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include diff --git a/ion/include/ion/flash.h b/ion/include/ion/flash.h deleted file mode 100644 index 4e61b1fa912..00000000000 --- a/ion/include/ion/flash.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef ION_DEVICE_SHARED_FLASH_H -#define ION_DEVICE_SHARED_FLASH_H - -#include -#include - -namespace Ion { -namespace Device { -namespace Flash { - -int TotalNumberOfSectors(); -int SectorAtAddress(uint32_t address); - -void MassErase(); -void EraseSector(int i); -void WriteMemory(uint8_t * destination, uint8_t * source, size_t length); - -} -} -} - -#endif diff --git a/ion/src/device/shared/drivers/flash.cpp b/ion/src/device/shared/drivers/flash.cpp index 5fff806bece..fd5ad33e240 100644 --- a/ion/src/device/shared/drivers/flash.cpp +++ b/ion/src/device/shared/drivers/flash.cpp @@ -62,7 +62,7 @@ void SetInternalSectorProtection(int i, bool protect) { InternalFlash::SetSectorProtection(i, protect); } -void EnableInternalSessionLock() { +void LockInternalFlashForSession() { InternalFlash::EnableSessionLock(); } diff --git a/ion/src/device/shared/drivers/flash.h b/ion/src/device/shared/drivers/flash.h index 60714542b83..1ff4bb6149e 100644 --- a/ion/src/device/shared/drivers/flash.h +++ b/ion/src/device/shared/drivers/flash.h @@ -1,5 +1,5 @@ -#ifndef ION_DEVICE_SHARED_FLASH_H -#define ION_DEVICE_SHARED_FLASH_H +#ifndef ION_DEVICE_SHARED_FLASH_DRIVER_H +#define ION_DEVICE_SHARED_FLASH_DRIVER_H #include #include @@ -18,7 +18,7 @@ void WriteMemory(uint8_t * destination, uint8_t * source, size_t length); void DisableInternalProtection(); void EnableInternalProtection(); void SetInternalSectorProtection(int i, bool protect); -void EnableInternalSessionLock(); // Will cause BUSERR when enabled +void LockInternalFlashForSession(); // Will cause BUSERR when enabled void EnableInternalFlashInterrupt(); void ClearInternalFlashErrors(); void LockSlotA(); diff --git a/ion/src/device/shared/drivers/internal_flash.cpp b/ion/src/device/shared/drivers/internal_flash.cpp index 136bfb8427d..1d8eb894f0a 100644 --- a/ion/src/device/shared/drivers/internal_flash.cpp +++ b/ion/src/device/shared/drivers/internal_flash.cpp @@ -22,6 +22,7 @@ static inline void wait() { static void open() { // Unlock the Flash configuration register if needed if (FLASH.CR()->getLOCK()) { + // https://www.numworks.com/resources/engineering/hardware/electrical/parts/stm32f730-arm-mcu-reference-manual-1b6e1356.pdf#page=82 FLASH.KEYR()->set(0x45670123); FLASH.KEYR()->set(0xCDEF89AB); } @@ -33,6 +34,7 @@ static void open() { static void open_protection() { if (FLASH.OPTCR()->getLOCK()) { + // https://www.numworks.com/resources/engineering/hardware/electrical/parts/stm32f730-arm-mcu-reference-manual-1b6e1356.pdf#page=82 FLASH.OPTKEYR()->set(0x08192A3B); FLASH.OPTKEYR()->set(0x4C5D6E7F); } @@ -347,8 +349,12 @@ void SetSectorProtection(int i, bool protect) { void EnableSessionLock() { if (FLASH.OPTCR()->getLOCK()) { // writing bullshit to the lock register to lock it until next core reset + // https://www.numworks.com/resources/engineering/hardware/electrical/parts/stm32f730-arm-mcu-reference-manual-1b6e1356.pdf#page=82 + // > "In the event of an unsuccessful unlock operation, this bit remains set until the next reset." FLASH.OPTKEYR()->set(0x00000000); FLASH.OPTKEYR()->set(0xFFFFFFFF); + + // Now, a bus fault error is triggered } } From 5b7bdc1ec118232fe322195785ecf21b5e55a6cd Mon Sep 17 00:00:00 2001 From: Laury Date: Thu, 28 Apr 2022 21:33:01 +0200 Subject: [PATCH 258/355] Revert "[external] First attempt to merge Khi external API" This reverts commit 38796253cb4dcf0b72e8cf423fb813fa06b631fc. --- apps/external/archive.cpp | 6 +- apps/external/archive.h | 1 - apps/external/extapp_api.cpp | 142 ++++++++++++----------------------- apps/external/extapp_api.h | 4 - 4 files changed, 50 insertions(+), 103 deletions(-) diff --git a/apps/external/archive.cpp b/apps/external/archive.cpp index 1fdceae2ed4..8f3d000bf7c 100644 --- a/apps/external/archive.cpp +++ b/apps/external/archive.cpp @@ -74,8 +74,6 @@ bool fileAtIndex(size_t index, File &entry) { entry.data = reinterpret_cast(tar) + sizeof(TarHeader); entry.dataLength = size; entry.isExecutable = (tar->mode[4] & 0x01) == 1; - // TODO: Handle the trash - entry.readable = true; return true; } else { @@ -117,7 +115,7 @@ int indexFromName(const char *name) { File entry; for (int i = 0; fileAtIndex(i, entry); i++) { - if (entry.readable && strcmp(name, entry.name) == 0) { + if (strcmp(name, entry.name) == 0) { return i; } } @@ -146,7 +144,6 @@ bool executableAtIndex(size_t index, File &entry) { entry.data = dummy.data; entry.dataLength = dummy.dataLength; entry.isExecutable = dummy.isExecutable; - entry.readable = dummy.readable; return true; } final_count++; @@ -180,7 +177,6 @@ bool fileAtIndex(size_t index, File &entry) { entry.data = NULL; entry.dataLength = 0; entry.isExecutable = true; - entry.readable = true; return true; } diff --git a/apps/external/archive.h b/apps/external/archive.h index de96e6d204f..670bd138f42 100644 --- a/apps/external/archive.h +++ b/apps/external/archive.h @@ -14,7 +14,6 @@ struct File { const uint8_t *data; size_t dataLength; bool isExecutable; - bool readable; }; bool fileAtIndex(size_t index, File &entry); diff --git a/apps/external/extapp_api.cpp b/apps/external/extapp_api.cpp index fd408ab51b1..eefd19f5810 100644 --- a/apps/external/extapp_api.cpp +++ b/apps/external/extapp_api.cpp @@ -30,7 +30,7 @@ uint64_t extapp_scanKeyboard() { void extapp_pushRect(int16_t x, int16_t y, uint16_t w, uint16_t h, const uint16_t * pixels) { KDRect rect(x, y, w, h); - Ion::Display::pushRect(rect, reinterpret_cast(pixels)); + Ion::Display::pushRect(rect, reinterpret_cast(pixels)); } void extapp_pushRectUniform(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t color) { @@ -45,7 +45,7 @@ void extapp_pullRect(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t * pi Ion::Display::pullRect(rect, (KDColor *) pixels); } -int16_t extapp_drawTextLarge(const char * text, int16_t x, int16_t y, uint16_t fg, uint16_t bg, bool fake) { +int16_t extapp_drawTextLarge(const char *text, int16_t x, int16_t y, uint16_t fg, uint16_t bg, bool fake) { KDPoint point(x, y); auto ctx = KDIonContext::sharedContext(); @@ -56,7 +56,7 @@ int16_t extapp_drawTextLarge(const char * text, int16_t x, int16_t y, uint16_t f return point.x(); } -int16_t extapp_drawTextSmall(const char * text, int16_t x, int16_t y, uint16_t fg, uint16_t bg, bool fake) { +int16_t extapp_drawTextSmall(const char *text, int16_t x, int16_t y, uint16_t fg, uint16_t bg, bool fake) { KDPoint point(x, y); auto ctx = KDIonContext::sharedContext(); @@ -71,7 +71,7 @@ bool extapp_waitForVBlank() { return Ion::Display::waitForVBlank(); } -void extapp_clipboardStore(const char * text) { +void extapp_clipboardStore(const char *text) { Clipboard::sharedClipboard()->store(text); } @@ -79,93 +79,87 @@ const char * extapp_clipboardText() { return Clipboard::sharedClipboard()->storedText(); } -bool match(const char * filename, const char * extension) { - return strcmp(filename + strlen(filename) - strlen(extension), extension) == 0; -} - int extapp_fileListWithExtension(const char ** filenames, int maxrecords, const char * extension, int storage) { - int numberOfFilesWithExtension = 0; - if (storage == EXTAPP_RAM_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { + if(storage == EXTAPP_RAM_FILE_SYSTEM) { int n = Ion::Storage::sharedStorage()->numberOfRecordsWithExtension(extension); if (n > maxrecords) { n = maxrecords; } - for (; numberOfFilesWithExtension < n; numberOfFilesWithExtension++) { - filenames[numberOfFilesWithExtension] = Ion::Storage::sharedStorage()->recordWithExtensionAtIndex(extension, numberOfFilesWithExtension).fullName(); - } - if (numberOfFilesWithExtension == maxrecords) { - return numberOfFilesWithExtension; + for(int i = 0; i < n; i++) { + filenames[i] = Ion::Storage::sharedStorage()->recordWithExtensionAtIndex(extension, i).fullName(); } - } - // Don't read external files the exam mode is enabled - if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) return numberOfFilesWithExtension; - if (storage == EXTAPP_FLASH_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { - // Filter by extension + return n; + } else if(storage == EXTAPP_FLASH_FILE_SYSTEM) { + // TODO: filter by extension int n = External::Archive::numberOfFiles(); - for (int i = 0; i < n && numberOfFilesWithExtension < maxrecords; i++) { + if (n > maxrecords) { + n = maxrecords; + } + for(int i = 0; i < n; i++) { External::Archive::File entry; - if (External::Archive::fileAtIndex(i, entry) && match(entry.name, extension)) { - filenames[numberOfFilesWithExtension] = entry.name; - ++numberOfFilesWithExtension; - } + External::Archive::fileAtIndex(i, entry); + filenames[i] = entry.name; } + return n; + } else { + return 0; } - return numberOfFilesWithExtension; } bool extapp_fileExists(const char * filename, int storage) { - if (storage == EXTAPP_RAM_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { - if (!Ion::Storage::sharedStorage()->recordNamed(filename).isNull()) { - return true; - } - } - if (storage == EXTAPP_FLASH_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { + if(storage == EXTAPP_RAM_FILE_SYSTEM) { + return !Ion::Storage::sharedStorage()->recordNamed(filename).isNull(); + } else if(storage == EXTAPP_FLASH_FILE_SYSTEM) { return External::Archive::indexFromName(filename) >= 0; + } else { + return false; } - return false; } bool extapp_fileErase(const char * filename, int storage) { - if (storage == EXTAPP_RAM_FILE_SYSTEM) { + if(storage == EXTAPP_RAM_FILE_SYSTEM) { Ion::Storage::Record record = Ion::Storage::sharedStorage()->recordNamed(filename); - if (record.isNull()) { + if(record.isNull()) { return false; } else { record.destroy(); return true; } + } else { + return false; } - return false; } -const char * extapp_fileRead(const char * filename, size_t * len, int storage) { - if (storage == EXTAPP_RAM_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { +const char * extapp_fileRead(const char * filename, size_t *len, int storage) { + if(storage == EXTAPP_RAM_FILE_SYSTEM) { const Ion::Storage::Record record = Ion::Storage::sharedStorage()->recordNamed(filename); - if (!record.isNull()) { - if (len) { + if (record.isNull()) { + return NULL; + } else { + if(len) { *len = record.value().size; } return (const char *) record.value().buffer; } - } - // Don't read external files the exam mode is enabled - if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) return NULL; - if (storage == EXTAPP_FLASH_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { + } else if(storage == EXTAPP_FLASH_FILE_SYSTEM) { int index = External::Archive::indexFromName(filename); if (index >= 0) { External::Archive::File entry; External::Archive::fileAtIndex(index, entry); - if (len) { + if(len) { *len = entry.dataLength; } - return (const char *) entry.data; + return (const char *)entry.data; + } else { + return NULL; } + } else { + return NULL; } - return NULL; } bool extapp_fileWrite(const char * filename, const char * content, size_t len, int storage) { - if (storage == EXTAPP_RAM_FILE_SYSTEM) { + if(storage == EXTAPP_RAM_FILE_SYSTEM) { Ion::Storage::Record::ErrorStatus status = Ion::Storage::sharedStorage()->createRecordWithFullName(filename, content, len); if (status == Ion::Storage::Record::ErrorStatus::NameTaken) { Ion::Storage::Record::Data data; @@ -174,9 +168,12 @@ bool extapp_fileWrite(const char * filename, const char * content, size_t len, i return Ion::Storage::sharedStorage()->recordNamed(filename).setValue(data) == Ion::Storage::Record::ErrorStatus::None; } else if (status == Ion::Storage::Record::ErrorStatus::None) { return true; + } else { + return false; } + } else { + return false; } - return false; } static void reloadTitleBar() { @@ -241,18 +238,18 @@ const int16_t translated_keys[] = '?','!',KEY_CHAR_EXPN10,KEY_CHAR_ANS,KEY_CTRL_EXE,-1, }; -#ifdef ION_SIMULATOR_FILES +#ifdef SIMULATOR #define TICKS_PER_MINUTE 60000 #else #define TICKS_PER_MINUTE 11862 #endif -int extapp_getKey(bool allowSuspend, bool * alphaWasActive) { +int extapp_getKey(bool allowSuspend, bool *alphaWasActive) { int key = -1; size_t t1 = Ion::Timing::millis(); for (;;) { int timeout = 10000; - if (alphaWasActive) { + if(alphaWasActive) { *alphaWasActive = Ion::Events::isAlphaActive(); } Ion::Events::Event event = Ion::Events::getEvent(&timeout); @@ -288,43 +285,6 @@ int extapp_getKey(bool allowSuspend, bool * alphaWasActive) { return translated_keys[key]; } -int extapp_restoreBackup(int mode) { - return 0; -} - -bool isKeydown(int k) { - Ion::Keyboard::State scan = Ion::Keyboard::scan(); - return scan.keyDown(Ion::Keyboard::Key(k)); -} - -bool extapp_eraseSector(void * ptr) { -#ifdef DEVICE - int i = Ion::Device::Flash::SectorAtAddress((size_t) ptr); - if (i < 0) - return false; - Ion::Device::Flash::EraseSector(i); - return true; -#else - return false; -#endif -} - -bool extapp_writeMemory(unsigned char * dest, const unsigned char * data, size_t length) { -#ifdef DEVICE - int n = Ion::Device::Flash::SectorAtAddress((uint64_t) dest); - if (n < 0) - return false; - Ion::Device::Flash::WriteMemory(dest, (unsigned char *) data, length); - return true; -#else - return false; -#endif -} - -bool extapp_inExamMode() { - return GlobalPreferences::sharedGlobalPreferences()->isInExamMode(); -} - extern "C" void (* const apiPointers[])(void) = { (void (*)(void)) extapp_millis, (void (*)(void)) extapp_msleep, @@ -345,9 +305,5 @@ extern "C" void (* const apiPointers[])(void) = { (void (*)(void)) extapp_lockAlpha, (void (*)(void)) extapp_resetKeyboard, (void (*)(void)) extapp_getKey, - (void (*)(void)) extapp_restoreBackup, - (void (*)(void)) extapp_eraseSector, - (void (*)(void)) extapp_writeMemory, - (void (*)(void)) extapp_inExamMode, (void (*)(void)) nullptr, }; diff --git a/apps/external/extapp_api.h b/apps/external/extapp_api.h index cc950fd7a94..b17b8643673 100644 --- a/apps/external/extapp_api.h +++ b/apps/external/extapp_api.h @@ -18,7 +18,6 @@ #define EXTAPP_RAM_FILE_SYSTEM 0 #define EXTAPP_FLASH_FILE_SYSTEM 1 -#define EXTAPP_BOTH_FILE_SYSTEM 2 #define SCANCODE_Left ((uint64_t)1 << 0) #define SCANCODE_Up ((uint64_t)1 << 1) @@ -253,8 +252,5 @@ EXTERNC bool extapp_fileWrite(const char * filename, const char * content, size_ EXTERNC void extapp_lockAlpha(); EXTERNC void extapp_resetKeyboard(); EXTERNC int extapp_getKey(bool allowSuspend, bool *alphaWasActive); -EXTERNC int extapp_restoreBackup(int mode); // Keep for compatibility with KhiCAS on Khi, but it do nothing -EXTERNC bool extapp_eraseSector(void * ptr); -EXTERNC bool extapp_writeMemory(unsigned char * dest,const unsigned char * data,size_t length); #endif From 03ae762b4a2d8c6cadd99d43104ece4816d7d847 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Fri, 29 Apr 2022 21:55:50 +0200 Subject: [PATCH 259/355] [external] Updated for compatibility with KhiCAS --- apps/external/archive.cpp | 94 ++++----- apps/external/archive.h | 1 + apps/external/extapp_api.cpp | 197 ++++++++++++------ apps/external/extapp_api.h | 7 +- apps/global_preferences.h | 8 + apps/settings/Makefile | 1 + apps/settings/base.de.i18n | 6 + apps/settings/base.en.i18n | 6 + apps/settings/base.es.i18n | 6 + apps/settings/base.fr.i18n | 6 + apps/settings/base.hu.i18n | 6 + apps/settings/base.it.i18n | 6 + apps/settings/base.nl.i18n | 6 + apps/settings/base.pt.i18n | 6 + apps/settings/main_controller.cpp | 6 +- apps/settings/main_controller.h | 3 + apps/settings/main_controller_prompt_beta.cpp | 7 +- apps/settings/main_controller_prompt_none.cpp | 3 +- .../main_controller_prompt_update.cpp | 2 + .../settings/sub_menu/external_controller.cpp | 72 +++++++ apps/settings/sub_menu/external_controller.h | 29 +++ ion/include/ion.h | 1 + ion/src/device/bootloader/platform_info.cpp | 8 +- ion/src/device/n0100/platform_info.cpp | 7 + ion/src/device/n0110/platform_info.cpp | 9 +- ion/src/simulator/shared/platform_info.cpp | 11 + 26 files changed, 392 insertions(+), 122 deletions(-) create mode 100644 apps/settings/sub_menu/external_controller.cpp create mode 100644 apps/settings/sub_menu/external_controller.h diff --git a/apps/external/archive.cpp b/apps/external/archive.cpp index 8f3d000bf7c..285e262a586 100644 --- a/apps/external/archive.cpp +++ b/apps/external/archive.cpp @@ -40,9 +40,10 @@ bool isExamModeAndFileNotExecutable(const TarHeader* tar) { } bool fileAtIndex(size_t index, File &entry) { - if (index == -1) + if (index == -1) { return false; - + } + const TarHeader* tar = reinterpret_cast(0x90200000); unsigned size = 0; @@ -74,6 +75,8 @@ bool fileAtIndex(size_t index, File &entry) { entry.data = reinterpret_cast(tar) + sizeof(TarHeader); entry.dataLength = size; entry.isExecutable = (tar->mode[4] & 0x01) == 1; + // TODO: Handle the trash + entry.readable = true; return true; } else { @@ -111,18 +114,31 @@ uint32_t executeFile(const char *name, void * heap, const uint32_t heapSize) { return -1; } -int indexFromName(const char *name) { - File entry; - for (int i = 0; fileAtIndex(i, entry); i++) { - if (strcmp(name, entry.name) == 0) { - return i; - } +#else + +bool fileAtIndex(size_t index, File &entry) { + if (index != 0) { + return false; } - return -1; + entry.name = "Built-in"; + entry.data = NULL; + entry.dataLength = 0; + entry.isExecutable = true; + entry.readable = true; + return true; +} + +extern "C" void extapp_main(void); + +uint32_t executeFile(const char *name, void * heap, const uint32_t heapSize) { + extapp_main(); + return 0; } +#endif + size_t numberOfFiles() { File dummy; size_t count; @@ -132,6 +148,18 @@ size_t numberOfFiles() { return count; } +int indexFromName(const char *name) { + File entry; + + for (int i = 0; fileAtIndex(i, entry); i++) { + if (entry.readable && strcmp(name, entry.name) == 0) { + return i; + } + } + + return -1; +} + bool executableAtIndex(size_t index, File &entry) { File dummy; size_t count; @@ -144,16 +172,19 @@ bool executableAtIndex(size_t index, File &entry) { entry.data = dummy.data; entry.dataLength = dummy.dataLength; entry.isExecutable = dummy.isExecutable; + entry.readable = dummy.readable; return true; } final_count++; } } - return false; } size_t numberOfExecutables() { + if (!GlobalPreferences::sharedGlobalPreferences()->externalAppShown()) { + return false; + } File dummy; size_t count; size_t final_count = 0; @@ -165,48 +196,5 @@ size_t numberOfExecutables() { return final_count; } - - -#else - -bool fileAtIndex(size_t index, File &entry) { - if (index != 0) - return false; - - entry.name = "Built-in"; - entry.data = NULL; - entry.dataLength = 0; - entry.isExecutable = true; - return true; -} - -bool executableAtIndex(size_t index, File &entry) { - return fileAtIndex(index, entry); -} - -size_t numberOfExecutables() { - return 1; -} - -extern "C" void extapp_main(void); - -uint32_t executeFile(const char *name, void * heap, const uint32_t heapSize) { - extapp_main(); - return 0; -} - -int indexFromName(const char *name) { - if (strcmp(name, "Built-in") == 0) - return 0; - else - return -1; -} - -size_t numberOfFiles() { - return 1; -} - -#endif - } } diff --git a/apps/external/archive.h b/apps/external/archive.h index 670bd138f42..de96e6d204f 100644 --- a/apps/external/archive.h +++ b/apps/external/archive.h @@ -14,6 +14,7 @@ struct File { const uint8_t *data; size_t dataLength; bool isExecutable; + bool readable; }; bool fileAtIndex(size_t index, File &entry); diff --git a/apps/external/extapp_api.cpp b/apps/external/extapp_api.cpp index eefd19f5810..6bc548da45a 100644 --- a/apps/external/extapp_api.cpp +++ b/apps/external/extapp_api.cpp @@ -9,10 +9,16 @@ #include "../apps_container.h" #include "../global_preferences.h" +#ifdef DEVICE +#include +#include +#include +#endif + #include extern "C" { - #include +#include } uint64_t extapp_millis() { @@ -30,7 +36,7 @@ uint64_t extapp_scanKeyboard() { void extapp_pushRect(int16_t x, int16_t y, uint16_t w, uint16_t h, const uint16_t * pixels) { KDRect rect(x, y, w, h); - Ion::Display::pushRect(rect, reinterpret_cast(pixels)); + Ion::Display::pushRect(rect, reinterpret_cast(pixels)); } void extapp_pushRectUniform(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t color) { @@ -42,10 +48,10 @@ void extapp_pushRectUniform(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16 void extapp_pullRect(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t * pixels) { KDRect rect(x, y, w, h); - Ion::Display::pullRect(rect, (KDColor *) pixels); + Ion::Display::pullRect(rect, (KDColor *)pixels); } -int16_t extapp_drawTextLarge(const char *text, int16_t x, int16_t y, uint16_t fg, uint16_t bg, bool fake) { +int16_t extapp_drawTextLarge(const char * text, int16_t x, int16_t y, uint16_t fg, uint16_t bg, bool fake) { KDPoint point(x, y); auto ctx = KDIonContext::sharedContext(); @@ -56,7 +62,7 @@ int16_t extapp_drawTextLarge(const char *text, int16_t x, int16_t y, uint16_t fg return point.x(); } -int16_t extapp_drawTextSmall(const char *text, int16_t x, int16_t y, uint16_t fg, uint16_t bg, bool fake) { +int16_t extapp_drawTextSmall(const char * text, int16_t x, int16_t y, uint16_t fg, uint16_t bg, bool fake) { KDPoint point(x, y); auto ctx = KDIonContext::sharedContext(); @@ -71,7 +77,7 @@ bool extapp_waitForVBlank() { return Ion::Display::waitForVBlank(); } -void extapp_clipboardStore(const char *text) { +void extapp_clipboardStore(const char * text) { Clipboard::sharedClipboard()->store(text); } @@ -79,101 +85,103 @@ const char * extapp_clipboardText() { return Clipboard::sharedClipboard()->storedText(); } +bool match(const char * filename, const char * extension) { + return strcmp(filename + strlen(filename) - strlen(extension), extension) == 0; +} + int extapp_fileListWithExtension(const char ** filenames, int maxrecords, const char * extension, int storage) { - if(storage == EXTAPP_RAM_FILE_SYSTEM) { + int j = 0; + if (storage == EXTAPP_RAM_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { int n = Ion::Storage::sharedStorage()->numberOfRecordsWithExtension(extension); if (n > maxrecords) { n = maxrecords; } - for(int i = 0; i < n; i++) { - filenames[i] = Ion::Storage::sharedStorage()->recordWithExtensionAtIndex(extension, i).fullName(); + for (; j < n; j++) { + filenames[j] = Ion::Storage::sharedStorage()->recordWithExtensionAtIndex(extension, j).fullName(); } - return n; - } else if(storage == EXTAPP_FLASH_FILE_SYSTEM) { - // TODO: filter by extension - int n = External::Archive::numberOfFiles(); - if (n > maxrecords) { - n = maxrecords; + if (j == maxrecords) { + return j; } - for(int i = 0; i < n; i++) { + } + // Don't read external files the exam mode is enabled + if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) return j; + if (storage == EXTAPP_FLASH_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { + int n = External::Archive::numberOfFiles(); + for (int i = 0; i < n && j < maxrecords; i++) { External::Archive::File entry; - External::Archive::fileAtIndex(i, entry); - filenames[i] = entry.name; + // Filter extension + if (External::Archive::fileAtIndex(i, entry) && match(entry.name, extension)) { + filenames[j] = entry.name; + ++j; + } } - return n; - } else { - return 0; } + return j; } bool extapp_fileExists(const char * filename, int storage) { - if(storage == EXTAPP_RAM_FILE_SYSTEM) { - return !Ion::Storage::sharedStorage()->recordNamed(filename).isNull(); - } else if(storage == EXTAPP_FLASH_FILE_SYSTEM) { + if (storage == EXTAPP_RAM_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { + if (!Ion::Storage::sharedStorage()->recordNamed(filename).isNull()) + return true; + } + if (storage == EXTAPP_FLASH_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { return External::Archive::indexFromName(filename) >= 0; - } else { - return false; } + return false; } bool extapp_fileErase(const char * filename, int storage) { - if(storage == EXTAPP_RAM_FILE_SYSTEM) { + if (storage == EXTAPP_RAM_FILE_SYSTEM) { Ion::Storage::Record record = Ion::Storage::sharedStorage()->recordNamed(filename); - if(record.isNull()) { + if (record.isNull()) { return false; - } else { - record.destroy(); - return true; } - } else { - return false; + record.destroy(); + return true; } + return false; } -const char * extapp_fileRead(const char * filename, size_t *len, int storage) { - if(storage == EXTAPP_RAM_FILE_SYSTEM) { +const char * extapp_fileRead(const char * filename, size_t * len, int storage) { + if (storage == EXTAPP_RAM_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { const Ion::Storage::Record record = Ion::Storage::sharedStorage()->recordNamed(filename); - if (record.isNull()) { - return NULL; - } else { - if(len) { - *len = record.value().size; - } - return (const char *) record.value().buffer; + if (!record.isNull()){ + int delta = 0; + if (match(filename, ".py") || match(filename, ".xw")) + delta++; + // skip record type + if (len) + *len = record.value().size - delta; + return (const char *)record.value().buffer + delta; } - } else if(storage == EXTAPP_FLASH_FILE_SYSTEM) { + } + if (storage == EXTAPP_FLASH_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { int index = External::Archive::indexFromName(filename); if (index >= 0) { External::Archive::File entry; External::Archive::fileAtIndex(index, entry); - if(len) { + if (len) { *len = entry.dataLength; } return (const char *)entry.data; - } else { - return NULL; } - } else { - return NULL; } + return NULL; } bool extapp_fileWrite(const char * filename, const char * content, size_t len, int storage) { - if(storage == EXTAPP_RAM_FILE_SYSTEM) { + if (storage == EXTAPP_RAM_FILE_SYSTEM) { Ion::Storage::Record::ErrorStatus status = Ion::Storage::sharedStorage()->createRecordWithFullName(filename, content, len); if (status == Ion::Storage::Record::ErrorStatus::NameTaken) { Ion::Storage::Record::Data data; data.buffer = content; data.size = len; return Ion::Storage::sharedStorage()->recordNamed(filename).setValue(data) == Ion::Storage::Record::ErrorStatus::None; - } else if (status == Ion::Storage::Record::ErrorStatus::None) { - return true; - } else { - return false; } - } else { - return false; + if (status == Ion::Storage::Record::ErrorStatus::None) + return true; } + return false; } static void reloadTitleBar() { @@ -244,19 +252,42 @@ const int16_t translated_keys[] = #define TICKS_PER_MINUTE 11862 #endif -int extapp_getKey(bool allowSuspend, bool *alphaWasActive) { + +int extapp_restoreBackup(int mode) { + // Restoring the backup is allowed even if the write protection is enabled, because it may have been writted by Khi.x + if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) + return 0; + size_t length = 32 * 1024; + if (mode == -1) { // restore backup saved when exam mode was set + uint8_t * src = (uint8_t *)(0x90800000 - 2 * length); + if (src[0] == 0xba && src[1] == 0xdd && src[2] == 0x0b && src[3] == 0xee) { + memcpy((uint8_t *)Ion::storageAddress(), src, length); + return 1; + } + } + if (mode >= 0 && mode < 16) { + uint8_t * src = (uint8_t *)(0x90180000 + mode * length); + if (src[0] == 0xba && src[1] == 0xdd && src[2] == 0x0b && src[3] == 0xee) { + memcpy((uint8_t *)Ion::storageAddress(), src, length); + return 1; + } + } + return 0; +} + +int extapp_getKey(int allowSuspend, bool * alphaWasActive) { int key = -1; size_t t1 = Ion::Timing::millis(); for (;;) { int timeout = 10000; - if(alphaWasActive) { + if (alphaWasActive) { *alphaWasActive = Ion::Events::isAlphaActive(); } Ion::Events::Event event = Ion::Events::getEvent(&timeout); reloadTitleBar(); if (event == Ion::Events::None) { size_t t2 = Ion::Timing::millis(); - if (t2 - t1 > 2 * TICKS_PER_MINUTE) { + if (t2 - t1 > 3 * TICKS_PER_MINUTE) { event = Ion::Events::OnOff; } } else { @@ -270,10 +301,10 @@ int extapp_getKey(bool allowSuspend, bool *alphaWasActive) { } if (event.isKeyboardEvent()) { key = static_cast(event); - if (key == 17 || key == 4 || key == 5 || key == 52) { + if (key == (int)Ion::Keyboard::Key::Backspace || key == (int)Ion::Keyboard::Key::OK || key == (int)Ion::Keyboard::Key::Back || key == (int)Ion::Keyboard::Key::EXE) { extapp_resetKeyboard(); } - if (allowSuspend && (key == 7 || key == 8)) { // power + if (allowSuspend && key == (int)Ion::Keyboard::Key::OnOff) { Ion::Power::suspend(true); extapp_pushRectUniform(0, 0, 320, 240, 65535); Ion::Backlight::setBrightness(GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()); @@ -285,6 +316,45 @@ int extapp_getKey(bool allowSuspend, bool *alphaWasActive) { return translated_keys[key]; } +bool extapp_isKeydown(int key) { + Ion::Keyboard::State scan = Ion::Keyboard::scan(); + return scan.keyDown(Ion::Keyboard::Key(key)); +} + +bool extapp_eraseSector(void * ptr) { +#ifdef DEVICE + if (ptr == 0) + Ion::Device::Reset::core(); + // Disable flash writting + if (GlobalPreferences::sharedGlobalPreferences()->externalAppWritePermission()) { + int i = Ion::Device::Flash::SectorAtAddress((size_t)ptr); + if (i < 0) + return false; + Ion::Device::Flash::EraseSector(i); + return true; + } +#endif + return false; +} + +bool extapp_writeMemory(unsigned char * dest, const unsigned char * data, size_t length) { +#ifdef DEVICE + // Disable flash writting + if (GlobalPreferences::sharedGlobalPreferences()->externalAppWritePermission()) { + int n = Ion::Device::Flash::SectorAtAddress((uint32_t)dest); + if (n < 0) + return false; + Ion::Device::Flash::WriteMemory(dest, (unsigned char *)data, length); + return true; + } +#endif + return false; +} + +bool extapp_inExamMode() { + return GlobalPreferences::sharedGlobalPreferences()->isInExamMode(); +} + extern "C" void (* const apiPointers[])(void) = { (void (*)(void)) extapp_millis, (void (*)(void)) extapp_msleep, @@ -305,5 +375,10 @@ extern "C" void (* const apiPointers[])(void) = { (void (*)(void)) extapp_lockAlpha, (void (*)(void)) extapp_resetKeyboard, (void (*)(void)) extapp_getKey, + (void (*)(void)) extapp_isKeydown, + (void (*)(void)) extapp_restoreBackup, + (void (*)(void)) extapp_eraseSector, + (void (*)(void)) extapp_writeMemory, + (void (*)(void)) extapp_inExamMode, (void (*)(void)) nullptr, -}; +}; \ No newline at end of file diff --git a/apps/external/extapp_api.h b/apps/external/extapp_api.h index b17b8643673..9a98e5526d4 100644 --- a/apps/external/extapp_api.h +++ b/apps/external/extapp_api.h @@ -18,6 +18,7 @@ #define EXTAPP_RAM_FILE_SYSTEM 0 #define EXTAPP_FLASH_FILE_SYSTEM 1 +#define EXTAPP_BOTH_FILE_SYSTEM 2 #define SCANCODE_Left ((uint64_t)1 << 0) #define SCANCODE_Up ((uint64_t)1 << 1) @@ -251,6 +252,10 @@ EXTERNC const char * extapp_fileRead(const char * filename, size_t *len, int sto EXTERNC bool extapp_fileWrite(const char * filename, const char * content, size_t len, int storage); EXTERNC void extapp_lockAlpha(); EXTERNC void extapp_resetKeyboard(); -EXTERNC int extapp_getKey(bool allowSuspend, bool *alphaWasActive); +EXTERNC int extapp_getKey(int allowSuspend, bool *alphaWasActive); +EXTERNC bool extapp_isKeydown(int key); +EXTERNC int extapp_restoreBackup(int mode); // Keep for compatibility with KhiCAS on Khi +EXTERNC bool extapp_eraseSector(void * ptr); +EXTERNC bool extapp_writeMemory(unsigned char * dest,const unsigned char * data,size_t length); #endif diff --git a/apps/global_preferences.h b/apps/global_preferences.h index f092fdaa08b..6b73e1b775a 100644 --- a/apps/global_preferences.h +++ b/apps/global_preferences.h @@ -51,6 +51,10 @@ class GlobalPreferences { void setIdleBeforeDimmingSeconds(int m_idleBeforeDimmingSeconds); int brightnessShortcut() const { return m_brightnessShortcut; } void setBrightnessShortcut(int m_BrightnessShortcut); + bool externalAppWritePermission() const { return m_externalAppWritePermission; } + void setExternalAppWritePermission(bool extapp_write) { m_externalAppWritePermission = extapp_write; } + bool externalAppShown() const { return m_externalAppShown; } + void setExternalAppShown(bool externalAppShown) { m_externalAppShown = externalAppShown; } private: static_assert(I18n::NumberOfLanguages > 0, "I18n::NumberOfLanguages is not superior to 0"); // There should already have been an error when processing an empty EPSILON_I18N flag static_assert(I18n::NumberOfCountries > 0, "I18n::NumberOfCountries is not superior to 0"); // There should already have been an error when processing an empty EPSILON_COUNTRIES flag @@ -69,6 +73,8 @@ class GlobalPreferences { m_idleBeforeSuspendSeconds(55), m_idleBeforeDimmingSeconds(45), m_brightnessShortcut(4), + m_externalAppWritePermission(false), + m_externalAppShown(true), m_font(KDFont::LargeFont) {} I18n::Language m_language; I18n::Country m_country; @@ -86,6 +92,8 @@ class GlobalPreferences { int m_idleBeforeSuspendSeconds; int m_idleBeforeDimmingSeconds; int m_brightnessShortcut; + bool m_externalAppWritePermission; + bool m_externalAppShown; const KDFont * m_font; }; diff --git a/apps/settings/Makefile b/apps/settings/Makefile index c806527cbc0..ddc7a41811c 100644 --- a/apps/settings/Makefile +++ b/apps/settings/Makefile @@ -26,6 +26,7 @@ app_settings_src = $(addprefix apps/settings/,\ sub_menu/math_options_controller.cpp \ sub_menu/selectable_view_with_messages.cpp \ sub_menu/usb_protection_controller.cpp \ + sub_menu/external_controller.cpp \ sub_menu/brightness_controller.cpp\ ) diff --git a/apps/settings/base.de.i18n b/apps/settings/base.de.i18n index 225fb46bfb2..dfd788b1b4e 100644 --- a/apps/settings/base.de.i18n +++ b/apps/settings/base.de.i18n @@ -65,6 +65,7 @@ SymbolArgFunction = "Leer " SymbolArgDefaultFunction = "Argument " MemUse = "Speicher" DateTime = "Datum/Uhrzeit" +ExternalApps = "Externe Apps" ActivateClock = "Uhr aktivieren" Date = "Datum" Time = "Uhrzeit" @@ -84,3 +85,8 @@ Normal = "Normal" IdleTimeBeforeDimming = "Abdunkeln nach (s)" IdleTimeBeforeSuspend = "Anhalten nach (s)" BrightnessShortcut = "Tastenkombinationsschritte" +ExtAppWrite = "Schreiben aktiviert" +ExtAppWriteExplanation1 = "Standardmäßig externe Anwendungen" +ExtAppWriteExplanation2 = "kann nicht in den Speicher schreiben" +ExtAppWriteExplanation3 = "Flash (dauerhaft) Ihres Rechners." +ExtAppEnabled = "Aufstecken" diff --git a/apps/settings/base.en.i18n b/apps/settings/base.en.i18n index 427eed03cf9..fe2428773c7 100644 --- a/apps/settings/base.en.i18n +++ b/apps/settings/base.en.i18n @@ -65,6 +65,7 @@ SymbolArgFunction = "Empty " SymbolArgDefaultFunction = "Argument " MemUse = "Memory" DateTime = "Date/time" +ExternalApps = "External Apps" ActivateClock = "Activate clock" Date = "Date" Time = "Time" @@ -84,3 +85,8 @@ Normal = "Normal" IdleTimeBeforeDimming = "Dim after (s)" IdleTimeBeforeSuspend = "Suspend after (s)" BrightnessShortcut = "Shortcut steps" +ExtAppWrite = "Write allowed" +ExtAppWriteExplanation1 = "By default, external applications" +ExtAppWriteExplanation2 = "cannot write to memory" +ExtAppWriteExplanation3 = "flash (persistent) of your calculator." +ExtAppEnabled = "Pin up" diff --git a/apps/settings/base.es.i18n b/apps/settings/base.es.i18n index b5723e5a755..c50ef97fa1f 100644 --- a/apps/settings/base.es.i18n +++ b/apps/settings/base.es.i18n @@ -65,6 +65,7 @@ SymbolArgFunction = "Vacío " SymbolArgDefaultFunction = "Argumento " MemUse = "Memoria" DateTime = "Fecha/Hora" +ExternalApps = "Aplicaciones externas" ActivateClock = "Activar el reloj" Date = "Fecha" Time = "Hora" @@ -84,3 +85,8 @@ Normal = "Normal" IdleTimeBeforeDimming = "Oscurecer después de (s)" IdleTimeBeforeSuspend = "Suspender después de (s)" BrightnessShortcut = "Pasos de acceso directo" +ExtAppWrite = "Escritura habilitada" +ExtAppWriteExplanation1 = "Por defecto, las aplicaciones externas" +ExtAppWriteExplanation2 = "no se puede escribir en la memoria" +ExtAppWriteExplanation3 = "flash (persistente) de su calculadora." +ExtAppEnabled = "Fijar" diff --git a/apps/settings/base.fr.i18n b/apps/settings/base.fr.i18n index c8131e9753f..55cea88dfb6 100644 --- a/apps/settings/base.fr.i18n +++ b/apps/settings/base.fr.i18n @@ -65,6 +65,7 @@ SymbolArgFunction = "Vide " SymbolArgDefaultFunction = "Arguments " MemUse = "Mémoire" DateTime = "Date/heure" +ExternalApps = "Applications externes" ActivateClock = "Activer horloge" Date = "Date" Time = "Heure" @@ -84,3 +85,8 @@ Normal = "Normale" IdleTimeBeforeDimming = "Assombrir après (s)" IdleTimeBeforeSuspend = "Éteindre après (s)" BrightnessShortcut = "Étapes du raccourci" +ExtAppWrite = "Écriture autorisée" +ExtAppWriteExplanation1 = "Par défaut, les applications externes" +ExtAppWriteExplanation2 = "ne peuvent écrire dans la mémoire" +ExtAppWriteExplanation3 = "flash (persistante) de votre calculatrice." +ExtAppEnabled = "Afficher" diff --git a/apps/settings/base.hu.i18n b/apps/settings/base.hu.i18n index 4d5485cdadd..8476c7eff40 100644 --- a/apps/settings/base.hu.i18n +++ b/apps/settings/base.hu.i18n @@ -65,6 +65,7 @@ SymbolArgFunction = "Üres " SymbolArgDefaultFunction = "Argumentummal " MemUse = "Memória" DateTime = "Dátum/óra" +ExternalApps = "Külső alkalmazások" ActivateClock = "Óra bekapcsolása" Date = "Datum" Time = "Óra" @@ -84,3 +85,8 @@ Normal = "Normale" IdleTimeBeforeDimming = "Assombrir après (s)" IdleTimeBeforeSuspend = "Éteindre après (s)" BrightnessShortcut = "Parancsikon lépések" +ExtAppWrite = "Írás engedélyezve" +ExtAppWriteExplanation1 = "Alapértelmezés szerint külső alkalmazások" +ExtAppWriteExplanation2 = "nem tud a memóriába írni" +ExtAppWriteExplanation3 = "villog (tartósan) a számológép." +ExtAppEnabled = "Feltűz" diff --git a/apps/settings/base.it.i18n b/apps/settings/base.it.i18n index 74701f626f1..854796e786a 100644 --- a/apps/settings/base.it.i18n +++ b/apps/settings/base.it.i18n @@ -65,6 +65,7 @@ SymbolArgFunction = "Empty " SymbolArgDefaultFunction = "Argument " MemUse = "Memory" DateTime = "Date/time" +ExternalApps = "App esterne" ActivateClock = "Activate clock" Date = "Date" Time = "Time" @@ -84,3 +85,8 @@ Normal = "Normale" IdleTimeBeforeDimming = "Scurisci dopo (s)" IdleTimeBeforeSuspend = "Sospendi dopo (s)" BrightnessShortcut = "Passaggi di scelta rapida" +ExtAppWrite = "Scrittura abilitata" +ExtAppWriteExplanation1 = "Per impostazione predefinita, applicazioni esterne" +ExtAppWriteExplanation2 = "non può scrivere in memoria" +ExtAppWriteExplanation3 = "flash (persistente) della calcolatrice." +ExtAppEnabled = "Affiggere" diff --git a/apps/settings/base.nl.i18n b/apps/settings/base.nl.i18n index b393b653b35..474812c5cea 100644 --- a/apps/settings/base.nl.i18n +++ b/apps/settings/base.nl.i18n @@ -65,6 +65,7 @@ SymbolArgFunction = "Empty " SymbolArgDefaultFunction = "Argument " MemUse = "Memory" DateTime = "Date/time" +ExternalApps = "Externe apps" ActivateClock = "Activate clock" Date = "Date" Time = "Time" @@ -84,3 +85,8 @@ Normal = "Normaal" IdleTimeBeforeDimming = "Donkerder maken na (s)" IdleTimeBeforeSuspend = "Suspend after (s)" BrightnessShortcut = "Snelkoppelingsstappen" +ExtAppWrite = "Schrijven ingeschakeld" +ExtAppWriteExplanation1 = "Standaard zijn externe toepassingen" +ExtAppWriteExplanation2 = "kan niet naar het geheugen schrijven" +ExtAppWriteExplanation3 = "flash (aanhoudend) van uw rekenmachine." +ExtAppEnabled = "Vastpinnen" diff --git a/apps/settings/base.pt.i18n b/apps/settings/base.pt.i18n index c11a54a1692..dd2a2cf26ee 100644 --- a/apps/settings/base.pt.i18n +++ b/apps/settings/base.pt.i18n @@ -65,6 +65,7 @@ SymbolArgFunction = "Vazio " SymbolArgDefaultFunction = "Argumento " MemUse = "Memória" DateTime = "Date/time" +ExternalApps = "Aplicativos externos" ActivateClock = "Activate clock" Date = "Date" Time = "Time" @@ -84,3 +85,8 @@ Normal = "Normal" IdleTimeBeforeDimming = "Diminuir depois (s)" IdleTimeBeforeSuspend = "Suspender depois (s)" BrightnessShortcut = "Passos de atalho" +ExtAppWrite = "Gravação ativada" +ExtAppWriteExplanation1 = "Por padrão, aplicativos externos" +ExtAppWriteExplanation2 = "não pode gravar na memória" +ExtAppWriteExplanation3 = "flash (persistente) de sua calculadora." +ExtAppEnabled = "Pôster" diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index 71f7a5a34aa..2e61b63aac5 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -17,6 +17,7 @@ constexpr SettingsMessageTree s_modelDateTimeChildren[3] = {SettingsMessageTree( constexpr SettingsMessageTree s_symbolChildren[4] = {SettingsMessageTree(I18n::Message::SymbolMultiplicationCross),SettingsMessageTree(I18n::Message::SymbolMultiplicationMiddleDot),SettingsMessageTree(I18n::Message::SymbolMultiplicationStar),SettingsMessageTree(I18n::Message::SymbolMultiplicationAutoSymbol)}; constexpr SettingsMessageTree s_usbProtectionChildren[2] = {SettingsMessageTree(I18n::Message::USBProtection), SettingsMessageTree(I18n::Message::USBProtectionLevel, s_usbProtectionLevelChildren)}; constexpr SettingsMessageTree s_usbProtectionLevelChildren[3] = {SettingsMessageTree(I18n::Message::USBDefaultLevel), SettingsMessageTree(I18n::Message::USBLowLevel), SettingsMessageTree(I18n::Message::USBParanoidLevel)}; +constexpr SettingsMessageTree s_externalChildren[2] = {SettingsMessageTree(I18n::Message::ExtAppWrite), SettingsMessageTree(I18n::Message::ExtAppEnabled)}; constexpr SettingsMessageTree s_symbolFunctionChildren[3] = {SettingsMessageTree(I18n::Message::SymbolDefaultFunction), SettingsMessageTree(I18n::Message::SymbolArgDefaultFunction), SettingsMessageTree(I18n::Message::SymbolArgFunction)}; constexpr SettingsMessageTree s_modelMathOptionsChildren[6] = {SettingsMessageTree(I18n::Message::AngleUnit, s_modelAngleChildren), SettingsMessageTree(I18n::Message::DisplayMode, s_modelFloatDisplayModeChildren), SettingsMessageTree(I18n::Message::EditionMode, s_modelEditionModeChildren), SettingsMessageTree(I18n::Message::SymbolFunction, s_symbolFunctionChildren), SettingsMessageTree(I18n::Message::ComplexFormat, s_modelComplexFormatChildren), SettingsMessageTree(I18n::Message::SymbolMultiplication, s_symbolChildren)}; constexpr SettingsMessageTree s_brightnessChildren[4] = {SettingsMessageTree(I18n::Message::Brightness), SettingsMessageTree(I18n::Message::IdleTimeBeforeDimming), SettingsMessageTree(I18n::Message::IdleTimeBeforeSuspend), SettingsMessageTree(I18n::Message::BrightnessShortcut)}; @@ -44,7 +45,8 @@ MainController::MainController(Responder * parentResponder, InputEventHandlerDel m_examModeController(this), m_aboutController(this), m_preferencesController(this), - m_usbInfoController(this) + m_usbInfoController(this), + m_externalController(this) { for (int i = 0; i < k_numberOfSimpleChevronCells; i++) { m_cells[i].setMessageFont(KDFont::LargeFont); @@ -103,6 +105,8 @@ bool MainController::handleEvent(Ion::Events::Event event) { subController = &m_usbInfoController; } else if (title == I18n::Message::CodeApp) { subController = &m_codeOptionsController; + } else if (title == I18n::Message::ExternalApps) { + subController = &m_externalController; } else { subController = &m_preferencesController; } diff --git a/apps/settings/main_controller.h b/apps/settings/main_controller.h index 5a721e0c4a2..618758643d4 100644 --- a/apps/settings/main_controller.h +++ b/apps/settings/main_controller.h @@ -13,6 +13,7 @@ #include "sub_menu/math_options_controller.h" #include "sub_menu/preferences_controller.h" #include "sub_menu/usb_protection_controller.h" +#include "sub_menu/external_controller.h" #include "sub_menu/brightness_controller.h" namespace Settings { @@ -32,6 +33,7 @@ extern const Shared::SettingsMessageTree s_contributorsChildren[18]; extern const Shared::SettingsMessageTree s_modelAboutChildren[10]; extern const Shared::SettingsMessageTree s_usbProtectionChildren[2]; extern const Shared::SettingsMessageTree s_usbProtectionLevelChildren[3]; +extern const Shared::SettingsMessageTree s_externalChildren[2]; extern const Shared::SettingsMessageTree s_brightnessChildren[4]; extern const Shared::SettingsMessageTree s_model; @@ -82,6 +84,7 @@ class MainController : public ViewController, public ListViewDataSource, public AboutController m_aboutController; PreferencesController m_preferencesController; UsbInfoController m_usbInfoController; + ExternalController m_externalController; }; } diff --git a/apps/settings/main_controller_prompt_beta.cpp b/apps/settings/main_controller_prompt_beta.cpp index 33337d606cc..3b169700c10 100644 --- a/apps/settings/main_controller_prompt_beta.cpp +++ b/apps/settings/main_controller_prompt_beta.cpp @@ -17,9 +17,10 @@ constexpr SettingsMessageTree s_modelMenu[] = SettingsMessageTree(I18n::Message::CodeApp, s_codeChildren), #endif SettingsMessageTree(I18n::Message::BetaPopUp), - SettingsMessageTree(I18n::Message::About, s_modelAboutChildren), - // SettingsMessageTree(I18n::Message::UsbSetting, s_usbProtectionChildren), - SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren)}; + //SettingsMessageTree(I18n::Message::UsbSetting, s_usbProtectionChildren), + SettingsMessageTree(I18n::Message::ExternalApps, s_externalChildren), + SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), + SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; constexpr SettingsMessageTree s_model = SettingsMessageTree(I18n::Message::SettingsApp, s_modelMenu); diff --git a/apps/settings/main_controller_prompt_none.cpp b/apps/settings/main_controller_prompt_none.cpp index 9f15d0c07ef..46511e36091 100644 --- a/apps/settings/main_controller_prompt_none.cpp +++ b/apps/settings/main_controller_prompt_none.cpp @@ -17,7 +17,8 @@ constexpr SettingsMessageTree s_modelMenu[] = #ifdef HAS_CODE SettingsMessageTree(I18n::Message::CodeApp, s_codeChildren), #endif - // SettingsMessageTree(I18n::Message::UsbSetting, s_usbProtectionChildren), + //SettingsMessageTree(I18n::Message::UsbSetting, s_usbProtectionChildren), + SettingsMessageTree(I18n::Message::ExternalApps, s_externalChildren), SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; diff --git a/apps/settings/main_controller_prompt_update.cpp b/apps/settings/main_controller_prompt_update.cpp index 161fc7d5d5e..b566124578a 100644 --- a/apps/settings/main_controller_prompt_update.cpp +++ b/apps/settings/main_controller_prompt_update.cpp @@ -19,6 +19,8 @@ constexpr SettingsMessageTree s_modelMenu[] = SettingsMessageTree(I18n::Message::UpdatePopUp), SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), // SettingsMessageTree(I18n::Message::UsbSetting, s_usbProtectionChildren), + SettingsMessageTree(I18n::Message::UsbSetting, s_usbProtectionChildren), + SettingsMessageTree(I18n::Message::ExternalApps, s_externalChildren), SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; constexpr SettingsMessageTree s_model = SettingsMessageTree(I18n::Message::SettingsApp, s_modelMenu); diff --git a/apps/settings/sub_menu/external_controller.cpp b/apps/settings/sub_menu/external_controller.cpp new file mode 100644 index 00000000000..4ba28901f70 --- /dev/null +++ b/apps/settings/sub_menu/external_controller.cpp @@ -0,0 +1,72 @@ +#include "external_controller.h" + +#include +#include +#include +#include +#include +#include "../../apps_container.h" +#include "../../global_preferences.h" + +using namespace Poincare; +using namespace Shared; + +namespace Settings { + +ExternalController::ExternalController(Responder *parentResponder): + GenericSubController(parentResponder), + m_contentView(&m_selectableTableView) +{ + m_writeSwitchCell.setMessageFont(KDFont::LargeFont); + m_enabledSwitchCell.setMessageFont(KDFont::LargeFont); +} + +bool ExternalController::handleEvent(Ion::Events::Event event) { + if ((Ion::Events::OK == event || Ion::Events::EXE == event || Ion::Events::Right == event) && selectedRow() == 0) { + bool externalWasUnlocked = GlobalPreferences::sharedGlobalPreferences()->externalAppWritePermission(); + GlobalPreferences::sharedGlobalPreferences()->setExternalAppWritePermission(!externalWasUnlocked); + m_selectableTableView.reloadCellAtLocation(0, selectedRow()); + return true; + } else if ((Ion::Events::OK == event || Ion::Events::EXE == event || Ion::Events::Right == event) && selectedRow() == 1) { + bool extappWasShowed = GlobalPreferences::sharedGlobalPreferences()->externalAppShown(); + GlobalPreferences::sharedGlobalPreferences()->setExternalAppShown(!extappWasShowed); + m_selectableTableView.reloadCellAtLocation(0, selectedRow()); + return true; + } + + return GenericSubController::handleEvent(event); +} + +HighlightCell *ExternalController::reusableCell(int index, int type) { + if (index == 0) { + return &m_writeSwitchCell; + } + assert(index == 1); + return &m_enabledSwitchCell; +} + +int ExternalController::reusableCellCount(int type) { + assert(type == 0); + return 2; +} + +void ExternalController::willDisplayCellForIndex(HighlightCell *cell, int index) { + GenericSubController::willDisplayCellForIndex(cell, index); + + if (index == 0) { + MessageTableCellWithSwitch *myCell = (MessageTableCellWithSwitch *)cell; + SwitchView *mySwitch = (SwitchView *)myCell->accessoryView(); + mySwitch->setState(GlobalPreferences::sharedGlobalPreferences()->externalAppWritePermission()); + } else if (index == 1) { + MessageTableCellWithSwitch *myCell = (MessageTableCellWithSwitch *)cell; + SwitchView *mySwitch = (SwitchView *)myCell->accessoryView(); + mySwitch->setState(GlobalPreferences::sharedGlobalPreferences()->externalAppShown()); + } +} + +void ExternalController::didEnterResponderChain(Responder *previousFirstResponder) { + m_contentView.reload(); + I18n::Message infoMessages[] = {I18n::Message::ExtAppWriteExplanation1, I18n::Message::ExtAppWriteExplanation2, I18n::Message::ExtAppWriteExplanation3}; + m_contentView.setMessages(infoMessages, k_numberOfExplanationMessages); +} +} diff --git a/apps/settings/sub_menu/external_controller.h b/apps/settings/sub_menu/external_controller.h new file mode 100644 index 00000000000..11e5ac1212e --- /dev/null +++ b/apps/settings/sub_menu/external_controller.h @@ -0,0 +1,29 @@ +#ifndef SETTINGS_EXTERNAL_CONTROLLER_H +#define SETTINGS_EXTERNAL_CONTROLLER_H + +#include "generic_sub_controller.h" +#include "preferences_controller.h" +#include "selectable_view_with_messages.h" + +namespace Settings { + +class ExternalController : public GenericSubController { +public: + ExternalController(Responder* parentResponder); + View* view() override { return &m_contentView; } + bool handleEvent(Ion::Events::Event event) override; + TELEMETRY_ID("ExternalSettings"); + void didEnterResponderChain(Responder* previousFirstResponder) override; + HighlightCell* reusableCell(int index, int type) override; + int reusableCellCount(int type) override; + void willDisplayCellForIndex(HighlightCell* cell, int index) override; +private: + static constexpr int k_numberOfExplanationMessages = 3; + SelectableViewWithMessages m_contentView; + MessageTableCellWithSwitch m_writeSwitchCell; + MessageTableCellWithSwitch m_enabledSwitchCell; +}; + +} + +#endif diff --git a/ion/include/ion.h b/ion/include/ion.h index e6072db2686..fb935183a41 100644 --- a/ion/include/ion.h +++ b/ion/include/ion.h @@ -41,6 +41,7 @@ const char * patchLevel(); const char * fccId(); const char * pcbVersion(); void updateSlotInfo(); +const void * storageAddress(); // CRC32 : non xor-ed, non reversed, direct, polynomial 4C11DB7 uint32_t crc32Word(const uint32_t * data, size_t length); // Only accepts whole 32bit values diff --git a/ion/src/device/bootloader/platform_info.cpp b/ion/src/device/bootloader/platform_info.cpp index 25a8366c9f5..3b08c186033 100644 --- a/ion/src/device/bootloader/platform_info.cpp +++ b/ion/src/device/bootloader/platform_info.cpp @@ -101,7 +101,9 @@ class UserlandHeader { assert(m_omegaMagicFooter == OmegaMagic); return m_username; } - + const void * storage_address() const { + return storageAddress; + } private: constexpr static uint32_t Magic = 0xDEC0EDFE; constexpr static uint32_t OmegaMagic = 0xEFBEADDE; @@ -172,6 +174,10 @@ const char * Ion::patchLevel() { return k_kernelHeader.patchLevel(); } +const void * Ion::storageAddress() { + return k_userlandHeader.storage_address(); +} + SlotInfo * slotInfo() { static SlotInfo __attribute__((used)) __attribute__((section(".slot_info"))) slotInformation; return &slotInformation; diff --git a/ion/src/device/n0100/platform_info.cpp b/ion/src/device/n0100/platform_info.cpp index 28a9b04dc66..084438ca8f1 100644 --- a/ion/src/device/n0100/platform_info.cpp +++ b/ion/src/device/n0100/platform_info.cpp @@ -94,6 +94,9 @@ class PlatformInfo { assert(m_omegaMagicFooter == OmegaMagic); return m_patchLevel; } + const void * storage_address() const { + return storageAddress; + } private: constexpr static uint32_t Magic = 0xDEC00DF0; constexpr static uint32_t OmegaMagic = 0xEFBEADDE; @@ -138,6 +141,10 @@ const char * Ion::patchLevel() { return platform_infos.patchLevel(); } +const void * Ion::storageAddress() { + return platform_infos.storage_address(); +} + void Ion::updateSlotInfo() { } diff --git a/ion/src/device/n0110/platform_info.cpp b/ion/src/device/n0110/platform_info.cpp index 5edbb65dbfe..cfd1c546c44 100644 --- a/ion/src/device/n0110/platform_info.cpp +++ b/ion/src/device/n0110/platform_info.cpp @@ -92,6 +92,9 @@ class PlatformInfo { assert(m_omegaMagicFooter == OmegaMagic); return m_patchLevel; } + const void * storage_address() const { + return storageAddress; + } private: constexpr static uint32_t Magic = 0xDEC00DF0; constexpr static uint32_t OmegaMagic = 0xEFBEADDE; @@ -136,6 +139,10 @@ const char * Ion::patchLevel() { return platform_infos.patchLevel(); } +const void * Ion::storageAddress() { + return platform_infos.storage_address(); +} + void Ion::updateSlotInfo() { -} +} \ No newline at end of file diff --git a/ion/src/simulator/shared/platform_info.cpp b/ion/src/simulator/shared/platform_info.cpp index 23160c6b61e..848285a871c 100644 --- a/ion/src/simulator/shared/platform_info.cpp +++ b/ion/src/simulator/shared/platform_info.cpp @@ -94,6 +94,9 @@ class PlatformInfo { assert(m_omegaMagicFooter == OmegaMagic); return m_patchLevel; } + const void * storage_address() const { + return storageAddress; + } private: constexpr static uint32_t Magic = 0xDEC00DF0; constexpr static uint32_t OmegaMagic = 0xEFBEADDE; @@ -137,3 +140,11 @@ const volatile char * Ion::username() { const char * Ion::patchLevel() { return platform_infos.patchLevel(); } + +void * storage_address(){ + return storageAddress; +} + +const void * Ion::storageAddress() { + return platform_infos.storage_address(); +} \ No newline at end of file From 745099842a91fbc2ff2f6348d158eeaedf14919a Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 30 Apr 2022 19:12:54 +0200 Subject: [PATCH 260/355] [poincare] Fixed two serious bugs in sequences simplification --- apps/shared/global_context.cpp | 15 +++++++++++---- poincare/src/sequence.cpp | 13 ++++++++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/apps/shared/global_context.cpp b/apps/shared/global_context.cpp index bf8c3cdb4c4..0bf41617540 100644 --- a/apps/shared/global_context.cpp +++ b/apps/shared/global_context.cpp @@ -128,6 +128,7 @@ const Expression GlobalContext::ExpressionForSequence(const SymbolAbstract & sym if (!rank.isUninitialized()) { bool rankIsInteger = false; double rankValue = rank.approximateToScalar(ctx, Poincare::Preferences::sharedPreferences()->complexFormat(), Poincare::Preferences::sharedPreferences()->angleUnit()); + int rankValueFloor = std::floor(rankValue); if (rank.type() == ExpressionNode::Type::Rational) { Rational n = static_cast(rank); rankIsInteger = n.isInteger(); @@ -137,11 +138,17 @@ const Expression GlobalContext::ExpressionForSequence(const SymbolAbstract & sym * approximate the rank and check if it is an integer. Unfortunately this * leads to some edge cases were, because of quantification, we have * floor(x) = x while x is not integer.*/ - rankIsInteger = std::floor(rankValue) == rankValue; + rankIsInteger = rankValueFloor == rankValue; } - if (rankIsInteger && !seq.badlyReferencesItself(ctx)) { - SequenceContext sqctx(ctx, sequenceStore()); - return Float::Builder(seq.evaluateXYAtParameter(rankValue, &sqctx).x2()); + if (rankIsInteger) { + if (rankValueFloor - seq.initialRank() < (int) seq.type()) { // Seq can reference itself but be defined explicitly for first values + assert(rankValueFloor - seq.initialRank() == 0 || rankValueFloor - seq.initialRank() == 1); + return rankValueFloor - seq.initialRank() == 0 ? seq.firstInitialConditionExpressionClone() : seq.secondInitialConditionExpressionClone(); + } + if (!seq.badlyReferencesItself(ctx)) { + SequenceContext sqctx(ctx, sequenceStore()); + return Float::Builder(seq.evaluateXYAtParameter(rankValue, &sqctx).x2()); + } } } return Float::Builder(NAN); diff --git a/poincare/src/sequence.cpp b/poincare/src/sequence.cpp index 5c24602d755..3eeae7e405f 100644 --- a/poincare/src/sequence.cpp +++ b/poincare/src/sequence.cpp @@ -118,7 +118,7 @@ Expression Sequence::shallowReduce(ExpressionNode::ReductionContext reductionCon return *this; } - Expression result = replacedByDefinitionIfPossible(reductionContext.context()); + Expression result = replacedByDefinitionIfPossible(reductionContext.context()); // We don't need to check if the result is uninitialized result = Expression::ExpressionWithoutSymbols(result, reductionContext.context()); if (result.isUninitialized()) { @@ -184,6 +184,17 @@ Expression Sequence::replacedByDefinitionIfPossible(Context * context) { } Expression result = seq.expressionClone(); + + if (result.hasExpression([](Expression e, const void * context) { + if (e.type() != ExpressionNode::Type::Sequence) { + return false; + } + return strcmp(static_cast(e).name(), reinterpret_cast(context)) == 0; + }, reinterpret_cast(name()))) + { + return Expression(); + } + return result.replaceSymbolWithExpression(Symbol::Builder(UCodePointUnknown), childAtIndex(0)); } From adab2c223b3de2fca43c2fb74c0c470d4924f247 Mon Sep 17 00:00:00 2001 From: Laury Date: Sun, 1 May 2022 21:02:07 +0200 Subject: [PATCH 261/355] [reader] Improved previous position algorithm and fixed symbols --- apps/reader/tex_parser.cpp | 8 +++++++- apps/reader/tex_parser.h | 5 +---- apps/reader/utility.cpp | 6 +++--- apps/reader/word_wrap_view.cpp | 28 ++++++++++++++++++++++++---- kandinsky/fonts/LargeFont.ttf | Bin 499852 -> 500292 bytes kandinsky/fonts/SmallFont.ttf | Bin 478448 -> 478328 bytes 6 files changed, 35 insertions(+), 12 deletions(-) diff --git a/apps/reader/tex_parser.cpp b/apps/reader/tex_parser.cpp index 1d6b4e87ea3..79b9ff0e41e 100644 --- a/apps/reader/tex_parser.cpp +++ b/apps/reader/tex_parser.cpp @@ -15,6 +15,8 @@ namespace Reader { "sim", }; + static constexpr int const k_NumberOfSymbols = sizeof(k_SymbolsCommands) / sizeof(char *); + // List of the available Symbol's CodePoints in the same order of the Symbol's list static constexpr uint32_t const k_SymbolsCodePoints[] = { 0xd7, 0xf7, 0x2200, 0x2202, 0x2203, 0xb1, 0x2248, 0x221e, 0x2260, 0x2261, 0x2264, 0x2265, @@ -27,14 +29,18 @@ namespace Reader { 0x7e, }; + static_assert(sizeof(k_SymbolsCodePoints) / sizeof(uint32_t) == k_NumberOfSymbols); + // List of available Function Commands that don't require a specific handling - static constexpr char const * k_FunctionCommands[] = { + static char const * k_FunctionCommands[] = { "arccos", "arcsin", "arctan", "arg", "cos", "cosh", "cot", "coth", "csc", "deg", "det", "dim", "exp", "gcd", "hom", "inf", "ker", "lg", "lim", "liminf", "limsup", "ln", "log", "max", "min", "Pr", "sec", "sin", "sinh", "sup", "tan", "tanh" }; + static int const k_NumberOfFunctionCommands = sizeof(k_FunctionCommands) / sizeof(char *); + TexParser::TexParser(const char * text, const char * endOfText) : m_text(text), m_endOfText(endOfText), diff --git a/apps/reader/tex_parser.h b/apps/reader/tex_parser.h index 82e2c1f06ba..ef2e30b4f06 100644 --- a/apps/reader/tex_parser.h +++ b/apps/reader/tex_parser.h @@ -47,11 +47,8 @@ class TexParser { static constexpr char const * k_sqrtCommand = "sqrt"; static constexpr char const * k_spaceCommand = "space"; static constexpr char const * k_overrightArrowCommand = "overrightarrow"; - - static constexpr int const k_NumberOfSymbols = 71; - static constexpr int const k_NumberOfFunctionCommands = 32; }; - + } #endif diff --git a/apps/reader/utility.cpp b/apps/reader/utility.cpp index d0f9e172c1c..c29c28f1f29 100644 --- a/apps/reader/utility.cpp +++ b/apps/reader/utility.cpp @@ -109,7 +109,7 @@ int filesWithExtension(const char* extension, External::Archive::File* files, in #endif const char * EndOfPrintableWord(const char * word, const char * end) { - if (word == end) { + if (word >= end) { return word; } UTF8Decoder decoder(word); @@ -126,11 +126,11 @@ const char * EndOfPrintableWord(const char * word, const char * end) { } const char * StartOfPrintableWord(const char * word, const char * start) { - if (word == start) { + if (word <= start) { return word; } // Go to start of code point with some code points dark magic - if (!(*word & 0x80 == 0)) { + if (!((*word & 0x80) == 0)) { word--; while (!(*word & 0x80 && *word & 0x40)) { word--; diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index 032b039ec08..1dddd0dd5e2 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -65,27 +65,41 @@ void WordWrapTextView::richTextPreviousPage() { const int charHeight = m_font->glyphSize().height(); const char * endOfWord = text() + m_pageOffset - 1; + if (*endOfWord == '\n') { + endOfWord --; + } - KDCoordinate baseline = charHeight; + KDCoordinate baseline = charHeight / 2; KDPoint textBottomEndPosition = KDPoint(m_frame.width() - k_margin, m_frame.height() - k_margin); KDCoordinate lineHeight = charHeight; while(endOfWord >= text()) { // 1. Skip whitespaces and line jumps - while(endOfWord >= text() && (*endOfWord == ' ' || *endOfWord == '\n')) { - if(*endOfWord == '\n') { + const char * invisiblesCharJumped = endOfWord; // We use this to update endOfWord only if we don't change page + bool changePage = false; + while(invisiblesCharJumped >= text() && (*invisiblesCharJumped == ' ' || *invisiblesCharJumped == '\n')) { + if(*invisiblesCharJumped == '\n') { textBottomEndPosition = KDPoint(m_frame.width() - k_margin, textBottomEndPosition.y() - lineHeight); lineHeight = charHeight; + baseline = charHeight / 2; // We check if we must change page if (textBottomEndPosition.y() - lineHeight <= k_margin) { + // We don't let text on a new line or a space + endOfWord ++; + changePage = true; break; } } else { textBottomEndPosition = KDPoint(textBottomEndPosition.x() - charWidth, textBottomEndPosition.y()); } - endOfWord--; + invisiblesCharJumped--; + } + + if (changePage) { + break; } + endOfWord = invisiblesCharJumped; // 3. If word is a color change if (*endOfWord == '%' && *(endOfWord - 1) != '\\') { @@ -146,6 +160,7 @@ void WordWrapTextView::richTextPreviousPage() { if (textBottomEndPosition.x() - textSize.width() <= k_margin) { textBottomEndPosition = KDPoint(m_frame.width() - k_margin, textBottomEndPosition.y() - lineHeight); lineHeight = 0; + baseline = charHeight; // We will check if we must change page below } textBottomEndPosition = KDPoint(textBottomEndPosition.x() - textSize.width(), textBottomEndPosition.y()); @@ -604,6 +619,11 @@ BookSave WordWrapTextView::getBookSave() const { void WordWrapTextView::setBookSave(BookSave save) { m_pageOffset = save.offset; m_textColor = save.color; + m_lastPagesOffsetsIndex = 0; + + for (int i = 0; i < k_lastOffsetsBufferSize; i++) { + m_lastPagesOffsets[i] = -1; // -1 Means : no informations + } } bool WordWrapTextView::updateTextColorForward(const char * colorStart) const { diff --git a/kandinsky/fonts/LargeFont.ttf b/kandinsky/fonts/LargeFont.ttf index 5446c81345e0052f09de201de4310080a84dcd42..927e03040ad1a69377052de83e2f47279117aca8 100644 GIT binary patch delta 100907 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 H9`Jtvk$846 delta 100738 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

    V6`0 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≤m4+=qd4(|U3rMH!E+BKPrFa-Ybfe76XJ zPi6v8ldUD>Zd*(4_C@4Ag+iZ3=4Xi9XMrG$f_CmFch_=qpC3!^pVP^G5pgde?q!7k z6~w%l$kvZ~Cd%T$3Pk`Vv0svvjPI5m-<`a9!{UV9nQe^rHHTmiwxhE0+4Z^c2HkIiVa?&W|;(To=5paM)-f;?v{KFKAMMUf#3RNqh zQ1vwws*y#Zxa}0GwFoBcsepYrLXO{a;sN-rn?Rw2R0`Egr%=OW3N_k9p~P$oH32jQ zG@DPMB;aeFOQ9Ahyd`k8!f%`P6l#mK?GV>-H-%D5D3ssjIEA_cVGn#}9DfON<)3JuGl(1=wO8hMaHqYys^L|h6)6Oex*U^2e1K%u!s6#9cl zp{qgcHH8$q7LYfKLNgdjDRg}v;1q>0+vbOEM5Y_hQs|~l6uLQ$LbE{7Y(&n@0Gy=I zEy)zR6>0M4QD}Z5U=M|EL->Lm01982O`%1A#mIX*1HzVMQ3y;Mx&s7YY7Q+uMxnx) z6uJv|mm%M>?G(B@6@d9av>X8|@+=_XN_?+EL913#XmtXG6#iG6DYT}TLiYjLecLH? zzegcR%+Lb}Tf3G*>lRVyp*jt5B5{&*i5wQ zN1*Qbb_#vEheDs@_XMD{m_p$4&{u~k^z~W_og_dv0K!fd0ZJ+KO(I|vg}yyYq3^ON zbSe&jwBNS}6a$dv2c-D{#GcNj(3x035ruw?r_fJ%6grD?e$GMv|8j;r=OB6RBJx7r z05bta?5xw5+)~**9r() zGn^zZC7ZnS@PE5B05W$NOJ2uP_?(O)FLeibT@lxH6?xs;lXreBdC(EOv=Z_z*i7Dq z2gpm$BCl5tU^jWa_mh`_v>0Jt-+V;&n@V0LJ_aU`Hz=39EWluoydkLNP#_+}dFywRw*l!k93XEah{~hFi_W*hCjRJsxLx95wJ6uBE(fsA)eFy{}9VG8# zP=6eheFBO<-Ai6cDS4k4k%x)L`*H#RaVOW3_bq-;A@2K30Eqg2A9+9E{~vaccX~Mh zac5Aeed;;Xfg>eo`gsPOa<&CAsYgaSMDVdgETRDfD#h1_&;_ziE6-C4fv}~ z2cW!aXGl~ZMfsuzkk;4?C?ye>N}^@~iCX(f)Q$xpL+zOWWQflI?7&MKASfXb0Hg^h zpx$~C4G)rNjIbsXNHhac%|T3a5ZN4fn;#?50^coGk!TqY$O6m<>;#<5C(#-R+9r`` z2L$awWk*n)3M#vxu&yY$8^XI~1LgsWNSvQbqDLX%IEgf*xv)K84T+xk?v)2XSf64N z8Gyc%N%SK?D&R1QO#GjjLt+338aN98{Z9g6P+A#xhRrUT)%StR}lgx4Ws-Z2t0K-l&B zNZhc9#LRdSH{}69Sc0^KXslVqnjlBvLz3Y?wO zNOl1cUAB`OHR*$?6Ukhec@4TvW>Fc+{HfUrTpH)uLw69ARU!gqewJp8kd z1CVxDG0EYHfNa2Vk|VN6=Byz(G8F(k7i9nr zle~Bl$x$d^)L1|fpp@h#>3~9#qq_le0cX(vV|I`ni-N}@;L@o8e2+Uray)819+@D` zfF(*ekP1MH9ThRGynW|F)S1>6J#Hzxp!NY29dEX2)PPjdDIl5-F~2gv5) z|9QnEZ$aFx9-0tDReig7UpW?l^s7+#Duk^jz%0N?lJ`yp93!c+0Xs>qX^8$` zgUB@qyboP)-)6uWlJ{o;3INFT0Ky;GL~?CBAQ!NQ#pLppfLoh5*EEJWTT8Tma%W0mr7D=>JD1lYBHD0A!D)1C|3yNN&ym;Q!6X zNIs65KE4-lmgEzu03dl{4PYP1Ed=NWmNkZuD~SU%*Rvq|n53z!es0VpB)*F-=Lpb&7F7umqVt zN2V`8DHKn6av#ZW&XW8N->24-{C*Y5ACTtsQ~>@zjrh}NNS;Xr0OyY=2+F5Cn-Y%8 JjY;eMzW~dKV0!=n diff --git a/kandinsky/fonts/SmallFont.ttf b/kandinsky/fonts/SmallFont.ttf index 9df34301658a08187473ec46b3a9b60a3fe7f1d6..c36fa112ecf0f013e0aa7cfba9a70ff170aa08e4 100644 GIT binary patch delta 94971 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 Cf|tbr delta 95407 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 e-%#Qg2yjlri(82AL&h&5! Date: Mon, 2 May 2022 17:01:20 +0200 Subject: [PATCH 262/355] [usb] Changed link in descriptor --- ion/src/device/shared/usb/calculator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/shared/usb/calculator.h b/ion/src/device/shared/usb/calculator.h index 308beb66b1d..29a7203c6b4 100644 --- a/ion/src/device/shared/usb/calculator.h +++ b/ion/src/device/shared/usb/calculator.h @@ -100,7 +100,7 @@ class Calculator : public Device { /* Switch to this descriptor to use dfu-util to write in the SRAM. * FIXME Should be an alternate Interface. */ m_microsoftOSStringDescriptor(k_microsoftOSVendorCode), - m_workshopURLDescriptor(URLDescriptor::Scheme::HTTPS, "getomega.dev"), + m_workshopURLDescriptor(URLDescriptor::Scheme::HTTPS, "getupsilon.web.app"), m_extendedCompatIdDescriptor("WINUSB"), m_descriptors{ &m_deviceDescriptor, // Type = Device, Index = 0 From 0e0dc4a9c2e2f50591a8079251a8603362943d89 Mon Sep 17 00:00:00 2001 From: Laury Date: Mon, 2 May 2022 18:55:46 +0200 Subject: [PATCH 263/355] Revert "[apps] Add Shift + Ans shortcut to go to the last application" --- apps/apps_container.cpp | 22 ---------------------- apps/apps_container.h | 3 --- apps/apps_container_storage.cpp | 17 ----------------- apps/apps_container_storage.h | 1 - ion/include/ion/events.h | 1 - 5 files changed, 44 deletions(-) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index 2ed9c7e7945..f464b07c187 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -220,7 +220,6 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { return didProcessEvent || alphaLockWantsRedraw; } -// List of keys that are used to switch between apps, in order of app to go (eg. 0 : First App, 1 : Second App, 2 : Third App, ...) static constexpr Ion::Events::Event switch_events[] = { Ion::Events::ShiftSeven, Ion::Events::ShiftEight, Ion::Events::ShiftNine, Ion::Events::ShiftFour, Ion::Events::ShiftFive, Ion::Events::ShiftSix, @@ -232,17 +231,14 @@ bool AppsContainer::processEvent(Ion::Events::Event event) { // Warning: if the window is dirtied, you need to call window()->redraw() if (event == Ion::Events::USBPlug) { if (Ion::USB::isPlugged()) { - // If the exam mode is enabled, we ask to disable it, else, we enable USB if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { displayExamModePopUp(GlobalPreferences::ExamMode::Off); window()->redraw(); } else { Ion::USB::enable(); } - // Update brightness when USB is plugged Ion::Backlight::setBrightness(GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()); } else { - // If the USB isn't plugged in USBPlug event, we disable USB Ion::USB::disable(); } return true; @@ -273,38 +269,20 @@ bool AppsContainer::processEvent(Ion::Events::Event event) { return true; } - // Add Shift + Ans shortcut to go to the previous app - if (event == Ion::Events::ShiftAns) { - switchTo(appSnapshotAtIndex(m_lastAppIndex)); - return true; - } - - // If the event is the OnOff key, we suspend the calculator. if (event == Ion::Events::OnOff) { suspend(true); return true; } - // If the event is a brightness event, we update the brightness according to the event. if (event == Ion::Events::BrightnessPlus || event == Ion::Events::BrightnessMinus) { int delta = Ion::Backlight::MaxBrightness/GlobalPreferences::NumberOfBrightnessStates; int NumberOfStepsPerShortcut = GlobalPreferences::sharedGlobalPreferences()->brightnessShortcut(); int direction = (event == Ion::Events::BrightnessPlus) ? NumberOfStepsPerShortcut*delta : -delta*NumberOfStepsPerShortcut; GlobalPreferences::sharedGlobalPreferences()->setBrightnessLevel(GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()+direction); } - // Else, the event was not processed. return false; } bool AppsContainer::switchTo(App::Snapshot * snapshot) { - // Get app index of the snapshot - int m_appIndexToSwitch = appIndexFromSnapshot(snapshot); - // If the app is home, skip app index saving - if (m_appIndexToSwitch != 0) { - // Save last app index - m_lastAppIndex = m_currentAppIndex; - // Save current app index - m_currentAppIndex = m_appIndexToSwitch; - } if (s_activeApp && snapshot != s_activeApp->snapshot()) { resetShiftAlphaStatus(); } diff --git a/apps/apps_container.h b/apps/apps_container.h index 07a642aee14..f52d50b6c50 100644 --- a/apps/apps_container.h +++ b/apps/apps_container.h @@ -28,7 +28,6 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag static bool poincareCircuitBreaker(); virtual int numberOfApps() = 0; virtual App::Snapshot * appSnapshotAtIndex(int index) = 0; - virtual int appIndexFromSnapshot(App::Snapshot * snapshot) = 0; App::Snapshot * initialAppSnapshot(); App::Snapshot * hardwareTestAppSnapshot(); App::Snapshot * onBoardingAppSnapshot(); @@ -70,8 +69,6 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag static KDColor k_promptFGColors[]; static KDColor k_promptBGColors[]; static int k_promptNumberOfMessages; - int m_currentAppIndex; // Home isn't included after the second app switching - int m_lastAppIndex; AppsWindow m_window; EmptyBatteryWindow m_emptyBatteryWindow; Shared::GlobalContext m_globalContext; diff --git a/apps/apps_container_storage.cpp b/apps/apps_container_storage.cpp index ad2dec6197c..2ef60aafa5c 100644 --- a/apps/apps_container_storage.cpp +++ b/apps/apps_container_storage.cpp @@ -36,20 +36,3 @@ App::Snapshot * AppsContainerStorage::appSnapshotAtIndex(int index) { assert(index >= 0 && index < k_numberOfCommonApps); return snapshots[index]; } - -// Get the index of the app from its snapshot -int AppsContainerStorage::appIndexFromSnapshot(App::Snapshot * snapshot) { - App::Snapshot * snapshots[] = { - homeAppSnapshot() - APPS_CONTAINER_SNAPSHOT_LIST - }; - assert(sizeof(snapshots)/sizeof(snapshots[0]) == k_numberOfCommonApps); - for (int i = 0; i < k_numberOfCommonApps; i++) { - if (snapshots[i] == snapshot) { - return i; - } - } - // Achievement unlock : how did you get here ? - assert(false); - return NULL; -} diff --git a/apps/apps_container_storage.h b/apps/apps_container_storage.h index c3191a751da..9abd3c27ad4 100644 --- a/apps/apps_container_storage.h +++ b/apps/apps_container_storage.h @@ -12,7 +12,6 @@ class AppsContainerStorage : public AppsContainer { AppsContainerStorage(); int numberOfApps() override; App::Snapshot * appSnapshotAtIndex(int index) override; - int appIndexFromSnapshot(App::Snapshot * snapshot) override; void * currentAppBuffer() override { return &m_apps; }; private: union Apps { diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index e7ae2feb2b1..51c8da397b7 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -185,7 +185,6 @@ constexpr Event ShiftSeven = Event::ShiftKey(Keyboard::Key::Seven); constexpr Event ShiftEight = Event::ShiftKey(Keyboard::Key::Eight); constexpr Event ShiftNine = Event::ShiftKey(Keyboard::Key::Nine); -constexpr Event ShiftAns = Event::ShiftKey(Keyboard::Key::Ans); constexpr Event ShiftEXE = Event::ShiftKey(Keyboard::Key::EXE); // Alpha From f348d9eb5da7fbfe014685adb0a53e5b06ee3506 Mon Sep 17 00:00:00 2001 From: Laury Date: Tue, 3 May 2022 16:34:07 +0200 Subject: [PATCH 264/355] [bootloader] Fixed compilation --- bootloader/interface/src/menu.cpp | 8 +++----- bootloader/interface/static/interface.cpp | 12 +++++++++--- bootloader/interface/static/interface.h | 3 ++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/bootloader/interface/src/menu.cpp b/bootloader/interface/src/menu.cpp index d45fb846621..c371bf01aa0 100644 --- a/bootloader/interface/src/menu.cpp +++ b/bootloader/interface/src/menu.cpp @@ -4,8 +4,6 @@ #include #include -#include - const Ion::Keyboard::Key Bootloader::Menu::k_breaking_keys[]; void Bootloader::Menu::setup() { @@ -36,15 +34,15 @@ int Bootloader::Menu::calculateCenterX(const char * text, int fontWidth) { void Bootloader::Menu::showMenu() { KDContext * ctx = KDIonContext::sharedContext(); ctx->fillRect(getScreen(), m_background); - Interface::drawImage(ctx, ImageStore::Computer, 25); - int y = ImageStore::Computer->height() + 25 + 10; + Interface::drawComputer(ctx, 25); + int y = Interface::computerHeight() + 25 + 10; int x = calculateCenterX(m_title, largeFontWidth()); ctx->drawString(m_title, KDPoint(x, y), k_large_font, m_foreground, m_background); y += largeFontHeight() + 10; //TODO: center the columns if m_centerY is true - for (ColumnBinder column : m_columns) { + for (ColumnBinder column : m_columns) { if (column.isNull()) { continue; } diff --git a/bootloader/interface/static/interface.cpp b/bootloader/interface/static/interface.cpp index 02ef4d0d2e2..c0e95db7316 100644 --- a/bootloader/interface/static/interface.cpp +++ b/bootloader/interface/static/interface.cpp @@ -8,7 +8,9 @@ namespace Bootloader { -void Interface::drawImage(KDContext * ctx, const Image * image, int offset) { +void Interface::drawComputer(KDContext * ctx, int offset) { + const Image * image = ImageStore::Computer; + const uint8_t * data; size_t size; size_t pixelBufferSize; @@ -37,11 +39,15 @@ void Interface::drawImage(KDContext * ctx, const Image * image, int offset) { ctx->fillRectWithPixels(bounds, pixelBuffer, nullptr); } +KDCoordinate Interface::computerHeight() { + return ImageStore::Computer->height(); +} + void Interface::drawFlasher() { KDContext * ctx = KDIonContext::sharedContext(); ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); - drawImage(ctx, ImageStore::Computer, 25); + drawComputer(ctx, 25); KDSize fontSize = KDFont::LargeFont->glyphSize(); int initPos = (320 - fontSize.width() * strlen(Messages::mainTitle)) / 2; @@ -55,7 +61,7 @@ void Interface::drawFlasher() { void Interface::drawLoading() { KDContext * ctx = KDIonContext::sharedContext(); ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); - drawImage(ctx, ImageStore::Computer, 25); + drawComputer(ctx, 25); Ion::Timing::msleep(250); KDSize fontSize = KDFont::LargeFont->glyphSize(); int initPos = (320 - fontSize.width() * strlen(Messages::mainTitle)) / 2; diff --git a/bootloader/interface/static/interface.h b/bootloader/interface/static/interface.h index 3741b5ec17e..ab44df846d3 100644 --- a/bootloader/interface/static/interface.h +++ b/bootloader/interface/static/interface.h @@ -9,7 +9,8 @@ namespace Bootloader { class Interface { public: - static void drawImage(KDContext * ctx, const Image * image, int offset); + static void drawComputer(KDContext * ctx, int offset); + static KDCoordinate computerHeight(); static void drawLoading(); static void drawFlasher(); }; From 6a894997fc45c0f930a8d48a0fada644f1c30fda Mon Sep 17 00:00:00 2001 From: Laury Date: Tue, 3 May 2022 16:34:22 +0200 Subject: [PATCH 265/355] [flasher] Fixed compilation --- ion/src/device/shared/ram.ld | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ion/src/device/shared/ram.ld b/ion/src/device/shared/ram.ld index 6cf176697df..ef027f3e0b2 100644 --- a/ion/src/device/shared/ram.ld +++ b/ion/src/device/shared/ram.ld @@ -22,7 +22,11 @@ MEMORY { * object). Using a stack too small would result in some memory being * overwritten (for instance, vtables that live in the .rodata section). */ -STACK_SIZE = 32K; +/* The image is quite large too! + * So we put the stack to 18K so there's still space + * for our image, if not LD will throw an error. */ + +STACK_SIZE = 18K; SECTIONS { .isr_vector_table ORIGIN(RAM_BUFFER) : { From caf23dcbaba7f39a42767cd57e90ae3e1ad0df4a Mon Sep 17 00:00:00 2001 From: Laury Date: Tue, 3 May 2022 17:48:16 +0200 Subject: [PATCH 266/355] [bootloader] Fix compilation to dfu file --- build/device/elf2dfu.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/build/device/elf2dfu.py b/build/device/elf2dfu.py index 6fc0523f0f5..cb62429bd5a 100644 --- a/build/device/elf2dfu.py +++ b/build/device/elf2dfu.py @@ -39,8 +39,9 @@ def generate_dfu_file(targets, usb_vid_pid, dfu_file): data += struct.pack(' Date: Tue, 3 May 2022 18:23:40 +0200 Subject: [PATCH 267/355] [bootloader] Fix "make binpack" in the CI --- .github/workflows/ci-workflow.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 2401a47f733..5c28cd7c9e6 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -132,7 +132,8 @@ jobs: - run: make -j2 epsilon.onboarding.beta.dfu - run: make -j2 flasher.light.dfu - run: make -j2 flasher.verbose.dfu - - run: make -j2 bench.ram.dfu + # We don't need bench as it is used only in factory + # - run: make -j2 bench.ram.dfu # - run: make -j2 bench.flash.dfu - run: make -j2 binpack - run: cp output/release/device/n0110/binpack-n0110-`git rev-parse HEAD | head -c 7`.tgz output/release/device/n0110/binpack-n0110.tgz @@ -168,7 +169,7 @@ jobs: - run: make MODEL=bootloader -j2 epsilon.onboarding.update.B.dfu - run: make MODEL=bootloader -j2 epsilon.onboarding.beta.A.dfu - run: make MODEL=bootloader -j2 epsilon.onboarding.beta.B.dfu - - run: make -j2 binpack + - run: make MODEL=bootloader -j2 binpack - run: cp output/release/device/bootloader/binpack-bootloader-`git rev-parse HEAD | head -c 7`.tgz output/release/device/bootloader/binpack-bootloader.tgz - uses: actions/upload-artifact@master with: From 9f6d52d9d5f00da6c8bd257c5b3a03af0ed41394 Mon Sep 17 00:00:00 2001 From: Laury Date: Wed, 4 May 2022 15:49:11 +0200 Subject: [PATCH 268/355] [usb] Removing residues from the USB protection --- apps/global_preferences.h | 4 - apps/settings/Makefile | 1 - apps/settings/main_controller.cpp | 3 - apps/settings/main_controller.h | 2 - .../sub_menu/preferences_controller.cpp | 5 -- .../sub_menu/usb_protection_controller.cpp | 84 ------------------- .../sub_menu/usb_protection_controller.h | 30 ------- apps/usb/base.de.i18n | 5 -- apps/usb/base.en.i18n | 5 -- apps/usb/base.es.i18n | 5 -- apps/usb/base.fr.i18n | 5 -- apps/usb/base.hu.i18n | 5 -- apps/usb/base.it.i18n | 5 -- apps/usb/base.nl.i18n | 5 -- apps/usb/base.pt.i18n | 5 -- apps/usb/usb_connected_controller.cpp | 34 ++------ apps/usb/usb_connected_controller.h | 7 +- 17 files changed, 8 insertions(+), 202 deletions(-) delete mode 100644 apps/settings/sub_menu/usb_protection_controller.cpp delete mode 100644 apps/settings/sub_menu/usb_protection_controller.h diff --git a/apps/global_preferences.h b/apps/global_preferences.h index 6b73e1b775a..ab48e2c1482 100644 --- a/apps/global_preferences.h +++ b/apps/global_preferences.h @@ -32,8 +32,6 @@ class GlobalPreferences { void setShowPopUp(bool showPopUp) { m_showPopUp = showPopUp; } bool dfuUnlocked() const { return m_dfuUnlocked; } void setDfuUnlocked(bool unlocked) { m_dfuUnlocked = unlocked; } - int dfuLevel() const { return m_dfuLevel; } - void setDfuLevel(int level) { m_dfuLevel = level; } bool autocomplete() const { return m_autoComplete; } void setAutocomplete(bool autocomple) { m_autoComplete = autocomple; } bool syntaxhighlighting() const { return m_syntaxhighlighting; } @@ -65,7 +63,6 @@ class GlobalPreferences { m_tempExamMode(ExamMode::Standard), m_showPopUp(true), m_dfuUnlocked(false), - m_dfuLevel(0), m_autoComplete(true), m_syntaxhighlighting(true), m_cursorSaving(true), @@ -84,7 +81,6 @@ class GlobalPreferences { mutable ExamMode m_tempExamMode; bool m_showPopUp; bool m_dfuUnlocked; - uint8_t m_dfuLevel; bool m_autoComplete; bool m_syntaxhighlighting; bool m_cursorSaving; diff --git a/apps/settings/Makefile b/apps/settings/Makefile index ddc7a41811c..5380cec810c 100644 --- a/apps/settings/Makefile +++ b/apps/settings/Makefile @@ -25,7 +25,6 @@ app_settings_src = $(addprefix apps/settings/,\ sub_menu/contributors_controller.cpp \ sub_menu/math_options_controller.cpp \ sub_menu/selectable_view_with_messages.cpp \ - sub_menu/usb_protection_controller.cpp \ sub_menu/external_controller.cpp \ sub_menu/brightness_controller.cpp\ ) diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index 2e61b63aac5..d7a15e3a1a4 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -45,7 +45,6 @@ MainController::MainController(Responder * parentResponder, InputEventHandlerDel m_examModeController(this), m_aboutController(this), m_preferencesController(this), - m_usbInfoController(this), m_externalController(this) { for (int i = 0; i < k_numberOfSimpleChevronCells; i++) { @@ -101,8 +100,6 @@ bool MainController::handleEvent(Ion::Events::Event event) { subController = &m_dateTimeController; } else if (title == I18n::Message::MathOptions) { subController = &m_mathOptionsController; - } else if (title == I18n::Message::UsbSetting) { - subController = &m_usbInfoController; } else if (title == I18n::Message::CodeApp) { subController = &m_codeOptionsController; } else if (title == I18n::Message::ExternalApps) { diff --git a/apps/settings/main_controller.h b/apps/settings/main_controller.h index 618758643d4..3e1c5ba82d2 100644 --- a/apps/settings/main_controller.h +++ b/apps/settings/main_controller.h @@ -12,7 +12,6 @@ #include "sub_menu/localization_controller.h" #include "sub_menu/math_options_controller.h" #include "sub_menu/preferences_controller.h" -#include "sub_menu/usb_protection_controller.h" #include "sub_menu/external_controller.h" #include "sub_menu/brightness_controller.h" @@ -83,7 +82,6 @@ class MainController : public ViewController, public ListViewDataSource, public ExamModeController m_examModeController; AboutController m_aboutController; PreferencesController m_preferencesController; - UsbInfoController m_usbInfoController; ExternalController m_externalController; }; diff --git a/apps/settings/sub_menu/preferences_controller.cpp b/apps/settings/sub_menu/preferences_controller.cpp index bba0ae46bd0..06650e842e9 100644 --- a/apps/settings/sub_menu/preferences_controller.cpp +++ b/apps/settings/sub_menu/preferences_controller.cpp @@ -222,8 +222,6 @@ void PreferencesController::setPreferenceWithValueIndex(I18n::Message message, i preferences->setSymbolOfFunction((Preferences::SymbolFunction)valueIndex); } else if (message == I18n::Message::FontSizes) { GlobalPreferences::sharedGlobalPreferences()->setFont(valueIndex == 0 ? KDFont::LargeFont : KDFont::SmallFont); - } else if (message == I18n::Message::USBProtectionLevel) { - GlobalPreferences::sharedGlobalPreferences()->setDfuLevel(valueIndex); } } @@ -250,9 +248,6 @@ int PreferencesController::valueIndexForPreference(I18n::Message message) const if (message == I18n::Message::FontSizes) { return GlobalPreferences::sharedGlobalPreferences()->font() == KDFont::LargeFont ? 0 : 1; } - if (message == I18n::Message::USBProtectionLevel) { - return GlobalPreferences::sharedGlobalPreferences()->dfuLevel(); - } return 0; } diff --git a/apps/settings/sub_menu/usb_protection_controller.cpp b/apps/settings/sub_menu/usb_protection_controller.cpp deleted file mode 100644 index 08dc94f235e..00000000000 --- a/apps/settings/sub_menu/usb_protection_controller.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "usb_protection_controller.h" - -#include -#include -#include -#include -#include -#include "../../apps_container.h" -#include "../../global_preferences.h" - -using namespace Poincare; -using namespace Shared; - -namespace Settings { - -UsbInfoController::UsbInfoController(Responder *parentResponder): - GenericSubController(parentResponder), - m_usbProtectionLevelController(this), - m_contentView(&m_selectableTableView) -{ - m_switchCell.setMessageFont(KDFont::LargeFont); - m_dfuLevelCell.setMessageFont(KDFont::LargeFont); -} - -bool UsbInfoController::handleEvent(Ion::Events::Event event) { - if ((Ion::Events::OK == event || Ion::Events::EXE == event || Ion::Events::Right == event) && selectedRow() == 0) { - bool dfuWasUnlocked = GlobalPreferences::sharedGlobalPreferences()->dfuUnlocked(); - GlobalPreferences::sharedGlobalPreferences()->setDfuUnlocked(!dfuWasUnlocked); - m_selectableTableView.reloadCellAtLocation(0, 0); - return true; - } - - if ((Ion::Events::OK == event || Ion::Events::EXE == event || Ion::Events::Right == event) && selectedRow() == 1) { - GenericSubController *subController = &m_usbProtectionLevelController; - subController->setMessageTreeModel(m_messageTreeModel->childAtIndex(1)); - StackViewController *stack = stackController(); - m_lastSelect = selectedRow(); - stack->push(subController); - return true; - } - - return GenericSubController::handleEvent(event); -} - -HighlightCell *UsbInfoController::reusableCell(int index, int type) { - if (index == 0) { - return &m_switchCell; - } - assert(index == 1); - return &m_dfuLevelCell; -} - -int UsbInfoController::reusableCellCount(int type) { - assert(type == 0); - return 2; -} - -void UsbInfoController::willDisplayCellForIndex(HighlightCell *cell, int index) { - GenericSubController::willDisplayCellForIndex(cell, index); - - if (index == 0) { - MessageTableCellWithSwitch *myCell = (MessageTableCellWithSwitch *)cell; - SwitchView *mySwitch = (SwitchView *)myCell->accessoryView(); - mySwitch->setState(!GlobalPreferences::sharedGlobalPreferences()->dfuUnlocked()); - } else if (index == 1) { - MessageTableCellWithChevronAndMessage *m_cell = (MessageTableCellWithChevronAndMessage *)cell; - int currentLevel = GlobalPreferences::sharedGlobalPreferences()->dfuLevel(); - if (currentLevel == 0) { - m_cell->setSubtitle(I18n::Message::USBDefaultLevelDesc); - } else if (currentLevel == 1) {; - m_cell->setSubtitle(I18n::Message::USBLowLevelDesc); - } else { - assert(currentLevel == 2); - m_cell->setSubtitle(I18n::Message::USBParanoidLevelDesc); - } - } -} - -void UsbInfoController::didEnterResponderChain(Responder *previousFirstResponder) { - m_contentView.reload(); - I18n::Message infoMessages[] = {I18n::Message::USBExplanation1, I18n::Message::USBExplanation2, I18n::Message::USBExplanation3}; - m_contentView.setMessages(infoMessages, k_numberOfExplanationMessages); -} -} diff --git a/apps/settings/sub_menu/usb_protection_controller.h b/apps/settings/sub_menu/usb_protection_controller.h deleted file mode 100644 index ec7b097fd5f..00000000000 --- a/apps/settings/sub_menu/usb_protection_controller.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef SETTINGS_usb_protection_controller_H -#define SETTINGS_usb_protection_controller_H - -#include "generic_sub_controller.h" -#include "preferences_controller.h" -#include "selectable_view_with_messages.h" - -namespace Settings { - -class UsbInfoController : public GenericSubController { -public: - UsbInfoController(Responder* parentResponder); - View* view() override { return &m_contentView; } - bool handleEvent(Ion::Events::Event event) override; - TELEMETRY_ID("UsbInfo"); - void didEnterResponderChain(Responder* previousFirstResponder) override; - HighlightCell* reusableCell(int index, int type) override; - int reusableCellCount(int type) override; - void willDisplayCellForIndex(HighlightCell* cell, int index) override; -private: - static constexpr int k_numberOfExplanationMessages = 3; - PreferencesController m_usbProtectionLevelController; - SelectableViewWithMessages m_contentView; - MessageTableCellWithSwitch m_switchCell; - MessageTableCellWithChevronAndMessage m_dfuLevelCell; -}; - -} - -#endif diff --git a/apps/usb/base.de.i18n b/apps/usb/base.de.i18n index 50aaa3a47b6..794b4d36bd7 100644 --- a/apps/usb/base.de.i18n +++ b/apps/usb/base.de.i18n @@ -5,8 +5,3 @@ ConnectedMessage3 = "getomega.dev/ide." ConnectedMessage4 = "Drücken Sie die Zurück-Taste am" ConnectedMessage5 = "Taschenrechner oder Kabel abziehen," ConnectedMessage6 = "um die Verbindung zu trennen." -DfuStatus = "Status des Rechners:" -DfuStatusUnprotected = "UNGESCHÜTZT" -USBProtectionLevel0 = "Standardschutz" -USBProtectionLevel1 = "Omega Schutz" -USBProtectionLevel2 = "Systemschutz" diff --git a/apps/usb/base.en.i18n b/apps/usb/base.en.i18n index c18e22d02de..0eb615fa2c6 100644 --- a/apps/usb/base.en.i18n +++ b/apps/usb/base.en.i18n @@ -5,8 +5,3 @@ ConnectedMessage3 = "getomega.dev/ide" ConnectedMessage4 = "Press the BACK key of your" ConnectedMessage5 = "calculator or unplug it to" ConnectedMessage6 = "disconnect it." -DfuStatus = "Calculator status:" -DfuStatusUnprotected = "UNPROTECTED" -USBProtectionLevel0 = "Default Protection" -USBProtectionLevel1 = "Omega Protection" -USBProtectionLevel2 = "System Protection" diff --git a/apps/usb/base.es.i18n b/apps/usb/base.es.i18n index da1f59b91be..89c2aa6d602 100644 --- a/apps/usb/base.es.i18n +++ b/apps/usb/base.es.i18n @@ -5,8 +5,3 @@ ConnectedMessage3 = "getomega.dev/ide" ConnectedMessage4 = "Pulse el botón RETURN de la" ConnectedMessage5 = "calculadora o desenchúfela para" ConnectedMessage6 = "desconectarla." -DfuStatus = "Estado de la calculadora:" -DfuStatusUnprotected = "DESABRIGADO" -USBProtectionLevel0 = "Protección predeterminada" -USBProtectionLevel1 = "Protección Omega" -USBProtectionLevel2 = "Protección del sistema" diff --git a/apps/usb/base.fr.i18n b/apps/usb/base.fr.i18n index 4a0564844e8..33762ca546a 100644 --- a/apps/usb/base.fr.i18n +++ b/apps/usb/base.fr.i18n @@ -5,8 +5,3 @@ ConnectedMessage3 = "getomega.dev/ide" ConnectedMessage4 = "Appuyez sur la touche RETOUR" ConnectedMessage5 = "de la calculatrice ou débranchez-la" ConnectedMessage6 = "pour la déconnecter." -DfuStatus = "Etat de la calculatrice:" -DfuStatusUnprotected = "NON PROTÉGÉE" -USBProtectionLevel0 = "Default Protection" -USBProtectionLevel1 = "Omega Protection" -USBProtectionLevel2 = "System Protection" diff --git a/apps/usb/base.hu.i18n b/apps/usb/base.hu.i18n index 14deb4dd5cf..605aed35e2e 100644 --- a/apps/usb/base.hu.i18n +++ b/apps/usb/base.hu.i18n @@ -5,8 +5,3 @@ ConnectedMessage3 = "fel getomega.dev/ide ra." ConnectedMessage4 = "Nyomjon majd a VISSZA gombra" ConnectedMessage5 = "vagy huzza ki a kábelt azért" ConnectedMessage6 = "hogy a másolás véget érjen." -DfuStatus = "Számológép állapota:" -DfuStatusUnprotected = "VÉDTELEN" -USBProtectionLevel0 = "Alapértelmezett védelem" -USBProtectionLevel1 = "Omega védelem" -USBProtectionLevel2 = "Rendszervédelem" diff --git a/apps/usb/base.it.i18n b/apps/usb/base.it.i18n index 3e6bff48123..fdc2be0bd1c 100644 --- a/apps/usb/base.it.i18n +++ b/apps/usb/base.it.i18n @@ -5,8 +5,3 @@ ConnectedMessage3 = "getomega.dev/ide" ConnectedMessage4 = "Premere sul tasto INDIETRO della" ConnectedMessage5 = "calcolatrice o scollegatela per" ConnectedMessage6 = "disconnetterla." -DfuStatus = "Stato della calcolatrice:" -DfuStatusUnprotected = "INDIFESO" -USBProtectionLevel0 = "Protezione predefinita" -USBProtectionLevel1 = "Protezione Omega" -USBProtectionLevel2 = "Protezione del sistema" diff --git a/apps/usb/base.nl.i18n b/apps/usb/base.nl.i18n index e77a91708b2..db29c112760 100644 --- a/apps/usb/base.nl.i18n +++ b/apps/usb/base.nl.i18n @@ -5,8 +5,3 @@ ConnectedMessage3 = "getomega.dev/ide" ConnectedMessage4 = "Druk op de TERUG toets van je" ConnectedMessage5 = "rekenmachine of verwijder de" ConnectedMessage6 = " kabel om hem los te koppelen." -DfuStatus = "Rekenmachine status:" -DfuStatusUnprotected = "NIET BESCHERMD" -USBProtectionLevel0 = "Standaardbeveiliging" -USBProtectionLevel1 = "Omega Bescherming" -USBProtectionLevel2 = "Systeembeveiliging" diff --git a/apps/usb/base.pt.i18n b/apps/usb/base.pt.i18n index 5abe517f09d..408b24db7a4 100644 --- a/apps/usb/base.pt.i18n +++ b/apps/usb/base.pt.i18n @@ -5,8 +5,3 @@ ConnectedMessage3 = "getomega.dev/ide" ConnectedMessage4 = "Pressione o botão RETURN na" ConnectedMessage5 = "calculadora ou desligue-a para a" ConnectedMessage6 = "desconectar." -DfuStatus = "Status da calculadora:" -DfuStatusUnprotected = "DESPROTEGIDO" -USBProtectionLevel0 = "Proteção padrão" -USBProtectionLevel1 = "Proteção Ômega" -USBProtectionLevel2 = "Proteção do sistema" diff --git a/apps/usb/usb_connected_controller.cpp b/apps/usb/usb_connected_controller.cpp index 3592d498eaf..09f2978d28c 100644 --- a/apps/usb/usb_connected_controller.cpp +++ b/apps/usb/usb_connected_controller.cpp @@ -12,8 +12,8 @@ static constexpr I18n::Message sUSBConnectedMessages[] = { I18n::Message::BlankMessage, I18n::Message::ConnectedMessage4, I18n::Message::ConnectedMessage5, - I18n::Message::ConnectedMessage6, - I18n::Message::DfuStatus}; + I18n::Message::ConnectedMessage6 +}; static constexpr KDColor sUSBConnectedFGColors[] = { Palette::PrimaryText, @@ -34,37 +34,13 @@ USBConnectedController::USBConnectedController() : } USBConnectedController::ContentView::ContentView() { - // We set the styles of the messages - for (uint8_t i = 0; i < k_numberOfMessages; i++) { + for (uint8_t i = 0; i < k_numberOfUSBMessages; i++) { m_messageTextViews[i].setFont(i == 0 ? KDFont::LargeFont : KDFont::SmallFont); m_messageTextViews[i].setAlignment(0.5f, 0.5f); m_messageTextViews[i].setTextColor(sUSBConnectedFGColors[i]); m_messageTextViews[i].setBackgroundColor(Palette::BackgroundHard); - } - - // We set the texts of the firsts defaults messages - for (uint8_t i = 0; i < k_numberOfUSBMessages; i++) { m_messageTextViews[i].setText(I18n::translate(sUSBConnectedMessages[i])); } - - // Last message, depending of the USB protection level - if (GlobalPreferences::sharedGlobalPreferences()->dfuUnlocked()) { - m_messageTextViews[k_numberOfUSBMessages].setText(I18n::translate(I18n::Message::DfuStatusUnprotected)); - } else { - int protectionLevel = GlobalPreferences::sharedGlobalPreferences()->dfuLevel(); - switch (protectionLevel) { - case 0: - m_messageTextViews[9].setText(I18n::translate(I18n::Message::USBProtectionLevel0)); - break; - case 1: - m_messageTextViews[9].setText(I18n::translate(I18n::Message::USBProtectionLevel1)); - break; - default: - assert(protectionLevel == 2); - m_messageTextViews[9].setText(I18n::translate(I18n::Message::USBProtectionLevel2)); - break; - } - } } void USBConnectedController::ContentView::drawRect(KDContext *ctx, KDRect rect) const { @@ -72,7 +48,7 @@ void USBConnectedController::ContentView::drawRect(KDContext *ctx, KDRect rect) } View *USBConnectedController::ContentView::subviewAtIndex(int index) { - assert(index < k_numberOfMessages); + assert(index < k_numberOfUSBMessages); return &(m_messageTextViews[index]); } @@ -81,7 +57,7 @@ void USBConnectedController::ContentView::layoutSubviews(bool force) { KDCoordinate titleHeight = m_messageTextViews[0].minimalSizeForOptimalDisplay().height(); KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height(); m_messageTextViews[0].setFrame(KDRect(0, k_titleMargin, width, titleHeight), force); - for (uint8_t i = 1; i < k_numberOfMessages; i++) { + for (uint8_t i = 1; i < k_numberOfUSBMessages; i++) { m_messageTextViews[i].setFrame(KDRect(0, k_paragraphHeight + (i - 1) * textHeight, width, textHeight), force); } } diff --git a/apps/usb/usb_connected_controller.h b/apps/usb/usb_connected_controller.h index c0b99152158..d6e3de3eb7a 100644 --- a/apps/usb/usb_connected_controller.h +++ b/apps/usb/usb_connected_controller.h @@ -16,15 +16,14 @@ class USBConnectedController : public ViewController { ContentView(); void drawRect(KDContext * ctx, KDRect rect) const override; protected: - int numberOfSubviews() const override { return k_numberOfMessages; } + int numberOfSubviews() const override { return k_numberOfUSBMessages; } View * subviewAtIndex(int index) override; void layoutSubviews(bool force = false) override; private: constexpr static KDCoordinate k_titleMargin = 30; constexpr static KDCoordinate k_paragraphHeight = 80; - constexpr static uint8_t k_numberOfUSBMessages = 9; - constexpr static uint8_t k_numberOfMessages = k_numberOfUSBMessages + 1; - MessageTextView m_messageTextViews[k_numberOfMessages]; + constexpr static uint8_t k_numberOfUSBMessages = 8; + MessageTextView m_messageTextViews[k_numberOfUSBMessages]; }; ContentView m_contentView; }; From 7a2ee2bc5637d6aafcd1f49a4d53ba646a06a510 Mon Sep 17 00:00:00 2001 From: Laury Date: Wed, 4 May 2022 15:49:24 +0200 Subject: [PATCH 269/355] [config] Set upsilon version to 1.0.0 --- build/config.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/config.mak b/build/config.mak index 9d6c999a99a..3b6ce62f621 100644 --- a/build/config.mak +++ b/build/config.mak @@ -6,7 +6,7 @@ DEBUG ?= 0 HOME_DISPLAY_EXTERNALS ?= 1 EPSILON_VERSION ?= 15.5.0 OMEGA_VERSION ?= 2.0.0 -UPSILON_VERSION ?= 1.0.0-dev +UPSILON_VERSION ?= 1.0.0 # OMEGA_USERNAME ?= N/A OMEGA_STATE ?= public EPSILON_APPS ?= calculation graph rpn code statistics probability solver atomic sequence regression reader settings external From 7c51b1e55d2f1925b1bec020f6a941703c1db78b Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Thu, 5 May 2022 17:55:19 +0200 Subject: [PATCH 270/355] [apps/code] Fix fr" typing in editor Fix #217 --- apps/code/python_text_area.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/code/python_text_area.cpp b/apps/code/python_text_area.cpp index 4bfb0fd86c4..788753f968a 100644 --- a/apps/code/python_text_area.cpp +++ b/apps/code/python_text_area.cpp @@ -162,7 +162,7 @@ PythonTextArea::AutocompletionType PythonTextArea::autocompletionType(const char const char * tokenEnd; _mp_token_kind_t currentTokenKind = lex->tok_kind; - while (currentTokenKind != MP_TOKEN_NEWLINE && currentTokenKind != MP_TOKEN_END) { + while (currentTokenKind != MP_TOKEN_NEWLINE && currentTokenKind != MP_TOKEN_END && currentTokenKind != MP_TOKEN_FSTRING_RAW) { tokenStart = firstNonSpace + lex->tok_column - 1; tokenEnd = tokenStart + TokenLength(lex, tokenStart); @@ -271,7 +271,7 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char const char * tokenFrom = firstNonSpace; size_t tokenLength = 0; const char * tokenEnd = firstNonSpace; - while (lex->tok_kind != MP_TOKEN_NEWLINE && lex->tok_kind != MP_TOKEN_END) { + while (lex->tok_kind != MP_TOKEN_NEWLINE && lex->tok_kind != MP_TOKEN_END && lex->tok_kind != MP_TOKEN_FSTRING_RAW) { tokenFrom = firstNonSpace + lex->tok_column - 1; if (tokenFrom != tokenEnd) { // We passed over white spaces, we need to color them From 5be8d6a1f5ce90c4bb1ef1cf0e49cb556538309e Mon Sep 17 00:00:00 2001 From: LeMoustachu Date: Sat, 7 May 2022 20:39:53 +0200 Subject: [PATCH 271/355] [minor changes] Just Update issues badge to work with new repo URL (#222) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5cc9ed4a581..754001cc970 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

    cc by-nc-sa 4.0 - Issues + Issues
    Discord

    From 6758a95292557a22bf36e37318d5ad6fafbb0f1e Mon Sep 17 00:00:00 2001 From: iamlambda <77165319+iamlambda@users.noreply.github.com> Date: Sat, 7 May 2022 20:40:23 +0200 Subject: [PATCH 272/355] misleading comment (#216) --- ion/src/device/shared/drivers/exam_mode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/shared/drivers/exam_mode.cpp b/ion/src/device/shared/drivers/exam_mode.cpp index 51fd7be06f2..121a9a15c23 100644 --- a/ion/src/device/shared/drivers/exam_mode.cpp +++ b/ion/src/device/shared/drivers/exam_mode.cpp @@ -34,7 +34,7 @@ char ones[Config::ExamModeBufferSize] constexpr static size_t numberOfBitsInByte = 8; -// if i = 0b000011101, firstOneBitInByte(i) returns 5 +// if i = 0b000011101, numberOfBitsAfterLeadingZeroes(i) returns 5 size_t numberOfBitsAfterLeadingZeroes(int i) { int minShift = 0; int maxShift = numberOfBitsInByte; From 36fa4a4152cf9dbe9cbb4bcf56cf5d40e3877302 Mon Sep 17 00:00:00 2001 From: Laury Date: Mon, 9 May 2022 18:39:02 +0200 Subject: [PATCH 273/355] Revert "[storage] Possibility to store metadata with records (cursor in scripts)" --- apps/code/editor_controller.cpp | 8 +- apps/code/script.cpp | 15 --- apps/code/script.h | 2 - ion/include/ion/internal_storage.h | 24 +---- ion/include/ion/storage.h | 1 - ion/src/shared/internal_storage.cpp | 152 ++-------------------------- ion/src/shared/storage.cpp | 2 +- 7 files changed, 9 insertions(+), 195 deletions(-) diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index d286b92cf2f..be09c9662b8 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -65,19 +65,13 @@ void EditorController::viewWillAppear() { ViewController::viewWillAppear(); m_editorView.loadSyntaxHighlighter(); if(GlobalPreferences::sharedGlobalPreferences()->cursorSaving()) { - int offset = m_script.cursorOffset(); - if (offset != -1) { - m_editorView.setCursorLocation(m_editorView.text() + offset); - } else { - m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); - } + m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); } else { m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); } } void EditorController::viewDidDisappear() { - m_script.setCursorOffset(m_editorView.cursorLocation() - m_script.content()); m_editorView.resetSelection(); m_menuController->scriptContentEditionDidFinish(); } diff --git a/apps/code/script.cpp b/apps/code/script.cpp index 3b73d54bd7e..4b39b345281 100644 --- a/apps/code/script.cpp +++ b/apps/code/script.cpp @@ -65,21 +65,6 @@ bool Script::nameCompliant(const char * name) { return false; } -uint16_t Script::cursorOffset() { - assert(!isNull()); - Ion::Storage::MetadataRowHeader * metadataForRecord = Ion::Storage::sharedStorage()->metadataForRecord(*this); - if (metadataForRecord != nullptr) { - assert(metadataForRecord->metadataSize == 2); - return *((uint16_t*) metadataForRecord->data()); - } - - return -1; -} -void Script::setCursorOffset(uint16_t position) { - assert(!isNull()); - Ion::Storage::sharedStorage()->setMetadataForRecord(*this, sizeof(uint16_t), &position); -} - uint8_t * StatusFromData(Script::Data d) { return const_cast(static_cast(d.buffer)); } diff --git a/apps/code/script.h b/apps/code/script.h index ffa8038bc14..6f7df9cdae7 100644 --- a/apps/code/script.h +++ b/apps/code/script.h @@ -50,8 +50,6 @@ class Script : public Ion::Storage::Record { void toggleAutoimportationStatus(); const char * content() const; size_t contentSize() { return value().size - k_statusSize; } - void setCursorOffset(uint16_t position); // -1 if no metadata - uint16_t cursorOffset(); /* Fetched status */ bool fetchedFromConsole() const; diff --git a/ion/include/ion/internal_storage.h b/ion/include/ion/internal_storage.h index c85282df233..257d243ce3c 100644 --- a/ion/include/ion/internal_storage.h +++ b/ion/include/ion/internal_storage.h @@ -64,7 +64,6 @@ class InternalStorage { return m_fullNameCRC32 == 0; } const char * fullName() const; - uint32_t fullNameCRC32() const { return m_fullNameCRC32; } ErrorStatus setBaseNameWithExtension(const char * baseName, const char * extension); ErrorStatus setName(const char * fullName); Data value() const; @@ -75,19 +74,11 @@ class InternalStorage { uint32_t m_fullNameCRC32; }; - struct MetadataRowHeader { // In fact, it's a struct with a method to get data - public: - char * data() const { return (char *) this + sizeof(MetadataRowHeader); } - uint32_t size() const { return sizeof(MetadataRowHeader) + metadataSize; } - uint32_t fullNameCRC32; - uint32_t metadataSize; // To fullfill alignment - MetadataRowHeader * nextRow; - }; #if ION_STORAGE_LOG void log(); #endif - size_t availableSize(char ** endBufferReturn = nullptr); + size_t availableSize(); size_t putAvailableSpaceAtEndOfRecord(Record r); void getAvailableSpaceFromEndOfRecord(Record r, size_t recordAvailableSpace); uint32_t checksum(); @@ -123,11 +114,6 @@ class InternalStorage { // Used by Python OS module int numberOfRecords(); Record recordAtIndex(int index); - - // Metadata - MetadataRowHeader * metadataForRecord(Record r); - bool setMetadataForRecord(Record r, int size, const void * metadata); - void removeMetadataForRecord(Record r); protected: InternalStorage(); /* Getters on address in buffer */ @@ -137,13 +123,6 @@ class InternalStorage { const void * valueOfRecordStarting(char * start) const; void destroyRecord(const Record record); - struct MetadataMapHeader { - char * startOfMetadataMap() { return (char *) this - metadataMapSize + sizeof(MetadataMapHeader); } - uint8_t metadataMapSize; - uint8_t numberOfRows; - MetadataRowHeader * firstRow; - }; - class RecordIterator { public: RecordIterator(char * start) : m_recordStart(start) {} @@ -196,7 +175,6 @@ class InternalStorage { protected: mutable Record m_lastRecordRetrieved; mutable char * m_lastRecordRetrievedPointer; - MetadataMapHeader * m_metadataMapHeader; }; /* Some apps memoize records and need to be notified when a record might have diff --git a/ion/include/ion/storage.h b/ion/include/ion/storage.h index 300e77d39e5..5385f4608d3 100644 --- a/ion/include/ion/storage.h +++ b/ion/include/ion/storage.h @@ -8,7 +8,6 @@ namespace Ion { class Storage : public InternalStorage { public: using InternalStorage::Record; - using InternalStorage::MetadataRowHeader; using InternalStorage::expExtension; using InternalStorage::funcExtension; diff --git a/ion/src/shared/internal_storage.cpp b/ion/src/shared/internal_storage.cpp index eade3a611b8..5fc4a90d10a 100644 --- a/ion/src/shared/internal_storage.cpp +++ b/ion/src/shared/internal_storage.cpp @@ -112,138 +112,11 @@ void InternalStorage::log() { } #endif -InternalStorage::MetadataRowHeader * InternalStorage::metadataForRecord(Record r) { - MetadataRowHeader * header = m_metadataMapHeader->firstRow; - for (int i = 0; i < m_metadataMapHeader->numberOfRows; i++) { - if (header->fullNameCRC32 == r.fullNameCRC32()) { - return header; - } - header = header->nextRow; - } - - return nullptr; -} - -void InternalStorage::removeMetadataForRecord(Record r) { - if (r.isNull()) { - return; - } - - MetadataRowHeader ** rowPointer = &m_metadataMapHeader->firstRow; - MetadataRowHeader * headerToRemove = nullptr; - size_t headerToRemoveSize = 0; // We compute it now as it will be more difficult later - for (int i = 0; i < m_metadataMapHeader->numberOfRows; i++) { - if ((*rowPointer)->fullNameCRC32 == r.fullNameCRC32()) { - headerToRemove = *rowPointer; - headerToRemoveSize = headerToRemove->size(); - if ((*rowPointer)->nextRow != nullptr) { - *rowPointer = (MetadataRowHeader *) ((char *) (*rowPointer)->nextRow + headerToRemove->size()); - } else { - *rowPointer = nullptr; - } - break; - } - - rowPointer = &(*rowPointer)->nextRow; - } - - if (headerToRemove == nullptr) { - return; - } - - MetadataRowHeader * header = headerToRemove->nextRow; - if (header != nullptr) { - while (header->nextRow) { - MetadataRowHeader * nextRow = header->nextRow; - header->nextRow = (MetadataRowHeader *) ((char *) header->nextRow +headerToRemoveSize); - header = nextRow; - } - } - - char * startOfMetadataMap = m_metadataMapHeader->startOfMetadataMap(); - uint8_t sizeToMove = (char *) headerToRemove - startOfMetadataMap; - - memmove(startOfMetadataMap + headerToRemoveSize, startOfMetadataMap, sizeToMove); - m_metadataMapHeader->numberOfRows--; - m_metadataMapHeader->metadataMapSize -= headerToRemoveSize; -} - -bool InternalStorage::setMetadataForRecord(Record r, int size, const void * metadata) { - int neededSize = 0; - char * endBufferPointer = nullptr; - MetadataRowHeader * headerToUpdate = nullptr; - MetadataRowHeader ** headerToUpdatePointer = nullptr; - int headerToUpdateIndex = -1; - - // We find the metadata row header for this record - MetadataRowHeader ** headerPointer = &m_metadataMapHeader->firstRow; - for (int i = 0; i < m_metadataMapHeader->numberOfRows; i++) { - if ((*headerPointer)->fullNameCRC32 == r.fullNameCRC32()) { - neededSize = size - (*headerPointer)->metadataSize; - headerToUpdate = (*headerPointer); - headerToUpdatePointer = headerPointer; - headerToUpdateIndex = i; - if (neededSize > 0 && neededSize > availableSize(&endBufferPointer)) { - return false; - } - break; - } - - headerPointer = &((*headerPointer)->nextRow); - } - - char * startOfMetadataMap = m_metadataMapHeader->startOfMetadataMap(); // Me must compute it now because it may change - - if (headerToUpdate == nullptr) { // If we didn't find a header, we need to create one - if (size != 0) { - uint8_t newRowSize = sizeof(MetadataRowHeader) + size; - - if (endBufferPointer < m_buffer + k_storageSize - m_metadataMapHeader->metadataMapSize - newRowSize) { - m_metadataMapHeader->numberOfRows++; - m_metadataMapHeader->metadataMapSize += newRowSize; - headerToUpdate = (MetadataRowHeader *) ((char *) startOfMetadataMap - newRowSize); - headerToUpdate->fullNameCRC32 = r.fullNameCRC32(); - headerToUpdate->nextRow = nullptr; - - if (m_metadataMapHeader->numberOfRows == 0) { - m_metadataMapHeader->firstRow = headerToUpdate; - } else { - ((MetadataRowHeader *) startOfMetadataMap)->nextRow = headerToUpdate; - } - - } else { - return false; - } - } else { - return true; - } - } - - if (neededSize != 0) { // If we must move some data to make or fill empty space - m_metadataMapHeader->metadataMapSize += neededSize; - memmove(startOfMetadataMap - neededSize, startOfMetadataMap, (char *) headerToUpdate + sizeof(MetadataRowHeader) - startOfMetadataMap); - - headerToUpdate = (MetadataRowHeader *) ((char *) headerToUpdate - neededSize); - MetadataRowHeader ** headerAfterPointer = headerToUpdatePointer; // Now we update each header below the one we just updated - for (int i = headerToUpdateIndex; i < m_metadataMapHeader->numberOfRows; i++) { - (*headerAfterPointer) = (MetadataRowHeader *) ((char *) (*headerAfterPointer) - neededSize); - headerAfterPointer = &((*headerAfterPointer)->nextRow); - } - } - - headerToUpdate->metadataSize = size; - memcpy(headerToUpdate->data(), metadata, size); - - return true; -} - -size_t InternalStorage::availableSize(char ** endBufferReturn) { - char * endBufferPointer = endBuffer(); - if (endBufferReturn) { - *endBufferReturn = endBufferPointer; - } - assert(k_storageSize >= (endBufferPointer - m_buffer) + sizeof(record_size_t) + m_metadataMapHeader->metadataMapSize); - return k_storageSize-(endBufferPointer-m_buffer)-sizeof(record_size_t) - m_metadataMapHeader->metadataMapSize; +size_t InternalStorage::availableSize() { + /* TODO maybe do: availableSize(char ** endBuffer) to get the endBuffer if it + * is needed after calling availableSize */ + assert(k_storageSize >= (endBuffer() - m_buffer) + sizeof(record_size_t)); + return k_storageSize-(endBuffer()-m_buffer)-sizeof(record_size_t); } size_t InternalStorage::putAvailableSpaceAtEndOfRecord(InternalStorage::Record r) { @@ -253,7 +126,7 @@ size_t InternalStorage::putAvailableSpaceAtEndOfRecord(InternalStorage::Record r char * nextRecord = p + previousRecordSize; memmove(nextRecord + availableStorageSize, nextRecord, - (m_buffer + k_storageSize - m_metadataMapHeader->metadataMapSize - availableStorageSize) - nextRecord); + (m_buffer + k_storageSize - availableStorageSize) - nextRecord); size_t newRecordSize = previousRecordSize + availableStorageSize; overrideSizeAtPosition(p, (record_size_t)newRecordSize); return newRecordSize; @@ -475,12 +348,6 @@ InternalStorage::InternalStorage() : assert(m_magicFooter == Magic); // Set the size of the first record to 0 overrideSizeAtPosition(m_buffer, 0); - - // Set the metadata map header at the end of the buffer - m_metadataMapHeader = (MetadataMapHeader*) (m_buffer + k_storageSize - sizeof(MetadataMapHeader)); - m_metadataMapHeader->numberOfRows = 0; - m_metadataMapHeader->firstRow = nullptr; - m_metadataMapHeader->metadataMapSize = sizeof(MetadataMapHeader); } // PRIVATE @@ -514,11 +381,6 @@ InternalStorage::Record::ErrorStatus InternalStorage::setFullNameOfRecord(const notifyChangeToDelegate(record); m_lastRecordRetrieved = record; m_lastRecordRetrievedPointer = p; - // Update metadata map - MetadataRowHeader * row = metadataForRecord(record); - if (row != nullptr) { - row->fullNameCRC32 = Record(fullName).fullNameCRC32(); - } return Record::ErrorStatus::None; } return Record::ErrorStatus::RecordDoesNotExist; @@ -590,8 +452,6 @@ void InternalStorage::destroyRecord(Record record) { } char * p = pointerOfRecord(record); if (p != nullptr) { - // Erase metadata - InternalStorage::removeMetadataForRecord(record); record_size_t previousRecordSize = sizeOfRecordStarting(p); slideBuffer(p+previousRecordSize, -previousRecordSize); notifyChangeToDelegate(); diff --git a/ion/src/shared/storage.cpp b/ion/src/shared/storage.cpp index 3a90f51adb9..50a8a4c7007 100644 --- a/ion/src/shared/storage.cpp +++ b/ion/src/shared/storage.cpp @@ -21,7 +21,7 @@ size_t Storage::availableSize() { bufferSize += sizeOfRecordStarting(p); } } - return k_storageSize-bufferSize-sizeof(record_size_t)-InternalStorage::m_metadataMapHeader->metadataMapSize; + return k_storageSize-bufferSize-sizeof(record_size_t); } else { return InternalStorage::availableSize(); } From be3c8fce9c76c1ac1701c4a2af950edb18be0e7c Mon Sep 17 00:00:00 2001 From: emanuel <76693837+emsquid@users.noreply.github.com> Date: Tue, 10 May 2022 18:06:46 +0200 Subject: [PATCH 274/355] Update config.mak (#230) [build/config.mak] Set OMEGA_VERSION to 2.0.2 --- build/config.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/config.mak b/build/config.mak index 3b6ce62f621..a912bd170f2 100644 --- a/build/config.mak +++ b/build/config.mak @@ -5,7 +5,7 @@ DEBUG ?= 0 HOME_DISPLAY_EXTERNALS ?= 1 EPSILON_VERSION ?= 15.5.0 -OMEGA_VERSION ?= 2.0.0 +OMEGA_VERSION ?= 2.0.2 UPSILON_VERSION ?= 1.0.0 # OMEGA_USERNAME ?= N/A OMEGA_STATE ?= public From 2ee7b67af16c39b239832c5f61eae4abe466a8fa Mon Sep 17 00:00:00 2001 From: Andrej Date: Wed, 11 May 2022 20:27:12 +0200 Subject: [PATCH 275/355] [bootloader/menu] Fix typo (#231) --- bootloader/interface/src/menu.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bootloader/interface/src/menu.h b/bootloader/interface/src/menu.h index ba8bae77c7f..0e88ba7c6e0 100644 --- a/bootloader/interface/src/menu.h +++ b/bootloader/interface/src/menu.h @@ -9,10 +9,10 @@ namespace Bootloader { class Menu { public: Menu() : Menu(KDColorBlack, KDColorWhite, Messages::mainTitle) { }; - Menu(KDColor forground, KDColor background, const char * title) : Menu(forground, background, title, nullptr) {}; - Menu(KDColor forground, KDColor background, const char * title, const char * bottom) : Menu(forground, background, title, bottom, false) {}; - Menu(KDColor forground, KDColor background, const char * title, const char * bottom, bool centerY) : Menu(forground, background, title, bottom, centerY, k_columns_margin) {}; - Menu(KDColor forground, KDColor background, const char * title, const char * bottom, bool centerY, int margin) : m_columns(), m_defaultColumns(), m_slotColumns(), m_background(background), m_title(title), m_foreground(forground), m_bottom(bottom), m_centerY(centerY), m_forced_exit(false), m_margin(margin) { + Menu(KDColor foreground, KDColor background, const char * title) : Menu(foreground, background, title, nullptr) {}; + Menu(KDColor foreground, KDColor background, const char * title, const char * bottom) : Menu(foreground, background, title, bottom, false) {}; + Menu(KDColor foreground, KDColor background, const char * title, const char * bottom, bool centerY) : Menu(foreground, background, title, bottom, centerY, k_columns_margin) {}; + Menu(KDColor foreground, KDColor background, const char * title, const char * bottom, bool centerY, int margin) : m_columns(), m_defaultColumns(), m_slotColumns(), m_background(background), m_title(title), m_foreground(foreground), m_bottom(bottom), m_centerY(centerY), m_forced_exit(false), m_margin(margin) { setup(); } static const int k_columns_margin = 5; From 2a6d110f2fbaf71cd3ccd10dbfdb5a4f19174139 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Wed, 18 May 2022 13:02:41 +0200 Subject: [PATCH 276/355] Change Upsilon version from 1.0.0 to 1.0.1-dev --- build/config.mak | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/config.mak b/build/config.mak index a912bd170f2..cf91239fece 100644 --- a/build/config.mak +++ b/build/config.mak @@ -6,9 +6,9 @@ DEBUG ?= 0 HOME_DISPLAY_EXTERNALS ?= 1 EPSILON_VERSION ?= 15.5.0 OMEGA_VERSION ?= 2.0.2 -UPSILON_VERSION ?= 1.0.0 +UPSILON_VERSION ?= 1.0.1-dev # OMEGA_USERNAME ?= N/A -OMEGA_STATE ?= public +OMEGA_STATE ?= dev EPSILON_APPS ?= calculation graph rpn code statistics probability solver atomic sequence regression reader settings external SUBMODULES_APPS = atomic rpn EPSILON_I18N ?= en fr nl pt it de es hu From e4f0762db3d865f697520575577e52e9661772c4 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Fri, 20 May 2022 19:07:50 +0200 Subject: [PATCH 277/355] [README/build] Add bootloader in README --- README.fr.md | 107 +++++++++++++++++++++++++++---------- README.md | 108 +++++++++++++++++++++++++++----------- build/platform.device.mak | 2 +- 3 files changed, 156 insertions(+), 61 deletions(-) diff --git a/README.fr.md b/README.fr.md index 31850ab0691..ff192e180a8 100644 --- a/README.fr.md +++ b/README.fr.md @@ -2,7 +2,7 @@

    cc by-nc-sa 4.0 - Issues + Issues
    Discord

    @@ -14,6 +14,7 @@ Upsilon est un fork d'Omega, un fork d'Epsilon, l'OS de Numworks tournant sur les calculatrices du même nom, qui apporte beaucoup de fonctionnalités en plus, mais qui fut archivé et fermé pour des raisons légales après un changement de politique de Numworks. Upsilon est fait pour ceux qui aimeraient voir un futur pour les OS créées par les utilisateurs pour Numworks, même après l'arrèt du projet initial. ### Quelques fonctionnalités supplémentaires + - Un module python kandinsky amélioré - Un support pour fonds d'écrans personnalisés - Des applications externes @@ -26,6 +27,7 @@ Upsilon est un fork d'Omega, un fork d'Epsilon, l'OS de Numworks tournant sur le ## Installation ### Site web + Rendez-vous sur le [site d'Upsilon](https://getupsilon.web.app/) à la section "Installer". Si votre calculatrice est reconnue, qu'elle contient une version d'Epsilon inférieure à 16 et que votre navigateur accepte WebUSB, la page vous proposera d'installer Upsilon. Ne débranchez votre calculatrice qu'une fois l'installation terminée. @@ -34,8 +36,6 @@ Ne débranchez votre calculatrice qu'une fois l'installation terminée. *Vous pouvez vous référer à ce [site internet](https://www.numworks.com/resources/engineering/software/build/)pour la première étape si vous avez des erreurs* - - ### 1. Installation du SDK
    @@ -46,7 +46,6 @@ Ne débranchez votre calculatrice qu'une fois l'installation terminée.
    -
    Debian ou Ubuntu @@ -102,9 +101,11 @@ dnf install arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++
    Il est recommandé d'utiliser [Homebrew](https://brew.sh/). Une fois intsallé, utilisez: + ```bash brew install numworks/tap/epsilon-sdk ``` + Et toutes les dependances seront installées.
    @@ -139,6 +140,7 @@ Ensuite, vous devrez installer [GCC toolchain for ARM](https://developer.arm.com ```bash echo "export PATH=$PATH:$HOME/gcc-arm/bin" >> .bashrc ``` + Redémarrez votre terminal et vous pouvez aller à l'étape 2!
    @@ -154,14 +156,17 @@ Votre version de windows doit être >= 1903. #### Installation de WSL 1. Apuyez simulatanément sur les touches "windows" et "x" puis cliquez sur "Powershell administrateur". Entrez ensuite ceci dans la nouvelle fenêtre: + ```powershell dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux all /norestart ``` + Cette commande active WSL ```powershell dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart ``` + Cette commande permet d'autoriser le démarrage des machines signées par Microsoft. 2. Redémarrez votre ordinateur. @@ -169,42 +174,56 @@ Cette commande permet d'autoriser le démarrage des machines signées par Micros 3. Téléchargez ce fichier [this file](https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi) et suivez les instructions d'installation. 4. Ouvrez votre fenêtre powershell comme avant et tapez: + ```powershell wsl --set-default-version 2 ``` + 5. téléchargez [Ubuntu](https://www.microsoft.com/store/apps/9n6svws3rx71) depuis le Microsoft store. Vous pouvez aussi installer [Debian](https://www.microsoft.com/store/productI9MSVKQC78PK6). WSL est maintenant installé. ### Installation d'usbipd pour connecter la calculatrice à WSL (facultatif) + Pour connecter la calculatrice, il faut installer cet [outil](https://github.com/dorssel/usbipd-win/releases/download/v1.3.0/usbipd-win_1.3.0.msi). Il permet de connecter deperiphériques USpar internet.Suivez les instructions pour installer. #### Ubuntu + 1. Dans un terminal WSL Ubuntu, tapez: + ```bash sudo apt install linux-tools-5.4.0-77-generic hwdata ``` + 2. Editez /etc/sudoers pour que l'on puisse utiliser la commande usbip. Sur Ubutu, cele est fait de cette manière: + ```bash sudo visudo ``` + 3. Ajoutez `/usr/lib/linux-tools/5.4.0-77-generic` au début du secure_path. Après édition, la ligne devrait ressembler à: `Defaults secure_path="/usr/lib/linux-tools/5.4.0-77-generic:/usr/local/sbin:..."` #### Debian + 1.Si vous utiliser Debian, utilisez cette commande: + ```bash sudo apt install usbip hwdata usbutils ``` ### Pour connecter la calculatrice à WSL + 1. Ouvrez encore un powershell en mode administrateur et tapez: + ```powershell usbipd wsl list ``` + Ceci va lister les périphériques USB connectés à l'ordinateur. Reagrdez le BUSID de votre "Numworks Calculator". 2. Maintenant, lancez cette commande en remplçant par celui de votre caculatrice: + ```powershell usbipd wsl attach --busid ``` @@ -221,20 +240,18 @@ Vous pouvez aller à l'étape 2. ### 2. Récupérer le code source - Le code source est disponible dans une repository git. Récupérez-le de cette manière: ```bash -git clone --recursive https://github.com/Lauryy06/Upsilon.git +git clone --recursive https://github.com/UpsilonNumworks/Upsilon.git cd Upsilon git checkout upsilon-dev ``` -
    +
    ### 3. Choisissez le système à compiler -
    Model n0100 @@ -251,6 +268,7 @@ Maintenant, lancez soit: ```bash make MODEL=n0100 epsilon_flash ``` + pour directement flasher la calculatrice après avoir appuyé simultanément sur `reset` et `6` et avoir branché la calculatrice à l'ordinateur.
    @@ -260,13 +278,14 @@ soit: ```bash make MODEL=n0100 OMEGA_USERNAME="" binpack -j4 ``` + pour compiler les binpacks que vous pouvez distribuer et flasher depuis le [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/).
    -Model n0110 +Model bootloader (N0110) ```bash make clean @@ -276,9 +295,10 @@ make OMEGA_USERNAME="{Votre nom, maximum 15 caractères}" -j4 Maintenant, lancez soit: ```bash -make epsilon_flash +make epsilon.A_flash ``` -pour directement flasher la calculatrice après avoir appuyé simultanément sur `reset` et `6` et avoir branché la calculatrice à l'ordinateur. + +pour directement flasher la calculatrice, ou avec le flasher de slots du bootloader avec RESET, puis 4 (menu de flash), et 1 (flash des slots).
    @@ -287,6 +307,34 @@ soit: ```bash make OMEGA_USERNAME="" binpack -j4 ``` + +pour compiler les binpacks que vous pouvez distribuer et flasher depuis le [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). + +
    + +Model n0110 (obsolète, utilisez le bootloader à la place, pas de protection contre Epsilon) + +```bash +make MODEL=n0110 clean +make MODEL=n0110 OMEGA_USERNAME="{Votre nom, maximum 15 caractères}" -j4 +``` + +Maintenant, lancez soit: + +```bash +make MODEL=n0110 epsilon_flash +``` + +pour directement flasher la calculatrice après avoir appuyé simultanément sur `reset` et `6` et avoir branché la calculatrice à l'ordinateur. + +
    + +soit: + +```bash +make MODEL=n0110 OMEGA_USERNAME="" binpack -j4 +``` + pour compiler les binpacks que vous pouvez distribuer et flasher depuis le [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/).
    @@ -300,8 +348,8 @@ D'abord, installez emsdk : ```bash git clone https://github.com/emscripten-core/emsdk.git cd emsdk -./emsdk install latest-fastcomp -./emsdk activate latest-fastcomp +./emsdk install 1.40.1 +./emsdk activate 1.40.1 source emsdk_env.sh ``` @@ -314,7 +362,6 @@ make PLATFORM=simulator TARGET=web OMEGA_USERNAME="{Votre nom, maximum 15 caract Le simulateur se trouve dans `output/release/simulator/web/simulator.zip` -
    @@ -324,11 +371,12 @@ Le simulateur se trouve dans `output/release/simulator/web/simulator.zip` Il vous faut devkitPro et devkitARM installés et dans votre path (les instructions sont [ici](https://devkitpro.org/wiki/Getting_Started)) ```bash -git clone --recursive https://github.com/Lauryy06/Upsilon.git +git clone --recursive https://github.com/UpsilonNumworks/Upsilon.git cd Upsilon git checkout --recursive upsilon-dev make PLATFORM=simulator TARGET=3ds -j ``` + Vous pouvez ensuite mettre epsilon.3dsx sur une carte SDpour le lancer depuis le HBC ou utilisez 3dslink pour le lancer via le réseau: ```bash @@ -343,14 +391,15 @@ Important: n'oubliez pas l'argument `--recursive` Parce qu'Upsilon dépend de su Aussi, vous pouvez changer le nombre de processus de compilation en parallèles en changeant le nombre après l'argument `-j`. N'oubliez pas de mettre votre nom à la place `{Votre nom, maximum 15 caractères}`.Si vous n'en voulez pas, enlevez l'argument `OMEGA_USERNAME`. -Si vous avez besoin d'aide, n'hésitez pas à rejoindre notre serveur discord : https://discord.gg/Q9buEMduXG +Si vous avez besoin d'aide, n'hésitez pas à rejoindre notre serveur discord :

    Omega Banner Discord

    --- ## Liens utiles -* [Upsilon external (pour installer des applications supplémentaires et des fonds d'écran)](https://lauryy06.github.io/Upsilon-External/) -* [Documentation d'ulab](https://micropython-ulab.readthedocs.io/en/latest/) + +- [Upsilon external (pour installer des applications supplémentaires et des fonds d'écran)](https://upsilonnumworks.github.io/Upsilon-External/) +- [Documentation d'ulab](https://micropython-ulab.readthedocs.io/en/latest/) ## Contribution @@ -360,14 +409,14 @@ Pour contribuer, merci de lire le [Wiki d'Omega](https://github.com/Omega-Numwor Les anciens projets d'Omega, avant sa fermeture, qui ont été utilisés pour ce projet -* [Omega Themes](https://github.com/Omega-Numworks/Omega-Themes) -* [Omega Website](https://github.com/Omega-Numworks/Omega-Website) -* [Omega RPN `APP`](https://github.com/Omega-Numworks/Omega-RPN) -* [Omega Atomic `APP`](https://github.com/Omega-Numworks/Omega-Atomic) -* [Omega Design](https://github.com/Omega-Numworks/Omega-Design) -* [Omega Discord Bot](https://github.com/Omega-Numworks/Omega-Discord-Bot) -* [Omega App Template `BETA`](https://github.com/Omega-Numworks/Omega-App-Template) -* [External Apps](https://github.com/Omega-Numworks/External-Apps) +- [Omega Themes](https://github.com/Omega-Numworks/Omega-Themes) +- [Omega Website](https://github.com/Omega-Numworks/Omega-Website) +- [Omega RPN `APP`](https://github.com/Omega-Numworks/Omega-RPN) +- [Omega Atomic `APP`](https://github.com/Omega-Numworks/Omega-Atomic) +- [Omega Design](https://github.com/Omega-Numworks/Omega-Design) +- [Omega Discord Bot](https://github.com/Omega-Numworks/Omega-Discord-Bot) +- [Omega App Template `BETA`](https://github.com/Omega-Numworks/Omega-App-Template) +- [External Apps](https://github.com/Omega-Numworks/External-Apps) ## À propos d'Epsilon @@ -383,6 +432,6 @@ NumWorks est une marque déposée de NumWorks SAS, 24 Rue Godot de Mauroy, 75009 Nintendo est Nintendo 3DS sont des marques déposées de Nintendo of America Inc, 4600 150th Ave NE, Redmond, WA 98052, Etats-Unis. NumWorks SAS et Nintendo of America Inc ne sont en aucun cas associés avec ce projet. -* NumWorks Epsilon est disponible sous [Lisense CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). -* Omega est disponible sous [Lisense CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). -* Upsilon est disponible sous [Lisense CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). +- NumWorks Epsilon est disponible sous [Lisense CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). +- Omega est disponible sous [Lisense CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). +- Upsilon est disponible sous [Lisense CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). diff --git a/README.md b/README.md index 754001cc970..09a5d7edeca 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,8 @@ Upsilon is a fork of Omega, an user-made OS that runs on the Numworks calculator, which brings many features to it, but was discontinued because of a policy change from Numworks. Upsilon is for the people who want to see a future for user-made OSes for Numworks, even after the closure and archiving of Omega. - - - ### Some new features + - Enhancements for the Kandinsky python module - Support for wallpapers - External apps @@ -39,8 +37,6 @@ Do not disconnect your calculator until the installation is complete. *You can refer to this [website](https://www.numworks.com/resources/engineering/software/build/) for the first step if you get errors.* - - ### 1. Install SDK
    @@ -145,6 +141,7 @@ Next, you'll need to install the [GCC toolchain for ARM](https://developer.arm.c ```bash echo "export PATH=$PATH:$HOME/gcc-arm/bin" >> .bashrc ``` + Just restart terminal and you can go to step 2!
    @@ -158,14 +155,17 @@ You need a windows version >= 1903. #### WSL Installation 1. Use simultaneously win + X keys and then click on "admin powershell". + ```powershell dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux all /norestart ``` + This command activate WSL functionalities. ```powershell dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart ``` + This one allows virtual machines developed by Microsoft. 2. Restart your computer. @@ -173,6 +173,7 @@ This one allows virtual machines developed by Microsoft. 3. Download [this file](https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi) and follow instructions. 4. Now open powershell admin like before and type: + ```powershell wsl --set-default-version 2 ``` @@ -187,31 +188,41 @@ If you want to connect to the calculator, you have to connect to install this [t ```bash sudo apt install linux-tools-5.4.0-77-generic hwdata ``` + 2. Edit /etc/sudoers so that root can find the usbip command. On Ubuntu, run this command. + ```bash sudo visudo ``` + 3. Add `/usr/lib/linux-tools/5.4.0-77-generic` to the beginning of secure_path. After editing, the line should look similar to this. `Defaults secure_path="/usr/lib/linux-tools/5.4.0-77-generic:/usr/local/sbin:..."` #### Debian 1. If you use debian for your WSL distro, use this command instead: + ```bash sudo apt install usbip hwdata usbutils ``` + And that's all for installation and set up. ### To connect your calculator + 1. Open an Admin powershell and type: + ```powershell usbipd wsl list ``` + This will list your usb devices connected. Look at the BUSID column and remember the one for your calculator (it should be called "Numworks Calculator"). -2. Now run this command replacing by your calculator's usb port id: +2. Now run this command replacing `` by your calculator's usb port id: + ```powershell usbipd wsl attach --busid ``` + It will ask you to type your wsl's password and will connect your calculator to WSL. You can now go to step 2! @@ -222,23 +233,20 @@ You can now go to step 2!
    - ### 2. Set up repo - Clone repo and use 'upsilon-dev' branch by pasting these two commands: ```bash -git clone --recursive https://github.com/Lauryy06/Upsilon.git +git clone --recursive https://github.com/UpsilonNumworks/Upsilon.git cd Upsilon git checkout upsilon-dev ``` -
    +
    ### 3. Choose the target -
    Model n0100 @@ -255,6 +263,7 @@ Now, run either: ```bash make MODEL=n0100 epsilon_flash ``` + to directly flash the calculator after pressing simultaneously `reset` and `6` buttons and plugging in.
    @@ -264,14 +273,16 @@ or: ```bash make MODEL=n0100 OMEGA_USERNAME="" binpack -j4 ``` + to make binpack which you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). Binpacks are a great way to share a custom build of Upsilonto friends.
    -Model n0110 +
    +Model bootloader (N0110) ```bash make clean @@ -281,9 +292,10 @@ make OMEGA_USERNAME="{Your name, max 15 characters}" -j4 Now, run either: ```bash -make epsilon_flash +make epsilon.A_flash ``` -to directly flash the calculator after pressing simultaneously `reset` and `6` buttons and plugging in. + +to directly flash the calculator into the current slot, or thought bootloader's slot flasher with RESET, then 4 (flash), and 1 (flash slots) for other slots.
    @@ -292,6 +304,36 @@ or: ```bash make OMEGA_USERNAME="" binpack -j4 ``` + +to make binpack witch you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0110/). Binpacks are a great way to share a custom build of Upsilon to friends. + +
    + +
    + +Model N0110 legacy (deprecated, use bootloader instead, no Epsilon protection) + +```bash +make MODEL=n0110 clean +make MODEL=n0110 OMEGA_USERNAME="{Your name, max 15 characters}" -j4 +``` + +Now, run either: + +```bash +make MODEL=n0110 epsilon_flash +``` + +to directly flash the calculator after pressing simultaneously `reset` and `6` buttons and plugging in. + +
    + +or: + +```bash +make MODEL=n0110 OMEGA_USERNAME="" binpack -j4 +``` + to make binpack witch you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0110/). Binpacks are a great way to share a custom build of Upsilon to friends.
    @@ -305,8 +347,8 @@ First, install emsdk : ```bash git clone https://github.com/emscripten-core/emsdk.git cd emsdk -./emsdk install latest-fastcomp -./emsdk activate latest-fastcomp +./emsdk install 1.40.1 +./emsdk activate 1.40.1 source emsdk_env.sh ``` @@ -328,11 +370,12 @@ The simulator is now in `output/release/simulator/web/simulator.zip` You need devkitPro and devkitARM installed and in your path (instructions [here](https://devkitpro.org/wiki/Getting_Started)) ```bash -git clone --recursive https://github.com/Lauryy06/Upsilon.git +git clone --recursive https://github.com/UpsilonNumworks/Upsilon.git cd Upsilon git checkout --recursive upsilon-dev make PLATFORM=simulator TARGET=3ds -j ``` + You can then put epsilon.3dsx on a SD card to run it from the HBC or use 3dslink to launch it over the network: ```bash @@ -349,13 +392,16 @@ Don't forget to put your pseudo instead of `{your pseudo, max 15 char}`. If you
    -If you need help, you can join our Discord server here : https://discord.gg/NFvzdCBTQn +If you need help, you can join our Discord server here :

    Omega Banner Discord

    + --- + ## Useful links -* [Upsilon external (to install additional apps and wallpapers)](https://lauryy06.github.io/Upsilon-External/) -* [Ulab documentation](https://micropython-ulab.readthedocs.io/en/latest/) + +- [Upsilon external (to install additional apps and wallpapers)](https://upsilonnumworks.github.io/Upsilon-External/) +- [Ulab documentation](https://micropython-ulab.readthedocs.io/en/latest/) ## Contributing @@ -365,14 +411,14 @@ To contribute, please refer to [Omega's Wiki](https://github.com/Omega-Numworks/ Here are the main links toward Omega's different websites and repositories, that have been used for the creation of Upsilon. -* [Omega Themes](https://github.com/Omega-Numworks/Omega-Themes) -* [Omega Website](https://github.com/Omega-Numworks/Omega-Website) -* [Omega RPN `APP`](https://github.com/Omega-Numworks/Omega-RPN) -* [Omega Atomic `APP`](https://github.com/Omega-Numworks/Omega-Atomic) -* [Omega Design](https://github.com/Omega-Numworks/Omega-Design) -* [Omega Discord Bot](https://github.com/Omega-Numworks/Omega-Discord-Bot) -* [Omega App Template `BETA`](https://github.com/Omega-Numworks/Omega-App-Template) -* [External Apps](https://github.com/Omega-Numworks/External-Apps) +- [Omega Themes](https://github.com/Omega-Numworks/Omega-Themes) +- [Omega Website](https://github.com/Omega-Numworks/Omega-Website) +- [Omega RPN `APP`](https://github.com/Omega-Numworks/Omega-RPN) +- [Omega Atomic `APP`](https://github.com/Omega-Numworks/Omega-Atomic) +- [Omega Design](https://github.com/Omega-Numworks/Omega-Design) +- [Omega Discord Bot](https://github.com/Omega-Numworks/Omega-Discord-Bot) +- [Omega App Template `BETA`](https://github.com/Omega-Numworks/Omega-App-Template) +- [External Apps](https://github.com/Omega-Numworks/External-Apps) ## About Epsilon @@ -388,6 +434,6 @@ NumWorks is a registered trademark of NumWorks SAS, 24 Rue Godot de Mauroy, 7500 Nintendo and Nintendo 3DS are registered trademarks of Nintendo of America Inc, 4600 150th Ave NE, Redmond, WA 98052, USA. NumWorks SAS and Nintendo of America Inc aren't associated in any shape or form with this project. -* NumWorks Epsilon is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). -* Omega is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). -* Upsilon is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). +- NumWorks Epsilon is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). +- Omega is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). +- Upsilon is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). diff --git a/build/platform.device.mak b/build/platform.device.mak index 34ca783d07f..a72e7aaafb6 100644 --- a/build/platform.device.mak +++ b/build/platform.device.mak @@ -1,4 +1,4 @@ -MODEL ?= n0110 +MODEL ?= bootloader USE_LIBA = 1 EXE = elf From 5f51ed06281711f8bec63ec81564102beae230ba Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Fri, 20 May 2022 19:31:21 +0200 Subject: [PATCH 278/355] [README/CI] Fix README and CI (#235) * [README] Fix readme * [CI] Fix CI * [CI] Fix bootloader --- .github/workflows/ci-workflow.yml | 32 +++++++++++++++---------------- README.fr.md | 3 +++ README.md | 2 -- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 5c28cd7c9e6..1f0850aeb56 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -126,16 +126,16 @@ jobs: - uses: actions/checkout@v2 with: submodules: 'recursive' - - run: make -j2 epsilon.dfu - - run: make -j2 epsilon.onboarding.dfu - - run: make -j2 epsilon.onboarding.update.dfu - - run: make -j2 epsilon.onboarding.beta.dfu - - run: make -j2 flasher.light.dfu - - run: make -j2 flasher.verbose.dfu + - run: make -j2 MODEL=n0110 epsilon.dfu + - run: make -j2 MODEL=n0110 epsilon.onboarding.dfu + - run: make -j2 MODEL=n0110 epsilon.onboarding.update.dfu + - run: make -j2 MODEL=n0110 epsilon.onboarding.beta.dfu + - run: make -j2 MODEL=n0110 flasher.light.dfu + - run: make -j2 MODEL=n0110 flasher.verbose.dfu # We don't need bench as it is used only in factory # - run: make -j2 bench.ram.dfu # - run: make -j2 bench.flash.dfu - - run: make -j2 binpack + - run: make -j2 MODEL=n0110 binpack - run: cp output/release/device/n0110/binpack-n0110-`git rev-parse HEAD | head -c 7`.tgz output/release/device/n0110/binpack-n0110.tgz - id: 'auth' if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} @@ -161,15 +161,15 @@ jobs: - uses: actions/checkout@v2 with: submodules: 'recursive' - - run: make -j2 bootloader.dfu - - run: make MODEL=bootloader -j2 epsilon.A.dfu epsilon.B.dfu - - run: make MODEL=bootloader -j2 epsilon.onboarding.A.dfu - - run: make MODEL=bootloader -j2 epsilon.onboarding.B.dfu - - run: make MODEL=bootloader -j2 epsilon.onboarding.update.A.dfu - - run: make MODEL=bootloader -j2 epsilon.onboarding.update.B.dfu - - run: make MODEL=bootloader -j2 epsilon.onboarding.beta.A.dfu - - run: make MODEL=bootloader -j2 epsilon.onboarding.beta.B.dfu - - run: make MODEL=bootloader -j2 binpack + - run: make -j2 MODEL=n0110 bootloader.dfu + - run: make -j2 epsilon.A.dfu epsilon.B.dfu + - run: make -j2 epsilon.onboarding.A.dfu + - run: make -j2 epsilon.onboarding.B.dfu + - run: make -j2 epsilon.onboarding.update.A.dfu + - run: make -j2 epsilon.onboarding.update.B.dfu + - run: make -j2 epsilon.onboarding.beta.A.dfu + - run: make -j2 epsilon.onboarding.beta.B.dfu + - run: make -j2 binpack - run: cp output/release/device/bootloader/binpack-bootloader-`git rev-parse HEAD | head -c 7`.tgz output/release/device/bootloader/binpack-bootloader.tgz - uses: actions/upload-artifact@master with: diff --git a/README.fr.md b/README.fr.md index ff192e180a8..be39a79c02d 100644 --- a/README.fr.md +++ b/README.fr.md @@ -312,6 +312,9 @@ pour compiler les binpacks que vous pouvez distribuer et flasher depuis le [Ti-p
    +
    + + Model n0110 (obsolète, utilisez le bootloader à la place, pas de protection contre Epsilon) ```bash diff --git a/README.md b/README.md index 09a5d7edeca..64e9217fb32 100644 --- a/README.md +++ b/README.md @@ -280,8 +280,6 @@ to make binpack which you can flash to the calculator from [Ti-planet's webDFU](
    -
    - Model bootloader (N0110) ```bash From 19de87f9b53558dff4bc912aaf3665d14592dd73 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Sat, 21 May 2022 10:54:24 +0200 Subject: [PATCH 279/355] [CI] Fix metric workflow --- .github/workflows/metric-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/metric-workflow.yml b/.github/workflows/metric-workflow.yml index 3816b84d764..d7ec925fccf 100644 --- a/.github/workflows/metric-workflow.yml +++ b/.github/workflows/metric-workflow.yml @@ -16,7 +16,7 @@ jobs: ref: ${{ github.event.pull_request.base.sha }} path: base - name: Build base - run: make -j2 -C base epsilon.elf + run: make -j2 -C base MODEL=n0110 epsilon.elf - name: Checkout PR head uses: actions/checkout@v2 with: @@ -24,7 +24,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} path: head - name: Build head - run: make -j2 -C head epsilon.elf + run: make -j2 -C head MODEL=n0110 epsilon.elf - name: Retrieve binary size analysis id: binary_size run: echo "::set-output name=table::$(python3 head/build/metrics/binary_size.py base/output/release/device/n0110/epsilon.elf head/output/release/device/n0110/epsilon.elf --labels Base Head --sections .text .rodata .bss .data --custom 'Total (RAM)' .data .bss --custom 'Total (ROM)' .text .rodata .data --escape)" From e4718fcb954236e98c31b8ab3488d63db5e30cf5 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Sat, 21 May 2022 17:38:26 +0200 Subject: [PATCH 280/355] [apps/external] Add simulator support (#238) --- Makefile | 9 +++------ apps/external/extapp_api.cpp | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index dae92cf995a..c42ed081ab2 100644 --- a/Makefile +++ b/Makefile @@ -31,14 +31,11 @@ ifeq ($(filter reader,$(apps_list)),) HAS_READER := 1 endif -ifeq (${MODEL}, n0110) - apps_list = ${EPSILON_APPS} +# Remove the external apps for the n0100 +ifeq (${MODEL}, n0100) + apps_list = $(foreach i, ${EPSILON_APPS}, $(if $(filter external, $(i)),,$(i))) else - ifeq (${MODEL},bootloader) apps_list = ${EPSILON_APPS} - else - apps_list = $(foreach i, ${EPSILON_APPS}, $(if $(filter external, $(i)),,$(i))) - endif endif ifdef FORCE_EXTERNAL diff --git a/apps/external/extapp_api.cpp b/apps/external/extapp_api.cpp index 6bc548da45a..23254ab9146 100644 --- a/apps/external/extapp_api.cpp +++ b/apps/external/extapp_api.cpp @@ -37,18 +37,30 @@ void extapp_pushRect(int16_t x, int16_t y, uint16_t w, uint16_t h, const uint16_ KDRect rect(x, y, w, h); Ion::Display::pushRect(rect, reinterpret_cast(pixels)); + #ifndef DEVICE + // Refresh the display. + Ion::Keyboard::scan(); + #endif } void extapp_pushRectUniform(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t color) { KDRect rect(x, y, w, h); Ion::Display::pushRectUniform(rect, KDColor::RGB16(color)); + #ifndef DEVICE + // Refresh the display. + Ion::Keyboard::scan(); + #endif } void extapp_pullRect(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t * pixels) { KDRect rect(x, y, w, h); Ion::Display::pullRect(rect, (KDColor *)pixels); + #ifndef DEVICE + // Refresh the display. + Ion::Keyboard::scan(); + #endif } int16_t extapp_drawTextLarge(const char * text, int16_t x, int16_t y, uint16_t fg, uint16_t bg, bool fake) { @@ -59,6 +71,11 @@ int16_t extapp_drawTextLarge(const char * text, int16_t x, int16_t y, uint16_t f ctx->setOrigin(KDPoint(0, 0)); point = ctx->drawString(text, point, KDFont::LargeFont, KDColor::RGB16(fg), KDColor::RGB16(bg)); + #ifndef DEVICE + // Refresh the display. + Ion::Keyboard::scan(); + #endif + return point.x(); } @@ -70,6 +87,11 @@ int16_t extapp_drawTextSmall(const char * text, int16_t x, int16_t y, uint16_t f ctx->setOrigin(KDPoint(0, 0)); point = ctx->drawString(text, point, KDFont::SmallFont, KDColor::RGB16(fg), KDColor::RGB16(bg)); + #ifndef DEVICE + // Refresh the display. + Ion::Keyboard::scan(); + #endif + return point.x(); } From 5444bdae99b81811130ac3f9f5d673c2ef04ef49 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Fri, 27 May 2022 16:56:27 +0200 Subject: [PATCH 281/355] [build] Disable telemetry on IOS --- build/platform.simulator.ios.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/platform.simulator.ios.mak b/build/platform.simulator.ios.mak index b53ad508ad3..15ef783b1cb 100644 --- a/build/platform.simulator.ios.mak +++ b/build/platform.simulator.ios.mak @@ -3,7 +3,7 @@ EXE = bin APPLE_PLATFORM ?= ios APPLE_PLATFORM_MIN_VERSION = 8.0 -EPSILON_TELEMETRY ?= 1 +EPSILON_TELEMETRY ?= 0 ifeq ($(APPLE_PLATFORM),ios) ARCHS = arm64 armv7 From d8423fb4f1e553ac22e84bf81afc889bddfcb042 Mon Sep 17 00:00:00 2001 From: Joachim Le Fournis Date: Sun, 29 May 2022 10:26:59 +0200 Subject: [PATCH 282/355] [LD] Fixed warning about different array declaration (#242) The size declared in toolbox_ion_keys.cpp for the modion_module_globals_table array was different that the defined array in micropython --- apps/code/toolbox_ion_keys.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/code/toolbox_ion_keys.cpp b/apps/code/toolbox_ion_keys.cpp index b5167d60c2f..edfd490aafa 100644 --- a/apps/code/toolbox_ion_keys.cpp +++ b/apps/code/toolbox_ion_keys.cpp @@ -6,7 +6,7 @@ extern "C" { #include #include } -extern "C" const mp_rom_map_elem_t modion_module_globals_table[52]; +extern "C" const mp_rom_map_elem_t modion_module_globals_table[55]; namespace Code { ToolboxIonKeys::ToolboxIonKeys() : From 1db74c78abc87dbadd6b5f86234c5218acee54de Mon Sep 17 00:00:00 2001 From: fmOOmf <98671961+fmOOmf@users.noreply.github.com> Date: Thu, 9 Jun 2022 17:10:07 +0200 Subject: [PATCH 283/355] Ajout de \binom pour afficher les coefficients binomiaux (#250) --- apps/reader/tex_parser.cpp | 16 ++++++++++++++-- apps/reader/tex_parser.h | 2 ++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/reader/tex_parser.cpp b/apps/reader/tex_parser.cpp index 79b9ff0e41e..238e09da212 100644 --- a/apps/reader/tex_parser.cpp +++ b/apps/reader/tex_parser.cpp @@ -190,7 +190,12 @@ Layout TexParser::popCommand() { return popOverrightarrowCommand(); } } - + if (strncmp(k_binomCommand, m_text, strlen(k_binomCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_binomCommand)))) { + m_text += strlen(k_binomCommand); + return popBinomCommand(); + } + } for (int i = 0; i < k_NumberOfSymbols; i++) { if (strncmp(k_SymbolsCommands[i], m_text, strlen(k_SymbolsCommands[i])) == 0) { if (isCommandEnded(*(m_text + strlen(k_SymbolsCommands[i])))) { @@ -264,6 +269,13 @@ Layout TexParser::popOverrightarrowCommand() { return VectorLayout::Builder(popBlock()); } +Layout TexParser::popBinomCommand() { + Layout numerator = popBlock(); + Layout denominator = popBlock(); + BinomialCoefficientLayout b = BinomialCoefficientLayout::Builder(numerator, denominator); + return b; +} + Layout TexParser::popSymbolCommand(int SymbolIndex) { uint32_t codePoint = k_SymbolsCodePoints[SymbolIndex]; return CodePointLayout::Builder(codePoint); @@ -273,4 +285,4 @@ inline bool TexParser::isCommandEnded(char c) const { return !(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z'); } -} \ No newline at end of file +} diff --git a/apps/reader/tex_parser.h b/apps/reader/tex_parser.h index ef2e30b4f06..bcc539656a3 100644 --- a/apps/reader/tex_parser.h +++ b/apps/reader/tex_parser.h @@ -28,6 +28,7 @@ class TexParser { Layout popSqrtCommand(); Layout popSpaceCommand(); Layout popOverrightarrowCommand(); + Layout popBinomCommand(); //Symbols Layout popSymbolCommand(int SymbolIndex); @@ -47,6 +48,7 @@ class TexParser { static constexpr char const * k_sqrtCommand = "sqrt"; static constexpr char const * k_spaceCommand = "space"; static constexpr char const * k_overrightArrowCommand = "overrightarrow"; + static constexpr char const * k_binomCommand = "binom"; }; } From c826e556a12deb89e33a121102ae55cd40a0b907 Mon Sep 17 00:00:00 2001 From: Laury Date: Fri, 10 Jun 2022 16:15:31 +0200 Subject: [PATCH 284/355] [storage] New attempt to save cursor position --- apps/code/editor_controller.cpp | 8 +++++++- apps/code/script.cpp | 15 ++++++++++++++ apps/code/script.h | 2 ++ ion/include/ion/internal_storage.h | 6 ++++++ ion/include/ion/storage.h | 7 +++++++ ion/include/ion/unicode/utf8_helper.h | 4 ++++ ion/src/shared/internal_storage.cpp | 2 +- ion/src/shared/storage.cpp | 21 +++++++++++++++++++ ion/src/shared/unicode/utf8_helper.cpp | 28 ++++++++++++++++++++++++++ 9 files changed, 91 insertions(+), 2 deletions(-) diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index be09c9662b8..d286b92cf2f 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -65,13 +65,19 @@ void EditorController::viewWillAppear() { ViewController::viewWillAppear(); m_editorView.loadSyntaxHighlighter(); if(GlobalPreferences::sharedGlobalPreferences()->cursorSaving()) { - m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); + int offset = m_script.cursorOffset(); + if (offset != -1) { + m_editorView.setCursorLocation(m_editorView.text() + offset); + } else { + m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); + } } else { m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); } } void EditorController::viewDidDisappear() { + m_script.setCursorOffset(m_editorView.cursorLocation() - m_script.content()); m_editorView.resetSelection(); m_menuController->scriptContentEditionDidFinish(); } diff --git a/apps/code/script.cpp b/apps/code/script.cpp index 4b39b345281..678f78da180 100644 --- a/apps/code/script.cpp +++ b/apps/code/script.cpp @@ -65,6 +65,21 @@ bool Script::nameCompliant(const char * name) { return false; } +uint16_t Script::cursorOffset() { + assert(!isNull()); + Ion::Storage::Metadata metadata = Ion::Storage::sharedStorage()->metadataForRecord(*this); + if (metadata.buffer != nullptr) { + assert(metadata.size == 2); + return *((uint16_t*) metadata.buffer); + } + + return -1; +} +void Script::setCursorOffset(uint16_t position) { + assert(!isNull()); + Ion::Storage::sharedStorage()->setMetadataForRecord(*this, { &position, sizeof(uint16_t) }); +} + uint8_t * StatusFromData(Script::Data d) { return const_cast(static_cast(d.buffer)); } diff --git a/apps/code/script.h b/apps/code/script.h index 6f7df9cdae7..ffa8038bc14 100644 --- a/apps/code/script.h +++ b/apps/code/script.h @@ -50,6 +50,8 @@ class Script : public Ion::Storage::Record { void toggleAutoimportationStatus(); const char * content() const; size_t contentSize() { return value().size - k_statusSize; } + void setCursorOffset(uint16_t position); // -1 if no metadata + uint16_t cursorOffset(); /* Fetched status */ bool fetchedFromConsole() const; diff --git a/ion/include/ion/internal_storage.h b/ion/include/ion/internal_storage.h index 257d243ce3c..c40edb3f119 100644 --- a/ion/include/ion/internal_storage.h +++ b/ion/include/ion/internal_storage.h @@ -14,6 +14,11 @@ namespace Ion { class StorageDelegate; +/** + * Purpose the two storage classes : + * - The first (InternalStorage) is the base, it allows to create, modify and delete records + * - The second (Storage) is the visible part. It handle the trash system and the metadata records + */ class InternalStorage { public: typedef uint16_t record_size_t; @@ -114,6 +119,7 @@ class InternalStorage { // Used by Python OS module int numberOfRecords(); Record recordAtIndex(int index); + protected: InternalStorage(); /* Getters on address in buffer */ diff --git a/ion/include/ion/storage.h b/ion/include/ion/storage.h index 5385f4608d3..ea217ac5264 100644 --- a/ion/include/ion/storage.h +++ b/ion/include/ion/storage.h @@ -37,9 +37,16 @@ class Storage : public InternalStorage { Record recordAtIndex(int index); void destroyRecord(Record record); + // Trash void reinsertTrash(const char * extension); void emptyTrash(); + // Metadata + typedef Record::Data Metadata; + Metadata metadataForRecord(Record record); + Record::ErrorStatus setMetadataForRecord(Record record, Metadata metadata); + void removeMetadataForRecord(Record record); + private: Storage(): InternalStorage() {} diff --git a/ion/include/ion/unicode/utf8_helper.h b/ion/include/ion/unicode/utf8_helper.h index a3ce96df81f..aa4b795cde3 100644 --- a/ion/include/ion/unicode/utf8_helper.h +++ b/ion/include/ion/unicode/utf8_helper.h @@ -26,6 +26,10 @@ int CountOccurrences(const char * s, CodePoint c); * null terminating char otherwise. */ const char * CodePointSearch(const char * s, CodePoint c, const char * stoppingPosition = nullptr); +/* Returns the last occurrence of a code point in a string, the position of the + * null terminating char otherwise. */ +const char * LastCodePoint(const char * s, CodePoint c, const char * stoppingPosition = nullptr); + // Returns true if the text had the code point bool HasCodePoint(const char * s, CodePoint c, const char * stoppingPosition = nullptr); diff --git a/ion/src/shared/internal_storage.cpp b/ion/src/shared/internal_storage.cpp index 5fc4a90d10a..1af85122572 100644 --- a/ion/src/shared/internal_storage.cpp +++ b/ion/src/shared/internal_storage.cpp @@ -55,7 +55,7 @@ InternalStorage::Record::Record(const char * fullName) { m_fullNameCRC32 = 0; return; } - const char * dotChar = UTF8Helper::CodePointSearch(fullName, k_dotChar); + const char * dotChar = UTF8Helper::LastCodePoint(fullName, k_dotChar); // If no extension, return empty record if (*dotChar == 0 || *(dotChar+1) == 0) { m_fullNameCRC32 = 0; diff --git a/ion/src/shared/storage.cpp b/ion/src/shared/storage.cpp index 50a8a4c7007..5a32b114737 100644 --- a/ion/src/shared/storage.cpp +++ b/ion/src/shared/storage.cpp @@ -61,6 +61,10 @@ bool Storage::hasRecord(Record r) { void Storage::destroyRecord(Record record) { emptyTrash(); + const Record metadataRecord = recordBaseNamedWithExtension(record.fullName(), "sys"); + if (!metadataRecord.isNull()) { + InternalStorage::destroyRecord(metadataRecord); + } m_trashRecord = record; } @@ -177,4 +181,21 @@ void Storage::emptyTrash() { } } +Storage::Metadata Storage::metadataForRecord(Record record) { + return recordBaseNamedWithExtension(record.fullName(), "sys").value(); +} + +Storage::Record::ErrorStatus Storage::setMetadataForRecord(Record record, Metadata data) { + Record metadataRecord = Record(record.fullName(), "sys"); + if (!hasRecord(metadataRecord)) { + return createRecordWithExtension(record.fullName(), "sys", data.buffer, data.size); + } else { + return metadataRecord.setValue(data); + } +} + +void Storage::removeMetadataForRecord(Record record) { + recordBaseNamedWithExtension(record.fullName(), "sys").destroy(); +} + } diff --git a/ion/src/shared/unicode/utf8_helper.cpp b/ion/src/shared/unicode/utf8_helper.cpp index 0481df94134..45d841efed8 100644 --- a/ion/src/shared/unicode/utf8_helper.cpp +++ b/ion/src/shared/unicode/utf8_helper.cpp @@ -54,6 +54,34 @@ const char * CodePointSearch(const char * s, CodePoint c, const char * stoppingP return currentPointer; } +const char * LastCodePoint(const char * s, CodePoint c, const char * stoppingPosition) { + if (UTF8Decoder::CharSizeOfCodePoint(c) == 1) { + const char * result = nullptr; + const char * position = s; + while (*position != 0 && (stoppingPosition == nullptr || position != stoppingPosition)) { + if (*position == c) { + result = position; + } + position++; + } + return result ? result : position; + } + UTF8Decoder decoder(s); + const char * currentPointer = s; + const char * result = nullptr; + CodePoint codePoint = decoder.nextCodePoint(); + const char * nextPointer = decoder.stringPosition(); + while (codePoint != UCodePointNull && (stoppingPosition == nullptr || currentPointer < stoppingPosition)) { + if (c == codePoint) { + result = currentPointer; + } + currentPointer = nextPointer; + codePoint = decoder.nextCodePoint(); + nextPointer = decoder.stringPosition(); + } + return result ? result : currentPointer; +} + bool HasCodePoint(const char * s, CodePoint c, const char * stoppingPosition) { assert(c != 0); const char * resultPosition = CodePointSearch(s, c, stoppingPosition); From 1f91a5f900a10e15defc8e119dba233625c47a2e Mon Sep 17 00:00:00 2001 From: Rathmox <55508107+Rathmox@users.noreply.github.com> Date: Sun, 12 Jun 2022 11:36:03 +0200 Subject: [PATCH 285/355] Fixing README Files (#241) --- README.fr.md | 7 ++++++- README.md | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/README.fr.md b/README.fr.md index be39a79c02d..c9e9184361f 100644 --- a/README.fr.md +++ b/README.fr.md @@ -171,7 +171,7 @@ Cette commande permet d'autoriser le démarrage des machines signées par Micros 2. Redémarrez votre ordinateur. -3. Téléchargez ce fichier [this file](https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi) et suivez les instructions d'installation. +3. Téléchargez [ce fichier](https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi) et suivez les instructions d'installation. 4. Ouvrez votre fenêtre powershell comme avant et tapez: @@ -183,6 +183,11 @@ wsl --set-default-version 2 WSL est maintenant installé. +6. Installez maintenant la version pour ARM de GCC. +```bash +sudo apt-get install build-essential git imagemagick libx11-dev libxext-dev libfreetype6-dev libpng-dev libjpeg-dev pkg-config gcc-arm-none-eabi binutils-arm-none-eabi +``` + ### Installation d'usbipd pour connecter la calculatrice à WSL (facultatif) Pour connecter la calculatrice, il faut installer cet [outil](https://github.com/dorssel/usbipd-win/releases/download/v1.3.0/usbipd-win_1.3.0.msi). Il permet de connecter deperiphériques USpar internet.Suivez les instructions pour installer. diff --git a/README.md b/README.md index 64e9217fb32..f143d941dc2 100644 --- a/README.md +++ b/README.md @@ -176,11 +176,16 @@ This one allows virtual machines developed by Microsoft. ```powershell wsl --set-default-version 2 - ``` +``` 5. Download [Ubuntu](https://www.microsoft.com/store/apps/9n6svws3rx71) from Microsoft store. WSL is now installed. +6. Then Install GCC cross compiler for ARM. +```bash +apt-get install build-essential git imagemagick libx11-dev libxext-dev libfreetype6-dev libpng-dev libjpeg-dev pkg-config gcc-arm-none-eabi binutils-arm-none-eabi +``` + ### Usbipd installation to connect your calculator If you want to connect to the calculator, you have to connect to install this [tool](https://github.com/dorssel/usbipd-win/releases/download/v1.3.0/usbipd-win_1.3.0.msi). This will allow you toconnect WSL to the calculator through internet. Follow the on screen information to install. #### Ubuntu From 3b12f298151d30c9a8ceafca60385062f8bc29a0 Mon Sep 17 00:00:00 2001 From: Mino1289 <68814671+Mino1289@users.noreply.github.com> Date: Sun, 12 Jun 2022 11:37:17 +0200 Subject: [PATCH 286/355] [apps/reader] Adding new symbols and functions in the TexParser (#237) --- apps/reader/tex_parser.cpp | 46 ++++++++++++++++++++++++++--------- apps/reader/tex_parser.h | 9 +++++-- kandinsky/fonts/code_points.h | 4 +++ 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/apps/reader/tex_parser.cpp b/apps/reader/tex_parser.cpp index 238e09da212..ff9b80749e0 100644 --- a/apps/reader/tex_parser.cpp +++ b/apps/reader/tex_parser.cpp @@ -12,7 +12,7 @@ namespace Reader { "Mu", "Nu", "Xi", "Omicron", "Pi", "Rho", "Sigma", "Tau", "Upsilon", "Phi", "Chi", "Psi","Omega", "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu", "nu", "xi", "omicron", "pi", "rho", "sigma", "tau", "upsilon", "phi", "chi", "psi", "omega", - "sim", + "sim", "f", "i", }; static constexpr int const k_NumberOfSymbols = sizeof(k_SymbolsCommands) / sizeof(char *); @@ -26,7 +26,7 @@ namespace Reader { 0x39c, 0x39d, 0x39e, 0x39f, 0x3a0, 0x3a1, 0x3a3, 0x3a4, 0x3a5, 0x3a6, 0x3a7, 0x3a8, 0x3a9, 0x3b1, 0x3b2, 0x3b3, 0x3b4, 0x3b5, 0x3b6, 0x3b7, 0x3b8, 0x3b9, 0x3ba, 0x3bb, 0x3bc, 0x3bd, 0x3be, 0x3bf, 0x3c0, 0x3c1, 0x3c3, 0x3c4, 0x3c5, 0x3c6, 0x3c7, 0x3c8, 0x3c9, - 0x7e, + 0x7e, 0x192, 0x1d422, }; static_assert(sizeof(k_SymbolsCodePoints) / sizeof(uint32_t) == k_NumberOfSymbols); @@ -141,6 +141,12 @@ Layout TexParser::popText(char stop) { Layout TexParser::popCommand() { // TODO: Factorize this code + if (strncmp(k_binomCommand, m_text, strlen(k_binomCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_binomCommand)))) { + m_text += strlen(k_binomCommand); + return popBinomCommand(); + } + } if (strncmp(k_ceilCommand, m_text, strlen(k_ceilCommand)) == 0) { if (isCommandEnded(*(m_text + strlen(k_ceilCommand)))) { m_text += strlen(k_ceilCommand); @@ -190,10 +196,16 @@ Layout TexParser::popCommand() { return popOverrightarrowCommand(); } } - if (strncmp(k_binomCommand, m_text, strlen(k_binomCommand)) == 0) { - if (isCommandEnded(*(m_text + strlen(k_binomCommand)))) { - m_text += strlen(k_binomCommand); - return popBinomCommand(); + if (strncmp(k_overlineCommand, m_text, strlen(k_overlineCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_overlineCommand)))) { + m_text += strlen(k_overlineCommand); + return popOverlineCommand(); + } + } + if (strncmp(k_intsetCommand, m_text, strlen(k_intsetCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_intsetCommand)))) { + m_text += strlen(k_intsetCommand); + return popIntsetCommand(); } } for (int i = 0; i < k_NumberOfSymbols; i++) { @@ -219,6 +231,13 @@ Layout TexParser::popCommand() { } // Expressions +Layout TexParser::popBinomCommand() { + Layout numerator = popBlock(); + Layout denominator = popBlock(); + BinomialCoefficientLayout b = BinomialCoefficientLayout::Builder(numerator, denominator); + return b; +} + Layout TexParser::popCeilCommand() { Layout ceil = popBlock(); return CeilingLayout::Builder(ceil); @@ -236,6 +255,14 @@ Layout TexParser::popFracCommand() { return l; } +Layout TexParser::popIntsetCommand() { + HorizontalLayout intset = HorizontalLayout::Builder(); + intset.addOrMergeChildAtIndex(CodePointLayout::Builder(0x27e6), 0, false); + intset.addOrMergeChildAtIndex(popBlock(), intset.numberOfChildren(), false); + intset.addOrMergeChildAtIndex(CodePointLayout::Builder(0x27e7), intset.numberOfChildren(), false); + return intset; +} + Layout TexParser::popLeftCommand() { m_text++; return LeftParenthesisLayout::Builder(); @@ -269,11 +296,8 @@ Layout TexParser::popOverrightarrowCommand() { return VectorLayout::Builder(popBlock()); } -Layout TexParser::popBinomCommand() { - Layout numerator = popBlock(); - Layout denominator = popBlock(); - BinomialCoefficientLayout b = BinomialCoefficientLayout::Builder(numerator, denominator); - return b; +Layout TexParser::popOverlineCommand() { + return ConjugateLayout::Builder(popBlock()); } Layout TexParser::popSymbolCommand(int SymbolIndex) { diff --git a/apps/reader/tex_parser.h b/apps/reader/tex_parser.h index bcc539656a3..4010ede3d90 100644 --- a/apps/reader/tex_parser.h +++ b/apps/reader/tex_parser.h @@ -20,15 +20,17 @@ class TexParser { Layout popCommand(); // Expressions + Layout popBinomCommand(); Layout popCeilCommand(); Layout popFloorCommand(); Layout popFracCommand(); + Layout popIntsetCommand(); Layout popLeftCommand(); Layout popRightCommand(); Layout popSqrtCommand(); Layout popSpaceCommand(); Layout popOverrightarrowCommand(); - Layout popBinomCommand(); + Layout popOverlineCommand(); //Symbols Layout popSymbolCommand(int SymbolIndex); @@ -40,15 +42,18 @@ class TexParser { inline bool isCommandEnded(char c) const; // Expressions that require specific handling + static constexpr char const * k_binomCommand = "binom"; static constexpr char const * k_ceilCommand = "ceil"; static constexpr char const * k_floorCommand = "floor"; static constexpr char const * k_fracCommand = "frac"; + static constexpr char const * k_intsetCommand = "intset"; static constexpr char const * k_leftCommand = "left"; static constexpr char const * k_rightCommand = "right"; static constexpr char const * k_sqrtCommand = "sqrt"; static constexpr char const * k_spaceCommand = "space"; static constexpr char const * k_overrightArrowCommand = "overrightarrow"; - static constexpr char const * k_binomCommand = "binom"; + static constexpr char const * k_overlineCommand = "overline"; + }; } diff --git a/kandinsky/fonts/code_points.h b/kandinsky/fonts/code_points.h index 5d57021ca37..da5c08592f5 100644 --- a/kandinsky/fonts/code_points.h +++ b/kandinsky/fonts/code_points.h @@ -266,6 +266,8 @@ uint32_t ExtendedCodePoints[] = { 0xf8, // ø // LATIN SMALL LETTER O WITH STROKE 0xfe, // þ // LATIN SMALL LETTER THORN + 0x192, // ƒ // LATIN SMALL LETTER F WITH HOOK + 0x300, // ̀ // COMBINING GRAVE ACCENT 0x301, // ́ // COMBINING ACUTE ACCENT 0x302, // ̂ // COMBINING CIRCUMFLEX ACCENT @@ -355,6 +357,8 @@ uint32_t ExtendedCodePoints[] = { 0x2264, // ≤ // LESS-THAN OR EQUAL TO 0x2265, // ≥ // GREATER-THAN OR EQUAL TO 0x2505, // ┅ // BOX DRAWING EQU HEAVY DASH HORIZONTAL + 0x27e6, // ⟦ // MATHEMATICAL LEFT INT BRACKET SET + 0x27e7, // ⟧ // MATHEMATICAL RIGHT INT BRACKET SET 0xFFFD, // � // REPLACEMENT CHARACTER 0x1d422, // 𝐢 // MATHEMATICAL BOLD SMALL I" }; From 35017ec16701093840e03e263796b489e62124cc Mon Sep 17 00:00:00 2001 From: Lisra-git <89012417+Lisra-git@users.noreply.github.com> Date: Sun, 12 Jun 2022 11:38:52 +0200 Subject: [PATCH 287/355] Lock Hardware Test while Exam Mode enabled (#227) --- apps/settings/sub_menu/about_controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/settings/sub_menu/about_controller.cpp b/apps/settings/sub_menu/about_controller.cpp index a95879f52ec..a022c48f0e4 100644 --- a/apps/settings/sub_menu/about_controller.cpp +++ b/apps/settings/sub_menu/about_controller.cpp @@ -37,7 +37,7 @@ bool AboutController::handleEvent(Ion::Events::Event event) { I18n::Message childLabel = m_messageTreeModel->childAtIndex(selectedRow()+(!hasUsernameCell()))->label(); /* We hide here the activation hardware test app: in the menu "about", by * clicking on '6' on the last row. */ - if ((event == Ion::Events::Six || event == Ion::Events::LowerT || event == Ion::Events::UpperT) && childLabel == I18n::Message::FccId) { + if ((event == Ion::Events::Six || event == Ion::Events::LowerT || event == Ion::Events::UpperT) && childLabel == I18n::Message::FccId && !GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { Container::activeApp()->displayModalViewController(&m_hardwareTestPopUpController, 0.f, 0.f, Metric::ExamPopUpTopMargin, Metric::PopUpRightMargin, Metric::ExamPopUpBottomMargin, Metric::PopUpLeftMargin); return true; } From 9588052d6873d7be0cbc3030a1fe293d45cd2860 Mon Sep 17 00:00:00 2001 From: fmOOmf <98671961+fmOOmf@users.noreply.github.com> Date: Sun, 12 Jun 2022 18:24:55 +0200 Subject: [PATCH 288/355] Correction de la gestion de la couleur marron Liseuse / Brown color processing in Reader (#251) * Ajout de \binom pour afficher les coefficients binomiaux * Correction in reader Brown color processing --- apps/reader/word_wrap_view.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index 1dddd0dd5e2..c13400e214b 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -668,7 +668,7 @@ bool WordWrapTextView::updateTextColorForward(const char * colorStart) const { m_textColor = Palette::Brown; keySize = 2; } - if (*(colorStart+2) == 'l') { + else if (*(colorStart+2) == 'l') { m_textColor = Palette::BlueLight; keySize = 2; } @@ -744,7 +744,7 @@ bool WordWrapTextView::updateTextColorBackward(const char * colorStart) const { m_textColor = Palette::Brown; keySize = 2; } - if (*(colorStart+2) == 'l') { + else if (*(colorStart+2) == 'l') { m_textColor = Palette::BlueLight; keySize = 2; } From 741a71554d3123fef30bab1672aed4df273d61c0 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Tue, 14 Jun 2022 19:35:08 +0200 Subject: [PATCH 289/355] [CI] Add bootloader upload to dev version on Firebase --- .github/workflows/ci-workflow.yml | 12 ++++++++++++ build/targets.device.bootloader.mak | 2 ++ 2 files changed, 14 insertions(+) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 1f0850aeb56..2d0a52687e1 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -171,6 +171,18 @@ jobs: - run: make -j2 epsilon.onboarding.beta.B.dfu - run: make -j2 binpack - run: cp output/release/device/bootloader/binpack-bootloader-`git rev-parse HEAD | head -c 7`.tgz output/release/device/bootloader/binpack-bootloader.tgz + - id: 'auth' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' + - id: 'upload-directory' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/upload-cloud-storage@v0' + with: + path: 'output/release/device/bootloader/binpack/' + destination: 'upsilon-binfiles.appspot.com/dev/n110/' + parent: false - uses: actions/upload-artifact@master with: name: epsilon-binpack-bootloader.tgz diff --git a/build/targets.device.bootloader.mak b/build/targets.device.bootloader.mak index 48466b778c3..930d69a47bb 100644 --- a/build/targets.device.bootloader.mak +++ b/build/targets.device.bootloader.mak @@ -51,5 +51,7 @@ binpack: $(BUILD_DIR)/epsilon.onboarding.bin rm -rf $(BUILD_DIR)/binpack mkdir -p $(BUILD_DIR)/binpack cp $(BUILD_DIR)/epsilon.onboarding.bin $(BUILD_DIR)/binpack + cp $(BUILD_DIR)/epsilon.onboarding.A.bin $(BUILD_DIR)/binpack + cp $(BUILD_DIR)/epsilon.onboarding.B.bin $(BUILD_DIR)/binpack cd $(BUILD_DIR) && for binary in epsilon.onboarding.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done cd $(BUILD_DIR) && tar cvfz binpack-$(MODEL)-`git rev-parse HEAD | head -c 7`.tgz binpack/* From 1319ad2f42ccb7b0d5c0d679e7e06afffe5bfcd5 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Tue, 14 Jun 2022 19:58:50 +0200 Subject: [PATCH 290/355] [Make] Add shasum for bootloader binpack --- build/targets.device.bootloader.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/targets.device.bootloader.mak b/build/targets.device.bootloader.mak index 930d69a47bb..030f95859e8 100644 --- a/build/targets.device.bootloader.mak +++ b/build/targets.device.bootloader.mak @@ -53,5 +53,5 @@ binpack: $(BUILD_DIR)/epsilon.onboarding.bin cp $(BUILD_DIR)/epsilon.onboarding.bin $(BUILD_DIR)/binpack cp $(BUILD_DIR)/epsilon.onboarding.A.bin $(BUILD_DIR)/binpack cp $(BUILD_DIR)/epsilon.onboarding.B.bin $(BUILD_DIR)/binpack - cd $(BUILD_DIR) && for binary in epsilon.onboarding.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done + cd $(BUILD_DIR) && for binary in epsilon.onboarding*.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done cd $(BUILD_DIR) && tar cvfz binpack-$(MODEL)-`git rev-parse HEAD | head -c 7`.tgz binpack/* From d983797ac8c02a7722cb317c33add1d582e20a2f Mon Sep 17 00:00:00 2001 From: Laury Date: Fri, 17 Jun 2022 12:06:56 +0200 Subject: [PATCH 291/355] Revert "[storage] New attempt to save cursor position" This reverts commit c826e556a12deb89e33a121102ae55cd40a0b907. --- apps/code/editor_controller.cpp | 8 +------- apps/code/script.cpp | 15 -------------- apps/code/script.h | 2 -- ion/include/ion/internal_storage.h | 6 ------ ion/include/ion/storage.h | 7 ------- ion/include/ion/unicode/utf8_helper.h | 4 ---- ion/src/shared/internal_storage.cpp | 2 +- ion/src/shared/storage.cpp | 21 ------------------- ion/src/shared/unicode/utf8_helper.cpp | 28 -------------------------- 9 files changed, 2 insertions(+), 91 deletions(-) diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index d286b92cf2f..be09c9662b8 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -65,19 +65,13 @@ void EditorController::viewWillAppear() { ViewController::viewWillAppear(); m_editorView.loadSyntaxHighlighter(); if(GlobalPreferences::sharedGlobalPreferences()->cursorSaving()) { - int offset = m_script.cursorOffset(); - if (offset != -1) { - m_editorView.setCursorLocation(m_editorView.text() + offset); - } else { - m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); - } + m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); } else { m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); } } void EditorController::viewDidDisappear() { - m_script.setCursorOffset(m_editorView.cursorLocation() - m_script.content()); m_editorView.resetSelection(); m_menuController->scriptContentEditionDidFinish(); } diff --git a/apps/code/script.cpp b/apps/code/script.cpp index 678f78da180..4b39b345281 100644 --- a/apps/code/script.cpp +++ b/apps/code/script.cpp @@ -65,21 +65,6 @@ bool Script::nameCompliant(const char * name) { return false; } -uint16_t Script::cursorOffset() { - assert(!isNull()); - Ion::Storage::Metadata metadata = Ion::Storage::sharedStorage()->metadataForRecord(*this); - if (metadata.buffer != nullptr) { - assert(metadata.size == 2); - return *((uint16_t*) metadata.buffer); - } - - return -1; -} -void Script::setCursorOffset(uint16_t position) { - assert(!isNull()); - Ion::Storage::sharedStorage()->setMetadataForRecord(*this, { &position, sizeof(uint16_t) }); -} - uint8_t * StatusFromData(Script::Data d) { return const_cast(static_cast(d.buffer)); } diff --git a/apps/code/script.h b/apps/code/script.h index ffa8038bc14..6f7df9cdae7 100644 --- a/apps/code/script.h +++ b/apps/code/script.h @@ -50,8 +50,6 @@ class Script : public Ion::Storage::Record { void toggleAutoimportationStatus(); const char * content() const; size_t contentSize() { return value().size - k_statusSize; } - void setCursorOffset(uint16_t position); // -1 if no metadata - uint16_t cursorOffset(); /* Fetched status */ bool fetchedFromConsole() const; diff --git a/ion/include/ion/internal_storage.h b/ion/include/ion/internal_storage.h index c40edb3f119..257d243ce3c 100644 --- a/ion/include/ion/internal_storage.h +++ b/ion/include/ion/internal_storage.h @@ -14,11 +14,6 @@ namespace Ion { class StorageDelegate; -/** - * Purpose the two storage classes : - * - The first (InternalStorage) is the base, it allows to create, modify and delete records - * - The second (Storage) is the visible part. It handle the trash system and the metadata records - */ class InternalStorage { public: typedef uint16_t record_size_t; @@ -119,7 +114,6 @@ class InternalStorage { // Used by Python OS module int numberOfRecords(); Record recordAtIndex(int index); - protected: InternalStorage(); /* Getters on address in buffer */ diff --git a/ion/include/ion/storage.h b/ion/include/ion/storage.h index ea217ac5264..5385f4608d3 100644 --- a/ion/include/ion/storage.h +++ b/ion/include/ion/storage.h @@ -37,16 +37,9 @@ class Storage : public InternalStorage { Record recordAtIndex(int index); void destroyRecord(Record record); - // Trash void reinsertTrash(const char * extension); void emptyTrash(); - // Metadata - typedef Record::Data Metadata; - Metadata metadataForRecord(Record record); - Record::ErrorStatus setMetadataForRecord(Record record, Metadata metadata); - void removeMetadataForRecord(Record record); - private: Storage(): InternalStorage() {} diff --git a/ion/include/ion/unicode/utf8_helper.h b/ion/include/ion/unicode/utf8_helper.h index aa4b795cde3..a3ce96df81f 100644 --- a/ion/include/ion/unicode/utf8_helper.h +++ b/ion/include/ion/unicode/utf8_helper.h @@ -26,10 +26,6 @@ int CountOccurrences(const char * s, CodePoint c); * null terminating char otherwise. */ const char * CodePointSearch(const char * s, CodePoint c, const char * stoppingPosition = nullptr); -/* Returns the last occurrence of a code point in a string, the position of the - * null terminating char otherwise. */ -const char * LastCodePoint(const char * s, CodePoint c, const char * stoppingPosition = nullptr); - // Returns true if the text had the code point bool HasCodePoint(const char * s, CodePoint c, const char * stoppingPosition = nullptr); diff --git a/ion/src/shared/internal_storage.cpp b/ion/src/shared/internal_storage.cpp index 1af85122572..5fc4a90d10a 100644 --- a/ion/src/shared/internal_storage.cpp +++ b/ion/src/shared/internal_storage.cpp @@ -55,7 +55,7 @@ InternalStorage::Record::Record(const char * fullName) { m_fullNameCRC32 = 0; return; } - const char * dotChar = UTF8Helper::LastCodePoint(fullName, k_dotChar); + const char * dotChar = UTF8Helper::CodePointSearch(fullName, k_dotChar); // If no extension, return empty record if (*dotChar == 0 || *(dotChar+1) == 0) { m_fullNameCRC32 = 0; diff --git a/ion/src/shared/storage.cpp b/ion/src/shared/storage.cpp index 5a32b114737..50a8a4c7007 100644 --- a/ion/src/shared/storage.cpp +++ b/ion/src/shared/storage.cpp @@ -61,10 +61,6 @@ bool Storage::hasRecord(Record r) { void Storage::destroyRecord(Record record) { emptyTrash(); - const Record metadataRecord = recordBaseNamedWithExtension(record.fullName(), "sys"); - if (!metadataRecord.isNull()) { - InternalStorage::destroyRecord(metadataRecord); - } m_trashRecord = record; } @@ -181,21 +177,4 @@ void Storage::emptyTrash() { } } -Storage::Metadata Storage::metadataForRecord(Record record) { - return recordBaseNamedWithExtension(record.fullName(), "sys").value(); -} - -Storage::Record::ErrorStatus Storage::setMetadataForRecord(Record record, Metadata data) { - Record metadataRecord = Record(record.fullName(), "sys"); - if (!hasRecord(metadataRecord)) { - return createRecordWithExtension(record.fullName(), "sys", data.buffer, data.size); - } else { - return metadataRecord.setValue(data); - } -} - -void Storage::removeMetadataForRecord(Record record) { - recordBaseNamedWithExtension(record.fullName(), "sys").destroy(); -} - } diff --git a/ion/src/shared/unicode/utf8_helper.cpp b/ion/src/shared/unicode/utf8_helper.cpp index 45d841efed8..0481df94134 100644 --- a/ion/src/shared/unicode/utf8_helper.cpp +++ b/ion/src/shared/unicode/utf8_helper.cpp @@ -54,34 +54,6 @@ const char * CodePointSearch(const char * s, CodePoint c, const char * stoppingP return currentPointer; } -const char * LastCodePoint(const char * s, CodePoint c, const char * stoppingPosition) { - if (UTF8Decoder::CharSizeOfCodePoint(c) == 1) { - const char * result = nullptr; - const char * position = s; - while (*position != 0 && (stoppingPosition == nullptr || position != stoppingPosition)) { - if (*position == c) { - result = position; - } - position++; - } - return result ? result : position; - } - UTF8Decoder decoder(s); - const char * currentPointer = s; - const char * result = nullptr; - CodePoint codePoint = decoder.nextCodePoint(); - const char * nextPointer = decoder.stringPosition(); - while (codePoint != UCodePointNull && (stoppingPosition == nullptr || currentPointer < stoppingPosition)) { - if (c == codePoint) { - result = currentPointer; - } - currentPointer = nextPointer; - codePoint = decoder.nextCodePoint(); - nextPointer = decoder.stringPosition(); - } - return result ? result : currentPointer; -} - bool HasCodePoint(const char * s, CodePoint c, const char * stoppingPosition) { assert(c != 0); const char * resultPosition = CodePointSearch(s, c, stoppingPosition); From 0751c88a2d018e93651ecd2baea1cf27ca30a859 Mon Sep 17 00:00:00 2001 From: Mino1289 <68814671+Mino1289@users.noreply.github.com> Date: Fri, 17 Jun 2022 12:25:38 +0200 Subject: [PATCH 292/355] [TexParser] Adding Sum, Product and Integral (#252) --- apps/reader/tex_parser.cpp | 85 ++++++++++++++++++++++++++++---------- apps/reader/tex_parser.h | 18 +++++--- 2 files changed, 77 insertions(+), 26 deletions(-) diff --git a/apps/reader/tex_parser.cpp b/apps/reader/tex_parser.cpp index ff9b80749e0..3008db6d913 100644 --- a/apps/reader/tex_parser.cpp +++ b/apps/reader/tex_parser.cpp @@ -153,6 +153,18 @@ Layout TexParser::popCommand() { return popCeilCommand(); } } + if (strncmp(k_integralCommand, m_text, strlen(k_integralCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_integralCommand)))) { + m_text += strlen(k_integralCommand); + return popIntegralCommand(); + } + } + if (strncmp(k_intsetCommand, m_text, strlen(k_intsetCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_intsetCommand)))) { + m_text += strlen(k_intsetCommand); + return popIntsetCommand(); + } + } if (strncmp(k_floorCommand, m_text, strlen(k_floorCommand)) == 0) { if (isCommandEnded(*(m_text + strlen(k_floorCommand)))) { m_text += strlen(k_floorCommand); @@ -171,43 +183,49 @@ Layout TexParser::popCommand() { return popLeftCommand(); } } + if (strncmp(k_overrightArrowCommand, m_text, strlen(k_overrightArrowCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_overrightArrowCommand)))) { + m_text += strlen(k_overrightArrowCommand); + return popOverrightarrowCommand(); + } + } + if (strncmp(k_overlineCommand, m_text, strlen(k_overlineCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_overlineCommand)))) { + m_text += strlen(k_overlineCommand); + return popOverlineCommand(); + } + } + if (strncmp(k_productCommand, m_text, strlen(k_productCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_productCommand)))) { + m_text += strlen(k_productCommand); + return popProductCommand(); + } + } if (strncmp(k_rightCommand, m_text, strlen(k_rightCommand)) == 0) { if (isCommandEnded(*(m_text + strlen(k_rightCommand)))) { m_text += strlen(k_rightCommand); return popRightCommand(); } } - if (strncmp(k_sqrtCommand, m_text, strlen(k_sqrtCommand)) == 0) { - if (isCommandEnded(*(m_text + strlen(k_sqrtCommand)))) { - m_text += strlen(k_sqrtCommand); - return popSqrtCommand(); - } - } - if (strncmp(k_spaceCommand, m_text, strlen(k_spaceCommand)) == 0) { if (isCommandEnded(*(m_text + strlen(k_spaceCommand)))) { m_text += strlen(k_spaceCommand); return popSpaceCommand(); } } - if (strncmp(k_overrightArrowCommand, m_text, strlen(k_overrightArrowCommand)) == 0) { - if (isCommandEnded(*(m_text + strlen(k_overrightArrowCommand)))) { - m_text += strlen(k_overrightArrowCommand); - return popOverrightarrowCommand(); - } - } - if (strncmp(k_overlineCommand, m_text, strlen(k_overlineCommand)) == 0) { - if (isCommandEnded(*(m_text + strlen(k_overlineCommand)))) { - m_text += strlen(k_overlineCommand); - return popOverlineCommand(); + if (strncmp(k_sqrtCommand, m_text, strlen(k_sqrtCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_sqrtCommand)))) { + m_text += strlen(k_sqrtCommand); + return popSqrtCommand(); } } - if (strncmp(k_intsetCommand, m_text, strlen(k_intsetCommand)) == 0) { - if (isCommandEnded(*(m_text + strlen(k_intsetCommand)))) { - m_text += strlen(k_intsetCommand); - return popIntsetCommand(); + if (strncmp(k_sumCommand, m_text, strlen(k_sumCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_sumCommand)))) { + m_text += strlen(k_sumCommand); + return popSumCommand(); } } + for (int i = 0; i < k_NumberOfSymbols; i++) { if (strncmp(k_SymbolsCommands[i], m_text, strlen(k_SymbolsCommands[i])) == 0) { if (isCommandEnded(*(m_text + strlen(k_SymbolsCommands[i])))) { @@ -255,6 +273,14 @@ Layout TexParser::popFracCommand() { return l; } +Layout TexParser::popIntegralCommand() { + Layout arg = popBlock(); + Layout var = popBlock(); + Layout start = popBlock(); + Layout end = popBlock(); + return IntegralLayout::Builder(arg, var, start, end); +} + Layout TexParser::popIntsetCommand() { HorizontalLayout intset = HorizontalLayout::Builder(); intset.addOrMergeChildAtIndex(CodePointLayout::Builder(0x27e6), 0, false); @@ -268,6 +294,15 @@ Layout TexParser::popLeftCommand() { return LeftParenthesisLayout::Builder(); } +Layout TexParser::popProductCommand() { + Layout arg = popBlock(); + Layout var = popBlock(); + Layout start = popBlock(); + Layout end = popBlock(); + return ProductLayout::Builder(arg, var, start, end); +} + + Layout TexParser::popRightCommand() { m_text++; return RightParenthesisLayout::Builder(); @@ -288,6 +323,14 @@ Layout TexParser::popSqrtCommand() { } } +Layout TexParser::popSumCommand() { + Layout arg = popBlock(); + Layout var = popBlock(); + Layout start = popBlock(); + Layout end = popBlock(); + return SumLayout::Builder(arg, var, start, end); +} + Layout TexParser::popSpaceCommand() { return LayoutHelper::String(" ", 1); } diff --git a/apps/reader/tex_parser.h b/apps/reader/tex_parser.h index 4010ede3d90..088b3385b3f 100644 --- a/apps/reader/tex_parser.h +++ b/apps/reader/tex_parser.h @@ -24,13 +24,17 @@ class TexParser { Layout popCeilCommand(); Layout popFloorCommand(); Layout popFracCommand(); + Layout popIntegralCommand(); Layout popIntsetCommand(); Layout popLeftCommand(); + Layout popOverrightarrowCommand(); + Layout popOverlineCommand(); + Layout popProductCommand(); Layout popRightCommand(); Layout popSqrtCommand(); + Layout popSumCommand(); Layout popSpaceCommand(); - Layout popOverrightarrowCommand(); - Layout popOverlineCommand(); + //Symbols Layout popSymbolCommand(int SymbolIndex); @@ -46,13 +50,17 @@ class TexParser { static constexpr char const * k_ceilCommand = "ceil"; static constexpr char const * k_floorCommand = "floor"; static constexpr char const * k_fracCommand = "frac"; + static constexpr char const * k_integralCommand = "int"; static constexpr char const * k_intsetCommand = "intset"; static constexpr char const * k_leftCommand = "left"; - static constexpr char const * k_rightCommand = "right"; - static constexpr char const * k_sqrtCommand = "sqrt"; - static constexpr char const * k_spaceCommand = "space"; static constexpr char const * k_overrightArrowCommand = "overrightarrow"; static constexpr char const * k_overlineCommand = "overline"; + static constexpr char const * k_productCommand = "prod"; + static constexpr char const * k_rightCommand = "right"; + static constexpr char const * k_spaceCommand = "space"; + static constexpr char const * k_sqrtCommand = "sqrt"; + static constexpr char const * k_sumCommand = "sum"; + }; From 125e1a8a82dcd05a9ef8b93117a37c68cf044e63 Mon Sep 17 00:00:00 2001 From: Laury Date: Fri, 17 Jun 2022 18:01:15 +0200 Subject: [PATCH 293/355] [settings] Remove option to save cursor --- apps/code/editor_controller.cpp | 6 +----- apps/global_preferences.h | 4 ---- apps/settings/base.de.i18n | 1 - apps/settings/base.en.i18n | 1 - apps/settings/base.es.i18n | 1 - apps/settings/base.fr.i18n | 1 - apps/settings/base.hu.i18n | 1 - apps/settings/base.it.i18n | 1 - apps/settings/base.nl.i18n | 1 - apps/settings/base.pt.i18n | 1 - apps/settings/main_controller.cpp | 2 +- apps/settings/main_controller.h | 2 +- apps/settings/sub_menu/code_options_controller.cpp | 13 ------------- apps/settings/sub_menu/code_options_controller.h | 1 - 14 files changed, 3 insertions(+), 33 deletions(-) diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index be09c9662b8..f314f3d7294 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -64,11 +64,7 @@ void EditorController::didBecomeFirstResponder() { void EditorController::viewWillAppear() { ViewController::viewWillAppear(); m_editorView.loadSyntaxHighlighter(); - if(GlobalPreferences::sharedGlobalPreferences()->cursorSaving()) { - m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); - } else { - m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); - } + m_editorView.setCursorLocation(m_editorView.text() + strlen(m_editorView.text())); } void EditorController::viewDidDisappear() { diff --git a/apps/global_preferences.h b/apps/global_preferences.h index ab48e2c1482..3077b93fee2 100644 --- a/apps/global_preferences.h +++ b/apps/global_preferences.h @@ -36,8 +36,6 @@ class GlobalPreferences { void setAutocomplete(bool autocomple) { m_autoComplete = autocomple; } bool syntaxhighlighting() const { return m_syntaxhighlighting; } void setSyntaxhighlighting(bool syntaxhighlight) { m_syntaxhighlighting = syntaxhighlight; } - bool cursorSaving() const { return m_cursorSaving; } - void setCursorSaving(bool cursorsave) { m_cursorSaving = cursorsave; } int brightnessLevel() const { return m_brightnessLevel; } void setBrightnessLevel(int brightnessLevel); const KDFont * font() const { return m_font; } @@ -65,7 +63,6 @@ class GlobalPreferences { m_dfuUnlocked(false), m_autoComplete(true), m_syntaxhighlighting(true), - m_cursorSaving(true), m_brightnessLevel(Ion::Backlight::MaxBrightness), m_idleBeforeSuspendSeconds(55), m_idleBeforeDimmingSeconds(45), @@ -83,7 +80,6 @@ class GlobalPreferences { bool m_dfuUnlocked; bool m_autoComplete; bool m_syntaxhighlighting; - bool m_cursorSaving; int m_brightnessLevel; int m_idleBeforeSuspendSeconds; int m_idleBeforeDimmingSeconds; diff --git a/apps/settings/base.de.i18n b/apps/settings/base.de.i18n index dfd788b1b4e..3eb0e59fbb7 100644 --- a/apps/settings/base.de.i18n +++ b/apps/settings/base.de.i18n @@ -72,7 +72,6 @@ Time = "Uhrzeit" RTCWarning1 = "Das Aktivieren der Uhr verkürzt die" RTCWarning2 = "Akkulaufzeit im Bereitschaftsmodus." SyntaxHighlighting = "Syntaxhervorhebung" -CursorSaving = "Cursor speichern" USBExplanation1 = "USB-Schutz schützt Ihren" USBExplanation2 = "Taschenrechner vor" USBExplanation3 = "unbeabsichtigter Verriegelung" diff --git a/apps/settings/base.en.i18n b/apps/settings/base.en.i18n index fe2428773c7..c6a048e2a7c 100644 --- a/apps/settings/base.en.i18n +++ b/apps/settings/base.en.i18n @@ -72,7 +72,6 @@ Time = "Time" RTCWarning1 = "Enabling the clock drains the battery faster" RTCWarning2 = "when the calculator is powered off." SyntaxHighlighting = "Syntax Highlighting" -CursorSaving = "Cursor saving" USBExplanation1 = "The USB protection protects" USBExplanation2 = "the calculator from" USBExplanation3 = "unintentional locking" diff --git a/apps/settings/base.es.i18n b/apps/settings/base.es.i18n index c50ef97fa1f..839f25ba8a3 100644 --- a/apps/settings/base.es.i18n +++ b/apps/settings/base.es.i18n @@ -72,7 +72,6 @@ Time = "Hora" RTCWarning1 = "Activar el reloj gasta la batería más rápido" RTCWarning2 = "cuando la calculadora está apagada." SyntaxHighlighting = "Resaltado de sintaxis" -CursorSaving = "Ahorro de cursor" USBExplanation1 = "La protección USB protege" USBExplanation2 = "su calculadora del" USBExplanation3 = "bloqueo involuntario" diff --git a/apps/settings/base.fr.i18n b/apps/settings/base.fr.i18n index 55cea88dfb6..87a4596ce1c 100644 --- a/apps/settings/base.fr.i18n +++ b/apps/settings/base.fr.i18n @@ -72,7 +72,6 @@ Time = "Heure" RTCWarning1 = "Activer l'horloge décharge la batterie plus" RTCWarning2 = "vite quand la calculatrice est éteinte." SyntaxHighlighting = "Coloration syntaxique" -CursorSaving = "Sauvegarde du curseur" USBExplanation1 = "La protection USB protège votre" USBExplanation2 = "calculatrice contre un verrouillage" USBExplanation3 = "non-intentionnel" diff --git a/apps/settings/base.hu.i18n b/apps/settings/base.hu.i18n index 8476c7eff40..99dc79fc3df 100644 --- a/apps/settings/base.hu.i18n +++ b/apps/settings/base.hu.i18n @@ -72,7 +72,6 @@ Time = "Óra" RTCWarning1 = "Amikor a számológép alvómódban van, az óra" RTCWarning2 = "használása az elemet gyorsabban meríti ki." SyntaxHighlighting = "Szintaxis kiemelés" -CursorSaving = "Kurzor mentése" USBExplanation1 = "Az USB-védelem megvédi" USBExplanation2 = "a számológépet a nem" USBExplanation3 = "szándékos reteszeléstől" diff --git a/apps/settings/base.it.i18n b/apps/settings/base.it.i18n index 854796e786a..2603da889ce 100644 --- a/apps/settings/base.it.i18n +++ b/apps/settings/base.it.i18n @@ -72,7 +72,6 @@ Time = "Time" RTCWarning1 = "Enabling the clock drains the battery faster" RTCWarning2 = "when the calculator is powered off." SyntaxHighlighting = "Evidenziazione della sintassi" -CursorSaving = "Salvataggio cursore" USBExplanation1 = "La protezione USB protegge" USBExplanation2 = "la calcolatrice dal" USBExplanation3 = "blocco involontario" diff --git a/apps/settings/base.nl.i18n b/apps/settings/base.nl.i18n index 474812c5cea..d71a7682472 100644 --- a/apps/settings/base.nl.i18n +++ b/apps/settings/base.nl.i18n @@ -72,7 +72,6 @@ Time = "Time" RTCWarning1 = "Enabling the clock drains the battery faster" RTCWarning2 = "when the calculator is powered off." SyntaxHighlighting = "Syntax Highlighting" -CursorSaving = "Cursor opslaan" USBExplanation1 = "USB-beveiliging beschermt uw" USBExplanation2 = "rekenmachine tegen" USBExplanation3 = "onbedoelde vergrendeling" diff --git a/apps/settings/base.pt.i18n b/apps/settings/base.pt.i18n index dd2a2cf26ee..b7639cb6e7b 100644 --- a/apps/settings/base.pt.i18n +++ b/apps/settings/base.pt.i18n @@ -72,7 +72,6 @@ Time = "Time" RTCWarning1 = "Enabling the clock drains the battery faster" RTCWarning2 = "when the calculator is powered off." SyntaxHighlighting = "Destaque da sintaxe" -CursorSaving = "Economia de cursor" USBExplanation1 = "A proteção USB protege" USBExplanation2 = "sua calculadora contra" USBExplanation3 = "bloqueios não intencionais" diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index d7a15e3a1a4..61eb08f13ce 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -26,7 +26,7 @@ constexpr SettingsMessageTree s_contributorsChildren[18] = {SettingsMessageTree( // Code Settings #ifdef HAS_CODE -constexpr SettingsMessageTree s_codeChildren[4] = {SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Autocomplete), SettingsMessageTree(I18n::Message::SyntaxHighlighting), SettingsMessageTree(I18n::Message::CursorSaving)}; +constexpr SettingsMessageTree s_codeChildren[3] = {SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Autocomplete), SettingsMessageTree(I18n::Message::SyntaxHighlighting)}; #endif constexpr SettingsMessageTree s_modelFontChildren[2] = {SettingsMessageTree(I18n::Message::LargeFont), SettingsMessageTree(I18n::Message::SmallFont)}; diff --git a/apps/settings/main_controller.h b/apps/settings/main_controller.h index 3e1c5ba82d2..e78aff9a243 100644 --- a/apps/settings/main_controller.h +++ b/apps/settings/main_controller.h @@ -25,7 +25,7 @@ extern const Shared::SettingsMessageTree s_symbolChildren[4]; extern const Shared::SettingsMessageTree s_symbolFunctionChildren[3]; extern const Shared::SettingsMessageTree s_modelMathOptionsChildren[6]; extern const Shared::SettingsMessageTree s_modelFontChildren[2]; -extern const Shared::SettingsMessageTree s_codeChildren[4]; +extern const Shared::SettingsMessageTree s_codeChildren[3]; extern const Shared::SettingsMessageTree s_modelDateTimeChildren[3]; extern const Shared::SettingsMessageTree s_accessibilityChildren[6]; extern const Shared::SettingsMessageTree s_contributorsChildren[18]; diff --git a/apps/settings/sub_menu/code_options_controller.cpp b/apps/settings/sub_menu/code_options_controller.cpp index f3c17b2fc3f..ae7d5bbc2a2 100644 --- a/apps/settings/sub_menu/code_options_controller.cpp +++ b/apps/settings/sub_menu/code_options_controller.cpp @@ -13,7 +13,6 @@ CodeOptionsController::CodeOptionsController(Responder * parentResponder) : m_chevronCellFontSize.setMessageFont(KDFont::LargeFont); m_switchCellAutoCompletion.setMessageFont(KDFont::LargeFont); m_switchCellSyntaxHighlighting.setMessageFont(KDFont::LargeFont); - m_switchCellCursorSaving.setMessageFont(KDFont::LargeFont); } bool CodeOptionsController::handleEvent(Ion::Events::Event event) { @@ -27,10 +26,6 @@ bool CodeOptionsController::handleEvent(Ion::Events::Event event) { GlobalPreferences::sharedGlobalPreferences()->setSyntaxhighlighting(!GlobalPreferences::sharedGlobalPreferences()->syntaxhighlighting()); m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); break; - case 3: - GlobalPreferences::sharedGlobalPreferences()->setCursorSaving(!GlobalPreferences::sharedGlobalPreferences()->cursorSaving()); - m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); - break; default: GenericSubController * subController = nullptr; subController = &m_preferencesController; @@ -54,9 +49,6 @@ HighlightCell * CodeOptionsController::reusableCell(int index, int type) { else if (index == 1) { return &m_switchCellAutoCompletion; } - else if (index == 2) { - return &m_switchCellCursorSaving; - } return &m_switchCellSyntaxHighlighting; } @@ -87,11 +79,6 @@ void CodeOptionsController::willDisplayCellForIndex(HighlightCell * cell, int in SwitchView * mySwitch = (SwitchView *)mySwitchCell->accessoryView(); mySwitch->setState(GlobalPreferences::sharedGlobalPreferences()->syntaxhighlighting()); } - else if (thisLabel == I18n::Message::CursorSaving) { - MessageTableCellWithSwitch * mySwitchCell = (MessageTableCellWithSwitch *)cell; - SwitchView * mySwitch = (SwitchView *)mySwitchCell->accessoryView(); - mySwitch->setState(GlobalPreferences::sharedGlobalPreferences()->cursorSaving()); - } #endif } diff --git a/apps/settings/sub_menu/code_options_controller.h b/apps/settings/sub_menu/code_options_controller.h index 854de5e8e8a..5f9b6acea22 100644 --- a/apps/settings/sub_menu/code_options_controller.h +++ b/apps/settings/sub_menu/code_options_controller.h @@ -19,7 +19,6 @@ class CodeOptionsController : public GenericSubController { MessageTableCellWithChevronAndMessage m_chevronCellFontSize; MessageTableCellWithSwitch m_switchCellAutoCompletion; MessageTableCellWithSwitch m_switchCellSyntaxHighlighting; - MessageTableCellWithSwitch m_switchCellCursorSaving; }; } From e8ea693e5c716174401b2c7552655a0930e506b1 Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 18 Jun 2022 22:35:09 +0200 Subject: [PATCH 294/355] [calculation] Dimension in additional output for values with units --- .../unit_list_controller.cpp | 56 ++++++++++++++++++- .../additional_outputs/unit_list_controller.h | 14 ++++- apps/calculation/base.de.i18n | 26 ++++++++- apps/calculation/base.en.i18n | 26 ++++++++- apps/calculation/base.es.i18n | 26 ++++++++- apps/calculation/base.fr.i18n | 26 ++++++++- apps/calculation/base.hu.i18n | 26 ++++++++- apps/calculation/base.it.i18n | 26 ++++++++- apps/calculation/base.nl.i18n | 26 ++++++++- apps/calculation/base.pt.i18n | 26 ++++++++- poincare/include/poincare/unit.h | 28 +++++++++- poincare/src/unit.cpp | 7 ++- poincare/test/expression_properties.cpp | 3 +- 13 files changed, 300 insertions(+), 16 deletions(-) diff --git a/apps/calculation/additional_outputs/unit_list_controller.cpp b/apps/calculation/additional_outputs/unit_list_controller.cpp index 869826d5e4f..31583c369e3 100644 --- a/apps/calculation/additional_outputs/unit_list_controller.cpp +++ b/apps/calculation/additional_outputs/unit_list_controller.cpp @@ -12,6 +12,21 @@ using namespace Shared; namespace Calculation { + +UnitListController::UnitListController(EditExpressionController * editExpressionController) : + ExpressionsListController(editExpressionController), + m_dimensionMessage(I18n::Message::Default) +{ + m_dimensionCell.setMessageFont(KDFont::LargeFont); +} + +bool UnitListController::handleEvent(Ion::Events::Event event) { + if (selectedRow() == 0 && (event == Ion::Events::OK || event == Ion::Events::EXE)) { + return true; + } + return ListController::handleEvent(event); +} + void UnitListController::setExpression(Poincare::Expression e) { ExpressionsListController::setExpression(e); assert(!m_expression.isUninitialized()); @@ -22,6 +37,7 @@ void UnitListController::setExpression(Poincare::Expression e) { for (size_t i = 0; i < k_maxNumberOfRows; i++) { expressions[i] = Expression(); } + m_dimensionMessage = I18n::Message::Default; /* 1. First rows: miscellaneous classic units for some dimensions, in both * metric and imperial units. */ @@ -37,7 +53,7 @@ void UnitListController::setExpression(Poincare::Expression e) { GlobalPreferences::sharedGlobalPreferences()->unitFormat(), ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); - int numberOfExpressions = Unit::SetAdditionalExpressions(units, value, expressions, k_maxNumberOfRows, reductionContext); + int numberOfExpressions = Unit::SetAdditionalExpressionsAndMessage(units, value, expressions, k_maxNumberOfRows, reductionContext, &m_dimensionMessage); // 2. SI units only assert(numberOfExpressions < k_maxNumberOfRows - 1); @@ -89,6 +105,44 @@ void UnitListController::setExpression(Poincare::Expression e) { } } +int UnitListController::numberOfRows() const { + int messageRow = m_dimensionMessage != I18n::Message::Default ? 1 : 0; + return ExpressionsListController::numberOfRows() + messageRow; +} + +void UnitListController::willDisplayCellForIndex(HighlightCell * cell, int index) { + if (index == 0) { + MessageTableCell * messageTableCell = (MessageTableCell *)cell; + messageTableCell->setMessage(m_dimensionMessage); + } else { + ExpressionsListController::willDisplayCellForIndex(cell, index - 1); + } +} + +KDCoordinate UnitListController::rowHeight(int index) { + if (index == 0) { + return 35; + } else { + return ExpressionsListController::rowHeight(index - 1); + } +} + +HighlightCell * UnitListController::reusableCell(int index, int type) { + if (type == 0) { + return ExpressionsListController::reusableCell(index, type); + } else { + return &m_dimensionCell; + } +} + +int UnitListController::typeAtLocation(int i, int j) { + if (j == 0) { + return 1; + } else { + return ExpressionsListController::typeAtLocation(i, j - 1); + } +} + I18n::Message UnitListController::messageAtIndex(int index) { return (I18n::Message)0; } diff --git a/apps/calculation/additional_outputs/unit_list_controller.h b/apps/calculation/additional_outputs/unit_list_controller.h index 58f6d1e0d90..477656b33d9 100644 --- a/apps/calculation/additional_outputs/unit_list_controller.h +++ b/apps/calculation/additional_outputs/unit_list_controller.h @@ -7,13 +7,23 @@ namespace Calculation { class UnitListController : public ExpressionsListController { public: - UnitListController(EditExpressionController * editExpressionController) : - ExpressionsListController(editExpressionController) {} + UnitListController(EditExpressionController * editExpressionController); + + /* Responder */ + bool handleEvent(Ion::Events::Event event) override; void setExpression(Poincare::Expression e) override; + int reusableCellCount(int type) override { return type == 0 ? ExpressionsListController::reusableCellCount(type) : 1; } + HighlightCell * reusableCell(int index, int type) override; + KDCoordinate rowHeight(int j) override; + int typeAtLocation(int i, int j) override; + void willDisplayCellForIndex(HighlightCell * cell, int index) override; + int numberOfRows() const override; private: I18n::Message messageAtIndex(int index) override; + I18n::Message m_dimensionMessage; + MessageTableCell m_dimensionCell; }; } diff --git a/apps/calculation/base.de.i18n b/apps/calculation/base.de.i18n index 5d12d9b2214..c2c11d837ab 100644 --- a/apps/calculation/base.de.i18n +++ b/apps/calculation/base.de.i18n @@ -17,4 +17,28 @@ FactorizedForm = "Factorisierte Form" Discriminant = "Diskriminante" OnlyRoot = "Wurzel" FirstRoot = "Erste Wurzel" -SecondRoot = "Zweite Wurzel" \ No newline at end of file +SecondRoot = "Zweite Wurzel" +TimeDimension = "Zeit" +DistanceDimension = "Distanz" +MassDimension = "Masse" +CurrentDimension = "Betrieb" +TemperatureDimension = "Temperatur" +AmountOfSubstanceDimension = "Quantität der Materie" +LuminousIntensityDimension = "Lichtintensität" +FrequencyDimension = "Frequenz" +ForceDimension = "Stärke" +PressureDimension = "Druck" +EnergyDimension = "Energie" +PowerDimension = "Mächtig" +ElectricChargeDimension = "Elektrische Ladung" +ElectricPotentialDimension = "Elektrisches Potenzial" +ElectricCapacitanceDimension = "Elektrische Kapazität" +ElectricResistanceDimension = "Elektrischer Wiederstand" +ElectricConductanceDimension = "elektrische Leitfähigkeit" +MagneticFluxDimension = "magnetischer Fluss" +MagneticFieldDimension = "Magnetfeld" +InductanceDimension = "Induktivität" +CatalyticActivityDimension = "Katalytische Aktivität" +SurfaceDimension = "Auftauchen" +VolumeDimension = "Volumen" +SpeedDimension = "Geschwindigkeit" diff --git a/apps/calculation/base.en.i18n b/apps/calculation/base.en.i18n index ada600b3c71..b636b47a51e 100644 --- a/apps/calculation/base.en.i18n +++ b/apps/calculation/base.en.i18n @@ -17,4 +17,28 @@ FactorizedForm = "Factorized form" Discriminant = "Discriminant" OnlyRoot = "Root" FirstRoot = "First root" -SecondRoot = "Second root" \ No newline at end of file +SecondRoot = "Second root" +TimeDimension = "Time" +DistanceDimension = "Distance" +MassDimension = "Mass" +CurrentDimension = "Running" +TemperatureDimension = "Temperature" +AmountOfSubstanceDimension = "Quantity of matter" +LuminousIntensityDimension = "Light intensity" +FrequencyDimension = "Frequency" +ForceDimension = "Strength" +PressureDimension = "Pressure" +EnergyDimension = "Energy" +PowerDimension = "Powerful" +ElectricChargeDimension = "Electrical charge" +ElectricPotentialDimension = "Electric potential" +ElectricCapacitanceDimension = "Electrical capacity" +ElectricResistanceDimension = "Electrical resistance" +ElectricConductanceDimension = "electrical conductance" +MagneticFluxDimension = "magnetic flux" +MagneticFieldDimension = "Magnetic field" +InductanceDimension = "Inductance" +CatalyticActivityDimension = "Catalytic activity" +SurfaceDimension = "Surface" +VolumeDimension = "Volume" +SpeedDimension = "Speed" diff --git a/apps/calculation/base.es.i18n b/apps/calculation/base.es.i18n index 1b9ffde00cb..b23115eab28 100644 --- a/apps/calculation/base.es.i18n +++ b/apps/calculation/base.es.i18n @@ -17,4 +17,28 @@ FactorizedForm = "Forma factorizada" Discriminant = "Discriminante" OnlyRoot = "Raíz" FirstRoot = "Primera raíz" -SecondRoot = "Segunda raíz" \ No newline at end of file +SecondRoot = "Segunda raíz" +TimeDimension = "Tiempo" +DistanceDimension = "Distancia" +MassDimension = "Masa" +CurrentDimension = "Correr" +TemperatureDimension = "La temperatura" +AmountOfSubstanceDimension = "cantidad de materia" +LuminousIntensityDimension = "Intensidad de luz" +FrequencyDimension = "Frecuencia" +ForceDimension = "Fuerza" +PressureDimension = "Presión" +EnergyDimension = "Energía" +PowerDimension = "Potencia" +ElectricChargeDimension = "Carga eléctrica" +ElectricPotentialDimension = "Potencial eléctrico" +ElectricCapacitanceDimension = "Capacidad eléctrica" +ElectricResistanceDimension = "Resistencia eléctrica" +ElectricConductanceDimension = "conductancia eléctrica" +MagneticFluxDimension = "flujo magnético" +MagneticFieldDimension = "Campo magnético" +InductanceDimension = "Inductancia" +CatalyticActivityDimension = "Actividad catalítica" +SurfaceDimension = "Superficie" +VolumeDimension = "Volumen" +SpeedDimension = "Velocidad" diff --git a/apps/calculation/base.fr.i18n b/apps/calculation/base.fr.i18n index a8432eeb09d..74416f32c0a 100644 --- a/apps/calculation/base.fr.i18n +++ b/apps/calculation/base.fr.i18n @@ -17,4 +17,28 @@ FactorizedForm = "Forme factorisée" Discriminant = "Discriminant" OnlyRoot = "Racine" FirstRoot = "Première racine" -SecondRoot = "Seconde racine" \ No newline at end of file +SecondRoot = "Seconde racine" +TimeDimension = "Temps" +DistanceDimension = "Distance" +MassDimension = "Masse" +CurrentDimension = "Courant" +TemperatureDimension = "Température" +AmountOfSubstanceDimension = "Quantité de matière" +LuminousIntensityDimension = "Intensité lumineuse" +FrequencyDimension = "Fréquence" +ForceDimension = "Force" +PressureDimension = "Pression" +EnergyDimension = "Énergie" +PowerDimension = "Puissance" +ElectricChargeDimension = "Charge électrique" +ElectricPotentialDimension = "Potentiel électrique" +ElectricCapacitanceDimension = "Capacité électrique" +ElectricResistanceDimension = "Résistance électrique" +ElectricConductanceDimension = "Conductance électrique" +MagneticFluxDimension = "Flux magnétique" +MagneticFieldDimension = "Champ magnétique" +InductanceDimension = "Inductance" +CatalyticActivityDimension = "Activité catalytique" +SurfaceDimension = "Surface" +VolumeDimension = "Volume" +SpeedDimension = "Vitesse" \ No newline at end of file diff --git a/apps/calculation/base.hu.i18n b/apps/calculation/base.hu.i18n index c798ac81719..7879cdf4280 100644 --- a/apps/calculation/base.hu.i18n +++ b/apps/calculation/base.hu.i18n @@ -17,4 +17,28 @@ FactorizedForm = "Factorizált forma" Discriminant = "Discriminant" OnlyRoot = "Gyökér" FirstRoot = "Első gyökér" -SecondRoot = "Második gyökér" \ No newline at end of file +SecondRoot = "Második gyökér" +TimeDimension = "Idő" +DistanceDimension = "Távolság" +MassDimension = "Tömeg" +CurrentDimension = "Futó" +TemperatureDimension = "Hőfok" +AmountOfSubstanceDimension = "Az anyag mennyisége" +LuminousIntensityDimension = "Fény intenzitása" +FrequencyDimension = "Frekvencia" +ForceDimension = "Erő" +PressureDimension = "Nyomás" +EnergyDimension = "Energia" +PowerDimension = "Erős" +ElectricChargeDimension = "Elektromos töltő" +ElectricPotentialDimension = "Elektromos potenciál" +ElectricCapacitanceDimension = "Elektromos kapacitás" +ElectricResistanceDimension = "Elektromos ellenállás" +ElectricConductanceDimension = "elektromos vezetőképesség" +MagneticFluxDimension = "mágneses fluxus" +MagneticFieldDimension = "Mágneses mező" +InductanceDimension = "Induktivitás" +CatalyticActivityDimension = "Katalitikus aktivitás" +SurfaceDimension = "Felület" +VolumeDimension = "Hangerő" +SpeedDimension = "Sebesség" diff --git a/apps/calculation/base.it.i18n b/apps/calculation/base.it.i18n index c3961427173..8e00cd884e2 100644 --- a/apps/calculation/base.it.i18n +++ b/apps/calculation/base.it.i18n @@ -17,4 +17,28 @@ FactorizedForm = "Forma fattorizzata" Discriminant = "Discriminante" OnlyRoot = "Radice" FirstRoot = "Prima radice" -SecondRoot = "Seconda radice" \ No newline at end of file +SecondRoot = "Seconda radice" +TimeDimension = "Volta" +DistanceDimension = "Distanza" +MassDimension = "Messa" +CurrentDimension = "In esecuzione" +TemperatureDimension = "Temperatura" +AmountOfSubstanceDimension = "Quantità di materia" +LuminousIntensityDimension = "Intensità luminosa" +FrequencyDimension = "Frequenza" +ForceDimension = "Forza" +PressureDimension = "Pressione" +EnergyDimension = "Energia" +PowerDimension = "Potere" +ElectricChargeDimension = "Carica elettrica" +ElectricPotentialDimension = "Potenziale elettrico" +ElectricCapacitanceDimension = "Capacità elettrica" +ElectricResistanceDimension = "Resistenza elettrica" +ElectricConductanceDimension = "conduttanza elettrica" +MagneticFluxDimension = "flusso magnetico" +MagneticFieldDimension = "Campo magnetico" +InductanceDimension = "Induttanza" +CatalyticActivityDimension = "Attività catalitica" +SurfaceDimension = "Superficie" +VolumeDimension = "Volume" +SpeedDimension = "Velocità" diff --git a/apps/calculation/base.nl.i18n b/apps/calculation/base.nl.i18n index b156ddd985d..c2385f0186b 100644 --- a/apps/calculation/base.nl.i18n +++ b/apps/calculation/base.nl.i18n @@ -17,4 +17,28 @@ FactorizedForm = "Factorized vorm" Discriminant = "Discriminant" OnlyRoot = "Wortel" FirstRoot = "Eerste wortel" -SecondRoot = "Tweede wortel" \ No newline at end of file +SecondRoot = "Tweede wortel" +TimeDimension = "Tijd" +DistanceDimension = "Afstand" +MassDimension = "Massa" +CurrentDimension = "Rennen" +TemperatureDimension = "Temperatuur" +AmountOfSubstanceDimension = "Hoeveelheid materie" +LuminousIntensityDimension = "Lichtsterkte" +FrequencyDimension = "Frequentie" +ForceDimension = "Kracht" +PressureDimension = "Druk" +EnergyDimension = "Energie" +PowerDimension = "Kracht" +ElectricChargeDimension = "Elektrische lading" +ElectricPotentialDimension = "elektrische potentiaal" +ElectricCapacitanceDimension = "elektrische capaciteit:" +ElectricResistanceDimension = "Elektrische weerstand" +ElectricConductanceDimension = "elektrische geleiding:" +MagneticFluxDimension = "magnetische flux" +MagneticFieldDimension = "Magnetisch veld" +InductanceDimension = "Inductie" +CatalyticActivityDimension = "Katalytische activiteit" +SurfaceDimension = "Oppervlak" +VolumeDimension = "Volume" +SpeedDimension = "Snelheid" diff --git a/apps/calculation/base.pt.i18n b/apps/calculation/base.pt.i18n index ae4c401babe..c6a1aa7ae01 100644 --- a/apps/calculation/base.pt.i18n +++ b/apps/calculation/base.pt.i18n @@ -17,4 +17,28 @@ FactorizedForm = "Factorized form" Discriminant = "Discriminante" OnlyRoot = "Raiz" FirstRoot = "Primeira raiz" -SecondRoot = "Segunda raiz" \ No newline at end of file +SecondRoot = "Segunda raiz" +TimeDimension = "Tempo" +DistanceDimension = "Distância" +MassDimension = "Massa" +CurrentDimension = "Corrida" +TemperatureDimension = "Temperatura" +AmountOfSubstanceDimension = "Quantidade de matéria" +LuminousIntensityDimension = "Intensidade da luz" +FrequencyDimension = "Frequência" +ForceDimension = "Força" +PressureDimension = "Pressão" +EnergyDimension = "Energia" +PowerDimension = "Poderoso" +ElectricChargeDimension = "Carga elétrica" +ElectricPotentialDimension = "Potencial elétrico" +ElectricCapacitanceDimension = "Capacidade elétrica" +ElectricResistanceDimension = "Resistência elétrica" +ElectricConductanceDimension = "condutância elétrica" +MagneticFluxDimension = "fluxo magnético" +MagneticFieldDimension = "Campo magnético" +InductanceDimension = "Indutância" +CatalyticActivityDimension = "Atividade catalítica" +SurfaceDimension = "Superfície" +VolumeDimension = "Volume" +SpeedDimension = "Velocidade" diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index f99d2d59d9b..9b16f895eed 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -2,6 +2,7 @@ #define POINCARE_UNIT_H #include +#include namespace Poincare { @@ -105,6 +106,7 @@ class UnitNode final : public ExpressionNode { {} virtual const Vector dimensionVector() const { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; }; + virtual const I18n::Message dimensionMessage() const = 0; virtual int numberOfRepresentatives() const { return 0; }; /* representativesOfSameDimension returns a pointer to the array containing * all representatives for this's dimension. */ @@ -146,6 +148,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static TimeRepresentative Default() { return TimeRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = 1, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::TimeDimension; } int numberOfRepresentatives() const override { return 7; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } @@ -160,6 +163,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static DistanceRepresentative Default() { return DistanceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 1, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::DistanceDimension; } int numberOfRepresentatives() const override { return 8; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } @@ -175,6 +179,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static MassRepresentative Default() { return MassRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::MassDimension; } int numberOfRepresentatives() const override { return 7; } const Representative * representativesOfSameDimension() const override; const Prefix * basePrefix() const override; @@ -191,6 +196,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static CurrentRepresentative Default() { return CurrentRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 1, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::CurrentDimension; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } @@ -204,6 +210,7 @@ class UnitNode final : public ExpressionNode { static double ConvertTemperatures(double value, const Representative * source, const Representative * target); constexpr static TemperatureRepresentative Default() { return TemperatureRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 1, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::TemperatureDimension; } int numberOfRepresentatives() const override { return 3; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } @@ -221,6 +228,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static AmountOfSubstanceRepresentative Default() { return AmountOfSubstanceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 1, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::AmountOfSubstanceDimension; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } @@ -233,6 +241,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static LuminousIntensityRepresentative Default() { return LuminousIntensityRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 1}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::LuminousIntensityDimension; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } @@ -245,6 +254,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static FrequencyRepresentative Default() { return FrequencyRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = -1, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::FrequencyDimension; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -256,6 +266,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static ForceRepresentative Default() { return ForceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 1, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::ForceDimension; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -267,6 +278,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static PressureRepresentative Default() { return PressureRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = -2, .distance = -1, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::PressureDimension; } int numberOfRepresentatives() const override { return 3; } const Representative * representativesOfSameDimension() const override; private: @@ -278,6 +290,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static EnergyRepresentative Default() { return EnergyRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 2, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::EnergyDimension; } int numberOfRepresentatives() const override { return 2; } const Representative * representativesOfSameDimension() const override; bool hasSpecialAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } @@ -291,6 +304,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static PowerRepresentative Default() { return PowerRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = -3, .distance = 2, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::PowerDimension; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -303,6 +317,7 @@ class UnitNode final : public ExpressionNode { using Representative::Representative; constexpr static ElectricChargeRepresentative Default() { return ElectricChargeRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = 1, .distance = 0, .mass = 0, .current = 1, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::ElectricChargeDimension; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; }; @@ -312,6 +327,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static ElectricPotentialRepresentative Default() { return ElectricPotentialRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = -3, .distance = 2, .mass = 1, .current = -1, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::ElectricPotentialDimension; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -323,6 +339,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static ElectricCapacitanceRepresentative Default() { return ElectricCapacitanceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = 4, .distance = -2, .mass = -1, .current = 2, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::ElectricCapacitanceDimension; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -334,6 +351,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static ElectricResistanceRepresentative Default() { return ElectricResistanceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = -3, .distance = 2, .mass = 1, .current = -2, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::ElectricResistanceDimension; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -345,6 +363,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static ElectricConductanceRepresentative Default() { return ElectricConductanceRepresentative(nullptr, 1., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = 3, .distance = -2, .mass = -1, .current = 2, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::ElectricConductanceDimension; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -356,6 +375,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static MagneticFluxRepresentative Default() { return MagneticFluxRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 2, .mass = 1, .current = -1, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::MagneticFluxDimension; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -367,6 +387,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static MagneticFieldRepresentative Default() { return MagneticFieldRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 0, .mass = 1, .current = -1, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::MagneticFieldDimension; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -378,6 +399,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static InductanceRepresentative Default() { return InductanceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 2, .mass = 1, .current = -2, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::InductanceDimension; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -389,6 +411,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static CatalyticActivityRepresentative Default() { return CatalyticActivityRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = -1, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 1, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::CatalyticActivityDimension; } int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; private: @@ -400,6 +423,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static SurfaceRepresentative Default() { return SurfaceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 2, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::SurfaceDimension; } int numberOfRepresentatives() const override { return 2; } const Representative * representativesOfSameDimension() const override; const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const override; @@ -414,6 +438,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static VolumeRepresentative Default() { return VolumeRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 3, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::VolumeDimension; } int numberOfRepresentatives() const override { return 8; } const Representative * representativesOfSameDimension() const override; const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const override; @@ -428,6 +453,7 @@ class UnitNode final : public ExpressionNode { public: constexpr static SpeedRepresentative Default() { return SpeedRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } const VectordimensionVector() const override { return Vector{.time = -1, .distance = 1, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminousIntensity = 0}; } + const I18n::Message dimensionMessage() const override { return I18n::Message::SpeedDimension; } const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const override { return nullptr; } bool hasSpecialAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const override; @@ -680,7 +706,7 @@ class Unit : public Expression { static bool CanParse(const char * symbol, size_t length, const Representative * * representative, const Prefix * * prefix); static void ChooseBestRepresentativeAndPrefixForValue(Expression units, double * value, ExpressionNode::ReductionContext reductionContext); static bool ShouldDisplayAdditionalOutputs(double value, Expression unit, Preferences::UnitFormat unitFormat); - static int SetAdditionalExpressions(Expression units, double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext); + static int SetAdditionalExpressionsAndMessage(Expression units, double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext, I18n::Message * message); static Expression BuildSplit(double value, const Unit * units, int length, ExpressionNode::ReductionContext reductionContext); static Expression ConvertTemperatureUnits(Expression e, Unit unit, ExpressionNode::ReductionContext reductionContext); diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 49515a52d61..6d792addd90 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace Poincare { @@ -775,11 +776,10 @@ bool Unit::ShouldDisplayAdditionalOutputs(double value, Expression unit, Prefere return e.type() == ExpressionNode::Type::Unit && !e.convert().isBaseUnit(); }; - return (representative != nullptr && representative->hasSpecialAdditionalExpressions(value, unitFormat)) - || unit.hasExpression(isNonBase, nullptr); + return representative != nullptr || unit.hasExpression(isNonBase, nullptr); } -int Unit::SetAdditionalExpressions(Expression units, double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) { +int Unit::SetAdditionalExpressionsAndMessage(Expression units, double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext, I18n::Message * message) { if (units.isUninitialized()) { return 0; } @@ -787,6 +787,7 @@ int Unit::SetAdditionalExpressions(Expression units, double value, Expression * if (!representative) { return 0; } + *message = representative->dimensionMessage(); return representative->setAdditionalExpressions(value, dest, availableLength, reductionContext); } diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index 3fe80a1b1cb..de29613a70b 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -456,7 +456,8 @@ void assert_additional_results_compute_to(const char * expression, const char * quiz_assert(length == 0); return; } - const int numberOfResults = Unit::SetAdditionalExpressions(units, value, additional, maxNumberOfResults, reductionContext); + I18n::Message unitMessage; + const int numberOfResults = Unit::SetAdditionalExpressionsAndMessage(units, value, additional, maxNumberOfResults, reductionContext, unitMessage); quiz_assert(numberOfResults == length); for (int i = 0; i < length; i++) { From 4d5b37fab17d3229b6a7a211a8ec95f4d41e5f57 Mon Sep 17 00:00:00 2001 From: LeMoustachu Date: Tue, 21 Jun 2022 18:09:41 +0200 Subject: [PATCH 295/355] Improvements to README.md and README.fr.md (#256) * [README] clean fedora dependencies installation instruction * [README] n0110 instructions clearification * [README] add instruction for native simulator build * Fix translation Changed "native simulator" into "simulateur natif" Co-authored-by: Yaya-Cout * [README] add automatic platform detection command to native simulator instructions * [README.fr] fix translations * Update README.fr.md change "apps external" into "appllications externes" Co-authored-by: Yaya-Cout * Update README.fr.md Co-authored-by: Yaya-Cout Co-authored-by: Yaya-Cout --- README.fr.md | 84 +++++++++++++++++++++++++++++++++------------------- README.md | 65 +++++++++++++++++++++++++++------------- 2 files changed, 99 insertions(+), 50 deletions(-) diff --git a/README.fr.md b/README.fr.md index c9e9184361f..9c6cbacd3c4 100644 --- a/README.fr.md +++ b/README.fr.md @@ -70,22 +70,10 @@ C'est fait! Vous pouvez aller à l'étape 2.
    -Installez tout d'abord des outils de développement. +Installez toutes les dépendances grâce à cette commande: ```bash -dnf install make automake gcc gcc-c++ kernel-devel -``` - -Puis les pquets requis: - -```bash -dnf install git ImageMagick libX11-devel libXext-devel freetype-devel libpng-devel libjpeg-devel pkg-config -``` - -Et enfin la version pour ARM de GCC: - -```bash -dnf install arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++ +dnf install make automake gcc gcc-c++ kernel-devel git ImageMagick libX11-devel libXext-devel freetype-devel libpng-devel libjpeg-devel pkg-config arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++ ```
    @@ -106,7 +94,7 @@ Il est recommandé d'utiliser [Homebrew](https://brew.sh/). Une fois intsallé, brew install numworks/tap/epsilon-sdk ``` -Et toutes les dependances seront installées. +Et toutes les dépendances seront installées.
    @@ -290,63 +278,99 @@ pour compiler les binpacks que vous pouvez distribuer et flasher depuis le [Ti-p
    -Model bootloader (N0110) +Model n0110 + +Le bootloader vous permet d'installer firmware dans des "slots" séparés. Dans ce cas les applications externes ne pourront pas utiliser toute la mémoire mais la moitié. Si un seul slot est utilisé, le bootloader permettra d'utiliser toute la mémoire. Sans bootloader, les apps external peuvent utiliser toute la mémoire. + +
    +Bootloader + +Votre calculatrice doit être flashé avec le bootloader d'[Upsilon](https://getupsilon.web.app) ou d'[Omega](https://getomega.dev). +Compilez avec: ```bash make clean -make OMEGA_USERNAME="{Votre nom, maximum 15 caractères}" -j4 +make OMEGA_USERNAME="{Votre nom, max 15 caractères}" -j4 ``` -Maintenant, lancez soit: +Ensuite lancez soit: ```bash make epsilon.A_flash ``` -pour directement flasher la calculatrice, ou avec le flasher de slots du bootloader avec RESET, puis 4 (menu de flash), et 1 (flash des slots). +pour flasher le slot actuel ou pour flasher par le flasher du booloader avec RESET, puis 4 (flash) et 1 (flash slots) pour flasher n'importe quel slot.
    soit: ```bash -make OMEGA_USERNAME="" binpack -j4 +make OMEGA_USERNAME="{Votre nom, max 15 caractères}" binpack -j4 ``` -pour compiler les binpacks que vous pouvez distribuer et flasher depuis le [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). - +pour compiler les binpacks que vous pouvez distribuer et flasher depuis le [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). Vous les trouverez dans `output/release/device/bootloader/`.
    -
    +
    -Model n0110 (obsolète, utilisez le bootloader à la place, pas de protection contre Epsilon) +Model n0110 sans bootloader (obsolète, utilisez le bootloader à la place pour la protection contre Epsilon) +Compilez avec: ```bash make MODEL=n0110 clean -make MODEL=n0110 OMEGA_USERNAME="{Votre nom, maximum 15 caractères}" -j4 +make MODEL=n0110 OMEGA_USERNAME="{Votre nom, max 15 caractères}" -j4 ``` -Maintenant, lancez soit: +Ensuite lancez soit: ```bash make MODEL=n0110 epsilon_flash ``` -pour directement flasher la calculatrice après avoir appuyé simultanément sur `reset` et `6` et avoir branché la calculatrice à l'ordinateur. - +pour directement flasher la calculatrice après avoir appuyé simultanément sur `RESET` et `6` et avoir branché la calculatrice à l'ordinateur.
    soit: ```bash -make MODEL=n0110 OMEGA_USERNAME="" binpack -j4 +make MODEL=n0110 OMEGA_USERNAME="{Votre nom, max 15 caractères}" binpack -j4 ``` -pour compiler les binpacks que vous pouvez distribuer et flasher depuis le [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). +pour compiler les binpacks que vous pouvez distribuer et flasher depuis le [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). Vous les trouverez dans `output/release/device/n0110/`. +
    + +
    + +Simulateur Natif + +Lancez cette commande: +```bash +make clean +``` +Vous pouvez soit choisir d'utiliser la commmande qui détectera automatiquement votre plateforme: +```bash +make PLATFORM=simulator +``` +Ou choisir une commande qui correspond à votre plateforme: +```bash +make PLATFORM=simulator TARGET=android +make PLATFORM=simulator TARGET=ios +make PLATFORM=simulator TARGET=macos +make PLATFORM=simulator TARGET=web +make PLATFORM=simulator TARGET=windows +make PLATFORM=simulator TARGET=3ds +``` + +Vous trouverez les fichiers du simulateur dans `output/release/simulator/`. + +
    + +
    Simulateur web diff --git a/README.md b/README.md index f143d941dc2..e2187c19964 100644 --- a/README.md +++ b/README.md @@ -71,22 +71,10 @@ And there you can go to step 2!
    -To install basics dev tools: +To install all dependencies: ```bash -dnf install make automake gcc gcc-c++ kernel-devel -``` - -And then install required packages. - -```bash -install git ImageMagick libX11-devel libXext-devel freetype-devel libpng-devel libjpeg-devel pkg-config -``` - -Then, install GCC cross compiler for ARM. - -```bash - dnf install arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++ +dnf install make automake gcc gcc-c++ kernel-devel git ImageMagick libX11-devel libXext-devel freetype-devel libpng-devel libjpeg-devel pkg-config arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++ ```
    @@ -285,7 +273,15 @@ to make binpack which you can flash to the calculator from [Ti-planet's webDFU](
    -Model bootloader (N0110) +Model n0110 + +The bootloader allows you to install 2 firmware in separated "slots". If so, external apps won't have all the space but half. Bootloader will allow use of all of the memory if only one slot is flashed. In legacy mode, external apps use all the space available. + +
    +Bootloader + +Your calculator must already have been flashed with [Upsilon](https://getupsilon.web.app)'s or [Omega](https://getomega.dev)'s bootloader. +Then, build with: ```bash make clean @@ -308,13 +304,13 @@ or: make OMEGA_USERNAME="" binpack -j4 ``` -to make binpack witch you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0110/). Binpacks are a great way to share a custom build of Upsilon to friends. - +to make binpack which you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0110/). You'll find them at `output/release/device/bootloader/`. Binpacks are a great way to share a custom build of Upsilon to friends.
    -
    -Model N0110 legacy (deprecated, use bootloader instead, no Epsilon protection) + +
    +Model N0110 legacy (deprecated, use bootloader instead for Epsilon protection) ```bash make MODEL=n0110 clean @@ -337,10 +333,39 @@ or: make MODEL=n0110 OMEGA_USERNAME="" binpack -j4 ``` -to make binpack witch you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0110/). Binpacks are a great way to share a custom build of Upsilon to friends. +to make binpack which you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0110/). You'll find them at `output/release/device/bootloader/`. Binpacks are a great way to share a custom build of Upsilon to friends. +
    + +
    + +Native simulator + +Run this command: +```bash +make clean +``` +You can either build using the following command that will automatically detect your platform: +```bash +make PLATFORM=simulator +``` +or, choose the command corresponding to your platform: +```bash +make PLATFORM=simulator TARGET=android +make PLATFORM=simulator TARGET=ios +make PLATFORM=simulator TARGET=macos +make PLATFORM=simulator TARGET=web +make PLATFORM=simulator TARGET=windows +make PLATFORM=simulator TARGET=3ds +``` + +You'll find simulator files in `output/release/simulator/`. + +
    + +
    Web simulator From ba43135fcbcdf0592076cc8874b9b49c7bc84bbb Mon Sep 17 00:00:00 2001 From: fmOOmf <98671961+fmOOmf@users.noreply.github.com> Date: Sat, 25 Jun 2022 12:32:06 +0200 Subject: [PATCH 296/355] [kandinsky/font] Fix replacement character --- kandinsky/include/kandinsky/font.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kandinsky/include/kandinsky/font.h b/kandinsky/include/kandinsky/font.h index f1bbf7ec939..2358877111f 100644 --- a/kandinsky/include/kandinsky/font.h +++ b/kandinsky/include/kandinsky/font.h @@ -61,7 +61,7 @@ class KDFont { CodePoint m_codePoint; GlyphIndex m_glyphIndex; }; - static constexpr GlyphIndex IndexForReplacementCharacterCodePoint = 197; + static constexpr GlyphIndex IndexForReplacementCharacterCodePoint = 200; GlyphIndex indexForCodePoint(CodePoint c) const; void setGlyphGrayscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const; From 391fd5e2436baff6a37324ef5a4b04b3fd91d17c Mon Sep 17 00:00:00 2001 From: Laury Date: Sun, 19 Jun 2022 10:26:50 +0200 Subject: [PATCH 297/355] [python/ion] Error if brightness isn't between 0 and 240 in set_brightness --- python/port/mod/ion/modion.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/python/port/mod/ion/modion.cpp b/python/port/mod/ion/modion.cpp index f698c0a21a7..ab38f7c3e4d 100644 --- a/python/port/mod/ion/modion.cpp +++ b/python/port/mod/ion/modion.cpp @@ -107,11 +107,16 @@ mp_obj_t modion_get_keys() { } mp_obj_t modion_set_brightness(mp_obj_t brightness_mp){ - uint8_t brightness = static_cast(mp_obj_get_int(brightness_mp)); - GlobalPreferences::sharedGlobalPreferences()->setBrightnessLevel(brightness); - Ion::Backlight::setBrightness(brightness); - micropython_port_interrupt_if_needed(); - return mp_const_none; + int brightness = mp_obj_get_int(brightness_mp); + if (brightness < 0 || brightness > 240) { + mp_raise_ValueError("Brightness must be between 0 and 240"); + } else { + uint8_t unsignedBrightness = static_cast(brightness); + GlobalPreferences::sharedGlobalPreferences()->setBrightnessLevel(unsignedBrightness); + Ion::Backlight::setBrightness(unsignedBrightness); + micropython_port_interrupt_if_needed(); + return mp_const_none; + } } mp_obj_t modion_get_brightness(){ From 0b2a58117745858765e6e3bbdac4dbef14cc1379 Mon Sep 17 00:00:00 2001 From: Laury Date: Fri, 24 Jun 2022 22:35:36 +0200 Subject: [PATCH 298/355] [escher] Rework of timers and bigger text in toolboxes --- apps/apps_container.cpp | 14 ++--- apps/apps_container.h | 2 - .../unit_list_controller.cpp | 2 +- .../additional_outputs/unit_list_controller.h | 2 +- apps/code/app.h | 2 +- apps/code/python_toolbox.cpp | 58 ++++++++--------- apps/code/python_toolbox.h | 14 +++-- apps/code/script_parameter_controller.h | 8 +-- apps/code/variable_box_controller.cpp | 2 +- apps/code/variable_box_controller.h | 2 +- .../calculation_parameter_controller.cpp | 2 +- .../graph/calculation_parameter_controller.h | 4 +- apps/graph/graph/curve_parameter_controller.h | 2 +- apps/graph/list/list_parameter_controller.h | 2 +- .../values/derivative_parameter_controller.h | 4 +- ...interval_parameter_selector_controller.cpp | 2 +- .../interval_parameter_selector_controller.h | 2 +- apps/math_toolbox.cpp | 8 ++- apps/math_toolbox.h | 8 +-- apps/math_variable_box_controller.cpp | 4 +- apps/math_variable_box_controller.h | 4 +- apps/on_boarding/app.cpp | 10 +-- apps/on_boarding/app.h | 3 - apps/on_boarding/logo_controller.cpp | 2 +- apps/reader/list_book_controller.cpp | 2 +- apps/reader/list_book_controller.h | 2 +- apps/regression/graph_options_controller.cpp | 2 +- apps/regression/graph_options_controller.h | 2 +- .../graph/curve_parameter_controller.h | 2 +- apps/settings/main_controller.cpp | 2 +- .../sub_menu/exam_mode_controller.cpp | 2 +- apps/settings/sub_menu/exam_mode_controller.h | 2 +- .../sub_menu/generic_sub_controller.cpp | 2 +- apps/shared/color_cell.h | 2 +- .../function_curve_parameter_controller.h | 2 +- apps/shared/list_parameter_controller.h | 2 +- apps/shared/localization_controller.cpp | 4 +- apps/shared/localization_controller.h | 2 +- apps/shared/store_parameter_controller.h | 2 +- .../values_function_parameter_controller.h | 2 +- apps/shared/values_parameter_controller.cpp | 2 +- apps/shared/values_parameter_controller.h | 6 +- .../equation_models_parameter_controller.h | 2 +- escher/Makefile | 2 + escher/include/escher.h | 3 + escher/include/escher/animated.h | 11 ++++ escher/include/escher/animation_timer.h | 25 ++++++++ escher/include/escher/app.h | 2 - escher/include/escher/container.h | 4 -- escher/include/escher/message_table_cell.h | 4 +- .../escher/message_table_cell_with_buffer.h | 2 +- .../escher/message_table_cell_with_chevron.h | 3 +- ...ssage_table_cell_with_chevron_and_buffer.h | 2 +- ...e_table_cell_with_chevron_and_expression.h | 2 +- ...sage_table_cell_with_chevron_and_message.h | 2 +- .../message_table_cell_with_editable_text.h | 2 +- .../message_table_cell_with_expression.h | 2 +- .../escher/message_table_cell_with_gauge.h | 2 +- .../escher/message_table_cell_with_message.h | 5 +- .../escher/message_table_cell_with_switch.h | 2 +- escher/include/escher/run_loop.h | 5 +- .../escher/slideable_message_text_view.h | 26 ++++++++ escher/include/escher/timer.h | 15 +++-- escher/include/escher/toolbox.h | 4 +- escher/include/escher/toolbox_message_tree.h | 22 +++++-- escher/src/animation_timer.cpp | 15 +++++ escher/src/container.cpp | 20 ------ escher/src/message_table_cell.cpp | 40 +++++++++--- .../src/message_table_cell_with_chevron.cpp | 18 +++++- .../src/message_table_cell_with_message.cpp | 33 ++++++---- escher/src/run_loop.cpp | 44 +++++++++---- escher/src/slideable_message_text_view.cpp | 63 +++++++++++++++++++ escher/src/toolbox.cpp | 9 +-- kandinsky/src/font.cpp | 19 ++++-- 74 files changed, 407 insertions(+), 208 deletions(-) create mode 100644 escher/include/escher/animated.h create mode 100644 escher/include/escher/animation_timer.h create mode 100644 escher/include/escher/slideable_message_text_view.h create mode 100644 escher/src/animation_timer.cpp create mode 100644 escher/src/slideable_message_text_view.cpp diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index f464b07c187..336b9e98916 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -58,6 +58,11 @@ AppsContainer::AppsContainer() : Poincare::Expression::SetCircuitBreaker(AppsContainer::poincareCircuitBreaker); // #endif Ion::Storage::sharedStorage()->setDelegate(this); + + addTimer(&m_batteryTimer); + addTimer(&m_suspendTimer); + addTimer(&m_backlightDimmingTimer); + addTimer(&m_clockTimer); } bool AppsContainer::poincareCircuitBreaker() { @@ -458,15 +463,6 @@ Window * AppsContainer::window() { return &m_window; } -int AppsContainer::numberOfContainerTimers() { - return 4; -} - -Timer * AppsContainer::containerTimerAtIndex(int i) { - Timer * timers[4] = {&m_batteryTimer, &m_suspendTimer, &m_backlightDimmingTimer, &m_clockTimer}; - return timers[i]; -} - void AppsContainer::resetShiftAlphaStatus() { Ion::Events::setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default); updateAlphaLock(); diff --git a/apps/apps_container.h b/apps/apps_container.h index f52d50b6c50..a8fca78ddcf 100644 --- a/apps/apps_container.h +++ b/apps/apps_container.h @@ -59,8 +59,6 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag Home::App::Snapshot * homeAppSnapshot() { return &m_homeSnapshot; } private: Window * window() override; - int numberOfContainerTimers() override; - Timer * containerTimerAtIndex(int i) override; bool processEvent(Ion::Events::Event event); void resetShiftAlphaStatus(); bool updateAlphaLock(); diff --git a/apps/calculation/additional_outputs/unit_list_controller.cpp b/apps/calculation/additional_outputs/unit_list_controller.cpp index 31583c369e3..41ac6d8f21b 100644 --- a/apps/calculation/additional_outputs/unit_list_controller.cpp +++ b/apps/calculation/additional_outputs/unit_list_controller.cpp @@ -112,7 +112,7 @@ int UnitListController::numberOfRows() const { void UnitListController::willDisplayCellForIndex(HighlightCell * cell, int index) { if (index == 0) { - MessageTableCell * messageTableCell = (MessageTableCell *)cell; + MessageTableCell<> * messageTableCell = (MessageTableCell<> *)cell; messageTableCell->setMessage(m_dimensionMessage); } else { ExpressionsListController::willDisplayCellForIndex(cell, index - 1); diff --git a/apps/calculation/additional_outputs/unit_list_controller.h b/apps/calculation/additional_outputs/unit_list_controller.h index 477656b33d9..fb351ec320c 100644 --- a/apps/calculation/additional_outputs/unit_list_controller.h +++ b/apps/calculation/additional_outputs/unit_list_controller.h @@ -23,7 +23,7 @@ class UnitListController : public ExpressionsListController { private: I18n::Message messageAtIndex(int index) override; I18n::Message m_dimensionMessage; - MessageTableCell m_dimensionCell; + MessageTableCell<> m_dimensionCell; }; } diff --git a/apps/code/app.h b/apps/code/app.h index 4e2ffc9aef0..67494d3f363 100644 --- a/apps/code/app.h +++ b/apps/code/app.h @@ -75,7 +75,7 @@ class App : public Shared::InputEventHandlerDelegateApp { VariableBoxController * variableBoxController() { return &m_variableBoxController; } - static constexpr int k_pythonHeapSize = 70000; + static constexpr int k_pythonHeapSize = 69500; private: /* Python delegate: diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index d6cc65dbaec..b4525591da7 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -10,22 +10,22 @@ extern "C" { namespace Code { const ToolboxMessageTree forLoopChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::ForInRange1ArgLoopWithArg, I18n::Message::Default, false, I18n::Message::ForInRange1ArgLoop), - ToolboxMessageTree::Leaf(I18n::Message::ForInRange2ArgsLoopWithArg, I18n::Message::Default, false, I18n::Message::ForInRange2ArgsLoop), - ToolboxMessageTree::Leaf(I18n::Message::ForInRange3ArgsLoopWithArg, I18n::Message::Default, false, I18n::Message::ForInRange3ArgsLoop), - ToolboxMessageTree::Leaf(I18n::Message::ForInListLoopWithArg, I18n::Message::Default, false, I18n::Message::ForInListLoop) + ToolboxMessageTree::Leaf(I18n::Message::ForInRange1ArgLoopWithArg, I18n::Message::Default, false, I18n::Message::ForInRange1ArgLoop, true, 2), + ToolboxMessageTree::Leaf(I18n::Message::ForInRange2ArgsLoopWithArg, I18n::Message::Default, false, I18n::Message::ForInRange2ArgsLoop, true, 2), + ToolboxMessageTree::Leaf(I18n::Message::ForInRange3ArgsLoopWithArg, I18n::Message::Default, false, I18n::Message::ForInRange3ArgsLoop, true, 2), + ToolboxMessageTree::Leaf(I18n::Message::ForInListLoopWithArg, I18n::Message::Default, false, I18n::Message::ForInListLoop, true, 2) }; const ToolboxMessageTree ifStatementChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::IfElseStatementWithArg, I18n::Message::Default, false, I18n::Message::IfElseStatement), - ToolboxMessageTree::Leaf(I18n::Message::IfThenStatementWithArg, I18n::Message::Default, false, I18n::Message::IfThenStatement), - ToolboxMessageTree::Leaf(I18n::Message::IfElifElseStatementWithArg, I18n::Message::Default, false, I18n::Message::IfElifElseStatement), - ToolboxMessageTree::Leaf(I18n::Message::IfAndIfElseStatementWithArg, I18n::Message::Default, false, I18n::Message::IfAndIfElseStatement), - ToolboxMessageTree::Leaf(I18n::Message::IfOrIfElseStatementWithArg, I18n::Message::Default, false, I18n::Message::IfOrIfElseStatement) + ToolboxMessageTree::Leaf(I18n::Message::IfElseStatementWithArg, I18n::Message::Default, false, I18n::Message::IfElseStatement, true, 4), + ToolboxMessageTree::Leaf(I18n::Message::IfThenStatementWithArg, I18n::Message::Default, false, I18n::Message::IfThenStatement, true, 2), + ToolboxMessageTree::Leaf(I18n::Message::IfElifElseStatementWithArg, I18n::Message::Default, false, I18n::Message::IfElifElseStatement, true, 6), + ToolboxMessageTree::Leaf(I18n::Message::IfAndIfElseStatementWithArg, I18n::Message::Default, false, I18n::Message::IfAndIfElseStatement, true, 4), + ToolboxMessageTree::Leaf(I18n::Message::IfOrIfElseStatementWithArg, I18n::Message::Default, false, I18n::Message::IfOrIfElseStatement, true, 4) }; const ToolboxMessageTree whileLoopChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::WhileLoopWithArg, I18n::Message::Default, false, I18n::Message::WhileLoop) + ToolboxMessageTree::Leaf(I18n::Message::WhileLoopWithArg, I18n::Message::Default, false, I18n::Message::WhileLoop, true, 2) }; const ToolboxMessageTree conditionsChildren[] = { @@ -683,10 +683,10 @@ const ToolboxMessageTree fileChildren[] { }; const ToolboxMessageTree exceptionsChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::TryExcept1ErrorWithArg, I18n::Message::Default, false, I18n::Message::TryExcept1Error), - ToolboxMessageTree::Leaf(I18n::Message::TryExcept1ErrorElseWithArg, I18n::Message::Default, false, I18n::Message::TryExcept1ErrorElse), - ToolboxMessageTree::Leaf(I18n::Message::TryExcept2ErrorWithArg, I18n::Message::Default, false, I18n::Message::TryExcept2Error), - ToolboxMessageTree::Leaf(I18n::Message::WithInstructionWithArg, I18n::Message::Default, false, I18n::Message::WithInstruction), + ToolboxMessageTree::Leaf(I18n::Message::TryExcept1ErrorWithArg, I18n::Message::Default, false, I18n::Message::TryExcept1Error, true, 4), + ToolboxMessageTree::Leaf(I18n::Message::TryExcept1ErrorElseWithArg, I18n::Message::Default, false, I18n::Message::TryExcept1ErrorElse, true, 6), + ToolboxMessageTree::Leaf(I18n::Message::TryExcept2ErrorWithArg, I18n::Message::Default, false, I18n::Message::TryExcept2Error, true, 4), + ToolboxMessageTree::Leaf(I18n::Message::WithInstructionWithArg, I18n::Message::Default, false, I18n::Message::WithInstruction, true, 2), }; const ToolboxMessageTree menu[] = { @@ -704,6 +704,10 @@ const ToolboxMessageTree toolboxModel = ToolboxMessageTree::Node(I18n::Message:: PythonToolbox::PythonToolbox() : Toolbox(nullptr, rootModel()->label()) { + for (int i=0; i < k_maxNumberOfDisplayedRows; i++) { + m_leafCells[i].setMessageFont(KDFont::LargeFont); + m_nodeCells[i].setMessageFont(KDFont::LargeFont); + } } const ToolboxMessageTree * PythonToolbox::moduleChildren(const char * name, int * numberOfNodes) const { @@ -734,19 +738,17 @@ bool PythonToolbox::handleEvent(Ion::Events::Event event) { return false; } +void PythonToolbox::willDisplayCellForIndex(HighlightCell * cell, int index) { + Toolbox::willDisplayCellForIndex(cell, index); + const ToolboxMessageTree * messageTree = static_cast(m_messageTreeModel->childAtIndex(index)); + MessageTableCell * myCell = static_cast *>(cell); + myCell->setMessageFont(messageTree->isMultiLine() ? KDFont::SmallFont : KDFont::LargeFont); +} + KDCoordinate PythonToolbox::rowHeight(int j) { - if (typeAtLocation(0, j) == Toolbox::LeafCellType && (m_messageTreeModel->label() == I18n::Message::IfStatementMenu || m_messageTreeModel->label() == I18n::Message::Exceptions)) { - /* To get the exact height needed for each cell, we have to compute its - * text size, which means scan the text char by char to look for '\n' - * chars. This is very costly and ruins the speed performance when - * scrolling at the bottom of a long table: to compute a position on the - * kth row, we call cumulatedHeightFromIndex(k), which calls rowHeight k - * times. - * We thus decided to compute the real height only for the ifStatement - * children of the toolbox, which is the only menu that has special height - * rows. */ - const ToolboxMessageTree * messageTree = static_cast(m_messageTreeModel->childAtIndex(j)); - return k_font->stringSize(I18n::translate(messageTree->label())).height() + 2*Metric::TableCellVerticalMargin + (messageTree->text() == I18n::Message::Default ? 0 : Toolbox::rowHeight(j)); + const ToolboxMessageTree * messageTree = static_cast(m_messageTreeModel->childAtIndex(j)); + if (messageTree->isMultiLine()) { + return k_fontForMultiLine->glyphSize().height() * messageTree->numberOfLines() + 2*Metric::TableCellVerticalMargin + (messageTree->text() == I18n::Message::Default ? 0 : Toolbox::rowHeight(j)); } return Toolbox::rowHeight(j); } @@ -791,12 +793,12 @@ const ToolboxMessageTree * PythonToolbox::rootModel() const { return &toolboxModel; } -MessageTableCellWithMessage * PythonToolbox::leafCellAtIndex(int index) { +MessageTableCellWithMessage * PythonToolbox::leafCellAtIndex(int index) { assert(index >= 0 && index < k_maxNumberOfDisplayedRows); return &m_leafCells[index]; } -MessageTableCellWithChevron* PythonToolbox::nodeCellAtIndex(int index) { +MessageTableCellWithChevron * PythonToolbox::nodeCellAtIndex(int index) { assert(index >= 0 && index < k_maxNumberOfDisplayedRows); return &m_nodeCells[index]; } diff --git a/apps/code/python_toolbox.h b/apps/code/python_toolbox.h index 4252b1f16b0..c641536a405 100644 --- a/apps/code/python_toolbox.h +++ b/apps/code/python_toolbox.h @@ -18,23 +18,27 @@ class PythonToolbox : public Toolbox { // Toolbox bool handleEvent(Ion::Events::Event event) override; const ToolboxMessageTree * rootModel() const override; + + // ListViewDataSource + void willDisplayCellForIndex(HighlightCell * cell, int index) override; + protected: KDCoordinate rowHeight(int j) override; bool selectLeaf(int selectedRow, bool quitToolbox) override; bool selectSubMenu(int selectedRow) override; - MessageTableCellWithMessage * leafCellAtIndex(int index) override; - MessageTableCellWithChevron* nodeCellAtIndex(int index) override; + MessageTableCellWithMessage * leafCellAtIndex(int index) override; + MessageTableCellWithChevron * nodeCellAtIndex(int index) override; int maxNumberOfDisplayedRows() override; bool canStayInMenu() override { return true; } constexpr static int k_maxNumberOfDisplayedRows = 13; // = 240/(13+2*3) // 13 = minimal string height size // 3 = vertical margins private: - constexpr static const KDFont * k_font = KDFont::SmallFont; + constexpr static const KDFont * k_fontForMultiLine = KDFont::SmallFont; void scrollToLetter(char letter); void scrollToAndSelectChild(int i); - MessageTableCellWithMessage m_leafCells[k_maxNumberOfDisplayedRows]; - MessageTableCellWithChevron m_nodeCells[k_maxNumberOfDisplayedRows]; + MessageTableCellWithMessage m_leafCells[k_maxNumberOfDisplayedRows]; + MessageTableCellWithChevron m_nodeCells[k_maxNumberOfDisplayedRows]; ToolboxIonKeys m_ionKeys; }; diff --git a/apps/code/script_parameter_controller.h b/apps/code/script_parameter_controller.h index e7065e7334f..193879b683c 100644 --- a/apps/code/script_parameter_controller.h +++ b/apps/code/script_parameter_controller.h @@ -34,11 +34,11 @@ class ScriptParameterController : public ViewController, public SimpleListViewDa constexpr static int k_totalNumberOfCell = 6; StackViewController * stackViewController(); I18n::Message m_pageTitle; - MessageTableCell m_executeScript; - MessageTableCell m_renameScript; + MessageTableCell<> m_executeScript; + MessageTableCell<> m_renameScript; MessageTableCellWithSwitch m_autoImportScript; - MessageTableCell m_deleteScript; - MessageTableCell m_duplicateScript; + MessageTableCell<> m_deleteScript; + MessageTableCell<> m_duplicateScript; MessageTableCellWithBuffer m_size; void GetScriptSize(MessageTableCellWithBuffer* myCell); SelectableTableView m_selectableTableView; diff --git a/apps/code/variable_box_controller.cpp b/apps/code/variable_box_controller.cpp index 31604b6716a..fcaf42bc581 100644 --- a/apps/code/variable_box_controller.cpp +++ b/apps/code/variable_box_controller.cpp @@ -128,7 +128,7 @@ void VariableBoxController::willDisplayCellForIndex(HighlightCell * cell, int in I18n::Message::BuiltinsAndKeywords, I18n::Message::ImportedModulesAndScripts }; - static_cast(cell)->setMessage(subtitleMessages[(int)cellOrigin]); + static_cast *>(cell)->setMessage(subtitleMessages[(int)cellOrigin]); } void VariableBoxController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { diff --git a/apps/code/variable_box_controller.h b/apps/code/variable_box_controller.h index eb25b054425..1a5a525eb45 100644 --- a/apps/code/variable_box_controller.h +++ b/apps/code/variable_box_controller.h @@ -104,7 +104,7 @@ class VariableBoxController : public AlternateEmptyNestedMenuController { ScriptNode m_builtinNodes[k_totalBuiltinNodesCount]; ScriptNode m_importedNodes[k_maxScriptNodesCount]; ScriptNodeCell m_itemCells[k_maxNumberOfDisplayedItems]; - MessageTableCell m_subtitleCells[k_scriptOriginsCount]; + MessageTableCell<> m_subtitleCells[k_scriptOriginsCount]; ScriptStore * m_scriptStore; size_t m_currentScriptNodesCount; size_t m_builtinNodesCount; diff --git a/apps/graph/graph/calculation_parameter_controller.cpp b/apps/graph/graph/calculation_parameter_controller.cpp index 93f98086fda..3e726666263 100644 --- a/apps/graph/graph/calculation_parameter_controller.cpp +++ b/apps/graph/graph/calculation_parameter_controller.cpp @@ -109,7 +109,7 @@ void CalculationParameterController::willDisplayCellForIndex(HighlightCell * cel assert(index >= 0 && index <= numberOfRows()); if (cell != &m_preimageCell) { I18n::Message titles[] = {I18n::Message::Intersection, I18n::Message::Maximum, I18n::Message::Minimum, I18n::Message::Zeros, I18n::Message::Tangent, I18n::Message::Integral}; - static_cast(cell)->setMessage(titles[index - 1 + !shouldDisplayIntersection()]); + static_cast *>(cell)->setMessage(titles[index - 1 + !shouldDisplayIntersection()]); } } diff --git a/apps/graph/graph/calculation_parameter_controller.h b/apps/graph/graph/calculation_parameter_controller.h index ffb38e6919a..0297b5b38c3 100644 --- a/apps/graph/graph/calculation_parameter_controller.h +++ b/apps/graph/graph/calculation_parameter_controller.h @@ -32,9 +32,9 @@ class CalculationParameterController : public ViewController, public ListViewDat void setRecord(Ion::Storage::Record record); private: bool shouldDisplayIntersection() const; - MessageTableCellWithChevron m_preimageCell; + MessageTableCellWithChevron<> m_preimageCell; constexpr static int k_totalNumberOfReusableCells = 6; - MessageTableCell m_cells[k_totalNumberOfReusableCells]; + MessageTableCell<> m_cells[k_totalNumberOfReusableCells]; SelectableTableView m_selectableTableView; Ion::Storage::Record m_record; PreimageParameterController m_preimageParameterController; diff --git a/apps/graph/graph/curve_parameter_controller.h b/apps/graph/graph/curve_parameter_controller.h index d5a587af16d..a14e329564c 100644 --- a/apps/graph/graph/curve_parameter_controller.h +++ b/apps/graph/graph/curve_parameter_controller.h @@ -25,7 +25,7 @@ class CurveParameterController : public Shared::FunctionCurveParameterController Shared::FunctionGoToParameterController * goToParameterController() override; Shared::FunctionGoToParameterController m_goToParameterController; GraphController * m_graphController; - MessageTableCellWithChevron m_calculationCell; + MessageTableCellWithChevron<> m_calculationCell; MessageTableCellWithSwitch m_derivativeCell; CalculationParameterController m_calculationParameterController; }; diff --git a/apps/graph/list/list_parameter_controller.h b/apps/graph/list/list_parameter_controller.h index 31c311ba4be..2246ab8666f 100644 --- a/apps/graph/list/list_parameter_controller.h +++ b/apps/graph/list/list_parameter_controller.h @@ -28,7 +28,7 @@ class ListParameterController : public Shared::ListParameterController { MessageTableCellWithChevronAndBuffer m_functionDomain; TypeParameterController m_typeParameterController; DomainParameterController m_domainParameterController; - MessageTableCell m_renameCell; + MessageTableCell<> m_renameCell; }; } diff --git a/apps/graph/values/derivative_parameter_controller.h b/apps/graph/values/derivative_parameter_controller.h index 10d64bec0a4..2c353a1d947 100644 --- a/apps/graph/values/derivative_parameter_controller.h +++ b/apps/graph/values/derivative_parameter_controller.h @@ -33,9 +33,9 @@ class DerivativeParameterController : public ViewController, public SimpleListVi #endif constexpr static int k_maxNumberOfCharsInTitle = Shared::Function::k_maxNameWithArgumentSize + 1; // +1 for the ' of the derivative char m_pageTitle[k_maxNumberOfCharsInTitle]; - MessageTableCell m_hideColumn; + MessageTableCell<> m_hideColumn; #if COPY_COLUMN - MessageTableCellWithChevron m_copyColumn; + MessageTableCellWithChevron<> m_copyColumn; #endif SelectableTableView m_selectableTableView; Ion::Storage::Record m_record; diff --git a/apps/graph/values/interval_parameter_selector_controller.cpp b/apps/graph/values/interval_parameter_selector_controller.cpp index 80cd68f7431..7707c83ed83 100644 --- a/apps/graph/values/interval_parameter_selector_controller.cpp +++ b/apps/graph/values/interval_parameter_selector_controller.cpp @@ -69,7 +69,7 @@ int IntervalParameterSelectorController::reusableCellCount() const { void IntervalParameterSelectorController::willDisplayCellForIndex(HighlightCell * cell, int index) { assert(0 <= index && index < numberOfRows()); Shared::ContinuousFunction::PlotType plotType = plotTypeAtRow(index); - static_cast(cell)->setMessage(messageForType(plotType)); + static_cast *>(cell)->setMessage(messageForType(plotType)); } Shared::ContinuousFunction::PlotType IntervalParameterSelectorController::plotTypeAtRow(int j) const { diff --git a/apps/graph/values/interval_parameter_selector_controller.h b/apps/graph/values/interval_parameter_selector_controller.h index eef6e1b6a99..a37e714ad5d 100644 --- a/apps/graph/values/interval_parameter_selector_controller.h +++ b/apps/graph/values/interval_parameter_selector_controller.h @@ -24,7 +24,7 @@ class IntervalParameterSelectorController : public ViewController, public Simple private: Shared::ContinuousFunction::PlotType plotTypeAtRow(int j) const; I18n::Message messageForType(Shared::ContinuousFunction::PlotType plotType); - MessageTableCellWithChevron m_intervalParameterCell[Shared::ContinuousFunction::k_numberOfPlotTypes]; + MessageTableCellWithChevron<> m_intervalParameterCell[Shared::ContinuousFunction::k_numberOfPlotTypes]; SelectableTableView m_selectableTableView; }; diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index 1ff78b68127..0cb24ab13c6 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -875,6 +875,10 @@ const ToolboxMessageTree toolboxModel = ToolboxMessageTree::Node(I18n::Message:: MathToolbox::MathToolbox() : Toolbox(nullptr, rootModel()->label()) { + for (int i=0; i < k_maxNumberOfDisplayedRows; i++) { + m_leafCells[i].setMessageFont(KDFont::LargeFont); + m_nodeCells[i].setMessageFont(KDFont::LargeFont); + } } bool MathToolbox::selectLeaf(int selectedRow, bool quitToolbox) { @@ -900,12 +904,12 @@ const ToolboxMessageTree * MathToolbox::rootModel() const { return &toolboxModel; } -MessageTableCellWithMessage * MathToolbox::leafCellAtIndex(int index) { +MessageTableCellWithMessage * MathToolbox::leafCellAtIndex(int index) { assert(index >= 0 && index < k_maxNumberOfDisplayedRows); return &m_leafCells[index]; } -MessageTableCellWithChevron* MathToolbox::nodeCellAtIndex(int index) { +MessageTableCellWithChevron * MathToolbox::nodeCellAtIndex(int index) { assert(index >= 0 && index < k_maxNumberOfDisplayedRows); return &m_nodeCells[index]; } diff --git a/apps/math_toolbox.h b/apps/math_toolbox.h index b883bf1823e..936cf42c94e 100644 --- a/apps/math_toolbox.h +++ b/apps/math_toolbox.h @@ -10,15 +10,15 @@ class MathToolbox : public Toolbox { const ToolboxMessageTree * rootModel() const override; protected: bool selectLeaf(int selectedRow, bool quitToolbox) override; - MessageTableCellWithMessage * leafCellAtIndex(int index) override; - MessageTableCellWithChevron* nodeCellAtIndex(int index) override; + MessageTableCellWithMessage * leafCellAtIndex(int index) override; + MessageTableCellWithChevron * nodeCellAtIndex(int index) override; int maxNumberOfDisplayedRows() override; constexpr static int k_maxNumberOfDisplayedRows = 6; // = 240/40 private: int indexAfterFork() const override; - MessageTableCellWithMessage m_leafCells[k_maxNumberOfDisplayedRows]; - MessageTableCellWithChevron m_nodeCells[k_maxNumberOfDisplayedRows]; + MessageTableCellWithMessage m_leafCells[k_maxNumberOfDisplayedRows]; + MessageTableCellWithChevron m_nodeCells[k_maxNumberOfDisplayedRows]; }; #endif diff --git a/apps/math_variable_box_controller.cpp b/apps/math_variable_box_controller.cpp index 28f179e26a9..fb39e3b3958 100644 --- a/apps/math_variable_box_controller.cpp +++ b/apps/math_variable_box_controller.cpp @@ -98,7 +98,7 @@ int MathVariableBoxController::reusableCellCount(int type) { void MathVariableBoxController::willDisplayCellForIndex(HighlightCell * cell, int index) { if (m_currentPage == Page::RootMenu) { I18n::Message label = nodeLabelAtIndex(index); - MessageTableCell * myCell = (MessageTableCell *)cell; + MessageTableCell<> * myCell = (MessageTableCell<> *)cell; myCell->setMessage(label); myCell->reloadCell(); return; @@ -158,7 +158,7 @@ ExpressionTableCellWithExpression * MathVariableBoxController::leafCellAtIndex(i return &m_leafCells[index]; } -MessageTableCellWithChevron * MathVariableBoxController::nodeCellAtIndex(int index) { +MessageTableCellWithChevron<> * MathVariableBoxController::nodeCellAtIndex(int index) { assert(index >= 0 && index < k_numberOfMenuRows); return &m_nodeCells[index]; } diff --git a/apps/math_variable_box_controller.h b/apps/math_variable_box_controller.h index 6fe80f576f3..cf2f2fb0bb2 100644 --- a/apps/math_variable_box_controller.h +++ b/apps/math_variable_box_controller.h @@ -37,7 +37,7 @@ class MathVariableBoxController : public AlternateEmptyNestedMenuController { constexpr static int k_numberOfMenuRows = 3; constexpr static KDCoordinate k_leafMargin = 20; ExpressionTableCellWithExpression * leafCellAtIndex(int index) override; - MessageTableCellWithChevron * nodeCellAtIndex(int index) override; + MessageTableCellWithChevron<> * nodeCellAtIndex(int index) override; Page pageAtIndex(int index); void setPage(Page page); bool selectSubMenu(int selectedRow) override; @@ -53,7 +53,7 @@ class MathVariableBoxController : public AlternateEmptyNestedMenuController { Page m_currentPage; Page m_lockPageDelete; ExpressionTableCellWithExpression m_leafCells[k_maxNumberOfDisplayedRows]; - MessageTableCellWithChevron m_nodeCells[k_numberOfMenuRows]; + MessageTableCellWithChevron<> m_nodeCells[k_numberOfMenuRows]; MathVariableBoxEmptyController m_emptyViewController; // Layout memoization // TODO: make a helper doing the RingMemoizationOfConsecutiveObjets to factorize this code and ExpressionModelStore code diff --git a/apps/on_boarding/app.cpp b/apps/on_boarding/app.cpp index 3e914f7d9d4..59f83fc2ff1 100644 --- a/apps/on_boarding/app.cpp +++ b/apps/on_boarding/app.cpp @@ -18,15 +18,7 @@ App::App(Snapshot * snapshot) : m_localizationController(&m_modalViewController, Metric::CommonTopMargin, LocalizationController::Mode::Language), m_logoController() { -} - -int App::numberOfTimers() { - return firstResponder() == &m_logoController; -} - -Timer * App::timerAtIndex(int i) { - assert(i == 0); - return &m_logoController; + AppsContainer::sharedAppsContainer()->addTimer(&m_logoController); } bool App::processEvent(Ion::Events::Event e) { diff --git a/apps/on_boarding/app.h b/apps/on_boarding/app.h index d9dbe43a487..38233c5bdf4 100644 --- a/apps/on_boarding/app.h +++ b/apps/on_boarding/app.h @@ -15,9 +15,6 @@ class App : public ::App { App * unpack(Container * container) override; Descriptor * descriptor() override; }; - - int numberOfTimers() override; - Timer * timerAtIndex(int i) override; bool processEvent(Ion::Events::Event) override; void didBecomeActive(Window * window) override; private: diff --git a/apps/on_boarding/logo_controller.cpp b/apps/on_boarding/logo_controller.cpp index 13df87e4d01..0597bfcdf09 100644 --- a/apps/on_boarding/logo_controller.cpp +++ b/apps/on_boarding/logo_controller.cpp @@ -17,6 +17,7 @@ LogoController::LogoController() : bool LogoController::fire() { Container::activeApp()->dismissModalViewController(); + AppsContainer::sharedAppsContainer()->removeTimer(this); return true; } @@ -41,7 +42,6 @@ void LogoController::viewWillAppear() { if (!backlightInitialized) { Ion::Backlight::init(); } - ViewController::viewWillAppear(); } void LogoController::viewDidDisappear() { diff --git a/apps/reader/list_book_controller.cpp b/apps/reader/list_book_controller.cpp index 445811a9e3c..8dbf7aa3b56 100644 --- a/apps/reader/list_book_controller.cpp +++ b/apps/reader/list_book_controller.cpp @@ -37,7 +37,7 @@ int ListBookController::reusableCellCount() const { } void ListBookController::willDisplayCellForIndex(HighlightCell * cell, int index) { - MessageTableCell* myTextCell = static_cast(cell); + MessageTableCell<> * myTextCell = static_cast *>(cell); MessageTextView* textView = static_cast(myTextCell->labelView()); textView->setText(m_files[index].name); myTextCell->setMessageFont(KDFont::LargeFont); //TODO set cell font at building ? diff --git a/apps/reader/list_book_controller.h b/apps/reader/list_book_controller.h index 13cacd2b7bd..3250b173f80 100644 --- a/apps/reader/list_book_controller.h +++ b/apps/reader/list_book_controller.h @@ -34,7 +34,7 @@ class ListBookController : public ViewController, public SimpleListViewDataSourc int m_txtFilesNumber; int m_urtFilesNumber; static const int k_cellsNumber = 6; - MessageTableCellWithChevron m_cells[k_cellsNumber]; + MessageTableCellWithChevron<> m_cells[k_cellsNumber]; ReadBookController m_readBookController; }; diff --git a/apps/regression/graph_options_controller.cpp b/apps/regression/graph_options_controller.cpp index 83d80db4343..70770ae0511 100644 --- a/apps/regression/graph_options_controller.cpp +++ b/apps/regression/graph_options_controller.cpp @@ -118,7 +118,7 @@ void GraphOptionsController::willDisplayCellForIndex(HighlightCell * cell, int i return; } assert(index >=0 && index < k_numberOfParameterCells); - MessageTableCellWithChevron * myCell = (MessageTableCellWithChevron *)cell; + MessageTableCellWithChevron<> * myCell = (MessageTableCellWithChevron<> *)cell; I18n::Message titles[k_numberOfParameterCells] = {I18n::Message::XPrediction, I18n::Message::YPrediction}; myCell->setMessage(titles[index]); } diff --git a/apps/regression/graph_options_controller.h b/apps/regression/graph_options_controller.h index 69ec697bf63..202c4d2f806 100644 --- a/apps/regression/graph_options_controller.h +++ b/apps/regression/graph_options_controller.h @@ -32,7 +32,7 @@ class GraphOptionsController : public ViewController, public ListViewDataSource, constexpr static int k_regressionCellType = 0; constexpr static int k_parameterCelltype = 1; constexpr static int k_numberOfParameterCells = 2; - MessageTableCellWithChevron m_parameterCells[k_numberOfParameterCells]; + MessageTableCellWithChevron<> m_parameterCells[k_numberOfParameterCells]; MessageTableCellWithChevronAndExpression m_changeRegressionCell; SelectableTableView m_selectableTableView; GoToParameterController m_goToParameterController; diff --git a/apps/sequence/graph/curve_parameter_controller.h b/apps/sequence/graph/curve_parameter_controller.h index 7d47f1f6d11..c47af5a3dad 100644 --- a/apps/sequence/graph/curve_parameter_controller.h +++ b/apps/sequence/graph/curve_parameter_controller.h @@ -20,7 +20,7 @@ class CurveParameterController : public Shared::FunctionCurveParameterController constexpr static int k_totalNumberOfCells = 2; GoToParameterController * goToParameterController() override; GoToParameterController m_goToParameterController; - MessageTableCell m_sumCell; + MessageTableCell<> m_sumCell; GraphController * m_graphController; }; diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index 61eb08f13ce..4b590b45f11 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -175,7 +175,7 @@ void MainController::willDisplayCellForIndex(HighlightCell * cell, int index) { myGauge->setLevel((float)globalPreferences->brightnessLevel()/(float)Ion::Backlight::MaxBrightness); return; } - MessageTableCell * myCell = (MessageTableCell *)cell; + MessageTableCell<> * myCell = (MessageTableCell<> *)cell; myCell->setMessage(title); if (model()->childAtIndex(index)->label() == I18n::Message::Language) { int index = (int)(globalPreferences->language()); diff --git a/apps/settings/sub_menu/exam_mode_controller.cpp b/apps/settings/sub_menu/exam_mode_controller.cpp index 33a853eaab1..9fd322321a7 100644 --- a/apps/settings/sub_menu/exam_mode_controller.cpp +++ b/apps/settings/sub_menu/exam_mode_controller.cpp @@ -88,7 +88,7 @@ void ExamModeController::willDisplayCellForIndex(HighlightCell * cell, int index I18n::Message thisLabel = m_messageTreeModel->childAtIndex(index)->label(); if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode() && (thisLabel == I18n::Message::ActivateExamMode || thisLabel == I18n::Message::ExamModeActive)) { - MessageTableCell * myCell = (MessageTableCell *)cell; + MessageTableCell<> * myCell = (MessageTableCell<> *)cell; myCell->setMessage(I18n::Message::ExamModeActive); } if (thisLabel == I18n::Message::ExamModeMode) { diff --git a/apps/settings/sub_menu/exam_mode_controller.h b/apps/settings/sub_menu/exam_mode_controller.h index e7abb1b1298..ebdfb219536 100644 --- a/apps/settings/sub_menu/exam_mode_controller.h +++ b/apps/settings/sub_menu/exam_mode_controller.h @@ -27,7 +27,7 @@ class ExamModeController : public GenericSubController { GlobalPreferences::ExamMode examMode(); static constexpr int k_maxNumberOfCells = 4; SelectableViewWithMessages m_contentView; - MessageTableCell m_cell[k_maxNumberOfCells]; + MessageTableCell<> m_cell[k_maxNumberOfCells]; PreferencesController m_ledController; PreferencesController m_examModeModeController; MessageTableCellWithChevronAndMessage m_examModeCell; diff --git a/apps/settings/sub_menu/generic_sub_controller.cpp b/apps/settings/sub_menu/generic_sub_controller.cpp index 5286743d1d1..25c5d15fade 100644 --- a/apps/settings/sub_menu/generic_sub_controller.cpp +++ b/apps/settings/sub_menu/generic_sub_controller.cpp @@ -75,7 +75,7 @@ int GenericSubController::typeAtLocation(int i, int j) { } void GenericSubController::willDisplayCellForIndex(HighlightCell * cell, int index) { - MessageTableCell * myCell = (MessageTableCell *)cell; + MessageTableCell<> * myCell = (MessageTableCell<> *)cell; myCell->setMessage(m_messageTreeModel->childAtIndex(index)->label()); } diff --git a/apps/shared/color_cell.h b/apps/shared/color_cell.h index e9d5e4b66de..1951cd68015 100644 --- a/apps/shared/color_cell.h +++ b/apps/shared/color_cell.h @@ -6,7 +6,7 @@ namespace Shared { -class MessageTableCellWithColor : public MessageTableCell { +class MessageTableCellWithColor : public MessageTableCell<> { public: MessageTableCellWithColor(); View * accessoryView() const override; diff --git a/apps/shared/function_curve_parameter_controller.h b/apps/shared/function_curve_parameter_controller.h index 96e8668445f..a720e2e9d39 100644 --- a/apps/shared/function_curve_parameter_controller.h +++ b/apps/shared/function_curve_parameter_controller.h @@ -16,7 +16,7 @@ class FunctionCurveParameterController : public ViewController, public SimpleLis void setRecord(Ion::Storage::Record record) { m_record = record; } protected: bool handleGotoSelection(); - MessageTableCellWithChevron m_goToCell; + MessageTableCellWithChevron<> m_goToCell; SelectableTableView m_selectableTableView; Ion::Storage::Record m_record; private: diff --git a/apps/shared/list_parameter_controller.h b/apps/shared/list_parameter_controller.h index e7ffb0fc6b8..d948073bb6f 100644 --- a/apps/shared/list_parameter_controller.h +++ b/apps/shared/list_parameter_controller.h @@ -41,7 +41,7 @@ class ListParameterController : public ViewController, public ListViewDataSource private: MessageTableCellWithChevronAndMessage m_colorCell; MessageTableCellWithSwitch m_enableCell; - MessageTableCell m_deleteCell; + MessageTableCell<> m_deleteCell; ColorParameterController m_colorParameterController; }; diff --git a/apps/shared/localization_controller.cpp b/apps/shared/localization_controller.cpp index b300e84f228..b1476aa3dcf 100644 --- a/apps/shared/localization_controller.cpp +++ b/apps/shared/localization_controller.cpp @@ -191,10 +191,10 @@ bool LocalizationController::handleEvent(Ion::Events::Event event) { void LocalizationController::willDisplayCellForIndex(HighlightCell * cell, int index) { if (mode() == Mode::Language) { - static_cast(cell)->setMessage(I18n::LanguageNames[index]); + static_cast *>(cell)->setMessage(I18n::LanguageNames[index]); return; } assert(mode() == Mode::Country); - static_cast(cell)->setMessage(I18n::CountryNames[static_cast(CountryAtIndex(index))]); + static_cast *>(cell)->setMessage(I18n::CountryNames[static_cast(CountryAtIndex(index))]); } } diff --git a/apps/shared/localization_controller.h b/apps/shared/localization_controller.h index 327d3ef5d3c..ddc8c1d08da 100644 --- a/apps/shared/localization_controller.h +++ b/apps/shared/localization_controller.h @@ -71,7 +71,7 @@ class LocalizationController : public ViewController, public SimpleListViewDataS private: static constexpr int k_numberOfCells = I18n::NumberOfLanguages > I18n::NumberOfCountries ? I18n::NumberOfLanguages : I18n::NumberOfCountries; - MessageTableCell m_cells[k_numberOfCells]; + MessageTableCell<> m_cells[k_numberOfCells]; Mode m_mode; }; diff --git a/apps/shared/store_parameter_controller.h b/apps/shared/store_parameter_controller.h index efc8c6f1273..4302efcc84a 100644 --- a/apps/shared/store_parameter_controller.h +++ b/apps/shared/store_parameter_controller.h @@ -41,7 +41,7 @@ class StoreParameterController : public ViewController, public ListViewDataSourc private: virtual I18n::Message sortMessage() { return m_xColumnSelected ? I18n::Message::SortValues : I18n::Message::SortSizes; } constexpr static int k_totalNumberOfCell = 3; - MessageTableCell m_cells[k_totalNumberOfCell]; + MessageTableCell<> m_cells[k_totalNumberOfCell]; StoreController * m_storeController; bool m_xColumnSelected; }; diff --git a/apps/shared/values_function_parameter_controller.h b/apps/shared/values_function_parameter_controller.h index f4365312300..940b769844d 100644 --- a/apps/shared/values_function_parameter_controller.h +++ b/apps/shared/values_function_parameter_controller.h @@ -29,7 +29,7 @@ class ValuesFunctionParameterController : public ViewController, public SimpleLi int reusableCellCount() const override { return 1; } void setRecord(Ion::Storage::Record record) { m_record = record; } protected: - MessageTableCellWithChevron m_copyColumn; + MessageTableCellWithChevron<> m_copyColumn; SelectableTableView m_selectableTableView; Ion::Storage::Record m_record; private: diff --git a/apps/shared/values_parameter_controller.cpp b/apps/shared/values_parameter_controller.cpp index 272808ae947..edfeeb9f3c6 100644 --- a/apps/shared/values_parameter_controller.cpp +++ b/apps/shared/values_parameter_controller.cpp @@ -24,7 +24,7 @@ View * ValuesParameterController::view() { } void ValuesParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { - MessageTableCell * myCell = (MessageTableCell *)cell; + MessageTableCell<> * myCell = (MessageTableCell<> *)cell; #if COPY_COLUMN I18n::Message labels[k_totalNumberOfCell] = {I18n::Message::ClearColumn, I18n::Message::CopyColumnInList, I18n::Message::IntervalSet}; #else diff --git a/apps/shared/values_parameter_controller.h b/apps/shared/values_parameter_controller.h index 5cc842f72df..777d1d205fb 100644 --- a/apps/shared/values_parameter_controller.h +++ b/apps/shared/values_parameter_controller.h @@ -21,13 +21,13 @@ class ValuesParameterController : public ViewController, public SimpleListViewDa private: #if COPY_COLUMN constexpr static int k_totalNumberOfCell = 3; - MessageTableCellWithChevron m_copyColumn; + MessageTableCellWithChevron<> m_copyColumn; #else constexpr static int k_totalNumberOfCell = 2; #endif I18n::Message m_pageTitle; - MessageTableCell m_deleteColumn; - MessageTableCellWithChevron m_setInterval; + MessageTableCell<> m_deleteColumn; + MessageTableCellWithChevron<> m_setInterval; SelectableTableView m_selectableTableView; }; diff --git a/apps/solver/equation_models_parameter_controller.h b/apps/solver/equation_models_parameter_controller.h index bb6812cb1ae..3eb88b5c317 100644 --- a/apps/solver/equation_models_parameter_controller.h +++ b/apps/solver/equation_models_parameter_controller.h @@ -30,7 +30,7 @@ class EquationModelsParameterController : public ViewController, public ListView }; StackViewController * stackController() const; constexpr static int k_numberOfExpressionCells = k_numberOfModels-1; - MessageTableCell m_emptyModelCell; + MessageTableCell<> m_emptyModelCell; ExpressionTableCell m_modelCells[k_numberOfExpressionCells]; Poincare::Layout m_layouts[k_numberOfExpressionCells]; SelectableTableView m_selectableTableView; diff --git a/escher/Makefile b/escher/Makefile index a8bb8491cc2..ca371fd33f7 100644 --- a/escher/Makefile +++ b/escher/Makefile @@ -21,6 +21,7 @@ endif escher_src += $(addprefix escher/src/,\ alternate_empty_view_controller.cpp \ + animation_timer.cpp \ app.cpp \ background_view.cpp \ bank_view_controller.cpp \ @@ -84,6 +85,7 @@ escher_src += $(addprefix escher/src/,\ selectable_table_view.cpp \ simple_list_view_data_source.cpp \ simple_table_view_data_source.cpp \ + slideable_message_text_view.cpp \ solid_color_view.cpp \ stack_view.cpp \ stack_view_controller.cpp \ diff --git a/escher/include/escher.h b/escher/include/escher.h index 62ceb7e1afc..bce7b8a2274 100644 --- a/escher/include/escher.h +++ b/escher/include/escher.h @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include #include @@ -66,6 +68,7 @@ #include #include #include +#include #include #include #include diff --git a/escher/include/escher/animated.h b/escher/include/escher/animated.h new file mode 100644 index 00000000000..ca92b4e3e9f --- /dev/null +++ b/escher/include/escher/animated.h @@ -0,0 +1,11 @@ +#ifndef APPS_ANIMATED_H +#define APPS_ANIMATED_H + +class Animated { +public: + virtual void willStartAnimation() {}; + virtual void didStopAnimation() {}; + virtual void animate() = 0; +}; + +#endif diff --git a/escher/include/escher/animation_timer.h b/escher/include/escher/animation_timer.h new file mode 100644 index 00000000000..9bb1b1f1bf7 --- /dev/null +++ b/escher/include/escher/animation_timer.h @@ -0,0 +1,25 @@ +#ifndef APPS_ANIMATION_TIMER_H +#define APPS_ANIMATION_TIMER_H + +#include +#include +#include + +class AnimationTimer : public Timer { +public: + AnimationTimer(): + Timer(1), + m_animated(nullptr) + {} + void setAnimated(Animated * animated); + void removeAnimated(Animated * animated=nullptr); +private: + bool fire() override { + assert(m_animated); + m_animated->animate(); + return true; + } + Animated * m_animated; +}; + +#endif diff --git a/escher/include/escher/app.h b/escher/include/escher/app.h index ab146713a7a..ce3883bef15 100644 --- a/escher/include/escher/app.h +++ b/escher/include/escher/app.h @@ -69,8 +69,6 @@ class App : public Responder { virtual void didBecomeActive(Window * window); virtual void willBecomeInactive(); View * modalView(); - virtual int numberOfTimers() { return 0; } - virtual Timer * timerAtIndex(int i) { assert(false); return nullptr; } virtual Poincare::Context * localContext() { return nullptr; } protected: App(Snapshot * snapshot, ViewController * rootViewController, I18n::Message warningMessage = (I18n::Message)0) : diff --git a/escher/include/escher/container.h b/escher/include/escher/container.h index 0070adb0285..02dbcd1a995 100644 --- a/escher/include/escher/container.h +++ b/escher/include/escher/container.h @@ -36,10 +36,6 @@ class Container : public RunLoop { static App * s_activeApp; private: void step(); - int numberOfTimers() override; - Timer * timerAtIndex(int i) override; - virtual int numberOfContainerTimers(); - virtual Timer * containerTimerAtIndex(int i); }; #endif diff --git a/escher/include/escher/message_table_cell.h b/escher/include/escher/message_table_cell.h index c43985d6dfa..65638e851ae 100644 --- a/escher/include/escher/message_table_cell.h +++ b/escher/include/escher/message_table_cell.h @@ -2,9 +2,11 @@ #define ESCHER_MESSAGE_TABLE_CELL_H #include +#include #include #include +template class MessageTableCell : public TableCell { public: MessageTableCell(I18n::Message label = (I18n::Message)0, const KDFont * font = KDFont::SmallFont, Layout layout = Layout::HorizontalLeftOverlap); @@ -17,7 +19,7 @@ class MessageTableCell : public TableCell { protected: KDColor backgroundColor() const override { return m_backgroundColor; } private: - MessageTextView m_messageTextView; + T m_messageTextView; KDColor m_backgroundColor; }; diff --git a/escher/include/escher/message_table_cell_with_buffer.h b/escher/include/escher/message_table_cell_with_buffer.h index 64866cce267..2b40e26be86 100644 --- a/escher/include/escher/message_table_cell_with_buffer.h +++ b/escher/include/escher/message_table_cell_with_buffer.h @@ -4,7 +4,7 @@ #include #include -class MessageTableCellWithBuffer : public MessageTableCell { +class MessageTableCellWithBuffer : public MessageTableCell<> { public: MessageTableCellWithBuffer(I18n::Message message = (I18n::Message)0, const KDFont * font = KDFont::SmallFont, const KDFont * accessoryFont = KDFont::LargeFont, KDColor accessoryTextColor = Palette::PrimaryText); View * accessoryView() const override; diff --git a/escher/include/escher/message_table_cell_with_chevron.h b/escher/include/escher/message_table_cell_with_chevron.h index 2611346d491..d29b68ea664 100644 --- a/escher/include/escher/message_table_cell_with_chevron.h +++ b/escher/include/escher/message_table_cell_with_chevron.h @@ -4,7 +4,8 @@ #include #include -class MessageTableCellWithChevron : public MessageTableCell { +template +class MessageTableCellWithChevron : public MessageTableCell { public: MessageTableCellWithChevron(I18n::Message message = (I18n::Message)0, const KDFont * font = KDFont::SmallFont); View * accessoryView() const override; diff --git a/escher/include/escher/message_table_cell_with_chevron_and_buffer.h b/escher/include/escher/message_table_cell_with_chevron_and_buffer.h index 86657ba92ee..1f952076ca1 100644 --- a/escher/include/escher/message_table_cell_with_chevron_and_buffer.h +++ b/escher/include/escher/message_table_cell_with_chevron_and_buffer.h @@ -4,7 +4,7 @@ #include #include -class MessageTableCellWithChevronAndBuffer : public MessageTableCellWithChevron { +class MessageTableCellWithChevronAndBuffer : public MessageTableCellWithChevron<> { public: MessageTableCellWithChevronAndBuffer(const KDFont * labelFont = KDFont::SmallFont, const KDFont * subAccessoryFont = KDFont::SmallFont); View * subAccessoryView() const override; diff --git a/escher/include/escher/message_table_cell_with_chevron_and_expression.h b/escher/include/escher/message_table_cell_with_chevron_and_expression.h index f94cac89bca..72b4f205934 100644 --- a/escher/include/escher/message_table_cell_with_chevron_and_expression.h +++ b/escher/include/escher/message_table_cell_with_chevron_and_expression.h @@ -4,7 +4,7 @@ #include #include -class MessageTableCellWithChevronAndExpression : public MessageTableCellWithChevron { +class MessageTableCellWithChevronAndExpression : public MessageTableCellWithChevron<> { public: MessageTableCellWithChevronAndExpression(I18n::Message message = (I18n::Message)0, const KDFont * font = KDFont::SmallFont); View * subAccessoryView() const override; diff --git a/escher/include/escher/message_table_cell_with_chevron_and_message.h b/escher/include/escher/message_table_cell_with_chevron_and_message.h index 976dc69ada6..041b49df324 100644 --- a/escher/include/escher/message_table_cell_with_chevron_and_message.h +++ b/escher/include/escher/message_table_cell_with_chevron_and_message.h @@ -3,7 +3,7 @@ #include -class MessageTableCellWithChevronAndMessage : public MessageTableCellWithChevron { +class MessageTableCellWithChevronAndMessage : public MessageTableCellWithChevron<> { public: MessageTableCellWithChevronAndMessage(const KDFont * labelFont = KDFont::SmallFont, const KDFont * contentFont = KDFont::SmallFont); View * subAccessoryView() const override; diff --git a/escher/include/escher/message_table_cell_with_editable_text.h b/escher/include/escher/message_table_cell_with_editable_text.h index 54e0e19c65c..ceb16e7ea1f 100644 --- a/escher/include/escher/message_table_cell_with_editable_text.h +++ b/escher/include/escher/message_table_cell_with_editable_text.h @@ -6,7 +6,7 @@ #include #include -class MessageTableCellWithEditableText : public Responder, public MessageTableCell { +class MessageTableCellWithEditableText : public Responder, public MessageTableCell<> { public: MessageTableCellWithEditableText(Responder * parentResponder = nullptr, InputEventHandlerDelegate * inputEventHandlerDelegate = nullptr, TextFieldDelegate * textFieldDelegate = nullptr, I18n::Message message = (I18n::Message)0); View * accessoryView() const override; diff --git a/escher/include/escher/message_table_cell_with_expression.h b/escher/include/escher/message_table_cell_with_expression.h index f9704eb2248..c5778393af5 100644 --- a/escher/include/escher/message_table_cell_with_expression.h +++ b/escher/include/escher/message_table_cell_with_expression.h @@ -4,7 +4,7 @@ #include #include -class MessageTableCellWithExpression : public MessageTableCell { +class MessageTableCellWithExpression : public MessageTableCell<> { public: MessageTableCellWithExpression(I18n::Message message = (I18n::Message)0, const KDFont * font = KDFont::LargeFont); View * accessoryView() const override; diff --git a/escher/include/escher/message_table_cell_with_gauge.h b/escher/include/escher/message_table_cell_with_gauge.h index 8dfddf59911..527ce948e29 100644 --- a/escher/include/escher/message_table_cell_with_gauge.h +++ b/escher/include/escher/message_table_cell_with_gauge.h @@ -4,7 +4,7 @@ #include #include -class MessageTableCellWithGauge : public MessageTableCell { +class MessageTableCellWithGauge : public MessageTableCell<> { public: MessageTableCellWithGauge(I18n::Message message = (I18n::Message)0, const KDFont * font = KDFont::SmallFont); View * accessoryView() const override; diff --git a/escher/include/escher/message_table_cell_with_message.h b/escher/include/escher/message_table_cell_with_message.h index 02257f96e6b..243b3be9224 100644 --- a/escher/include/escher/message_table_cell_with_message.h +++ b/escher/include/escher/message_table_cell_with_message.h @@ -3,9 +3,10 @@ #include -class MessageTableCellWithMessage : public MessageTableCell { +template +class MessageTableCellWithMessage : public MessageTableCell { public: - MessageTableCellWithMessage(I18n::Message message = (I18n::Message)0, Layout layout = Layout::Vertical); + MessageTableCellWithMessage(I18n::Message message = (I18n::Message)0, TableCell::Layout layout = TableCell::Layout::Vertical); View * accessoryView() const override; void setHighlighted(bool highlight) override; void setAccessoryMessage(I18n::Message textBody); diff --git a/escher/include/escher/message_table_cell_with_switch.h b/escher/include/escher/message_table_cell_with_switch.h index 78e27ef07a0..1a334872a1b 100644 --- a/escher/include/escher/message_table_cell_with_switch.h +++ b/escher/include/escher/message_table_cell_with_switch.h @@ -4,7 +4,7 @@ #include #include -class MessageTableCellWithSwitch : public MessageTableCell { +class MessageTableCellWithSwitch : public MessageTableCell<> { public: MessageTableCellWithSwitch(I18n::Message message = (I18n::Message)0, const KDFont * font = KDFont::SmallFont); View * accessoryView() const override; diff --git a/escher/include/escher/run_loop.h b/escher/include/escher/run_loop.h index 2ef4b9207d2..26ddc0ad60b 100644 --- a/escher/include/escher/run_loop.h +++ b/escher/include/escher/run_loop.h @@ -9,13 +9,14 @@ class RunLoop { RunLoop(); void run(); void runWhile(bool (*callback)(void * ctx), void * ctx); + void addTimer(Timer * timer); + void removeTimer(Timer * timer); protected: virtual bool dispatchEvent(Ion::Events::Event e) = 0; - virtual int numberOfTimers(); - virtual Timer * timerAtIndex(int i); private: bool step(); int m_time; + Timer * m_firstTimer; }; #endif diff --git a/escher/include/escher/slideable_message_text_view.h b/escher/include/escher/slideable_message_text_view.h new file mode 100644 index 00000000000..fa9ae4e561a --- /dev/null +++ b/escher/include/escher/slideable_message_text_view.h @@ -0,0 +1,26 @@ + +#ifndef ESCHER_SLIDEABLE_MESSAGE_TEXT_VIEW_H +#define ESCHER_SLIDEABLE_MESSAGE_TEXT_VIEW_H + +#include +#include + +class SlideableMessageTextView : public MessageTextView, public Animated { +public: + SlideableMessageTextView(const KDFont * font = KDFont::LargeFont, I18n::Message message = (I18n::Message)0, float horizontalAlignment = 0.0f, float verticalAlignment = 0.0f, + KDColor textColor = Palette::PrimaryText, KDColor backgroundColor = Palette::ListCellBackground); + void willStartAnimation() override; + void didStopAnimation() override; + void animate() override; + + /* TextView */ + void drawRect(KDContext * ctx, KDRect rect) const override; + +private: + static constexpr uint8_t k_numberOfSpaces = 3; + KDCoordinate m_textOffset; + bool m_goingLeft; // true if we are going left, false if we are going right + bool m_paused; +}; + +#endif diff --git a/escher/include/escher/timer.h b/escher/include/escher/timer.h index 94ed476c4c4..7c9f4cb16df 100644 --- a/escher/include/escher/timer.h +++ b/escher/include/escher/timer.h @@ -3,25 +3,24 @@ #include -/* Timers we'll need - * - Blink cursor timer - * - Dim Screen timer - * - Power down timer - * - Watchdog timer ? - * - Battery level timer - * - LED blink timer +/** + * A timer that can be used to schedule events. + * We organize timers in a linked list. */ - class Timer { public: static constexpr int TickDuration = 300; // In Miliseconds Timer(uint32_t period); // Period is in ticks bool tick(); void reset(uint32_t NewPeriod = -1); + void setNext(Timer * next) { m_next = next; } + Timer * next() { return m_next; } protected: virtual bool fire() = 0; uint32_t m_period; uint32_t m_numberOfTicksBeforeFire; +private: + Timer * m_next; }; #endif diff --git a/escher/include/escher/toolbox.h b/escher/include/escher/toolbox.h index f128f54ebb9..9a9ff09642c 100644 --- a/escher/include/escher/toolbox.h +++ b/escher/include/escher/toolbox.h @@ -28,8 +28,8 @@ class Toolbox : public NestedMenuController { /* indexAfterFork is called when a fork-node is encountered to choose which * of its children should be selected, based on external context. */ virtual int indexAfterFork() const { assert(false); return 0; }; - MessageTableCellWithMessage * leafCellAtIndex(int index) override = 0; - MessageTableCellWithChevron * nodeCellAtIndex(int index) override = 0; + MessageTableCellWithMessage * leafCellAtIndex(int index) override = 0; + MessageTableCellWithChevron * nodeCellAtIndex(int index) override = 0; mutable const ToolboxMessageTree * m_messageTreeModel; /* m_messageTreeModel points at the messageTree of the tree (describing the * whole model) where we are located. It enables to know which rows are leaves diff --git a/escher/include/escher/toolbox_message_tree.h b/escher/include/escher/toolbox_message_tree.h index ba8ac5005a3..f99db10627d 100644 --- a/escher/include/escher/toolbox_message_tree.h +++ b/escher/include/escher/toolbox_message_tree.h @@ -5,14 +5,16 @@ class ToolboxMessageTree : public MessageTree { public: - constexpr static ToolboxMessageTree Leaf(I18n::Message label, I18n::Message text = (I18n::Message)0, bool stripInsertedText = true, I18n::Message insertedText = (I18n::Message)0) { + constexpr static ToolboxMessageTree Leaf(I18n::Message label, I18n::Message text = (I18n::Message)0, bool stripInsertedText = true, I18n::Message insertedText = (I18n::Message)0, bool multiLine = false, uint8_t numberOfLines = 1) { return ToolboxMessageTree( label, text, (insertedText == (I18n::Message)0) ? label : insertedText, static_cast(0), 0, - stripInsertedText); + stripInsertedText, + multiLine, + numberOfLines); }; template constexpr static ToolboxMessageTree Node(I18n::Message label, const ToolboxMessageTree (&children)[N], bool fork = false) { @@ -41,22 +43,28 @@ class ToolboxMessageTree : public MessageTree { I18n::Message insertedText() const { return m_insertedText; } bool stripInsertedText() const { return m_stripInsertedText; } bool isFork() const { return numberOfChildren() < 0; } + bool isMultiLine() const { return m_multiLine; } + uint8_t numberOfLines() const { return m_numberOfLines; } private: - constexpr ToolboxMessageTree(I18n::Message label, I18n::Message text, I18n::Message insertedText, const ToolboxMessageTree * children, int numberOfChildren, bool stripInsertedText) : + constexpr ToolboxMessageTree(I18n::Message label, I18n::Message text, I18n::Message insertedText, const ToolboxMessageTree * children, int numberOfChildren, bool stripInsertedText, bool multiLine = false, uint8_t numberOfLines = 1) : MessageTree(label, numberOfChildren), m_children(children), m_text(text), m_insertedText(insertedText), m_stripInsertedText(stripInsertedText), - m_childrenConsecutive(true) + m_childrenConsecutive(true), + m_multiLine(multiLine), + m_numberOfLines(numberOfLines) {} - constexpr ToolboxMessageTree(I18n::Message label, I18n::Message text, I18n::Message insertedText, const ToolboxMessageTree ** children, int numberOfChildren, bool stripInsertedText) : + constexpr ToolboxMessageTree(I18n::Message label, I18n::Message text, I18n::Message insertedText, const ToolboxMessageTree ** children, int numberOfChildren, bool stripInsertedText, bool multiLine = false, uint8_t numberOfLines = 1) : MessageTree(label, numberOfChildren), m_children(children), m_text(text), m_insertedText(insertedText), m_stripInsertedText(stripInsertedText), - m_childrenConsecutive(false) + m_childrenConsecutive(false), + m_multiLine(multiLine), + m_numberOfLines(numberOfLines) {} union Children { @@ -71,6 +79,8 @@ class ToolboxMessageTree : public MessageTree { I18n::Message m_insertedText; bool m_stripInsertedText; const bool m_childrenConsecutive; + bool m_multiLine; + uint8_t m_numberOfLines; }; #endif diff --git a/escher/src/animation_timer.cpp b/escher/src/animation_timer.cpp new file mode 100644 index 00000000000..2dd2b898e6d --- /dev/null +++ b/escher/src/animation_timer.cpp @@ -0,0 +1,15 @@ +#include +#include + + +void AnimationTimer::setAnimated(Animated * animated) { + m_animated = animated; + AppsContainer::sharedAppsContainer()->addTimer(this); +} + +void AnimationTimer::removeAnimated(Animated * animated) { + if (m_animated == animated || animated == nullptr) { + m_animated = nullptr; + AppsContainer::sharedAppsContainer()->removeTimer(this); + } +} diff --git a/escher/src/container.cpp b/escher/src/container.cpp index a307e4cfc94..731ad5d0f12 100644 --- a/escher/src/container.cpp +++ b/escher/src/container.cpp @@ -55,23 +55,3 @@ void Container::run() { window()->redraw(); RunLoop::run(); } - -int Container::numberOfTimers() { - return s_activeApp->numberOfTimers() + numberOfContainerTimers(); -} - -Timer * Container::timerAtIndex(int i) { - if (i < s_activeApp->numberOfTimers()) { - return s_activeApp->timerAtIndex(i); - } - return containerTimerAtIndex(i-s_activeApp->numberOfTimers()); -} - -int Container::numberOfContainerTimers() { - return 0; -} - -Timer * Container::containerTimerAtIndex(int i) { - assert(false); - return nullptr; -} diff --git a/escher/src/message_table_cell.cpp b/escher/src/message_table_cell.cpp index 648ef906873..af52d0098ac 100644 --- a/escher/src/message_table_cell.cpp +++ b/escher/src/message_table_cell.cpp @@ -1,39 +1,65 @@ #include #include +#include #include -MessageTableCell::MessageTableCell(I18n::Message label, const KDFont * font, Layout layout) : +template +MessageTableCell::MessageTableCell(I18n::Message label, const KDFont * font, Layout layout) : TableCell(layout), m_messageTextView(font, label, 0, 0.5, Palette::PrimaryText, Palette::ListCellBackground), m_backgroundColor(KDColorWhite) { } -View * MessageTableCell::labelView() const { +template +View * MessageTableCell::labelView() const { return (View *)&m_messageTextView; } -void MessageTableCell::setHighlighted(bool highlight) { +template<> +void MessageTableCell::setHighlighted(bool highlight) { HighlightCell::setHighlighted(highlight); KDColor backgroundColor = highlight? Palette::ListCellBackgroundSelected : Palette::ListCellBackground; m_messageTextView.setBackgroundColor(backgroundColor); + static AnimationTimer s_animationTimer = AnimationTimer(); + if (highlight) { + m_messageTextView.willStartAnimation(); + s_animationTimer.setAnimated(&m_messageTextView); + } else { + s_animationTimer.removeAnimated(&m_messageTextView); + m_messageTextView.didStopAnimation(); + } } -void MessageTableCell::setMessage(I18n::Message text) { +template<> +void MessageTableCell::setHighlighted(bool highlight) { + HighlightCell::setHighlighted(highlight); + KDColor backgroundColor = highlight? Palette::ListCellBackgroundSelected : Palette::ListCellBackground; + m_messageTextView.setBackgroundColor(backgroundColor); +} + +template +void MessageTableCell::setMessage(I18n::Message text) { m_messageTextView.setMessage(text); layoutSubviews(); } -void MessageTableCell::setTextColor(KDColor color) { +template +void MessageTableCell::setTextColor(KDColor color) { m_messageTextView.setTextColor(color); } -void MessageTableCell::setMessageFont(const KDFont * font) { +template +void MessageTableCell::setMessageFont(const KDFont * font) { m_messageTextView.setFont(font); layoutSubviews(); } -void MessageTableCell::setBackgroundColor(KDColor color) { +template +void MessageTableCell::setBackgroundColor(KDColor color) { m_backgroundColor = color; m_messageTextView.setBackgroundColor(color); } + +template class MessageTableCell; +template class MessageTableCell; diff --git a/escher/src/message_table_cell_with_chevron.cpp b/escher/src/message_table_cell_with_chevron.cpp index 0f80c80343a..9711d2f79de 100644 --- a/escher/src/message_table_cell_with_chevron.cpp +++ b/escher/src/message_table_cell_with_chevron.cpp @@ -1,12 +1,24 @@ #include -MessageTableCellWithChevron::MessageTableCellWithChevron(I18n::Message message, const KDFont * font) : - MessageTableCell(message, font), +template<> +MessageTableCellWithChevron::MessageTableCellWithChevron(I18n::Message message, const KDFont * font) : + MessageTableCell(message, font), m_accessoryView() { } -View * MessageTableCellWithChevron::accessoryView() const { +template<> +MessageTableCellWithChevron::MessageTableCellWithChevron(I18n::Message message, const KDFont * font) : + MessageTableCell(message, font,TableCell::Layout::HorizontalRightOverlap), + m_accessoryView() +{ +} + +template +View * MessageTableCellWithChevron::accessoryView() const { return (View *)&m_accessoryView; } +template class MessageTableCellWithChevron; +template class MessageTableCellWithChevron; + diff --git a/escher/src/message_table_cell_with_message.cpp b/escher/src/message_table_cell_with_message.cpp index 16cfd3c6ed5..5c3d43aa47c 100644 --- a/escher/src/message_table_cell_with_message.cpp +++ b/escher/src/message_table_cell_with_message.cpp @@ -2,38 +2,47 @@ #include #include -MessageTableCellWithMessage::MessageTableCellWithMessage(I18n::Message message, Layout layout) : - MessageTableCell(message, KDFont::SmallFont, layout), +template +MessageTableCellWithMessage::MessageTableCellWithMessage(I18n::Message message, TableCell::Layout layout) : + MessageTableCell(message, KDFont::SmallFont, layout), m_accessoryView(KDFont::SmallFont, (I18n::Message)0, 0.0f, 0.5f) { - if (layout != Layout::Vertical) { + if (layout != TableCell::Layout::Vertical) { m_accessoryView.setAlignment(1.0f, 0.5f); } } -void MessageTableCellWithMessage::setAccessoryMessage(I18n::Message textBody) { +template +void MessageTableCellWithMessage::setAccessoryMessage(I18n::Message textBody) { m_accessoryView.setMessage(textBody); - reloadCell(); + this->reloadCell(); } -View * MessageTableCellWithMessage::accessoryView() const { +template +View * MessageTableCellWithMessage::accessoryView() const { if (strlen(m_accessoryView.text()) == 0) { return nullptr; } return (View *)&m_accessoryView; } -void MessageTableCellWithMessage::setHighlighted(bool highlight) { - MessageTableCell::setHighlighted(highlight); - KDColor backgroundColor = isHighlighted()? Palette::ListCellBackgroundSelected : Palette::ListCellBackground; +template +void MessageTableCellWithMessage::setHighlighted(bool highlight) { + MessageTableCell::setHighlighted(highlight); + KDColor backgroundColor = this->isHighlighted()? Palette::ListCellBackgroundSelected : Palette::ListCellBackground; m_accessoryView.setBackgroundColor(backgroundColor); } -void MessageTableCellWithMessage::setTextColor(KDColor color) { +template +void MessageTableCellWithMessage::setTextColor(KDColor color) { m_accessoryView.setTextColor(color); - MessageTableCell::setTextColor(color); + MessageTableCell::setTextColor(color); } -void MessageTableCellWithMessage::setAccessoryTextColor(KDColor color) { +template +void MessageTableCellWithMessage::setAccessoryTextColor(KDColor color) { m_accessoryView.setTextColor(color); } + +template class MessageTableCellWithMessage; +template class MessageTableCellWithMessage; diff --git a/escher/src/run_loop.cpp b/escher/src/run_loop.cpp index f5d8c3b9734..bc2f02a0901 100644 --- a/escher/src/run_loop.cpp +++ b/escher/src/run_loop.cpp @@ -3,16 +3,9 @@ #include RunLoop::RunLoop() : - m_time(0) { -} - -int RunLoop::numberOfTimers() { - return 0; -} - -Timer * RunLoop::timerAtIndex(int i) { - assert(false); - return nullptr; + m_time(0), + m_firstTimer(nullptr) +{ } void RunLoop::run() { @@ -45,11 +38,12 @@ bool RunLoop::step() { if (m_time >= Timer::TickDuration) { m_time -= Timer::TickDuration; - for (int i=0; itick()) { dispatchEvent(Ion::Events::TimerFire); } + timer = timer->next(); } } @@ -65,7 +59,7 @@ bool RunLoop::step() { #endif if (event != Ion::Events::None) { -#if !PLATFORM_DEVICE +#if !PLATFORM_DEVICEdidStopAnimation if (event == Ion::Events::ExternalText && !KDFont::CanBeWrittenWithGlyphs(event.text())) { return true; } @@ -75,3 +69,27 @@ bool RunLoop::step() { return event != Ion::Events::Termination; } + +void RunLoop::addTimer(Timer * timer) { + if (m_firstTimer == nullptr) { + m_firstTimer = timer; + } else { + Timer * actual = m_firstTimer; + while (actual->next()) { + actual = actual->next(); + } + actual->setNext(timer); + } +} + +void RunLoop::removeTimer(Timer * timer) { + if (m_firstTimer == timer) { + m_firstTimer = timer->next(); + } else { + Timer * actual = m_firstTimer; + while (actual->next() != timer) { + actual = actual->next(); + } + actual->setNext(timer->next()); + } +} diff --git a/escher/src/slideable_message_text_view.cpp b/escher/src/slideable_message_text_view.cpp new file mode 100644 index 00000000000..f55b3f22a5d --- /dev/null +++ b/escher/src/slideable_message_text_view.cpp @@ -0,0 +1,63 @@ +#include +#include + +SlideableMessageTextView::SlideableMessageTextView(const KDFont * font, I18n::Message message, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor backgroundColor) : + MessageTextView(font, message, horizontalAlignment, verticalAlignment, textColor, backgroundColor), + m_textOffset(0) +{ + +} + +void SlideableMessageTextView::willStartAnimation() { + m_textOffset = 0; + m_goingLeft = true; + m_paused = true; +} + +void SlideableMessageTextView::didStopAnimation() { + m_textOffset = 0; +} + +void SlideableMessageTextView::animate() { + if (m_paused) { + m_paused = false; + return; + } + + if (text() == nullptr) { + return; + } + + KDSize textSize = m_font->stringSize(text()); + + if (textSize.width() <= bounds().width()) { + return; + } + + KDCoordinate glyphWidth = m_font->glyphSize().width(); + m_textOffset += glyphWidth * (m_goingLeft ? -1 : 1); + + if (m_goingLeft && textSize.width() + m_textOffset < bounds().width()) { + m_goingLeft = false; + m_textOffset = bounds().width() - textSize.width(); + m_paused = true; + } else if (!m_goingLeft && m_textOffset > 0) { + m_goingLeft = true; + m_textOffset = 0; + m_paused = true; + } + + markRectAsDirty(bounds()); +} + +void SlideableMessageTextView::drawRect(KDContext * ctx, KDRect rect) const { + if (text() == nullptr) { + return; + } + KDSize textSize = m_font->stringSize(text()); + KDPoint origin( + m_horizontalAlignment * (m_frame.width() - textSize.width()) + m_textOffset, + m_verticalAlignment * (m_frame.height() - textSize.height())); + ctx->fillRect(bounds(), m_backgroundColor); + ctx->drawString(text(), origin, m_font, m_textColor, m_backgroundColor); +} diff --git a/escher/src/toolbox.cpp b/escher/src/toolbox.cpp index 29f22a0412b..14bc47014a6 100644 --- a/escher/src/toolbox.cpp +++ b/escher/src/toolbox.cpp @@ -27,15 +27,16 @@ int Toolbox::reusableCellCount(int type) { void Toolbox::willDisplayCellForIndex(HighlightCell * cell, int index) { ToolboxMessageTree * messageTree = (ToolboxMessageTree *)m_messageTreeModel->childAtIndex(index); if (messageTree->numberOfChildren() == 0) { - MessageTableCellWithMessage * myCell = (MessageTableCellWithMessage *)cell; + MessageTableCellWithMessage * myCell = (MessageTableCellWithMessage *)cell; myCell->setMessage(messageTree->label()); myCell->setAccessoryMessage(messageTree->text()); myCell->setAccessoryTextColor(Palette::SecondaryText); return; + } else { + MessageTableCell<> * myCell = (MessageTableCell<> *)cell; + myCell->setMessage(messageTree->label()); + myCell->reloadCell(); } - MessageTableCell * myCell = (MessageTableCell *)cell; - myCell->setMessage(messageTree->label()); - myCell->reloadCell(); } int Toolbox::typeAtLocation(int i, int j) { diff --git a/kandinsky/src/font.cpp b/kandinsky/src/font.cpp index 67d39383bf5..7889fde815a 100644 --- a/kandinsky/src/font.cpp +++ b/kandinsky/src/font.cpp @@ -5,6 +5,7 @@ extern "C" { #include #include #include +#include constexpr static int k_tabCharacterWidth = 4; @@ -12,24 +13,30 @@ KDSize KDFont::stringSizeUntil(const char * text, const char * limit) const { if (text == nullptr || (limit != nullptr && text >= limit)) { return KDSizeZero; } - KDSize stringSize = KDSize(0, m_glyphSize.height()); + KDSize stringSize = KDSize(0, 0); + KDSize lineSize = KDSize(0, m_glyphSize.height()); UTF8Decoder decoder(text); const char * currentStringPosition = decoder.stringPosition(); CodePoint codePoint = decoder.nextCodePoint(); while (codePoint != UCodePointNull && (limit == nullptr || currentStringPosition < limit)) { - KDSize cSize = KDSize(m_glyphSize.width(), 0); + KDCoordinate codePointWidth = m_glyphSize.width(); if (codePoint == UCodePointLineFeed) { - cSize = KDSize(0, m_glyphSize.height()); + KDCoordinate width = std::max(lineSize.width(), stringSize.width()); + stringSize = KDSize(width, stringSize.height() + m_glyphSize.height()); + lineSize = KDSize(0, m_glyphSize.height()); + codePointWidth = 0; } else if (codePoint == UCodePointTabulation) { - cSize = KDSize(k_tabCharacterWidth * m_glyphSize.width(), 0); + codePointWidth = k_tabCharacterWidth * m_glyphSize.width(); } else if (codePoint.isCombining()) { - cSize = KDSizeZero; + codePointWidth = 0; } - stringSize = KDSize(stringSize.width() + cSize.width(), stringSize.height() + cSize.height()); + lineSize = KDSize(lineSize.width() + codePointWidth, lineSize.height()); currentStringPosition = decoder.stringPosition(); codePoint = decoder.nextCodePoint(); } + KDCoordinate width = std::max(lineSize.width(), stringSize.width()); + stringSize = KDSize(width, stringSize.height() + m_glyphSize.height()); assert(stringSize.width() >= 0 && stringSize.height() >= 0); return stringSize; } From 4893b73917c404c9319159af49e20a3d92add7d8 Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 25 Jun 2022 19:43:58 +0200 Subject: [PATCH 299/355] [test] Fixed tests compilation --- apps/Makefile | 2 ++ apps/dummy_timer_manager.cpp | 6 ++++++ apps/timer_manager.cpp | 10 ++++++++++ escher/include/escher.h | 1 + escher/include/escher/timer_manager.h | 13 +++++++++++++ escher/src/animation_timer.cpp | 5 +++-- escher/src/slideable_message_text_view.cpp | 1 - poincare/test/expression_properties.cpp | 2 +- 8 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 apps/dummy_timer_manager.cpp create mode 100644 apps/timer_manager.cpp create mode 100644 escher/include/escher/timer_manager.h diff --git a/apps/Makefile b/apps/Makefile index e1ff3c84981..b4d2c701635 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -58,6 +58,7 @@ apps_src += $(addprefix apps/,\ math_variable_box_empty_controller.cpp \ shift_alpha_lock_view.cpp \ suspend_timer.cpp \ + timer_manager.cpp \ title_bar_view.cpp \ ) @@ -117,6 +118,7 @@ apps_tests_src = $(app_calculation_test_src) $(app_code_test_src) $(app_graph_te apps_tests_src += $(addprefix apps/,\ alternate_empty_nested_menu_controller.cpp \ global_preferences.cpp \ + dummy_timer_manager.cpp \ ) ifeq ($(THEME_REPO),local) diff --git a/apps/dummy_timer_manager.cpp b/apps/dummy_timer_manager.cpp new file mode 100644 index 00000000000..1855d494017 --- /dev/null +++ b/apps/dummy_timer_manager.cpp @@ -0,0 +1,6 @@ +#include + +// This is the dummy implementation used in tests + +void TimerManager::AddTimer(Timer * timer) { } +void TimerManager::RemoveTimer(Timer * timer) { } diff --git a/apps/timer_manager.cpp b/apps/timer_manager.cpp new file mode 100644 index 00000000000..1d13ec37661 --- /dev/null +++ b/apps/timer_manager.cpp @@ -0,0 +1,10 @@ +#include +#include + +void TimerManager::AddTimer(Timer * timer) { + AppsContainer::sharedAppsContainer()->addTimer(timer); +} + +void TimerManager::RemoveTimer(Timer * timer) { + AppsContainer::sharedAppsContainer()->removeTimer(timer); +} diff --git a/escher/include/escher.h b/escher/include/escher.h index bce7b8a2274..1112a4d6ecf 100644 --- a/escher/include/escher.h +++ b/escher/include/escher.h @@ -86,6 +86,7 @@ #include #include #include +#include #include #include #include diff --git a/escher/include/escher/timer_manager.h b/escher/include/escher/timer_manager.h new file mode 100644 index 00000000000..c7e92cfec04 --- /dev/null +++ b/escher/include/escher/timer_manager.h @@ -0,0 +1,13 @@ +#ifndef ESCHER_TIMER_MANAGER_H +#define ESCHER_TIMER_MANAGER_H + +#include + +class TimerManager { +public: + // We need this file to not create dependece between apps and escher. + static void AddTimer(Timer * timer); + static void RemoveTimer(Timer * timer); +}; + +#endif diff --git a/escher/src/animation_timer.cpp b/escher/src/animation_timer.cpp index 2dd2b898e6d..017589895e2 100644 --- a/escher/src/animation_timer.cpp +++ b/escher/src/animation_timer.cpp @@ -1,15 +1,16 @@ #include #include +#include void AnimationTimer::setAnimated(Animated * animated) { m_animated = animated; - AppsContainer::sharedAppsContainer()->addTimer(this); + TimerManager::AddTimer(this); } void AnimationTimer::removeAnimated(Animated * animated) { if (m_animated == animated || animated == nullptr) { m_animated = nullptr; - AppsContainer::sharedAppsContainer()->removeTimer(this); + TimerManager::RemoveTimer(this); } } diff --git a/escher/src/slideable_message_text_view.cpp b/escher/src/slideable_message_text_view.cpp index f55b3f22a5d..d81ad7ba607 100644 --- a/escher/src/slideable_message_text_view.cpp +++ b/escher/src/slideable_message_text_view.cpp @@ -1,5 +1,4 @@ #include -#include SlideableMessageTextView::SlideableMessageTextView(const KDFont * font, I18n::Message message, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor backgroundColor) : MessageTextView(font, message, horizontalAlignment, verticalAlignment, textColor, backgroundColor), diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index de29613a70b..c3b0e21b0ed 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -457,7 +457,7 @@ void assert_additional_results_compute_to(const char * expression, const char * return; } I18n::Message unitMessage; - const int numberOfResults = Unit::SetAdditionalExpressionsAndMessage(units, value, additional, maxNumberOfResults, reductionContext, unitMessage); + const int numberOfResults = Unit::SetAdditionalExpressionsAndMessage(units, value, additional, maxNumberOfResults, reductionContext, &unitMessage); quiz_assert(numberOfResults == length); for (int i = 0; i < length; i++) { From 25d47934412b8a20832f037cf5ea3a287ba3222b Mon Sep 17 00:00:00 2001 From: Laury Date: Sat, 25 Jun 2022 23:35:38 +0200 Subject: [PATCH 300/355] [poincare] Fixed tests --- apps/calculation/calculation.cpp | 5 +++-- poincare/include/poincare/unit.h | 2 +- poincare/src/unit.cpp | 12 +++++++++--- poincare/test/expression_properties.cpp | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index fb02fea6f75..b67efc2b89b 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -257,8 +257,9 @@ Calculation::AdditionalInformationType Calculation::additionalInformationType(Co if (o.hasUnit()) { Expression unit; PoincareHelpers::ReduceAndRemoveUnit(&o, App::app()->localContext(), ExpressionNode::ReductionTarget::User, &unit, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined, ExpressionNode::UnitConversion::None); - double value = PoincareHelpers::ApproximateToScalar(o, App::app()->localContext()); - return (Unit::ShouldDisplayAdditionalOutputs(value, unit, GlobalPreferences::sharedGlobalPreferences()->unitFormat())) ? AdditionalInformationType::Unit : AdditionalInformationType::None; + UnitNode::Vector vector = UnitNode::Vector::FromBaseUnits(unit); + const Unit::Representative * representative = Unit::Representative::RepresentativeForDimension(vector); + return representative != nullptr ? AdditionalInformationType::Unit : AdditionalInformationType::None; } if (o.isBasedIntegerCappedBy(k_maximalIntegerWithAdditionalInformation)) { return AdditionalInformationType::Integer; diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 9b16f895eed..6693e0c969f 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -705,7 +705,7 @@ class Unit : public Expression { static Unit Builder(const Representative * representative, const Prefix * prefix); static bool CanParse(const char * symbol, size_t length, const Representative * * representative, const Prefix * * prefix); static void ChooseBestRepresentativeAndPrefixForValue(Expression units, double * value, ExpressionNode::ReductionContext reductionContext); - static bool ShouldDisplayAdditionalOutputs(double value, Expression unit, Preferences::UnitFormat unitFormat); + static bool HaveAdditionalOutputs(double value, Expression unit, Preferences::UnitFormat unitFormat); static int SetAdditionalExpressionsAndMessage(Expression units, double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext, I18n::Message * message); static Expression BuildSplit(double value, const Unit * units, int length, ExpressionNode::ReductionContext reductionContext); static Expression ConvertTemperatureUnits(Expression e, Unit unit, ExpressionNode::ReductionContext reductionContext); diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 6d792addd90..841adfec9ca 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -11,6 +11,8 @@ #include #include #include +#include + namespace Poincare { @@ -765,7 +767,7 @@ void Unit::ChooseBestRepresentativeAndPrefixForValue(Expression units, double * } } -bool Unit::ShouldDisplayAdditionalOutputs(double value, Expression unit, Preferences::UnitFormat unitFormat) { +bool Unit::HaveAdditionalOutputs(double value, Expression unit, Preferences::UnitFormat unitFormat) { if (unit.isUninitialized()) { return false; } @@ -776,7 +778,8 @@ bool Unit::ShouldDisplayAdditionalOutputs(double value, Expression unit, Prefere return e.type() == ExpressionNode::Type::Unit && !e.convert().isBaseUnit(); }; - return representative != nullptr || unit.hasExpression(isNonBase, nullptr); + return (representative != nullptr && representative->hasSpecialAdditionalExpressions(value, unitFormat)) + || unit.hasExpression(isNonBase, nullptr); } int Unit::SetAdditionalExpressionsAndMessage(Expression units, double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext, I18n::Message * message) { @@ -788,7 +791,10 @@ int Unit::SetAdditionalExpressionsAndMessage(Expression units, double value, Exp return 0; } *message = representative->dimensionMessage(); - return representative->setAdditionalExpressions(value, dest, availableLength, reductionContext); + if (Unit::HaveAdditionalOutputs(value, units, reductionContext.unitFormat())) { + return representative->setAdditionalExpressions(value, dest, availableLength, reductionContext); + } + return 0; } Expression Unit::BuildSplit(double value, const Unit * units, int length, ExpressionNode::ReductionContext reductionContext) { diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index c3b0e21b0ed..36e01507416 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -452,7 +452,7 @@ void assert_additional_results_compute_to(const char * expression, const char * Expression e = parse_expression(expression, &globalContext, false).reduceAndRemoveUnit(reductionContext, &units); double value = e.approximateToScalar(&globalContext, Cartesian, Degree); - if (!Unit::ShouldDisplayAdditionalOutputs(value, units, unitFormat)) { + if (!Unit::HaveAdditionalOutputs(value, units, unitFormat)) { quiz_assert(length == 0); return; } From 92c653f2f213f6f52f2352726623ccb8b271b565 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Sun, 26 Jun 2022 13:03:22 +0200 Subject: [PATCH 301/355] [python] Upgrade MicroPython to version 1.19.1, Ulab and add unit tests for Ulab (#259) --- apps/code/script_template.h | 2 +- apps/code/variable_box_controller.cpp | 2 +- python/Makefile | 1 + python/port/genhdr/moduledefs.h | 115 ++- python/port/genhdr/mpversion.h | 4 + python/port/genhdr/qstrdefs.in.h | 24 +- .../port/mod/matplotlib/modmatplotlib_table.c | 17 +- python/port/mod/ulab/micropython.cmake | 18 + python/port/mod/ulab/micropython.mk | 39 + python/port/mod/ulab/ndarray.c | 632 ++++++++----- python/port/mod/ulab/ndarray.h | 121 +-- python/port/mod/ulab/ndarray_operators.c | 34 +- python/port/mod/ulab/ndarray_properties.c | 15 + python/port/mod/ulab/numpy/approx.c | 6 + python/port/mod/ulab/numpy/carray/carray.c | 826 +++++++++++++++++ python/port/mod/ulab/numpy/carray/carray.h | 237 +++++ .../port/mod/ulab/numpy/carray/carray_tools.c | 28 + .../port/mod/ulab/numpy/carray/carray_tools.h | 25 + python/port/mod/ulab/numpy/compare.c | 11 + python/port/mod/ulab/numpy/create.c | 843 ++++++++++++++++++ python/port/mod/ulab/numpy/create.h | 84 ++ python/port/mod/ulab/numpy/fft/fft.c | 23 +- python/port/mod/ulab/numpy/fft/fft.h | 6 + python/port/mod/ulab/numpy/fft/fft_tools.c | 136 ++- python/port/mod/ulab/numpy/fft/fft_tools.h | 5 + python/port/mod/ulab/numpy/filter.c | 66 +- python/port/mod/ulab/numpy/io/io.c | 817 +++++++++++++++++ python/port/mod/ulab/numpy/io/io.h | 19 + python/port/mod/ulab/numpy/linalg/linalg.c | 14 +- .../port/mod/ulab/numpy/linalg/linalg_tools.c | 17 +- python/port/mod/ulab/numpy/numerical.c | 231 +++-- python/port/mod/ulab/numpy/numerical.h | 1 + python/port/mod/ulab/numpy/numpy.c | 98 +- python/port/mod/ulab/numpy/poly.c | 20 +- python/port/mod/ulab/numpy/stats.c | 2 + python/port/mod/ulab/numpy/transform.c | 364 +++++++- python/port/mod/ulab/numpy/transform.h | 4 +- python/port/mod/ulab/numpy/vector.c | 304 ++++++- python/port/mod/ulab/numpy/vector.h | 71 +- .../port/mod/ulab/scipy/optimize/optimize.c | 4 +- python/port/mod/ulab/scipy/scipy.c | 2 +- python/port/mod/ulab/scipy/signal/signal.c | 40 +- python/port/mod/ulab/scipy/signal/signal.h | 1 - python/port/mod/ulab/scipy/special/special.c | 8 +- python/port/mod/ulab/ulab.c | 24 +- python/port/mod/ulab/ulab.h | 93 +- python/port/mod/ulab/ulab_tools.c | 45 +- python/port/mod/ulab/ulab_tools.h | 11 +- python/port/mod/ulab/user/user.c | 2 +- python/port/mod/ulab/utils/utils.c | 40 +- python/port/mpconfigport.h | 42 +- python/src/extmod/moduplatform.h | 7 + python/src/py/asmarm.c | 5 + python/src/py/asmarm.h | 2 + python/src/py/asmbase.c | 3 +- python/src/py/asmbase.h | 2 +- python/src/py/asmthumb.c | 283 +++--- python/src/py/asmthumb.h | 40 +- python/src/py/asmx64.c | 4 +- python/src/py/asmx64.h | 1 + python/src/py/asmx86.h | 1 + python/src/py/asmxtensa.c | 32 +- python/src/py/asmxtensa.h | 7 +- python/src/py/bc.c | 184 ++-- python/src/py/bc.h | 108 ++- python/src/py/bc0.h | 34 +- python/src/py/builtin.h | 41 +- python/src/py/builtinevex.c | 5 +- python/src/py/builtinhelp.c | 17 +- python/src/py/builtinimport.c | 575 +++++++----- python/src/py/compile.c | 411 ++++----- python/src/py/compile.h | 3 +- python/src/py/dynruntime.h | 26 +- python/src/py/dynruntime.mk | 9 +- python/src/py/emit.h | 49 +- python/src/py/emitbc.c | 317 +++---- python/src/py/emitcommon.c | 69 +- python/src/py/emitglue.c | 64 +- python/src/py/emitglue.h | 34 +- python/src/py/emitinlinethumb.c | 58 +- python/src/py/emitnarm.c | 2 - python/src/py/emitnative.c | 531 ++++++----- python/src/py/emitnthumb.c | 2 - python/src/py/emitnx64.c | 2 - python/src/py/emitnx86.c | 4 +- python/src/py/emitnxtensa.c | 2 - python/src/py/emitnxtensawin.c | 3 - python/src/py/formatfloat.c | 2 +- python/src/py/frozenmod.c | 165 ++-- python/src/py/frozenmod.h | 6 +- python/src/py/gc.c | 15 +- python/src/py/lexer.c | 49 +- python/src/py/lexer.h | 21 +- python/src/py/makecompresseddata.py | 2 +- python/src/py/makemoduledefs.py | 90 +- python/src/py/makeqstrdata.py | 29 +- python/src/py/makeqstrdefs.py | 25 +- python/src/py/malloc.c | 93 ++ python/src/py/map.c | 37 +- python/src/py/misc.h | 7 + python/src/py/mkrules.cmake | 62 +- python/src/py/mkrules.mk | 79 +- python/src/py/modarray.c | 2 +- python/src/py/modbuiltins.c | 8 +- python/src/py/modcmath.c | 4 +- python/src/py/modcollections.c | 2 + python/src/py/modgc.c | 2 + python/src/py/modio.c | 52 +- python/src/py/modmath.c | 7 + python/src/py/modmicropython.c | 2 + python/src/py/modstruct.c | 2 + python/src/py/modsys.c | 45 +- python/src/py/modthread.c | 5 +- python/src/py/moduerrno.c | 2 + python/src/py/mpconfig.h | 456 +++++++--- python/src/py/mpstate.h | 44 +- python/src/py/mpz.c | 19 +- python/src/py/mpz.h | 3 +- python/src/py/nativeglue.c | 8 +- python/src/py/nativeglue.h | 14 +- python/src/py/nlr.h | 2 +- python/src/py/obj.c | 11 +- python/src/py/obj.h | 46 +- python/src/py/objarray.c | 3 +- python/src/py/objattrtuple.c | 3 +- python/src/py/objboundmeth.c | 3 +- python/src/py/objcell.c | 3 +- python/src/py/objclosure.c | 3 +- python/src/py/objcomplex.c | 3 +- python/src/py/objdeque.c | 3 +- python/src/py/objdict.c | 3 +- python/src/py/objenumerate.c | 6 +- python/src/py/objexcept.c | 10 + python/src/py/objfilter.c | 3 +- python/src/py/objfloat.c | 11 +- python/src/py/objfun.c | 63 +- python/src/py/objfun.h | 12 +- python/src/py/objgenerator.c | 67 +- python/src/py/objint_longlong.c | 9 +- python/src/py/objint_mpz.c | 3 +- python/src/py/objmap.c | 3 +- python/src/py/objmodule.c | 220 ++--- python/src/py/objmodule.h | 20 +- python/src/py/objobject.c | 3 +- python/src/py/objproperty.c | 3 +- python/src/py/objrange.c | 6 +- python/src/py/objreversed.c | 3 +- python/src/py/objset.c | 6 +- python/src/py/objslice.c | 3 +- python/src/py/objstr.c | 8 +- python/src/py/objstringio.c | 3 +- python/src/py/objtuple.c | 3 +- python/src/py/objtype.c | 5 +- python/src/py/objzip.c | 3 +- python/src/py/parse.c | 278 +++++- python/src/py/parse.h | 17 +- python/src/py/parsenum.c | 2 +- python/src/py/persistentcode.c | 703 +++++---------- python/src/py/persistentcode.h | 43 +- python/src/py/profile.c | 59 +- python/src/py/profile.h | 4 +- python/src/py/py.mk | 17 +- python/src/py/qstr.c | 148 +-- python/src/py/qstr.h | 25 +- python/src/py/qstrdefs.h | 8 + python/src/py/repl.c | 15 +- python/src/py/repl.h | 26 + python/src/py/runtime.c | 309 ++++--- python/src/py/runtime.h | 10 + python/src/py/scheduler.c | 52 +- python/src/py/scope.c | 3 +- python/src/py/scope.h | 4 +- python/src/py/showbc.c | 163 ++-- python/src/py/smallint.h | 7 + python/src/py/vm.c | 229 ++--- python/test/ulab.cpp | 52 ++ python/upgrade.md | 20 +- 177 files changed, 8815 insertions(+), 3825 deletions(-) create mode 100644 python/port/genhdr/mpversion.h create mode 100644 python/port/mod/ulab/micropython.cmake create mode 100644 python/port/mod/ulab/micropython.mk create mode 100644 python/port/mod/ulab/numpy/carray/carray.c create mode 100644 python/port/mod/ulab/numpy/carray/carray.h create mode 100644 python/port/mod/ulab/numpy/carray/carray_tools.c create mode 100644 python/port/mod/ulab/numpy/carray/carray_tools.h create mode 100644 python/port/mod/ulab/numpy/create.c create mode 100644 python/port/mod/ulab/numpy/create.h create mode 100644 python/port/mod/ulab/numpy/io/io.c create mode 100644 python/port/mod/ulab/numpy/io/io.h create mode 100644 python/src/extmod/moduplatform.h create mode 100644 python/test/ulab.cpp diff --git a/apps/code/script_template.h b/apps/code/script_template.h index 6ca117f4874..c3f0f5b99b4 100644 --- a/apps/code/script_template.h +++ b/apps/code/script_template.h @@ -10,7 +10,7 @@ class ScriptTemplate { constexpr ScriptTemplate(const char * name, const char * value) : m_name(name), m_value(value) {} static const ScriptTemplate * Empty(); const char * name() const { return m_name; } - const char * content() const { return m_value; + Script::StatusSize();} + const char * content() const { return m_value;} const char * value() const { return m_value; } private: const char * m_name; diff --git a/apps/code/variable_box_controller.cpp b/apps/code/variable_box_controller.cpp index fcaf42bc581..ac39f9986a3 100644 --- a/apps/code/variable_box_controller.cpp +++ b/apps/code/variable_box_controller.cpp @@ -823,7 +823,7 @@ bool VariableBoxController::importationSourceIsModule(const char * sourceName, c return true; } // The sourceName might be a module that is not in the toolbox - return mp_module_get(qstr_from_str(sourceName)) != MP_OBJ_NULL; + return mp_module_get_loaded_or_builtin(qstr_from_str(sourceName)) != MP_OBJ_NULL; } bool VariableBoxController::importationSourceIsScript(const char * sourceName, const char * * scriptFullName, Script * retrievedScript) { diff --git a/python/Makefile b/python/Makefile index 95b95bbab0e..c9942fb02e2 100644 --- a/python/Makefile +++ b/python/Makefile @@ -248,4 +248,5 @@ tests_src += $(addprefix python/test/,\ time.cpp \ turtle.cpp \ matplotlib.cpp \ + ulab.cpp \ ) diff --git a/python/port/genhdr/moduledefs.h b/python/port/genhdr/moduledefs.h index c86562df8d1..a130c2a6778 100644 --- a/python/port/genhdr/moduledefs.h +++ b/python/port/genhdr/moduledefs.h @@ -1,13 +1,112 @@ -// Automatically generated by makemoduledefs.py. +/* In the standard MicroPython build system, this file is autogenerated from the + * reset of the sources. We manually include it here some modules are not included + * by the build system, so we need to manually update the MicroPython part + * + * How to update this file with a new MicroPython release + * - Get a clean copy of MicroPython + * - Copy our mpconfigport.h over the "bare-arm" port of MicroPython + * - "make" the bare-arm port of MicroPython (don't worry if it doesn't finish) + * - "cat build/genhdr/moduledefs.h". + * - Insert the result below in the MicroPython section, + * until the definition of MICROPY_REGISTERED_MODULES + * - copy the MICROPY_REGISTERED_MODULES section at the end of this file, + * /!\ this section is present twice in the file, so you need to copy it twice + * Keep the Upsilon part when copying the MICROPY_REGISTERED_MODULES section +*/ -#if (MICROPY_PY_ARRAY) - extern const struct _mp_obj_module_t mp_module_uarray; - #define MODULE_DEF_MP_QSTR_UARRAY { MP_ROM_QSTR(MP_QSTR_uarray), MP_ROM_PTR(&mp_module_uarray) }, -#else - #define MODULE_DEF_MP_QSTR_UARRAY -#endif +// MicroPython part + +extern const struct _mp_obj_module_t mp_module___main__; +#undef MODULE_DEF_MP_QSTR___MAIN__ +#define MODULE_DEF_MP_QSTR___MAIN__ { MP_ROM_QSTR(MP_QSTR___main__), MP_ROM_PTR(&mp_module___main__) }, + +extern const struct _mp_obj_module_t mp_module_builtins; +#undef MODULE_DEF_MP_QSTR_BUILTINS +#define MODULE_DEF_MP_QSTR_BUILTINS { MP_ROM_QSTR(MP_QSTR_builtins), MP_ROM_PTR(&mp_module_builtins) }, + +extern const struct _mp_obj_module_t mp_module_cmath; +#undef MODULE_DEF_MP_QSTR_CMATH +#define MODULE_DEF_MP_QSTR_CMATH { MP_ROM_QSTR(MP_QSTR_cmath), MP_ROM_PTR(&mp_module_cmath) }, + +extern const struct _mp_obj_module_t mp_module_math; +#undef MODULE_DEF_MP_QSTR_MATH +#define MODULE_DEF_MP_QSTR_MATH { MP_ROM_QSTR(MP_QSTR_math), MP_ROM_PTR(&mp_module_math) }, + +extern const struct _mp_obj_module_t mp_module_micropython; +#undef MODULE_DEF_MP_QSTR_MICROPYTHON +#define MODULE_DEF_MP_QSTR_MICROPYTHON { MP_ROM_QSTR(MP_QSTR_micropython), MP_ROM_PTR(&mp_module_micropython) }, + +extern const struct _mp_obj_module_t mp_module_urandom; +#undef MODULE_DEF_MP_QSTR_URANDOM +#define MODULE_DEF_MP_QSTR_URANDOM { MP_ROM_QSTR(MP_QSTR_urandom), MP_ROM_PTR(&mp_module_urandom) }, + +// Upsilon's modules part + +extern const struct _mp_obj_module_t modion_module; +#undef MODULE_DEF_MP_QSTR_ION +#define MODULE_DEF_MP_QSTR_ION { MP_ROM_QSTR(MP_QSTR_ion), MP_ROM_PTR(&modion_module) }, +extern const struct _mp_obj_module_t modkandinsky_module; +#undef MODULE_DEF_MP_QSTR_KANDINSKY +#define MODULE_DEF_MP_QSTR_KANDINSKY { MP_ROM_QSTR(MP_QSTR_kandinsky), MP_ROM_PTR(&modkandinsky_module) }, + +extern const struct _mp_obj_module_t modmatplotlib_module; +#undef MODULE_DEF_MP_QSTR_MATPLOTLIB +#define MODULE_DEF_MP_QSTR_MATPLOTLIB { MP_ROM_QSTR(MP_QSTR_matplotlib), MP_ROM_PTR(&modmatplotlib_module) }, + +extern const struct _mp_obj_module_t modpyplot_module; +#undef MODULE_DEF_MP_QSTR_PYPLOT +#define MODULE_DEF_MP_QSTR_PYPLOT { MP_ROM_QSTR(MP_QSTR_matplotlib_dot_pyplot), MP_ROM_PTR(&modpyplot_module) }, + +extern const struct _mp_obj_module_t modtime_module; +#undef MODULE_DEF_MP_QSTR_TIME +#define MODULE_DEF_MP_QSTR_TIME { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&modtime_module) }, + +extern const struct _mp_obj_module_t modos_module; +#undef MODULE_DEF_MP_QSTR_OS +#define MODULE_DEF_MP_QSTR_OS { MP_ROM_QSTR(MP_QSTR_os), MP_ROM_PTR(&modos_module) }, + +extern const struct _mp_obj_module_t modturtle_module; +#undef MODULE_DEF_MP_QSTR_TURTLE +#define MODULE_DEF_MP_QSTR_TURTLE { MP_ROM_QSTR(MP_QSTR_turtle), MP_ROM_PTR(&modturtle_module) }, + +#if !defined(INCLUDE_ULAB) #define MICROPY_REGISTERED_MODULES \ - MODULE_DEF_MP_QSTR_UARRAY \ + MODULE_DEF_MP_QSTR_BUILTINS \ + MODULE_DEF_MP_QSTR_CMATH \ + MODULE_DEF_MP_QSTR_MATH \ + MODULE_DEF_MP_QSTR_MICROPYTHON \ + MODULE_DEF_MP_QSTR_URANDOM \ + MODULE_DEF_MP_QSTR___MAIN__ \ +/* Upsilon's modules part */ \ + MODULE_DEF_MP_QSTR_ION \ + MODULE_DEF_MP_QSTR_KANDINSKY \ + MODULE_DEF_MP_QSTR_MATPLOTLIB \ + MODULE_DEF_MP_QSTR_PYPLOT \ + MODULE_DEF_MP_QSTR_TIME \ + MODULE_DEF_MP_QSTR_OS \ + MODULE_DEF_MP_QSTR_TURTLE +#else +extern const struct _mp_obj_module_t ulab_user_cmodule; +#undef MODULE_DEF_MP_QSTR_ULAB +#define MODULE_DEF_MP_QSTR_ULAB { MP_ROM_QSTR(MP_QSTR_ulab), MP_ROM_PTR(&ulab_user_cmodule) }, + +#define MICROPY_REGISTERED_MODULES \ + MODULE_DEF_MP_QSTR_BUILTINS \ + MODULE_DEF_MP_QSTR_CMATH \ + MODULE_DEF_MP_QSTR_MATH \ + MODULE_DEF_MP_QSTR_MICROPYTHON \ + MODULE_DEF_MP_QSTR_URANDOM \ + MODULE_DEF_MP_QSTR___MAIN__ \ +/* Upsilon's modules part */ \ + MODULE_DEF_MP_QSTR_ION \ + MODULE_DEF_MP_QSTR_KANDINSKY \ + MODULE_DEF_MP_QSTR_MATPLOTLIB \ + MODULE_DEF_MP_QSTR_PYPLOT \ + MODULE_DEF_MP_QSTR_TIME \ + MODULE_DEF_MP_QSTR_OS \ + MODULE_DEF_MP_QSTR_TURTLE \ + MODULE_DEF_MP_QSTR_ULAB +#endif // MICROPY_REGISTERED_MODULES diff --git a/python/port/genhdr/mpversion.h b/python/port/genhdr/mpversion.h new file mode 100644 index 00000000000..c247b0f79e0 --- /dev/null +++ b/python/port/genhdr/mpversion.h @@ -0,0 +1,4 @@ +// This file was generated by py/makeversionhdr.py +#define MICROPY_GIT_TAG "v1.19.1" +#define MICROPY_GIT_HASH "9b486340d" +#define MICROPY_BUILD_DATE "2022-06-22" diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index b3d03ab3a2f..3fae92b55b0 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -74,6 +74,7 @@ Q(__call__) Q(__class__) Q(__contains__) Q(__delitem__) +Q(__dir__) Q(__divmod__) Q(__enter__) Q(__eq__) @@ -101,9 +102,7 @@ Q(__mod__) Q(__module__) Q(__mul__) Q(__name__) -#if __EMSCRIPTEN__ Q(__ne__) -#endif Q(__neg__) Q(__new__) Q(__next__) @@ -142,7 +141,6 @@ Q(all) Q(any) Q(append) Q(args) -Q(argv) Q(asin) Q(asinh) Q(atan) @@ -154,7 +152,6 @@ Q(bound_method) Q(builtins) Q(bytearray) Q(bytecode) -Q(byteorder) Q(bytes) Q(callable) Q(ceil) @@ -192,7 +189,6 @@ Q(erfc) Q(errno) Q(eval) Q(exec) -Q(exit) Q(exp) Q(expm1) Q(extend) @@ -223,14 +219,12 @@ Q(heap_unlock) Q(hex) Q(id) Q(imag) -Q(implementation) Q(index) Q(input) Q(insert) Q(int) Q(intersection) Q(intersection_update) -Q(ion) Q(isalpha) Q(isdigit) Q(isdisjoint) @@ -248,7 +242,6 @@ Q(items) Q(iter) Q(iterator) Q(join) -Q(kandinsky) Q(kbd_intr) Q(key) Q(keys) @@ -265,23 +258,18 @@ Q(lower) Q(lstrip) Q(map) Q(math) -Q(matplotlib) -Q(matplotlib.pyplot) Q(max) Q(maximum_space_recursion_space_depth_space_exceeded) Q(micropython) Q(min) Q(modf) Q(module) -Q(modules) Q(next) Q(object) Q(oct) Q(open) Q(opt_level) Q(ord) -Q(os) -Q(path) Q(pend_throw) Q(phase) Q(pi) @@ -290,7 +278,6 @@ Q(pop) Q(popitem) Q(pow) Q(print) -Q(print_exception) Q(property) Q(radians) Q(randint) @@ -334,26 +321,20 @@ Q(sum) Q(super) Q(symmetric_difference) Q(symmetric_difference_update) -Q(sys) Q(tan) Q(tanh) Q(throw) -Q(time) Q(to_bytes) Q(trunc) Q(tuple) -Q(turtle) Q(type) Q(uniform) Q(union) Q(update) Q(upper) Q(random) -Q(sys) Q(value) Q(values) -Q(version) -Q(version_info) Q(zip) // Ion QSTR @@ -643,7 +624,9 @@ Q(set_printoptions) Q(get_printoptions) Q(ndinfo) Q(arange) +Q(compress) Q(concatenate) +Q(delete) Q(diag) Q(empty) Q(eye) @@ -706,6 +689,7 @@ Q(byteswap) Q(flatten) Q(k) Q(tobytes) +Q(tolist) Q(M) Q(ulab) Q(num) diff --git a/python/port/mod/matplotlib/modmatplotlib_table.c b/python/port/mod/matplotlib/modmatplotlib_table.c index 0178bd5e162..19714b838c5 100644 --- a/python/port/mod/matplotlib/modmatplotlib_table.c +++ b/python/port/mod/matplotlib/modmatplotlib_table.c @@ -4,13 +4,26 @@ extern const mp_obj_module_t modpyplot_module; STATIC MP_DEFINE_CONST_FUN_OBJ_0(modmatplotlib___init___obj, modmatplotlib___init__); -STATIC const mp_rom_map_elem_t modmatplotlib_module_globals_table[] = { +// Define the module table as non-const, because MicroPython needs to be able to modify it. +STATIC mp_rom_map_elem_t modmatplotlib_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_matplotlib) }, { MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&modmatplotlib___init___obj) }, { MP_ROM_QSTR(MP_QSTR_pyplot), MP_ROM_PTR(&modpyplot_module) } }; -STATIC MP_DEFINE_CONST_DICT(modmatplotlib_module_globals, modmatplotlib_module_globals_table); +// Define the module object, not as a constant, because MicroPython needs to be able to dynamically add attributes to it. +mp_obj_dict_t modmatplotlib_module_globals = { \ + .base = {&mp_type_dict}, \ + .map = { \ + .all_keys_are_qstrs = 1, \ + .is_fixed = 0, \ + .is_ordered = 1, \ + .used = MP_ARRAY_SIZE(modmatplotlib_module_globals_table), \ + .alloc = MP_ARRAY_SIZE(modmatplotlib_module_globals_table), \ + .table = (mp_map_elem_t *)(mp_rom_map_elem_t *)modmatplotlib_module_globals_table, \ + }, \ +}; + const mp_obj_module_t modmatplotlib_module = { .base = { &mp_type_module }, diff --git a/python/port/mod/ulab/micropython.cmake b/python/port/mod/ulab/micropython.cmake new file mode 100644 index 00000000000..66890c0dbeb --- /dev/null +++ b/python/port/mod/ulab/micropython.cmake @@ -0,0 +1,18 @@ +add_library(usermod_ulab INTERFACE) + +file(GLOB_RECURSE ULAB_SOURCES ${CMAKE_CURRENT_LIST_DIR}/*.c) + +target_sources(usermod_ulab INTERFACE + ${ULAB_SOURCES} +) + +target_include_directories(usermod_ulab INTERFACE + ${CMAKE_CURRENT_LIST_DIR} +) + +target_compile_definitions(usermod_ulab INTERFACE + MODULE_ULAB_ENABLED=1 +) + +target_link_libraries(usermod INTERFACE usermod_ulab) + diff --git a/python/port/mod/ulab/micropython.mk b/python/port/mod/ulab/micropython.mk new file mode 100644 index 00000000000..f36d1d61155 --- /dev/null +++ b/python/port/mod/ulab/micropython.mk @@ -0,0 +1,39 @@ + +USERMODULES_DIR := $(USERMOD_DIR) + +# Add all C files to SRC_USERMOD. +SRC_USERMOD += $(USERMODULES_DIR)/scipy/linalg/linalg.c +SRC_USERMOD += $(USERMODULES_DIR)/scipy/optimize/optimize.c +SRC_USERMOD += $(USERMODULES_DIR)/scipy/signal/signal.c +SRC_USERMOD += $(USERMODULES_DIR)/scipy/special/special.c +SRC_USERMOD += $(USERMODULES_DIR)/ndarray_operators.c +SRC_USERMOD += $(USERMODULES_DIR)/ulab_tools.c +SRC_USERMOD += $(USERMODULES_DIR)/ndarray.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/ndarray/ndarray_iter.c +SRC_USERMOD += $(USERMODULES_DIR)/ndarray_properties.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/approx.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/compare.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/carray/carray.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/carray/carray_tools.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/create.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/fft/fft.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/fft/fft_tools.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/filter.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/io/io.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/linalg/linalg.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/linalg/linalg_tools.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/numerical.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/poly.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/stats.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/transform.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/vector.c + +SRC_USERMOD += $(USERMODULES_DIR)/numpy/numpy.c +SRC_USERMOD += $(USERMODULES_DIR)/scipy/scipy.c +SRC_USERMOD += $(USERMODULES_DIR)/user/user.c +SRC_USERMOD += $(USERMODULES_DIR)/utils/utils.c +SRC_USERMOD += $(USERMODULES_DIR)/ulab.c + +CFLAGS_USERMOD += -I$(USERMODULES_DIR) + +override CFLAGS_EXTRA += -DMODULE_ULAB_ENABLED=1 diff --git a/python/port/mod/ulab/ndarray.c b/python/port/mod/ulab/ndarray.c index 88fbcfbdafc..5a7abce1291 100644 --- a/python/port/mod/ulab/ndarray.c +++ b/python/port/mod/ulab/ndarray.c @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2021 Zoltán Vörös + * Copyright (c) 2019-2022 Zoltán Vörös * 2020 Jeff Epler for Adafruit Industries * 2020 Taku Fukada */ @@ -25,6 +25,8 @@ #include "ulab_tools.h" #include "ndarray.h" #include "ndarray_operators.h" +#include "numpy/carray/carray.h" +#include "numpy/carray/carray_tools.h" mp_uint_t ndarray_print_threshold = NDARRAY_PRINT_THRESHOLD; mp_uint_t ndarray_print_edgeitems = NDARRAY_PRINT_EDGEITEMS; @@ -46,6 +48,19 @@ mp_uint_t ndarray_print_edgeitems = NDARRAY_PRINT_EDGEITEMS; //| https://docs.scipy.org/doc/numpy/index.html""" //| +void ndarray_set_complex_value(void *p, size_t index, mp_obj_t value) { + mp_float_t real, imag; + if(mp_obj_is_type(value, &mp_type_complex)) { + mp_obj_get_complex(value, &real, &imag); + ((mp_float_t *)p)[2 * index] = real; + ((mp_float_t *)p)[2 * index + 1] = imag; + } else { + real = mp_obj_get_float(value); + ((mp_float_t *)p)[2 * index] = real; + ((mp_float_t *)p)[2 * index + 1] = MICROPY_FLOAT_CONST(0.0); + } +} + #ifdef CIRCUITPY void ndarray_set_value(char typecode, void *p, size_t index, mp_obj_t val_in) { switch (typecode) { @@ -64,6 +79,11 @@ void ndarray_set_value(char typecode, void *p, size_t index, mp_obj_t val_in) { case NDARRAY_FLOAT: ((mp_float_t *)p)[index] = mp_obj_get_float(val_in); break; + #if ULAB_SUPPORTS_COMPLEX + case NDARRAY_COMPLEX: + ndarray_set_complex_value(p, index, val_in); + break; + #endif } } #endif @@ -143,8 +163,7 @@ void ndarray_fill_array_iterable(mp_float_t *array, mp_obj_t iterable) { #if ULAB_HAS_FUNCTION_ITERATOR size_t *ndarray_new_coords(uint8_t ndim) { - size_t *coords = m_new(size_t, ndim); - memset(coords, 0, ndim*sizeof(size_t)); + size_t *coords = m_new0(size_t, ndim); return coords; } @@ -171,7 +190,7 @@ void ndarray_rewind_array(uint8_t ndim, uint8_t *array, size_t *shape, int32_t * static int32_t *strides_from_shape(size_t *shape, uint8_t dtype) { // returns a strides array that corresponds to a dense array with the prescribed shape int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); - strides[ULAB_MAX_DIMS-1] = (int32_t)mp_binary_get_size('@', dtype, NULL); + strides[ULAB_MAX_DIMS-1] = (int32_t)ulab_binary_get_size(dtype); for(uint8_t i=ULAB_MAX_DIMS; i > 1; i--) { strides[i-2] = strides[i-1] * shape[i-1]; } @@ -231,7 +250,13 @@ void ndarray_dtype_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kin mp_print_str(print, "uint16')"); } else if(self->dtype == NDARRAY_INT16) { mp_print_str(print, "int16')"); - } else { + } + #if ULAB_SUPPORTS_COMPLEX + else if(self->dtype == NDARRAY_COMPLEX) { + mp_print_str(print, "complex')"); + } + #endif + else { #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT mp_print_str(print, "float32')"); #else @@ -280,7 +305,13 @@ mp_obj_t ndarray_dtype_make_new(const mp_obj_type_t *type, size_t n_args, size_t _dtype = NDARRAY_INT16; } else if(memcmp(_dtype_, "float", 5) == 0) { _dtype = NDARRAY_FLOAT; - } else { + } + #if ULAB_SUPPORTS_COMPLEX + else if(memcmp(_dtype_, "complex", 7) == 0) { + _dtype = NDARRAY_COMPLEX; + } + #endif + else { mp_raise_TypeError(translate("data type not understood")); } } @@ -308,7 +339,11 @@ mp_obj_t ndarray_dtype(mp_obj_t self_in) { GET_STR_DATA_LEN(self_in, _dtype, len); if((len != 1) || ((*_dtype != NDARRAY_BOOL) && (*_dtype != NDARRAY_UINT8) && (*_dtype != NDARRAY_INT8) && (*_dtype != NDARRAY_UINT16) - && (*_dtype != NDARRAY_INT16) && (*_dtype != NDARRAY_FLOAT))) { + && (*_dtype != NDARRAY_INT16) && (*_dtype != NDARRAY_FLOAT) + #if ULAB_SUPPORTS_COMPLEX + && (*_dtype != NDARRAY_COMPLEX) + #endif + )) { mp_raise_TypeError(translate("data type not understood")); } dtype = *_dtype; @@ -351,6 +386,14 @@ MP_DEFINE_CONST_FUN_OBJ_0(ndarray_get_printoptions_obj, ndarray_get_printoptions mp_obj_t ndarray_get_item(ndarray_obj_t *ndarray, void *array) { // returns a proper micropython object from an array if(!ndarray->boolean) { + #if ULAB_SUPPORTS_COMPLEX + if(ndarray->dtype == NDARRAY_COMPLEX) { + mp_float_t *c = (mp_float_t *)array; + mp_float_t real = *c++; + mp_float_t imag = *c; + return mp_obj_new_complex(real, imag); + } + #endif return mp_binary_get_val_array(ndarray->dtype, array, 0); } else { if(*(uint8_t *)array) { @@ -361,32 +404,55 @@ mp_obj_t ndarray_get_item(ndarray_obj_t *ndarray, void *array) { } } -static void ndarray_print_row(const mp_print_t *print, ndarray_obj_t * ndarray, uint8_t *array, size_t stride, size_t n) { +static void ndarray_print_element(const mp_print_t *print, ndarray_obj_t *ndarray, uint8_t *array) { + #if ULAB_SUPPORTS_COMPLEX + if(ndarray->dtype == NDARRAY_COMPLEX) { + // real part first + mp_float_t fvalue = *(mp_float_t *)array; + mp_obj_print_helper(print, mp_obj_new_float(fvalue), PRINT_REPR); + // imaginary part + array += ndarray->itemsize / 2; + fvalue = *(mp_float_t *)array; + if(fvalue >= MICROPY_FLOAT_CONST(0.0) || isnan(fvalue)) { + mp_print_str(print, "+"); + } + array += ndarray->itemsize / 2; + mp_obj_print_helper(print, mp_obj_new_float(fvalue), PRINT_REPR); + mp_print_str(print, "j"); + } else { + mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR); + } + #else + mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR); + #endif +} + +static void ndarray_print_row(const mp_print_t *print, ndarray_obj_t *ndarray, uint8_t *array, int32_t stride, size_t n) { if(n == 0) { return; } mp_print_str(print, "["); if((n <= ndarray_print_threshold) || (n <= 2*ndarray_print_edgeitems)) { // if the array is short, print everything - mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR); + ndarray_print_element(print, ndarray, array); array += stride; for(size_t i=1; i < n; i++, array += stride) { mp_print_str(print, ", "); - mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR); + ndarray_print_element(print, ndarray, array); } } else { - mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR); + ndarray_print_element(print, ndarray, array); array += stride; for(size_t i=1; i < ndarray_print_edgeitems; i++, array += stride) { mp_print_str(print, ", "); - mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR); + ndarray_print_element(print, ndarray, array); } mp_printf(print, ", ..., "); - array += stride * (n - 2 * ndarray_print_edgeitems); - mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR); + array += stride * (n - 2 * ndarray_print_edgeitems); + ndarray_print_element(print, ndarray, array); array += stride; for(size_t i=1; i < ndarray_print_edgeitems; i++, array += stride) { mp_print_str(print, ", "); - mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR); + ndarray_print_element(print, ndarray, array); } } mp_print_str(print, "]"); @@ -459,21 +525,28 @@ void ndarray_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t ki ndarray_print_bracket(print, 0, self->shape[ULAB_MAX_DIMS-4], "]"); #endif } + mp_print_str(print, ", dtype="); if(self->boolean) { - mp_print_str(print, ", dtype=bool)"); + mp_print_str(print, "bool)"); } else if(self->dtype == NDARRAY_UINT8) { - mp_print_str(print, ", dtype=uint8)"); + mp_print_str(print, "uint8)"); } else if(self->dtype == NDARRAY_INT8) { - mp_print_str(print, ", dtype=int8)"); + mp_print_str(print, "int8)"); } else if(self->dtype == NDARRAY_UINT16) { - mp_print_str(print, ", dtype=uint16)"); + mp_print_str(print, "uint16)"); } else if(self->dtype == NDARRAY_INT16) { - mp_print_str(print, ", dtype=int16)"); - } else { + mp_print_str(print, "int16)"); + } + #if ULAB_SUPPORTS_COMPLEX + else if(self->dtype == NDARRAY_COMPLEX) { + mp_print_str(print, "complex)"); + } + #endif /* ULAB_SUPPORTS_COMPLEX */ + else { #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT - mp_print_str(print, ", dtype=float32)"); + mp_print_str(print, "float32)"); #else - mp_print_str(print, ", dtype=float64)"); + mp_print_str(print, "float64)"); #endif } } @@ -485,7 +558,6 @@ void ndarray_assign_elements(ndarray_obj_t *ndarray, mp_obj_t iterable, uint8_t uint8_t *array = (uint8_t *)ndarray->array; array += *idx; while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { - // TODO: this might be wrong here: we have to check for the trueness of item if(mp_obj_is_true(item)) { *array = 1; } @@ -494,7 +566,19 @@ void ndarray_assign_elements(ndarray_obj_t *ndarray, mp_obj_t iterable, uint8_t } } else { while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { - ndarray_set_value(dtype, ndarray->array, (*idx)++, item); + #if ULAB_SUPPORTS_COMPLEX + mp_float_t real; + mp_float_t imag; + if(dtype == NDARRAY_COMPLEX) { + mp_obj_get_complex(item, &real, &imag); + ndarray_set_value(NDARRAY_FLOAT, ndarray->array, (*idx)++, mp_obj_new_float(real)); + ndarray_set_value(NDARRAY_FLOAT, ndarray->array, (*idx)++, mp_obj_new_float(imag)); + } else { + ndarray_set_value(dtype, ndarray->array, (*idx)++, item); + } + #else + ndarray_set_value(dtype, ndarray->array, (*idx)++, item); + #endif } } } @@ -518,7 +602,7 @@ ndarray_obj_t *ndarray_new_ndarray(uint8_t ndim, size_t *shape, int32_t *strides ndarray->boolean = dtype == NDARRAY_BOOL ? NDARRAY_BOOLEAN : NDARRAY_NUMERIC; ndarray->ndim = ndim; ndarray->len = ndim == 0 ? 0 : 1; - ndarray->itemsize = mp_binary_get_size('@', ndarray->dtype, NULL); + ndarray->itemsize = ulab_binary_get_size(dtype); int32_t *_strides; if(strides == NULL) { _strides = strides_from_shape(shape, ndarray->dtype); @@ -533,10 +617,9 @@ ndarray_obj_t *ndarray_new_ndarray(uint8_t ndim, size_t *shape, int32_t *strides // if the length is 0, still allocate a single item, so that contractions can be handled size_t len = ndarray->itemsize * MAX(1, ndarray->len); - uint8_t *array = m_new(byte, len); + uint8_t *array = m_new0(byte, len); // this should set all elements to 0, irrespective of the of the dtype (all bits are zero) // we could, perhaps, leave this step out, and initialise the array only, when needed - memset(array, 0, len); ndarray->array = array; ndarray->origin = array; return ndarray; @@ -546,7 +629,7 @@ ndarray_obj_t *ndarray_new_dense_ndarray(uint8_t ndim, size_t *shape, uint8_t dt // creates a dense array, i.e., one, where the strides are derived directly from the shapes // the function should work in the general n-dimensional case int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); - strides[ULAB_MAX_DIMS-1] = dtype == NDARRAY_BOOL ? 1 : mp_binary_get_size('@', dtype, NULL); + strides[ULAB_MAX_DIMS-1] = (int32_t)ulab_binary_get_size(dtype); for(size_t i=ULAB_MAX_DIMS; i > 1; i--) { strides[i-2] = strides[i-1] * MAX(1, shape[i-1]); } @@ -567,13 +650,18 @@ ndarray_obj_t *ndarray_new_ndarray_from_tuple(mp_obj_tuple_t *_shape, uint8_t dt return ndarray_new_dense_ndarray(_shape->len, shape, dtype); } -void ndarray_copy_array(ndarray_obj_t *source, ndarray_obj_t *target) { +void ndarray_copy_array(ndarray_obj_t *source, ndarray_obj_t *target, uint8_t shift) { // TODO: if the array is dense, the content could be copied in a single pass // copies the content of source->array into a new dense void pointer // it is assumed that the dtypes in source and target are the same // Since the target is a new array, it is supposed to be dense uint8_t *sarray = (uint8_t *)source->array; uint8_t *tarray = (uint8_t *)target->array; + #if ULAB_SUPPORTS_COMPLEX + if(source->dtype == NDARRAY_COMPLEX) { + sarray += shift; + } + #endif #if ULAB_MAX_DIMS > 3 size_t i = 0; @@ -589,7 +677,7 @@ void ndarray_copy_array(ndarray_obj_t *source, ndarray_obj_t *target) { #endif size_t l = 0; do { - memcpy(tarray, sarray, source->itemsize); + memcpy(tarray, sarray, target->itemsize); tarray += target->itemsize; sarray += source->strides[ULAB_MAX_DIMS - 1]; l++; @@ -645,13 +733,92 @@ ndarray_obj_t *ndarray_copy_view(ndarray_obj_t *source) { uint8_t dtype = source->dtype; if(source->boolean) { - dtype = NDARRAY_BOOLEAN; + dtype = NDARRAY_BOOL; } ndarray_obj_t *ndarray = ndarray_new_ndarray(source->ndim, source->shape, strides, dtype); - ndarray_copy_array(source, ndarray); + ndarray_copy_array(source, ndarray, 0); return ndarray; } +ndarray_obj_t *ndarray_copy_view_convert_type(ndarray_obj_t *source, uint8_t dtype) { + // creates a copy, similar to ndarray_copy_view, but it also converts the dtype, if necessary + if(dtype == source->dtype) { + return ndarray_copy_view(source); + } + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, dtype); + uint8_t *sarray = (uint8_t *)source->array; + uint8_t *array = (uint8_t *)ndarray->array; + + #if ULAB_SUPPORTS_COMPLEX + uint8_t complex_size = 2 * sizeof(mp_float_t); + #endif + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_obj_t item; + #if ULAB_SUPPORTS_COMPLEX + if(source->dtype == NDARRAY_COMPLEX) { + if(dtype != NDARRAY_COMPLEX) { + mp_raise_TypeError(translate("cannot convert complex type")); + } else { + memcpy(array, sarray, complex_size); + } + } else { + #endif + if((source->dtype == NDARRAY_FLOAT) && (dtype != NDARRAY_FLOAT)) { + // floats must be treated separately, because they can't directly be converted to integer types + mp_float_t f = ndarray_get_float_value(sarray, source->dtype); + item = mp_obj_new_int((int32_t)MICROPY_FLOAT_C_FUN(round)(f)); + } else { + item = mp_binary_get_val_array(source->dtype, sarray, 0); + } + #if ULAB_SUPPORTS_COMPLEX + if(dtype == NDARRAY_COMPLEX) { + ndarray_set_value(NDARRAY_FLOAT, array, 0, item); + } else { + ndarray_set_value(dtype, array, 0, item); + } + } + #else + ndarray_set_value(dtype, array, 0, item); + #endif + array += ndarray->itemsize; + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif + return MP_OBJ_FROM_PTR(ndarray); +} + #if NDARRAY_HAS_BYTESWAP mp_obj_t ndarray_byteswap(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { // changes the endiannes of an array @@ -849,7 +1016,7 @@ STATIC uint8_t ndarray_init_helper(size_t n_args, const mp_obj_t *pos_args, mp_m if(mp_obj_is_type(args[1].u_obj, &ulab_dtype_type)) { dtype_obj_t *dtype = MP_OBJ_TO_PTR(args[1].u_obj); _dtype = dtype->dtype; - } else { // this must be an integer defined as a class constant (ulab.uint8 etc.) + } else { // this must be an integer defined as a class constant (ulab.numpy.uint8 etc.) _dtype = mp_obj_get_int(args[1].u_obj); } #else @@ -863,58 +1030,7 @@ STATIC mp_obj_t ndarray_make_new_core(const mp_obj_type_t *type, size_t n_args, if(mp_obj_is_type(args[0], &ulab_ndarray_type)) { ndarray_obj_t *source = MP_OBJ_TO_PTR(args[0]); - if(dtype == source->dtype) { - return ndarray_copy_view(source); - } - ndarray_obj_t *target = ndarray_new_dense_ndarray(source->ndim, source->shape, dtype); - uint8_t *sarray = (uint8_t *)source->array; - uint8_t *tarray = (uint8_t *)target->array; - #if ULAB_MAX_DIMS > 3 - size_t i = 0; - do { - #endif - #if ULAB_MAX_DIMS > 2 - size_t j = 0; - do { - #endif - #if ULAB_MAX_DIMS > 1 - size_t k = 0; - do { - #endif - size_t l = 0; - do { - mp_obj_t item; - if((source->dtype == NDARRAY_FLOAT) && (dtype != NDARRAY_FLOAT)) { - // floats must be treated separately, because they can't directly be converted to integer types - mp_float_t f = ndarray_get_float_value(sarray, source->dtype); - item = mp_obj_new_int((int32_t)MICROPY_FLOAT_C_FUN(floor)(f)); - } else { - item = mp_binary_get_val_array(source->dtype, sarray, 0); - } - ndarray_set_value(dtype, tarray, 0, item); - tarray += target->itemsize; - sarray += source->strides[ULAB_MAX_DIMS - 1]; - l++; - } while(l < source->shape[ULAB_MAX_DIMS - 1]); - #if ULAB_MAX_DIMS > 1 - sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; - sarray += source->strides[ULAB_MAX_DIMS - 2]; - k++; - } while(k < source->shape[ULAB_MAX_DIMS - 2]); - #endif - #if ULAB_MAX_DIMS > 2 - sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; - sarray += source->strides[ULAB_MAX_DIMS - 3]; - j++; - } while(j < source->shape[ULAB_MAX_DIMS - 3]); - #endif - #if ULAB_MAX_DIMS > 3 - sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; - sarray += source->strides[ULAB_MAX_DIMS - 4]; - i++; - } while(i < source->shape[ULAB_MAX_DIMS - 4]); - #endif - return MP_OBJ_FROM_PTR(target); + return MP_OBJ_FROM_PTR(ndarray_copy_view_convert_type(source, dtype)); } else { // assume that the input is an iterable return MP_OBJ_FROM_PTR(ndarray_from_iterable(args[0], dtype)); @@ -942,8 +1058,7 @@ bool ndarray_can_broadcast(ndarray_obj_t *lhs, ndarray_obj_t *rhs, uint8_t *ndim // // 1. the two shapes are either equal // 2. one of the shapes is 1 - memset(lstrides, 0, sizeof(size_t)*ULAB_MAX_DIMS); - memset(rstrides, 0, sizeof(size_t)*ULAB_MAX_DIMS); + lstrides[ULAB_MAX_DIMS - 1] = lhs->strides[ULAB_MAX_DIMS - 1]; rstrides[ULAB_MAX_DIMS - 1] = rhs->strides[ULAB_MAX_DIMS - 1]; for(uint8_t i=ULAB_MAX_DIMS; i > 0; i--) { @@ -976,7 +1091,7 @@ bool ndarray_can_broadcast_inplace(ndarray_obj_t *lhs, ndarray_obj_t *rhs, int32 // // 1. the two shapes are either equal // 2. the shapes on the right hand side is 1 - memset(rstrides, 0, sizeof(size_t)*ULAB_MAX_DIMS); + rstrides[ULAB_MAX_DIMS - 1] = rhs->strides[ULAB_MAX_DIMS - 1]; for(uint8_t i=ULAB_MAX_DIMS; i > 0; i--) { if((lhs->shape[i-1] == rhs->shape[i-1]) || (rhs->shape[i-1] == 0) || (rhs->shape[i-1] == 1)) { @@ -1024,10 +1139,8 @@ static mp_bound_slice_t generate_slice(mp_int_t n, mp_obj_t index) { } static ndarray_obj_t *ndarray_view_from_slices(ndarray_obj_t *ndarray, mp_obj_tuple_t *tuple) { - size_t *shape = m_new(size_t, ULAB_MAX_DIMS); - memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); - int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); - memset(strides, 0, sizeof(size_t)*ULAB_MAX_DIMS); + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *strides = m_new0(int32_t, ULAB_MAX_DIMS); uint8_t ndim = ndarray->ndim; @@ -1069,83 +1182,71 @@ void ndarray_assign_view(ndarray_obj_t *view, ndarray_obj_t *values) { return; } uint8_t ndim = 0; - size_t *shape = m_new(size_t, ULAB_MAX_DIMS); - int32_t *lstrides = m_new(int32_t, ULAB_MAX_DIMS); - int32_t *rstrides = m_new(int32_t, ULAB_MAX_DIMS); + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *lstrides = m_new0(int32_t, ULAB_MAX_DIMS); + int32_t *rstrides = m_new0(int32_t, ULAB_MAX_DIMS); if(!ndarray_can_broadcast(view, values, &ndim, shape, lstrides, rstrides)) { mp_raise_ValueError(translate("operands could not be broadcast together")); - m_del(size_t, shape, ULAB_MAX_DIMS); - m_del(int32_t, lstrides, ULAB_MAX_DIMS); - m_del(int32_t, rstrides, ULAB_MAX_DIMS); - } + } else { - uint8_t *rarray = (uint8_t *)values->array; - // since in ASSIGNMENT_LOOP the array has a type, we have to divide the strides by the itemsize - for(uint8_t i=0; i < ULAB_MAX_DIMS; i++) { - lstrides[i] /= view->itemsize; - } + ndarray_obj_t *ndarray = ndarray_copy_view_convert_type(values, view->dtype); + // re-calculate rstrides, since the copy operation might have changed the directions of the strides + ndarray_can_broadcast(view, ndarray, &ndim, shape, lstrides, rstrides); + uint8_t *rarray = (uint8_t *)ndarray->array; - if(view->dtype == NDARRAY_UINT8) { - if(values->dtype == NDARRAY_UINT8) { - ASSIGNMENT_LOOP(view, uint8_t, uint8_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_INT8) { - ASSIGNMENT_LOOP(view, uint8_t, int8_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_UINT16) { - ASSIGNMENT_LOOP(view, uint8_t, uint16_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_INT16) { - ASSIGNMENT_LOOP(view, uint8_t, int16_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_FLOAT) { - ASSIGNMENT_LOOP(view, uint8_t, mp_float_t, lstrides, rarray, rstrides); - } - } else if(view->dtype == NDARRAY_INT8) { - if(values->dtype == NDARRAY_UINT8) { - ASSIGNMENT_LOOP(view, int8_t, uint8_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_INT8) { - ASSIGNMENT_LOOP(view, int8_t, int8_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_UINT16) { - ASSIGNMENT_LOOP(view, int8_t, uint16_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_INT16) { - ASSIGNMENT_LOOP(view, int8_t, int16_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_FLOAT) { - ASSIGNMENT_LOOP(view, int8_t, mp_float_t, lstrides, rarray, rstrides); - } - } else if(view->dtype == NDARRAY_UINT16) { - if(values->dtype == NDARRAY_UINT8) { - ASSIGNMENT_LOOP(view, uint16_t, uint8_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_INT8) { - ASSIGNMENT_LOOP(view, uint16_t, int8_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_UINT16) { - ASSIGNMENT_LOOP(view, uint16_t, uint16_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_INT16) { - ASSIGNMENT_LOOP(view, uint16_t, int16_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_FLOAT) { - ASSIGNMENT_LOOP(view, uint16_t, mp_float_t, lstrides, rarray, rstrides); - } - } else if(view->dtype == NDARRAY_INT16) { - if(values->dtype == NDARRAY_UINT8) { - ASSIGNMENT_LOOP(view, int16_t, uint8_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_INT8) { - ASSIGNMENT_LOOP(view, int16_t, int8_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_UINT16) { - ASSIGNMENT_LOOP(view, int16_t, uint16_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_INT16) { - ASSIGNMENT_LOOP(view, int16_t, int16_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_FLOAT) { - ASSIGNMENT_LOOP(view, int16_t, mp_float_t, lstrides, rarray, rstrides); - } - } else { // the dtype must be an mp_float_t now - if(values->dtype == NDARRAY_UINT8) { - ASSIGNMENT_LOOP(view, mp_float_t, uint8_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_INT8) { - ASSIGNMENT_LOOP(view, mp_float_t, int8_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_UINT16) { - ASSIGNMENT_LOOP(view, mp_float_t, uint16_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_INT16) { - ASSIGNMENT_LOOP(view, mp_float_t, int16_t, lstrides, rarray, rstrides); - } else if(values->dtype == NDARRAY_FLOAT) { - ASSIGNMENT_LOOP(view, mp_float_t, mp_float_t, lstrides, rarray, rstrides); - } + + uint8_t *larray = (uint8_t *)view->array; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + memcpy(larray, rarray, view->itemsize); + larray += lstrides[ULAB_MAX_DIMS - 1]; + rarray += rstrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < view->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + larray -= lstrides[ULAB_MAX_DIMS - 1] * view->shape[ULAB_MAX_DIMS-1]; + larray += lstrides[ULAB_MAX_DIMS - 2]; + rarray -= rstrides[ULAB_MAX_DIMS - 1] * view->shape[ULAB_MAX_DIMS-1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < view->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + larray -= lstrides[ULAB_MAX_DIMS - 2] * view->shape[ULAB_MAX_DIMS-2]; + larray += lstrides[ULAB_MAX_DIMS - 3]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * view->shape[ULAB_MAX_DIMS-2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < view->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + larray -= lstrides[ULAB_MAX_DIMS - 3] * view->shape[ULAB_MAX_DIMS-3]; + larray += lstrides[ULAB_MAX_DIMS - 4]; + rarray -= rstrides[ULAB_MAX_DIMS - 3] * view->shape[ULAB_MAX_DIMS-3]; + rarray += rstrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < view->shape[ULAB_MAX_DIMS - 4]); + #endif } + + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(int32_t, lstrides, ULAB_MAX_DIMS); + m_del(int32_t, rstrides, ULAB_MAX_DIMS); + + return; } static mp_obj_t ndarray_from_boolean_index(ndarray_obj_t *ndarray, ndarray_obj_t *index) { @@ -1181,16 +1282,16 @@ static mp_obj_t ndarray_assign_from_boolean_index(ndarray_obj_t *ndarray, ndarra // assigns values to a Boolean-indexed array // first we have to find out how many trues there are uint8_t *iarray = (uint8_t *)index->array; + size_t istride = index->strides[ULAB_MAX_DIMS - 1]; size_t count = 0; for(size_t i=0; i < index->len; i++) { count += *iarray; - iarray += index->strides[ULAB_MAX_DIMS - 1]; + iarray += istride; } // re-wind the index array iarray = index->array; uint8_t *varray = (uint8_t *)values->array; size_t vstride; - size_t istride = index->strides[ULAB_MAX_DIMS - 1]; if(count == values->len) { // there are as many values as true indices @@ -1199,65 +1300,92 @@ static mp_obj_t ndarray_assign_from_boolean_index(ndarray_obj_t *ndarray, ndarra // there is a single value vstride = 0; } + + #if ULAB_SUPPORTS_COMPLEX + if(values->dtype == NDARRAY_COMPLEX) { + if(ndarray->dtype != NDARRAY_COMPLEX) { + mp_raise_TypeError(translate("cannot convert complex to dtype")); + } else { + uint8_t *array = (uint8_t *)ndarray->array; + for(size_t i = 0; i < ndarray->len; i++) { + if(*iarray) { + memcpy(array, varray, ndarray->itemsize); + varray += vstride; + } + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + iarray += istride; + } while(0); + return MP_OBJ_FROM_PTR(ndarray); + } + } + #endif + + int32_t lstrides = ndarray->strides[ULAB_MAX_DIMS - 1] / ndarray->itemsize; + if(ndarray->dtype == NDARRAY_UINT8) { if(values->dtype == NDARRAY_UINT8) { - BOOLEAN_ASSIGNMENT_LOOP(uint8_t, uint8_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(uint8_t, uint8_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_INT8) { - BOOLEAN_ASSIGNMENT_LOOP(uint8_t, int8_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(uint8_t, int8_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_UINT16) { - BOOLEAN_ASSIGNMENT_LOOP(uint8_t, uint16_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(uint8_t, uint16_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_INT16) { - BOOLEAN_ASSIGNMENT_LOOP(uint8_t, int16_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(uint8_t, int16_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_FLOAT) { - BOOLEAN_ASSIGNMENT_LOOP(uint8_t, mp_float_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(uint8_t, mp_float_t, ndarray, lstrides, iarray, istride, varray, vstride); } } else if(ndarray->dtype == NDARRAY_INT8) { if(values->dtype == NDARRAY_UINT8) { - BOOLEAN_ASSIGNMENT_LOOP(int8_t, uint8_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(int8_t, uint8_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_INT8) { - BOOLEAN_ASSIGNMENT_LOOP(int8_t, int8_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(int8_t, int8_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_UINT16) { - BOOLEAN_ASSIGNMENT_LOOP(int8_t, uint16_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(int8_t, uint16_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_INT16) { - BOOLEAN_ASSIGNMENT_LOOP(int8_t, int16_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(int8_t, int16_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_FLOAT) { - BOOLEAN_ASSIGNMENT_LOOP(int8_t, mp_float_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(int8_t, mp_float_t, ndarray, lstrides, iarray, istride, varray, vstride); } } else if(ndarray->dtype == NDARRAY_UINT16) { if(values->dtype == NDARRAY_UINT8) { - BOOLEAN_ASSIGNMENT_LOOP(uint16_t, uint8_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(uint16_t, uint8_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_INT8) { - BOOLEAN_ASSIGNMENT_LOOP(uint16_t, int8_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(uint16_t, int8_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_UINT16) { - BOOLEAN_ASSIGNMENT_LOOP(uint16_t, uint16_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(uint16_t, uint16_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_INT16) { - BOOLEAN_ASSIGNMENT_LOOP(uint16_t, int16_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(uint16_t, int16_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_FLOAT) { - BOOLEAN_ASSIGNMENT_LOOP(uint16_t, mp_float_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(uint16_t, mp_float_t, ndarray, lstrides, iarray, istride, varray, vstride); } } else if(ndarray->dtype == NDARRAY_INT16) { if(values->dtype == NDARRAY_UINT8) { - BOOLEAN_ASSIGNMENT_LOOP(int16_t, uint8_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(int16_t, uint8_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_INT8) { - BOOLEAN_ASSIGNMENT_LOOP(int16_t, int8_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(int16_t, int8_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_UINT16) { - BOOLEAN_ASSIGNMENT_LOOP(int16_t, uint16_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(int16_t, uint16_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_INT16) { - BOOLEAN_ASSIGNMENT_LOOP(int16_t, int16_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(int16_t, int16_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_FLOAT) { - BOOLEAN_ASSIGNMENT_LOOP(int16_t, mp_float_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(int16_t, mp_float_t, ndarray, lstrides, iarray, istride, varray, vstride); } } else { + #if ULAB_SUPPORTS_COMPLEX + if(ndarray->dtype == NDARRAY_COMPLEX) { + lstrides *= 2; + } + #endif if(values->dtype == NDARRAY_UINT8) { - BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, uint8_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, uint8_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_INT8) { - BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, int8_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, int8_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_UINT16) { - BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, uint16_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, uint16_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_INT16) { - BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, int16_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, int16_t, ndarray, lstrides, iarray, istride, varray, vstride); } else if(values->dtype == NDARRAY_FLOAT) { - BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, mp_float_t, ndarray, iarray, istride, varray, vstride); + BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, mp_float_t, ndarray, lstrides, iarray, istride, varray, vstride); } } return MP_OBJ_FROM_PTR(ndarray); @@ -1272,7 +1400,7 @@ static mp_obj_t ndarray_get_slice(ndarray_obj_t *ndarray, mp_obj_t index, ndarra if(values == NULL) { // return value(s) return ndarray_from_boolean_index(ndarray, nindex); } else { // assign value(s) - ndarray_assign_from_boolean_index(ndarray, index, values); + ndarray_assign_from_boolean_index(ndarray, nindex, values); } } if(mp_obj_is_type(index, &mp_type_tuple) || mp_obj_is_int(index) || mp_obj_is_type(index, &mp_type_slice)) { @@ -1291,7 +1419,7 @@ static mp_obj_t ndarray_get_slice(ndarray_obj_t *ndarray, mp_obj_t index, ndarra if(values == NULL) { // return value(s) // if the view has been reduced to nothing, return a single value if(view->ndim == 0) { - return mp_binary_get_val_array(view->dtype, view->array, 0); + return ndarray_get_item(view, view->array); } else { return MP_OBJ_FROM_PTR(view); } @@ -1525,6 +1653,32 @@ mp_obj_t ndarray_tobytes(mp_obj_t self_in) { MP_DEFINE_CONST_FUN_OBJ_1(ndarray_tobytes_obj, ndarray_tobytes); #endif +#if NDARRAY_HAS_TOLIST +static mp_obj_t ndarray_recursive_list(ndarray_obj_t *self, uint8_t *array, uint8_t dim) { + int32_t stride = self->strides[ULAB_MAX_DIMS - dim]; + size_t len = self->shape[ULAB_MAX_DIMS - dim]; + + mp_obj_list_t *list = MP_OBJ_TO_PTR(mp_obj_new_list(len, NULL)); + for(size_t i = 0; i < len; i++) { + if(dim == 1) { + list->items[i] = ndarray_get_item(self, array); + } else { + list->items[i] = ndarray_recursive_list(self, array, dim-1); + } + array += stride; + } + return MP_OBJ_FROM_PTR(list); +} + +mp_obj_t ndarray_tolist(mp_obj_t self_in) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + uint8_t *array = (uint8_t *)self->array; + return ndarray_recursive_list(self, array, self->ndim); +} + +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_tolist_obj, ndarray_tolist); +#endif + // Binary operations ndarray_obj_t *ndarray_from_mp_obj(mp_obj_t obj, uint8_t other_type) { // creates an ndarray from a micropython int or float @@ -1571,7 +1725,15 @@ ndarray_obj_t *ndarray_from_mp_obj(mp_obj_t obj, uint8_t other_type) { array[0] = mp_obj_get_float(obj); } else if(mp_obj_is_type(obj, &ulab_ndarray_type)){ return obj; - } else { + } + #if ULAB_SUPPORTS_COMPLEX + else if(mp_obj_is_type(obj, &mp_type_complex)) { + ndarray = ndarray_new_linear_array(1, NDARRAY_COMPLEX); + mp_float_t *array = (mp_float_t *)ndarray->array; + mp_obj_get_complex(obj, &array[0], &array[1]); + } + #endif + else { // assume that the input is an iterable (raises an exception, if it is not the case) ndarray = ndarray_from_iterable(obj, NDARRAY_FLOAT); } @@ -1606,9 +1768,9 @@ mp_obj_t ndarray_binary_op(mp_binary_op_t _op, mp_obj_t lobj, mp_obj_t robj) { } uint8_t ndim = 0; - size_t *shape = m_new(size_t, ULAB_MAX_DIMS); - int32_t *lstrides = m_new(int32_t, ULAB_MAX_DIMS); - int32_t *rstrides = m_new(int32_t, ULAB_MAX_DIMS); + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *lstrides = m_new0(int32_t, ULAB_MAX_DIMS); + int32_t *rstrides = m_new0(int32_t, ULAB_MAX_DIMS); uint8_t broadcastable; if((op == MP_BINARY_OP_INPLACE_ADD) || (op == MP_BINARY_OP_INPLACE_MULTIPLY) || (op == MP_BINARY_OP_INPLACE_POWER) || (op == MP_BINARY_OP_INPLACE_SUBTRACT) || (op == MP_BINARY_OP_INPLACE_TRUE_DIVIDE)) { @@ -1625,7 +1787,7 @@ mp_obj_t ndarray_binary_op(mp_binary_op_t _op, mp_obj_t lobj, mp_obj_t robj) { // the empty arrays have to be treated separately uint8_t dtype = NDARRAY_INT16; ndarray_obj_t *nd; - if((lhs->ndim == 0) || (rhs->ndim == 0)) { + if((lhs->len == 0) || (rhs->len == 0)) { switch(op) { case MP_BINARY_OP_INPLACE_ADD: case MP_BINARY_OP_INPLACE_MULTIPLY: @@ -1662,7 +1824,7 @@ mp_obj_t ndarray_binary_op(mp_binary_op_t _op, mp_obj_t lobj, mp_obj_t robj) { case MP_BINARY_OP_EQUAL: case MP_BINARY_OP_NOT_EQUAL: nd = ndarray_new_linear_array(0, NDARRAY_UINT8); - nd->boolean = true; + nd->boolean = 1; return MP_OBJ_FROM_PTR(nd); default: @@ -1675,26 +1837,31 @@ mp_obj_t ndarray_binary_op(mp_binary_op_t _op, mp_obj_t lobj, mp_obj_t robj) { // first the in-place operators #if NDARRAY_HAS_INPLACE_ADD case MP_BINARY_OP_INPLACE_ADD: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); return ndarray_inplace_ams(lhs, rhs, rstrides, op); break; #endif #if NDARRAY_HAS_INPLACE_MULTIPLY case MP_BINARY_OP_INPLACE_MULTIPLY: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); return ndarray_inplace_ams(lhs, rhs, rstrides, op); break; #endif #if NDARRAY_HAS_INPLACE_POWER case MP_BINARY_OP_INPLACE_POWER: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); return ndarray_inplace_power(lhs, rhs, rstrides); break; #endif #if NDARRAY_HAS_INPLACE_SUBTRACT case MP_BINARY_OP_INPLACE_SUBTRACT: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); return ndarray_inplace_ams(lhs, rhs, rstrides, op); break; #endif #if NDARRAY_HAS_INPLACE_TRUE_DIVIDE case MP_BINARY_OP_INPLACE_TRUE_DIVIDE: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); return ndarray_inplace_divide(lhs, rhs, rstrides); break; #endif @@ -1702,12 +1869,14 @@ mp_obj_t ndarray_binary_op(mp_binary_op_t _op, mp_obj_t lobj, mp_obj_t robj) { #if NDARRAY_HAS_BINARY_OP_LESS case MP_BINARY_OP_LESS: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); // here we simply swap the operands return ndarray_binary_more(rhs, lhs, ndim, shape, rstrides, lstrides, MP_BINARY_OP_MORE); break; #endif #if NDARRAY_HAS_BINARY_OP_LESS_EQUAL case MP_BINARY_OP_LESS_EQUAL: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); // here we simply swap the operands return ndarray_binary_more(rhs, lhs, ndim, shape, rstrides, lstrides, MP_BINARY_OP_MORE_EQUAL); break; @@ -1734,11 +1903,13 @@ mp_obj_t ndarray_binary_op(mp_binary_op_t _op, mp_obj_t lobj, mp_obj_t robj) { #endif #if NDARRAY_HAS_BINARY_OP_MORE case MP_BINARY_OP_MORE: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); return ndarray_binary_more(lhs, rhs, ndim, shape, lstrides, rstrides, MP_BINARY_OP_MORE); break; #endif #if NDARRAY_HAS_BINARY_OP_MORE_EQUAL case MP_BINARY_OP_MORE_EQUAL: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); return ndarray_binary_more(lhs, rhs, ndim, shape, lstrides, rstrides, MP_BINARY_OP_MORE_EQUAL); break; #endif @@ -1754,6 +1925,7 @@ mp_obj_t ndarray_binary_op(mp_binary_op_t _op, mp_obj_t lobj, mp_obj_t robj) { #endif #if NDARRAY_HAS_BINARY_OP_POWER case MP_BINARY_OP_POWER: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); return ndarray_binary_power(lhs, rhs, ndim, shape, lstrides, rstrides); break; #endif @@ -1773,30 +1945,44 @@ mp_obj_t ndarray_unary_op(mp_unary_op_t op, mp_obj_t self_in) { switch (op) { #if NDARRAY_HAS_UNARY_OP_ABS case MP_UNARY_OP_ABS: - ndarray = ndarray_copy_view(self); - // if Boolean, NDARRAY_UINT8, or NDARRAY_UINT16, there is nothing to do - if(self->dtype == NDARRAY_INT8) { - int8_t *array = (int8_t *)ndarray->array; - for(size_t i=0; i < self->len; i++, array++) { - if(*array < 0) *array = -(*array); - } - } else if(self->dtype == NDARRAY_INT16) { - int16_t *array = (int16_t *)ndarray->array; - for(size_t i=0; i < self->len; i++, array++) { - if(*array < 0) *array = -(*array); - } + #if ULAB_SUPPORTS_COMPLEX + if(self->dtype == NDARRAY_COMPLEX) { + int32_t *strides = strides_from_shape(self->shape, NDARRAY_FLOAT); + ndarray_obj_t *target = ndarray_new_ndarray(self->ndim, self->shape, strides, NDARRAY_FLOAT); + ndarray = carray_abs(self, target); } else { - mp_float_t *array = (mp_float_t *)ndarray->array; - for(size_t i=0; i < self->len; i++, array++) { - if(*array < 0) *array = -(*array); + #endif + ndarray = ndarray_copy_view(self); + // if Boolean, NDARRAY_UINT8, or NDARRAY_UINT16, there is nothing to do + if(self->dtype == NDARRAY_INT8) { + int8_t *array = (int8_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) { + if(*array < 0) *array = -(*array); + } + } else if(self->dtype == NDARRAY_INT16) { + int16_t *array = (int16_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) { + if(*array < 0) *array = -(*array); + } + } else { + mp_float_t *array = (mp_float_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) { + if(*array < 0) *array = -(*array); + } } + #if ULAB_SUPPORTS_COMPLEX } + #endif return MP_OBJ_FROM_PTR(ndarray); break; #endif #if NDARRAY_HAS_UNARY_OP_INVERT case MP_UNARY_OP_INVERT: + #if ULAB_SUPPORTS_COMPLEX + if(self->dtype == NDARRAY_FLOAT || self->dtype == NDARRAY_COMPLEX) { + #else if(self->dtype == NDARRAY_FLOAT) { + #endif mp_raise_ValueError(translate("operation is not supported for given type")); } // we can invert the content byte by byte, no need to distinguish between different dtypes @@ -1805,7 +1991,7 @@ mp_obj_t ndarray_unary_op(mp_unary_op_t op, mp_obj_t self_in) { if(ndarray->boolean) { for(size_t i=0; i < ndarray->len; i++, array++) *array = *array ^ 0x01; } else { - uint8_t itemsize = mp_binary_get_size('@', self->dtype, NULL); + uint8_t itemsize = ulab_binary_get_size(self->dtype); for(size_t i=0; i < ndarray->len*itemsize; i++, array++) *array ^= 0xFF; } return MP_OBJ_FROM_PTR(ndarray); @@ -1833,7 +2019,13 @@ mp_obj_t ndarray_unary_op(mp_unary_op_t op, mp_obj_t self_in) { for(size_t i=0; i < self->len; i++, array++) *array = -(*array); } else { mp_float_t *array = (mp_float_t *)ndarray->array; - for(size_t i=0; i < self->len; i++, array++) *array = -(*array); + size_t len = self->len; + #if ULAB_SUPPORTS_COMPLEX + if(self->dtype == NDARRAY_COMPLEX) { + len *= 2; + } + #endif + for(size_t i=0; i < len; i++, array++) *array = -(*array); } return MP_OBJ_FROM_PTR(ndarray); break; @@ -1887,8 +2079,8 @@ mp_obj_t ndarray_reshape_core(mp_obj_t oin, mp_obj_t _shape, bool inplace) { if(shape->len > ULAB_MAX_DIMS) { mp_raise_ValueError(translate("maximum number of dimensions is 4")); } - size_t *new_shape = m_new(size_t, ULAB_MAX_DIMS); - memset(new_shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); + size_t *new_shape = m_new0(size_t, ULAB_MAX_DIMS); + size_t new_length = 1; for(uint8_t i=0; i < shape->len; i++) { new_shape[ULAB_MAX_DIMS - i - 1] = mp_obj_get_int(shape->items[shape->len - i - 1]); @@ -1914,7 +2106,7 @@ mp_obj_t ndarray_reshape_core(mp_obj_t oin, mp_obj_t _shape, bool inplace) { mp_raise_ValueError(translate("cannot assign new shape")); } ndarray = ndarray_new_ndarray_from_tuple(shape, source->dtype); - ndarray_copy_array(source, ndarray); + ndarray_copy_array(source, ndarray, 0); } return MP_OBJ_FROM_PTR(ndarray); } diff --git a/python/port/mod/ulab/ndarray.h b/python/port/mod/ulab/ndarray.h index 04abd96598a..7fc4dc2c14c 100644 --- a/python/port/mod/ulab/ndarray.h +++ b/python/port/mod/ulab/ndarray.h @@ -63,6 +63,8 @@ typedef struct _mp_obj_slice_t { void ndarray_set_value(char , void *, size_t , mp_obj_t ); #endif +void ndarray_set_complex_value(void *, size_t , mp_obj_t ); + #define NDARRAY_NUMERIC 0 #define NDARRAY_BOOLEAN 1 @@ -77,6 +79,9 @@ enum NDARRAY_TYPE { NDARRAY_INT8 = 'b', NDARRAY_UINT16 = 'H', NDARRAY_INT16 = 'h', + #if ULAB_SUPPORTS_COMPLEX + NDARRAY_COMPLEX = 'c', + #endif NDARRAY_FLOAT = FLOAT_TYPECODE, }; @@ -131,6 +136,7 @@ void ndarray_assign_elements(ndarray_obj_t *, mp_obj_t , uint8_t , size_t *); size_t *ndarray_contract_shape(ndarray_obj_t *, uint8_t ); int32_t *ndarray_contract_strides(ndarray_obj_t *, uint8_t ); +ndarray_obj_t *ndarray_from_iterable(mp_obj_t , uint8_t ); ndarray_obj_t *ndarray_new_dense_ndarray(uint8_t , size_t *, uint8_t ); ndarray_obj_t *ndarray_new_ndarray_from_tuple(mp_obj_tuple_t *, uint8_t ); ndarray_obj_t *ndarray_new_ndarray(uint8_t , size_t *, int32_t *, uint8_t ); @@ -138,7 +144,8 @@ ndarray_obj_t *ndarray_new_linear_array(size_t , uint8_t ); ndarray_obj_t *ndarray_new_view(ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t ); bool ndarray_is_dense(ndarray_obj_t *); ndarray_obj_t *ndarray_copy_view(ndarray_obj_t *); -void ndarray_copy_array(ndarray_obj_t *, ndarray_obj_t *); +ndarray_obj_t *ndarray_copy_view_convert_type(ndarray_obj_t *, uint8_t ); +void ndarray_copy_array(ndarray_obj_t *, ndarray_obj_t *, uint8_t ); MP_DECLARE_CONST_FUN_OBJ_KW(ndarray_array_constructor_obj); mp_obj_t ndarray_make_new(const mp_obj_type_t *, size_t , size_t , const mp_obj_t *); @@ -185,6 +192,11 @@ mp_obj_t ndarray_tobytes(mp_obj_t ); MP_DECLARE_CONST_FUN_OBJ_1(ndarray_tobytes_obj); #endif +#if NDARRAY_HAS_TOBYTES +mp_obj_t ndarray_tolist(mp_obj_t ); +MP_DECLARE_CONST_FUN_OBJ_1(ndarray_tolist_obj); +#endif + #if NDARRAY_HAS_TRANSPOSE mp_obj_t ndarray_transpose(mp_obj_t ); MP_DECLARE_CONST_FUN_OBJ_1(ndarray_transpose_obj); @@ -201,15 +213,15 @@ mp_int_t ndarray_get_buffer(mp_obj_t , mp_buffer_info_t *, mp_uint_t ); ndarray_obj_t *ndarray_from_mp_obj(mp_obj_t , uint8_t ); -#define BOOLEAN_ASSIGNMENT_LOOP(type_left, type_right, ndarray, iarray, istride, varray, vstride)\ +#define BOOLEAN_ASSIGNMENT_LOOP(type_left, type_right, ndarray, lstrides, iarray, istride, varray, vstride)\ type_left *array = (type_left *)(ndarray)->array;\ for(size_t i=0; i < (ndarray)->len; i++) {\ if(*(iarray)) {\ *array = (type_left)(*((type_right *)(varray)));\ + (varray) += (vstride);\ }\ - array += (ndarray)->strides[ULAB_MAX_DIMS - 1] / (ndarray)->itemsize;\ + array += (lstrides);\ (iarray) += (istride);\ - (varray) += (vstride);\ } while(0) #if ULAB_HAS_FUNCTION_ITERATOR @@ -634,105 +646,4 @@ ndarray_obj_t *ndarray_from_mp_obj(mp_obj_t , uint8_t ); #endif /* ULAB_MAX_DIMS == 4 */ #endif /* ULAB_HAS_FUNCTION_ITERATOR */ - -#if ULAB_MAX_DIMS == 1 -#define ASSIGNMENT_LOOP(results, type_left, type_right, lstrides, rarray, rstrides)\ - type_left *larray = (type_left *)(results)->array;\ - size_t l = 0;\ - do {\ - *larray = (type_left)(*((type_right *)(rarray)));\ - (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ - (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ - l++;\ - } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ - -#endif /* ULAB_MAX_DIMS == 1 */ - -#if ULAB_MAX_DIMS == 2 -#define ASSIGNMENT_LOOP(results, type_left, type_right, lstrides, rarray, rstrides)\ - type_left *larray = (type_left *)(results)->array;\ - size_t k = 0;\ - do {\ - size_t l = 0;\ - do {\ - *larray = (type_left)(*((type_right *)(rarray)));\ - (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ - (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ - l++;\ - } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ - (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ - (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ - (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ - (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ - k++;\ - } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ - -#endif /* ULAB_MAX_DIMS == 2 */ - -#if ULAB_MAX_DIMS == 3 -#define ASSIGNMENT_LOOP(results, type_left, type_right, lstrides, rarray, rstrides)\ - type_left *larray = (type_left *)(results)->array;\ - size_t j = 0;\ - do {\ - size_t k = 0;\ - do {\ - size_t l = 0;\ - do {\ - *larray = (type_left)(*((type_right *)(rarray)));\ - (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ - (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ - l++;\ - } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ - (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ - (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ - (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ - (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ - k++;\ - } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ - (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\ - (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ - (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\ - (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ - j++;\ - } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ - -#endif /* ULAB_MAX_DIMS == 3 */ - -#if ULAB_MAX_DIMS == 4 -#define ASSIGNMENT_LOOP(results, type_left, type_right, lstrides, rarray, rstrides)\ - type_left *larray = (type_left *)(results)->array;\ - size_t i = 0;\ - do {\ - size_t j = 0;\ - do {\ - size_t k = 0;\ - do {\ - size_t l = 0;\ - do {\ - *larray = (type_left)(*((type_right *)(rarray)));\ - (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ - (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ - l++;\ - } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ - (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ - (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ - (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ - (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ - k++;\ - } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ - (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\ - (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ - (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\ - (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ - j++;\ - } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ - (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ - (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ - (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ - (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ - i++;\ - } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ - -#endif /* ULAB_MAX_DIMS == 4 */ - #endif diff --git a/python/port/mod/ulab/ndarray_operators.c b/python/port/mod/ulab/ndarray_operators.c index 465140b65b0..de1042cc89f 100644 --- a/python/port/mod/ulab/ndarray_operators.c +++ b/python/port/mod/ulab/ndarray_operators.c @@ -17,6 +17,7 @@ #include "ndarray_operators.h" #include "ulab.h" #include "ulab_tools.h" +#include "numpy/carray/carray.h" /* This file contains the actual implementations of the various @@ -24,7 +25,8 @@ These are the upcasting rules of the binary operators - - if one of the operarands is a float, the result is always float + - if complex is supported, and if one of the operarands is a complex, the result is always complex + - if both operarands are real one of them is a float, then the result is also a float - operation on identical types preserves type uint8 + int8 => int16 @@ -39,6 +41,12 @@ mp_obj_t ndarray_binary_equality(ndarray_obj_t *lhs, ndarray_obj_t *rhs, uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides, mp_binary_op_t op) { + #if ULAB_SUPPORTS_COMPLEX + if((lhs->dtype == NDARRAY_COMPLEX) || (rhs->dtype == NDARRAY_COMPLEX)) { + return carray_binary_equal_not_equal(lhs, rhs, ndim, shape, lstrides, rstrides, op); + } + #endif + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT8); results->boolean = 1; uint8_t *array = (uint8_t *)results->array; @@ -161,6 +169,12 @@ mp_obj_t ndarray_binary_equality(ndarray_obj_t *lhs, ndarray_obj_t *rhs, mp_obj_t ndarray_binary_add(ndarray_obj_t *lhs, ndarray_obj_t *rhs, uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + #if ULAB_SUPPORTS_COMPLEX + if((lhs->dtype == NDARRAY_COMPLEX) || (rhs->dtype == NDARRAY_COMPLEX)) { + return carray_binary_add(lhs, rhs, ndim, shape, lstrides, rstrides); + } + #endif + ndarray_obj_t *results = NULL; uint8_t *larray = (uint8_t *)lhs->array; uint8_t *rarray = (uint8_t *)rhs->array; @@ -238,6 +252,12 @@ mp_obj_t ndarray_binary_add(ndarray_obj_t *lhs, ndarray_obj_t *rhs, mp_obj_t ndarray_binary_multiply(ndarray_obj_t *lhs, ndarray_obj_t *rhs, uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + #if ULAB_SUPPORTS_COMPLEX + if((lhs->dtype == NDARRAY_COMPLEX) || (rhs->dtype == NDARRAY_COMPLEX)) { + return carray_binary_multiply(lhs, rhs, ndim, shape, lstrides, rstrides); + } + #endif + ndarray_obj_t *results = NULL; uint8_t *larray = (uint8_t *)lhs->array; uint8_t *rarray = (uint8_t *)rhs->array; @@ -460,6 +480,12 @@ mp_obj_t ndarray_binary_more(ndarray_obj_t *lhs, ndarray_obj_t *rhs, mp_obj_t ndarray_binary_subtract(ndarray_obj_t *lhs, ndarray_obj_t *rhs, uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + #if ULAB_SUPPORTS_COMPLEX + if((lhs->dtype == NDARRAY_COMPLEX) || (rhs->dtype == NDARRAY_COMPLEX)) { + return carray_binary_subtract(lhs, rhs, ndim, shape, lstrides, rstrides); + } + #endif + ndarray_obj_t *results = NULL; uint8_t *larray = (uint8_t *)lhs->array; uint8_t *rarray = (uint8_t *)rhs->array; @@ -559,6 +585,12 @@ mp_obj_t ndarray_binary_subtract(ndarray_obj_t *lhs, ndarray_obj_t *rhs, mp_obj_t ndarray_binary_true_divide(ndarray_obj_t *lhs, ndarray_obj_t *rhs, uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + #if ULAB_SUPPORTS_COMPLEX + if((lhs->dtype == NDARRAY_COMPLEX) || (rhs->dtype == NDARRAY_COMPLEX)) { + return carray_binary_divide(lhs, rhs, ndim, shape, lstrides, rstrides); + } + #endif + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); uint8_t *larray = (uint8_t *)lhs->array; uint8_t *rarray = (uint8_t *)rhs->array; diff --git a/python/port/mod/ulab/ndarray_properties.c b/python/port/mod/ulab/ndarray_properties.c index 4a93fb82375..5464b31d54c 100644 --- a/python/port/mod/ulab/ndarray_properties.c +++ b/python/port/mod/ulab/ndarray_properties.c @@ -20,6 +20,9 @@ #include "ulab.h" #include "ndarray.h" #include "numpy/ndarray/ndarray_iter.h" +#if ULAB_SUPPORTS_COMPLEX +#include "numpy/carray/carray.h" +#endif #ifndef CIRCUITPY @@ -82,6 +85,18 @@ void ndarray_properties_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { dest[0] = ndarray_transpose(self_in); break; #endif + #if ULAB_SUPPORTS_COMPLEX + #if ULAB_NUMPY_HAS_IMAG + case MP_QSTR_imag: + dest[0] = carray_imag(self_in); + break; + #endif + #if ULAB_NUMPY_HAS_IMAG + case MP_QSTR_real: + dest[0] = carray_real(self_in); + break; + #endif + #endif /* ULAB_SUPPORTS_COMPLEX */ default: call_local_method(self_in, attr, dest); break; diff --git a/python/port/mod/ulab/numpy/approx.c b/python/port/mod/ulab/numpy/approx.c index 6ed5d7c2d7e..85cdbf78d19 100644 --- a/python/port/mod/ulab/numpy/approx.c +++ b/python/port/mod/ulab/numpy/approx.c @@ -19,6 +19,7 @@ #include "../ulab.h" #include "../ulab_tools.h" +#include "carray/carray_tools.h" #include "approx.h" //| """Numerical approximation methods""" @@ -60,6 +61,9 @@ STATIC mp_obj_t approx_interp(size_t n_args, const mp_obj_t *pos_args, mp_map_t ndarray_obj_t *x = ndarray_from_mp_obj(args[0].u_obj, 0); ndarray_obj_t *xp = ndarray_from_mp_obj(args[1].u_obj, 0); // xp must hold an increasing sequence of independent values ndarray_obj_t *fp = ndarray_from_mp_obj(args[2].u_obj, 0); + COMPLEX_DTYPE_NOT_IMPLEMENTED(x->dtype) + COMPLEX_DTYPE_NOT_IMPLEMENTED(xp->dtype) + COMPLEX_DTYPE_NOT_IMPLEMENTED(fp->dtype) if((xp->ndim != 1) || (fp->ndim != 1) || (xp->len < 2) || (fp->len < 2) || (xp->len != fp->len)) { mp_raise_ValueError(translate("interp is defined for 1D iterables of equal length")); } @@ -157,6 +161,7 @@ STATIC mp_obj_t approx_trapz(size_t n_args, const mp_obj_t *pos_args, mp_map_t * mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); ndarray_obj_t *y = ndarray_from_mp_obj(args[0].u_obj, 0); + COMPLEX_DTYPE_NOT_IMPLEMENTED(y->dtype) ndarray_obj_t *x; mp_float_t mean = MICROPY_FLOAT_CONST(0.0); if(y->len < 2) { @@ -174,6 +179,7 @@ STATIC mp_obj_t approx_trapz(size_t n_args, const mp_obj_t *pos_args, mp_map_t * if(args[1].u_obj != mp_const_none) { x = ndarray_from_mp_obj(args[1].u_obj, 0); // x must hold an increasing sequence of independent values + COMPLEX_DTYPE_NOT_IMPLEMENTED(x->dtype) if((x->ndim != 1) || (y->len != x->len)) { mp_raise_ValueError(translate("trapz is defined for 1D arrays of equal length")); } diff --git a/python/port/mod/ulab/numpy/carray/carray.c b/python/port/mod/ulab/numpy/carray/carray.c new file mode 100644 index 00000000000..a5f8a2b1262 --- /dev/null +++ b/python/port/mod/ulab/numpy/carray/carray.c @@ -0,0 +1,826 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2021-2022 Zoltán Vörös +*/ + +#include +#include +#include +#include "py/obj.h" +#include "py/objint.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/misc.h" + +#include "../../ulab.h" +#include "../../ndarray.h" +#include "../../ulab_tools.h" +#include "carray.h" + +#if ULAB_SUPPORTS_COMPLEX + +//| import ulab.numpy + +//| def real(val): +//| """ +//| Return the real part of the complex argument, which can be +//| either an ndarray, or a scalar.""" +//| ... +//| + +mp_obj_t carray_real(mp_obj_t _source) { + if(mp_obj_is_type(_source, &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(_source); + if(source->dtype != NDARRAY_COMPLEX) { + ndarray_obj_t *target = ndarray_new_dense_ndarray(source->ndim, source->shape, source->dtype); + ndarray_copy_array(source, target, 0); + return MP_OBJ_FROM_PTR(target); + } else { // the input is most definitely a complex array + ndarray_obj_t *target = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_FLOAT); + ndarray_copy_array(source, target, 0); + return MP_OBJ_FROM_PTR(target); + } + } else { + mp_raise_NotImplementedError(translate("function is implemented for ndarrays only")); + } + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_1(carray_real_obj, carray_real); + +//| def imag(val): +//| """ +//| Return the imaginary part of the complex argument, which can be +//| either an ndarray, or a scalar.""" +//| ... +//| + +mp_obj_t carray_imag(mp_obj_t _source) { + if(mp_obj_is_type(_source, &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(_source); + if(source->dtype != NDARRAY_COMPLEX) { // if not complex, then the imaginary part is zero + ndarray_obj_t *target = ndarray_new_dense_ndarray(source->ndim, source->shape, source->dtype); + return MP_OBJ_FROM_PTR(target); + } else { // the input is most definitely a complex array + ndarray_obj_t *target = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_FLOAT); + ndarray_copy_array(source, target, source->itemsize / 2); + return MP_OBJ_FROM_PTR(target); + } + } else { + mp_raise_NotImplementedError(translate("function is implemented for ndarrays only")); + } + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_1(carray_imag_obj, carray_imag); + +#if ULAB_NUMPY_HAS_CONJUGATE + +//| def conjugate(val): +//| """ +//| Return the conjugate of the complex argument, which can be +//| either an ndarray, or a scalar.""" +//| ... +//| +mp_obj_t carray_conjugate(mp_obj_t _source) { + if(mp_obj_is_type(_source, &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(_source); + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, source->dtype); + ndarray_copy_array(source, ndarray, 0); + if(source->dtype == NDARRAY_COMPLEX) { + mp_float_t *array = (mp_float_t *)ndarray->array; + array++; + for(size_t i = 0; i < ndarray->len; i++) { + *array *= MICROPY_FLOAT_CONST(-1.0); + array += 2; + } + } + return MP_OBJ_FROM_PTR(ndarray); + } else { + if(mp_obj_is_type(_source, &mp_type_complex)) { + mp_float_t real, imag; + mp_obj_get_complex(_source, &real, &imag); + imag = imag * MICROPY_FLOAT_CONST(-1.0); + return mp_obj_new_complex(real, imag); + } else if(mp_obj_is_int(_source) || mp_obj_is_float(_source)) { + return _source; + } else { + mp_raise_TypeError(translate("input must be an ndarray, or a scalar")); + } + } + // this should never happen + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_1(carray_conjugate_obj, carray_conjugate); +#endif + +#if ULAB_NUMPY_HAS_SORT_COMPLEX +//| def sort_complex(a: ulab.numpy.ndarray) -> ulab.numpy.ndarray: +//| """ +//| .. param: a +//| a one-dimensional ndarray +//| +//| Sort a complex array using the real part first, then the imaginary part. +//| Always returns a sorted complex array, even if the input was real.""" +//| ... +//| + +static void carray_sort_complex_(mp_float_t *array, size_t len) { + // array is assumed to be a floating vector containing the real and imaginary parts + // of a complex array at alternating positions as + // array[0] = real[0] + // array[1] = imag[0] + // array[2] = real[1] + // array[3] = imag[1] + + mp_float_t real, imag; + size_t c, q = len, p, r = len >> 1; + for (;;) { + if (r > 0) { + r--; + real = array[2 * r]; + imag = array[2 * r + 1]; + } else { + q--; + if(q == 0) { + break; + } + real = array[2 * q]; + imag = array[2 * q + 1]; + array[2 * q] = array[0]; + array[2 * q + 1] = array[1]; + } + p = r; + c = r + r + 1; + while (c < q) { + if(c + 1 < q) { + if((array[2 * (c+1)] > array[2 * c]) || + ((array[2 * (c+1)] == array[2 * c]) && (array[2 * (c+1) + 1] > array[2 * c + 1]))) { + c++; + } + } + if((array[2 * c] > real) || + ((array[2 * c] == real) && (array[2 * c + 1] > imag))) { + array[2 * p] = array[2 * c]; // real part + array[2 * p + 1] = array[2 * c + 1]; // imag part + p = c; + c = p + p + 1; + } else { + break; + } + } + array[2 * p] = real; + array[2 * p + 1] = imag; + } +} + +mp_obj_t carray_sort_complex(mp_obj_t _source) { + if(!mp_obj_is_type(_source, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("input must be a 1D ndarray")); + } + ndarray_obj_t *source = MP_OBJ_TO_PTR(_source); + if(source->ndim != 1) { + mp_raise_TypeError(translate("input must be a 1D ndarray")); + } + + ndarray_obj_t *ndarray = ndarray_copy_view_convert_type(source, NDARRAY_COMPLEX); + mp_float_t *array = (mp_float_t *)ndarray->array; + carray_sort_complex_(array, ndarray->len); + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_1(carray_sort_complex_obj, carray_sort_complex); +#endif + +//| def abs(a: ulab.numpy.ndarray) -> ulab.numpy.ndarray: +//| """ +//| .. param: a +//| a one-dimensional ndarray +//| +//| Return the absolute value of complex ndarray.""" +//| ... +//| + +mp_obj_t carray_abs(ndarray_obj_t *source, ndarray_obj_t *target) { + // calculates the absolute value of a complex array and returns a dense array + uint8_t *sarray = (uint8_t *)source->array; + mp_float_t *tarray = (mp_float_t *)target->array; + uint8_t itemsize = mp_binary_get_size('@', NDARRAY_FLOAT, NULL); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t rvalue = *(mp_float_t *)sarray; + mp_float_t ivalue = *(mp_float_t *)(sarray + itemsize); + *tarray++ = MICROPY_FLOAT_C_FUN(sqrt)(rvalue * rvalue + ivalue * ivalue); + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif + return MP_OBJ_FROM_PTR(target); +} + +static void carray_copy_part(uint8_t *tarray, uint8_t *sarray, size_t *shape, int32_t *strides) { + // copies the real or imaginary part of an array + // into the respective part of a dense complex array + uint8_t sz = sizeof(mp_float_t); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + memcpy(tarray, sarray, sz); + tarray += 2 * sz; + sarray += strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= strides[ULAB_MAX_DIMS - 1] * shape[ULAB_MAX_DIMS-1]; + sarray += strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + sarray -= strides[ULAB_MAX_DIMS - 2] * shape[ULAB_MAX_DIMS-2]; + sarray += strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + sarray -= strides[ULAB_MAX_DIMS - 3] * shape[ULAB_MAX_DIMS-3]; + sarray += strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ +} + +mp_obj_t carray_binary_equal_not_equal(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides, mp_binary_op_t op) { + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT8); + results->boolean = 1; + uint8_t *array = (uint8_t *)results->array; + + if(op == MP_BINARY_OP_NOT_EQUAL) { + memset(array, 1, results->len); + } + + if((lhs->dtype == NDARRAY_COMPLEX) && (rhs->dtype == NDARRAY_COMPLEX)) { + mp_float_t *larray = (mp_float_t *)lhs->array; + mp_float_t *rarray = (mp_float_t *)rhs->array; + + ulab_rescale_float_strides(lstrides); + ulab_rescale_float_strides(rstrides); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + if((larray[0] == rarray[0]) && (larray[1] == rarray[1])) { + *array ^= 0x01; + } + array++; + larray += lstrides[ULAB_MAX_DIMS - 1]; + rarray += rstrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < results->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + larray -= lstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + larray += lstrides[ULAB_MAX_DIMS - 2]; + rarray -= rstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < results->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + larray -= lstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + larray += lstrides[ULAB_MAX_DIMS - 3]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < results->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + larray -= lstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + larray += lstrides[ULAB_MAX_DIMS - 4]; + rarray -= rstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + rarray += rstrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < results->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + } else { // only one of the operands is complex + mp_float_t *larray = (mp_float_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + // align the complex array to the left + uint8_t rdtype = rhs->dtype; + int32_t *lstrides_ = lstrides; + int32_t *rstrides_ = rstrides; + + if(rhs->dtype == NDARRAY_COMPLEX) { + larray = (mp_float_t *)rhs->array; + rarray = (uint8_t *)lhs->array; + lstrides_ = rstrides; + rstrides_ = lstrides; + rdtype = lhs->dtype; + } + + ulab_rescale_float_strides(lstrides_); + + if(rdtype == NDARRAY_UINT8) { + BINARY_LOOP_COMPLEX_EQUAL(results, array, uint8_t, larray, lstrides_, rarray, rstrides_); + } else if(rdtype == NDARRAY_INT8) { + BINARY_LOOP_COMPLEX_EQUAL(results, array, int8_t, larray, lstrides_, rarray, rstrides_); + } else if(rdtype == NDARRAY_UINT16) { + BINARY_LOOP_COMPLEX_EQUAL(results, array, uint16_t, larray, lstrides_, rarray, rstrides_); + } else if(rdtype == NDARRAY_INT16) { + BINARY_LOOP_COMPLEX_EQUAL(results, array, int16_t, larray, lstrides_, rarray, rstrides_); + } else if(rdtype == NDARRAY_FLOAT) { + BINARY_LOOP_COMPLEX_EQUAL(results, array, mp_float_t, larray, lstrides_, rarray, rstrides_); + } + } + return MP_OBJ_FROM_PTR(results); +} + +mp_obj_t carray_binary_add(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_COMPLEX); + mp_float_t *resarray = (mp_float_t *)results->array; + + if((lhs->dtype == NDARRAY_COMPLEX) && (rhs->dtype == NDARRAY_COMPLEX)) { + mp_float_t *larray = (mp_float_t *)lhs->array; + mp_float_t *rarray = (mp_float_t *)rhs->array; + + ulab_rescale_float_strides(lstrides); + ulab_rescale_float_strides(rstrides); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + // real part + *resarray++ = larray[0] + rarray[0]; + // imaginary part + *resarray++ = larray[1] + rarray[1]; + larray += lstrides[ULAB_MAX_DIMS - 1]; + rarray += rstrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < results->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + larray -= lstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + larray += lstrides[ULAB_MAX_DIMS - 2]; + rarray -= rstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < results->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + larray -= lstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + larray += lstrides[ULAB_MAX_DIMS - 3]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < results->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + larray -= lstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + larray += lstrides[ULAB_MAX_DIMS - 4]; + rarray -= rstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + rarray += rstrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < results->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + } else { // only one of the operands is complex + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + // align the complex array to the left + uint8_t rdtype = rhs->dtype; + int32_t *lstrides_ = lstrides; + int32_t *rstrides_ = rstrides; + + if(rhs->dtype == NDARRAY_COMPLEX) { + larray = (uint8_t *)rhs->array; + rarray = (uint8_t *)lhs->array; + lstrides_ = rstrides; + rstrides_ = lstrides; + rdtype = lhs->dtype; + } + + if(rdtype == NDARRAY_UINT8) { + BINARY_LOOP_COMPLEX(results, resarray, uint8_t, larray, lstrides_, rarray, rstrides_, +); + } else if(rdtype == NDARRAY_INT8) { + BINARY_LOOP_COMPLEX(results, resarray, int8_t, larray, lstrides_, rarray, rstrides_, +); + } else if(rdtype == NDARRAY_UINT16) { + BINARY_LOOP_COMPLEX(results, resarray, uint16_t, larray, lstrides_, rarray, rstrides_, +); + } else if(rdtype == NDARRAY_INT16) { + BINARY_LOOP_COMPLEX(results, resarray, int16_t, larray, lstrides_, rarray, rstrides_, +); + } else if(rdtype == NDARRAY_FLOAT) { + BINARY_LOOP_COMPLEX(results, resarray, mp_float_t, larray, lstrides_, rarray, rstrides_, +); + } + + // simply copy the imaginary part + uint8_t *tarray = (uint8_t *)results->array; + tarray += sizeof(mp_float_t); + + if(lhs->dtype == NDARRAY_COMPLEX) { + rarray = (uint8_t *)lhs->array; + rstrides = lstrides; + } else { + rarray = (uint8_t *)rhs->array; + } + rarray += sizeof(mp_float_t); + carray_copy_part(tarray, rarray, results->shape, rstrides); + } + return MP_OBJ_FROM_PTR(results); +} + +static void carray_binary_multiply_(ndarray_obj_t *results, mp_float_t *resarray, uint8_t *larray, uint8_t *rarray, + int32_t *lstrides, int32_t *rstrides, uint8_t rdtype) { + + if(rdtype == NDARRAY_UINT8) { + BINARY_LOOP_COMPLEX(results, resarray, uint8_t, larray, lstrides, rarray, rstrides, *); + } else if(rdtype == NDARRAY_INT8) { + BINARY_LOOP_COMPLEX(results, resarray, int8_t, larray, lstrides, rarray, rstrides, *); + } else if(rdtype == NDARRAY_UINT16) { + BINARY_LOOP_COMPLEX(results, resarray, uint16_t, larray, lstrides, rarray, rstrides, *); + } else if(rdtype == NDARRAY_INT16) { + BINARY_LOOP_COMPLEX(results, resarray, int16_t, larray, lstrides, rarray, rstrides, *); + } else if(rdtype == NDARRAY_FLOAT) { + BINARY_LOOP_COMPLEX(results, resarray, mp_float_t, larray, lstrides, rarray, rstrides, *); + } +} + +mp_obj_t carray_binary_multiply(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_COMPLEX); + mp_float_t *resarray = (mp_float_t *)results->array; + + if((lhs->dtype == NDARRAY_COMPLEX) && (rhs->dtype == NDARRAY_COMPLEX)) { + mp_float_t *larray = (mp_float_t *)lhs->array; + mp_float_t *rarray = (mp_float_t *)rhs->array; + + ulab_rescale_float_strides(lstrides); + ulab_rescale_float_strides(rstrides); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + // real part + *resarray++ = larray[0] * rarray[0] - larray[1] * rarray[1]; + // imaginary part + *resarray++ = larray[0] * rarray[1] + larray[1] * rarray[0]; + larray += lstrides[ULAB_MAX_DIMS - 1]; + rarray += rstrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < results->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + larray -= lstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + larray += lstrides[ULAB_MAX_DIMS - 2]; + rarray -= rstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < results->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + larray -= lstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + larray += lstrides[ULAB_MAX_DIMS - 3]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < results->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + larray -= lstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + larray += lstrides[ULAB_MAX_DIMS - 4]; + rarray -= rstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + rarray += rstrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < results->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + } else { // only one of the operands is complex + + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + uint8_t *lo = larray, *ro = rarray; + int32_t *left_strides = lstrides; + int32_t *right_strides = rstrides; + uint8_t rdtype = rhs->dtype; + + // align the complex array to the left + if(rhs->dtype == NDARRAY_COMPLEX) { + lo = (uint8_t *)rhs->array; + ro = (uint8_t *)lhs->array; + rdtype = lhs->dtype; + left_strides = rstrides; + right_strides = lstrides; + } + + larray = lo; + rarray = ro; + // real part + carray_binary_multiply_(results, resarray, larray, rarray, left_strides, right_strides, rdtype); + + larray = lo + sizeof(mp_float_t); + rarray = ro; + resarray = (mp_float_t *)results->array; + resarray++; + // imaginary part + carray_binary_multiply_(results, resarray, larray, rarray, left_strides, right_strides, rdtype); + } + return MP_OBJ_FROM_PTR(results); +} + +mp_obj_t carray_binary_subtract(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_COMPLEX); + mp_float_t *resarray = (mp_float_t *)results->array; + + if((lhs->dtype == NDARRAY_COMPLEX) && (rhs->dtype == NDARRAY_COMPLEX)) { + mp_float_t *larray = (mp_float_t *)lhs->array; + mp_float_t *rarray = (mp_float_t *)rhs->array; + + ulab_rescale_float_strides(lstrides); + ulab_rescale_float_strides(rstrides); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + // real part + *resarray++ = larray[0] - rarray[0]; + // imaginary part + *resarray++ = larray[1] - rarray[1]; + larray += lstrides[ULAB_MAX_DIMS - 1]; + rarray += rstrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < results->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + larray -= lstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + larray += lstrides[ULAB_MAX_DIMS - 2]; + rarray -= rstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < results->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + larray -= lstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + larray += lstrides[ULAB_MAX_DIMS - 3]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < results->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + larray -= lstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + larray += lstrides[ULAB_MAX_DIMS - 4]; + rarray -= rstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + rarray += rstrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < results->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + } else { + uint8_t *larray = (uint8_t *)lhs->array; + if(lhs->dtype == NDARRAY_COMPLEX) { + uint8_t *rarray = (uint8_t *)rhs->array; + if(rhs->dtype == NDARRAY_UINT8) { + BINARY_LOOP_COMPLEX(results, resarray, uint8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT8) { + BINARY_LOOP_COMPLEX(results, resarray, int8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_UINT16) { + BINARY_LOOP_COMPLEX(results, resarray, uint16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT16) { + BINARY_LOOP_COMPLEX(results, resarray, int16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_FLOAT) { + BINARY_LOOP_COMPLEX(results, resarray, mp_float_t, larray, lstrides, rarray, rstrides, -); + } + // copy the imaginary part + uint8_t *tarray = (uint8_t *)results->array; + tarray += sizeof(mp_float_t); + + larray = (uint8_t *)lhs->array; + larray += sizeof(mp_float_t); + + carray_copy_part(tarray, larray, results->shape, lstrides); + } else if(rhs->dtype == NDARRAY_COMPLEX) { + mp_float_t *rarray = (mp_float_t *)rhs->array; + ulab_rescale_float_strides(rstrides); + + if(lhs->dtype == NDARRAY_UINT8) { + BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT(results, resarray, uint8_t, larray, lstrides, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_INT8) { + BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT(results, resarray, int8_t, larray, lstrides, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_UINT16) { + BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT(results, resarray, uint16_t, larray, lstrides, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_INT16) { + BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT(results, resarray, int16_t, larray, lstrides, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_FLOAT) { + BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT(results, resarray, mp_float_t, larray, lstrides, rarray, rstrides); + } + } + } + + return MP_OBJ_FROM_PTR(results); +} + +static void carray_binary_left_divide_(ndarray_obj_t *results, mp_float_t *resarray, uint8_t *larray, uint8_t *rarray, + int32_t *lstrides, int32_t *rstrides, uint8_t rdtype) { + + if(rdtype == NDARRAY_UINT8) { + BINARY_LOOP_COMPLEX(results, resarray, uint8_t, larray, lstrides, rarray, rstrides, /); + } else if(rdtype == NDARRAY_INT8) { + BINARY_LOOP_COMPLEX(results, resarray, int8_t, larray, lstrides, rarray, rstrides, /); + } else if(rdtype == NDARRAY_UINT16) { + BINARY_LOOP_COMPLEX(results, resarray, uint16_t, larray, lstrides, rarray, rstrides, /); + } else if(rdtype == NDARRAY_INT16) { + BINARY_LOOP_COMPLEX(results, resarray, int16_t, larray, lstrides, rarray, rstrides, /); + } else if(rdtype == NDARRAY_FLOAT) { + BINARY_LOOP_COMPLEX(results, resarray, mp_float_t, larray, lstrides, rarray, rstrides, /); + } +} + +mp_obj_t carray_binary_divide(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_COMPLEX); + mp_float_t *resarray = (mp_float_t *)results->array; + + if((lhs->dtype == NDARRAY_COMPLEX) && (rhs->dtype == NDARRAY_COMPLEX)) { + mp_float_t *larray = (mp_float_t *)lhs->array; + mp_float_t *rarray = (mp_float_t *)rhs->array; + + ulab_rescale_float_strides(lstrides); + ulab_rescale_float_strides(rstrides); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + // (a + bi) / (c + di) = + // (ac + bd) / (c^2 + d^2) + i (bc - ad) / (c^2 + d^2) + // denominator + mp_float_t denom = rarray[0] * rarray[0] + rarray[1] * rarray[1]; + + // real part + *resarray++ = (larray[0] * rarray[0] + larray[1] * rarray[1]) / denom; + // imaginary part + *resarray++ = (larray[1] * rarray[0] - larray[0] * rarray[1]) / denom; + larray += lstrides[ULAB_MAX_DIMS - 1]; + rarray += rstrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < results->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + larray -= lstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + larray += lstrides[ULAB_MAX_DIMS - 2]; + rarray -= rstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < results->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + larray -= lstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + larray += lstrides[ULAB_MAX_DIMS - 3]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < results->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + larray -= lstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + larray += lstrides[ULAB_MAX_DIMS - 4]; + rarray -= rstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + rarray += rstrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < results->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + } else { + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + if(lhs->dtype == NDARRAY_COMPLEX) { + // real part + carray_binary_left_divide_(results, resarray, larray, rarray, lstrides, rstrides, rhs->dtype); + // imaginary part + resarray = (mp_float_t *)results->array; + resarray++; + larray = (uint8_t *)lhs->array; + larray += sizeof(mp_float_t); + rarray = (uint8_t *)rhs->array; + carray_binary_left_divide_(results, resarray, larray, rarray, lstrides, rstrides, rhs->dtype); + } else { + if(lhs->dtype == NDARRAY_UINT8) { + BINARY_LOOP_COMPLEX_RIGHT_DIVIDE(results, resarray, uint8_t, larray, lstrides, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_INT8) { + BINARY_LOOP_COMPLEX_RIGHT_DIVIDE(results, resarray, int8_t, larray, lstrides, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_UINT16) { + BINARY_LOOP_COMPLEX_RIGHT_DIVIDE(results, resarray, uint16_t, larray, lstrides, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_INT16) { + BINARY_LOOP_COMPLEX_RIGHT_DIVIDE(results, resarray, int16_t, larray, lstrides, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_FLOAT) { + BINARY_LOOP_COMPLEX_RIGHT_DIVIDE(results, resarray, mp_float_t, larray, lstrides, rarray, rstrides); + } + } + } + + return MP_OBJ_FROM_PTR(results); +} + +#endif diff --git a/python/port/mod/ulab/numpy/carray/carray.h b/python/port/mod/ulab/numpy/carray/carray.h new file mode 100644 index 00000000000..8ca5de2ddc1 --- /dev/null +++ b/python/port/mod/ulab/numpy/carray/carray.h @@ -0,0 +1,237 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2021-2022 Zoltán Vörös +*/ + +#ifndef _CARRAY_ +#define _CARRAY_ + +MP_DECLARE_CONST_FUN_OBJ_1(carray_real_obj); +MP_DECLARE_CONST_FUN_OBJ_1(carray_imag_obj); +MP_DECLARE_CONST_FUN_OBJ_1(carray_conjugate_obj); +MP_DECLARE_CONST_FUN_OBJ_1(carray_sort_complex_obj); + + +mp_obj_t carray_imag(mp_obj_t ); +mp_obj_t carray_real(mp_obj_t ); + +mp_obj_t carray_abs(ndarray_obj_t *, ndarray_obj_t *); +mp_obj_t carray_binary_add(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); +mp_obj_t carray_binary_multiply(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); +mp_obj_t carray_binary_subtract(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); +mp_obj_t carray_binary_divide(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); +mp_obj_t carray_binary_equal_not_equal(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *, mp_binary_op_t ); + +#define BINARY_LOOP_COMPLEX1(results, resarray, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t l = 0;\ + do {\ + *(resarray) = *((mp_float_t *)(larray)) OPERATOR *((type_right *)(rarray));\ + (resarray) += 2;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + +#define BINARY_LOOP_COMPLEX2(results, resarray, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t k = 0;\ + do {\ + BINARY_LOOP_COMPLEX1((results), (resarray), type_right, (larray), (lstrides), (rarray), (rstrides), OPERATOR);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + +#define BINARY_LOOP_COMPLEX3(results, resarray, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t j = 0;\ + do {\ + BINARY_LOOP_COMPLEX2((results), (resarray), type_right, (larray), (lstrides), (rarray), (rstrides), OPERATOR);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + +#define BINARY_LOOP_COMPLEX4(results, resarray, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t i = 0;\ + do {\ + BINARY_LOOP_COMPLEX3((results), (resarray), type_right, (larray), (lstrides), (rarray), (rstrides), OPERATOR);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ + +#define BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT1(results, resarray, type_left, larray, lstrides, rarray, rstrides)\ + size_t l = 0;\ + do {\ + *(resarray)++ = *((type_left *)(larray)) - (rarray)[0];\ + *(resarray)++ = -(rarray)[1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + +#define BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT2(results, resarray, type_left, larray, lstrides, rarray, rstrides)\ + size_t k = 0;\ + do {\ + BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT1((results), (resarray), type_left, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + +#define BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT3(results, resarray, type_left, larray, lstrides, rarray, rstrides)\ + size_t j = 0;\ + do {\ + BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT2((results), (resarray), type_left, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + +#define BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT4(results, resarray, type_left, larray, lstrides, rarray, rstrides)\ + size_t i = 0;\ + do {\ + BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT3((results), (resarray), type_left, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ + +#define BINARY_LOOP_COMPLEX_RIGHT_DIVIDE1(results, resarray, type_left, larray, lstrides, rarray, rstrides)\ + size_t l = 0;\ + do {\ + mp_float_t *c = (mp_float_t *)(rarray);\ + mp_float_t denom = c[0] * c[0] + c[1] * c[1];\ + mp_float_t a = *((type_left *)(larray)) / denom;\ + *(resarray)++ = a * c[0];\ + *(resarray)++ = -a * c[1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + +#define BINARY_LOOP_COMPLEX_RIGHT_DIVIDE2(results, resarray, type_left, larray, lstrides, rarray, rstrides)\ + size_t k = 0;\ + do {\ + BINARY_LOOP_COMPLEX_RIGHT_DIVIDE1((results), (resarray), type_left, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + +#define BINARY_LOOP_COMPLEX_RIGHT_DIVIDE3(results, resarray, type_left, larray, lstrides, rarray, rstrides)\ + size_t j = 0;\ + do {\ + BINARY_LOOP_COMPLEX_RIGHT_DIVIDE2((results), (resarray), type_left, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + +#define BINARY_LOOP_COMPLEX_RIGHT_DIVIDE4(results, resarray, type_left, larray, lstrides, rarray, rstrides)\ + size_t i = 0;\ + do {\ + BINARY_LOOP_COMPLEX_RIGHT_DIVIDE3((results), (resarray), type_left, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ + + +#define BINARY_LOOP_COMPLEX_EQUAL1(results, array, type_right, larray, lstrides, rarray, rstrides)\ + size_t l = 0;\ + do {\ + if((*(larray) == *((type_right *)(rarray))) && ((larray)[1] == MICROPY_FLOAT_CONST(0.0))) {\ + *(array) ^= 0x01;\ + }\ + (array)++;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + +#define BINARY_LOOP_COMPLEX_EQUAL2(results, array, type_right, larray, lstrides, rarray, rstrides)\ + size_t k = 0;\ + do {\ + BINARY_LOOP_COMPLEX_EQUAL1((results), (array), type_right, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + +#define BINARY_LOOP_COMPLEX_EQUAL3(results, array, type_right, larray, lstrides, rarray, rstrides)\ + size_t j = 0;\ + do {\ + BINARY_LOOP_COMPLEX_EQUAL2((results), (array), type_right, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + +#define BINARY_LOOP_COMPLEX_EQUAL4(results, array, type_right, larray, lstrides, rarray, rstrides)\ + size_t i = 0;\ + do {\ + BINARY_LOOP_COMPLEX_EQUAL3((results), (array), type_right, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ + +#if ULAB_MAX_DIMS == 1 +#define BINARY_LOOP_COMPLEX BINARY_LOOP_COMPLEX1 +#define BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT1 +#define BINARY_LOOP_COMPLEX_RIGHT_DIVIDE BINARY_LOOP_COMPLEX_RIGHT_DIVIDE1 +#define BINARY_LOOP_COMPLEX_EQUAL BINARY_LOOP_COMPLEX_EQUAL1 +#endif /* ULAB_MAX_DIMS == 1 */ + +#if ULAB_MAX_DIMS == 2 +#define BINARY_LOOP_COMPLEX BINARY_LOOP_COMPLEX2 +#define BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT2 +#define BINARY_LOOP_COMPLEX_RIGHT_DIVIDE BINARY_LOOP_COMPLEX_RIGHT_DIVIDE2 +#define BINARY_LOOP_COMPLEX_EQUAL BINARY_LOOP_COMPLEX_EQUAL2 +#endif /* ULAB_MAX_DIMS == 2 */ + +#if ULAB_MAX_DIMS == 3 +#define BINARY_LOOP_COMPLEX BINARY_LOOP_COMPLEX3 +#define BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT3 +#define BINARY_LOOP_COMPLEX_RIGHT_DIVIDE BINARY_LOOP_COMPLEX_RIGHT_DIVIDE3 +#define BINARY_LOOP_COMPLEX_EQUAL BINARY_LOOP_COMPLEX_EQUAL3 +#endif /* ULAB_MAX_DIMS == 3 */ + +#if ULAB_MAX_DIMS == 4 +#define BINARY_LOOP_COMPLEX BINARY_LOOP_COMPLEX4 +#define BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT4 +#define BINARY_LOOP_COMPLEX_RIGHT_DIVIDE BINARY_LOOP_COMPLEX_RIGHT_DIVIDE4 +#define BINARY_LOOP_COMPLEX_EQUAL BINARY_LOOP_COMPLEX_EQUAL4 +#endif /* ULAB_MAX_DIMS == 4 */ + +#endif diff --git a/python/port/mod/ulab/numpy/carray/carray_tools.c b/python/port/mod/ulab/numpy/carray/carray_tools.c new file mode 100644 index 00000000000..7b623d34979 --- /dev/null +++ b/python/port/mod/ulab/numpy/carray/carray_tools.c @@ -0,0 +1,28 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Zoltán Vörös +*/ + +#include +#include +#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" + +#include "../../ulab.h" +#include "../../ndarray.h" + +#if ULAB_SUPPORTS_COMPLEX + +void raise_complex_NotImplementedError(void) { + mp_raise_NotImplementedError(translate("not implemented for complex dtype")); +} + +#endif diff --git a/python/port/mod/ulab/numpy/carray/carray_tools.h b/python/port/mod/ulab/numpy/carray/carray_tools.h new file mode 100644 index 00000000000..3ac79b5f44c --- /dev/null +++ b/python/port/mod/ulab/numpy/carray/carray_tools.h @@ -0,0 +1,25 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Zoltán Vörös +*/ + +#ifndef _CARRAY_TOOLS_ +#define _CARRAY_TOOLS_ + +void raise_complex_NotImplementedError(void); + +#if ULAB_SUPPORTS_COMPLEX + #define NOT_IMPLEMENTED_FOR_COMPLEX() raise_complex_NotImplementedError(); + #define COMPLEX_DTYPE_NOT_IMPLEMENTED(dtype) if((dtype) == NDARRAY_COMPLEX) raise_complex_NotImplementedError(); +#else + #define NOT_IMPLEMENTED_FOR_COMPLEX() // do nothing + #define COMPLEX_DTYPE_NOT_IMPLEMENTED(dtype) // do nothing +#endif + +#endif diff --git a/python/port/mod/ulab/numpy/compare.c b/python/port/mod/ulab/numpy/compare.c index b9154569c91..5a820725c58 100644 --- a/python/port/mod/ulab/numpy/compare.c +++ b/python/port/mod/ulab/numpy/compare.c @@ -20,11 +20,17 @@ #include "../ulab.h" #include "../ndarray_operators.h" #include "../ulab_tools.h" +#include "carray/carray_tools.h" #include "compare.h" static mp_obj_t compare_function(mp_obj_t x1, mp_obj_t x2, uint8_t op) { ndarray_obj_t *lhs = ndarray_from_mp_obj(x1, 0); ndarray_obj_t *rhs = ndarray_from_mp_obj(x2, 0); + #if ULAB_SUPPORTS_COMPLEX + if((lhs->dtype == NDARRAY_COMPLEX) || (rhs->dtype == NDARRAY_COMPLEX)) { + NOT_IMPLEMENTED_FOR_COMPLEX() + } + #endif uint8_t ndim = 0; size_t *shape = m_new(size_t, ULAB_MAX_DIMS); int32_t *lstrides = m_new(int32_t, ULAB_MAX_DIMS); @@ -197,6 +203,7 @@ static mp_obj_t compare_isinf_isfinite(mp_obj_t _x, uint8_t mask) { } } else if(mp_obj_is_type(_x, &ulab_ndarray_type)) { ndarray_obj_t *x = MP_OBJ_TO_PTR(_x); + COMPLEX_DTYPE_NOT_IMPLEMENTED(x->dtype) ndarray_obj_t *results = ndarray_new_dense_ndarray(x->ndim, x->shape, NDARRAY_BOOL); // At this point, results is all False uint8_t *rarray = (uint8_t *)results->array; @@ -313,6 +320,10 @@ mp_obj_t compare_where(mp_obj_t _condition, mp_obj_t _x, mp_obj_t _y) { ndarray_obj_t *x = ndarray_from_mp_obj(_x, 0); ndarray_obj_t *y = ndarray_from_mp_obj(_y, 0); + COMPLEX_DTYPE_NOT_IMPLEMENTED(c->dtype) + COMPLEX_DTYPE_NOT_IMPLEMENTED(x->dtype) + COMPLEX_DTYPE_NOT_IMPLEMENTED(y->dtype) + int32_t *cstrides = m_new(int32_t, ULAB_MAX_DIMS); int32_t *xstrides = m_new(int32_t, ULAB_MAX_DIMS); int32_t *ystrides = m_new(int32_t, ULAB_MAX_DIMS); diff --git a/python/port/mod/ulab/numpy/create.c b/python/port/mod/ulab/numpy/create.c new file mode 100644 index 00000000000..883fc32619c --- /dev/null +++ b/python/port/mod/ulab/numpy/create.c @@ -0,0 +1,843 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2019-2021 Zoltán Vörös + * 2020 Taku Fukada +*/ + +#include +#include +#include +#include +#include "py/obj.h" +#include "py/runtime.h" + +#include "create.h" +#include "../ulab.h" +#include "../ulab_tools.h" + +#if ULAB_NUMPY_HAS_ONES | ULAB_NUMPY_HAS_ZEROS | ULAB_NUMPY_HAS_FULL | ULAB_NUMPY_HAS_EMPTY +static mp_obj_t create_zeros_ones_full(mp_obj_t oshape, uint8_t dtype, mp_obj_t value) { + if(!mp_obj_is_int(oshape) && !mp_obj_is_type(oshape, &mp_type_tuple) && !mp_obj_is_type(oshape, &mp_type_list)) { + mp_raise_TypeError(translate("input argument must be an integer, a tuple, or a list")); + } + ndarray_obj_t *ndarray = NULL; + if(mp_obj_is_int(oshape)) { + size_t n = mp_obj_get_int(oshape); + ndarray = ndarray_new_linear_array(n, dtype); + } else if(mp_obj_is_type(oshape, &mp_type_tuple) || mp_obj_is_type(oshape, &mp_type_list)) { + uint8_t len = (uint8_t)mp_obj_get_int(mp_obj_len_maybe(oshape)); + if(len > ULAB_MAX_DIMS) { + mp_raise_TypeError(translate("too many dimensions")); + } + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + + size_t i = 0; + mp_obj_iter_buf_t iter_buf; + mp_obj_t item, iterable = mp_getiter(oshape, &iter_buf); + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION){ + shape[ULAB_MAX_DIMS - len + i] = (size_t)mp_obj_get_int(item); + i++; + } + ndarray = ndarray_new_dense_ndarray(len, shape, dtype); + } + if(value != mp_const_none) { + if(dtype == NDARRAY_BOOL) { + dtype = NDARRAY_UINT8; + if(mp_obj_is_true(value)) { + value = mp_obj_new_int(1); + } else { + value = mp_obj_new_int(0); + } + } + for(size_t i=0; i < ndarray->len; i++) { + #if ULAB_SUPPORTS_COMPLEX + if(dtype == NDARRAY_COMPLEX) { + ndarray_set_complex_value(ndarray->array, i, value); + } else { + ndarray_set_value(dtype, ndarray->array, i, value); + } + #else + ndarray_set_value(dtype, ndarray->array, i, value); + #endif + } + } + // if zeros calls the function, we don't have to do anything + return MP_OBJ_FROM_PTR(ndarray); +} +#endif + +#if ULAB_NUMPY_HAS_ARANGE | ULAB_NUMPY_HAS_LINSPACE +static ndarray_obj_t *create_linspace_arange(mp_float_t start, mp_float_t step, mp_float_t stop, size_t len, uint8_t dtype) { + mp_float_t value = start; + + ndarray_obj_t *ndarray = ndarray_new_linear_array(len, dtype); + if(ndarray->boolean == NDARRAY_BOOLEAN) { + uint8_t *array = (uint8_t *)ndarray->array; + for(size_t i=0; i < len; i++, value += step) { + *array++ = value == MICROPY_FLOAT_CONST(0.0) ? 0 : 1; + } + } else if(dtype == NDARRAY_UINT8) { + ARANGE_LOOP(uint8_t, ndarray, len, step, stop); + } else if(dtype == NDARRAY_INT8) { + ARANGE_LOOP(int8_t, ndarray, len, step, stop); + } else if(dtype == NDARRAY_UINT16) { + ARANGE_LOOP(uint16_t, ndarray, len, step, stop); + } else if(dtype == NDARRAY_INT16) { + ARANGE_LOOP(int16_t, ndarray, len, step, stop); + } else { + ARANGE_LOOP(mp_float_t, ndarray, len, step, stop); + } + return ndarray; +} +#endif + +#if ULAB_NUMPY_HAS_ARANGE +//| @overload +//| def arange(stop: _float, step: _float = 1, *, dtype: _DType = ulab.numpy.float) -> ulab.numpy.ndarray: ... +//| @overload +//| def arange(start: _float, stop: _float, step: _float = 1, *, dtype: _DType = ulab.numpy.float) -> ulab.numpy.ndarray: +//| """ +//| .. param: start +//| First value in the array, optional, defaults to 0 +//| .. param: stop +//| Final value in the array +//| .. param: step +//| Difference between consecutive elements, optional, defaults to 1.0 +//| .. param: dtype +//| Type of values in the array +//| +//| Return a new 1-D array with elements ranging from ``start`` to ``stop``, with step size ``step``.""" +//| ... +//| + +mp_obj_t create_arange(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + uint8_t dtype = NDARRAY_FLOAT; + mp_float_t start, stop, step; + if(n_args == 1) { + start = MICROPY_FLOAT_CONST(0.0); + stop = mp_obj_get_float(args[0].u_obj); + step = MICROPY_FLOAT_CONST(1.0); + if(mp_obj_is_int(args[0].u_obj)) dtype = NDARRAY_INT16; + } else if(n_args == 2) { + start = mp_obj_get_float(args[0].u_obj); + stop = mp_obj_get_float(args[1].u_obj); + step = MICROPY_FLOAT_CONST(1.0); + if(mp_obj_is_int(args[0].u_obj) && mp_obj_is_int(args[1].u_obj)) dtype = NDARRAY_INT16; + } else if(n_args == 3) { + start = mp_obj_get_float(args[0].u_obj); + stop = mp_obj_get_float(args[1].u_obj); + step = mp_obj_get_float(args[2].u_obj); + if(mp_obj_is_int(args[0].u_obj) && mp_obj_is_int(args[1].u_obj) && mp_obj_is_int(args[2].u_obj)) dtype = NDARRAY_INT16; + } else { + mp_raise_TypeError(translate("wrong number of arguments")); + } + if((MICROPY_FLOAT_C_FUN(fabs)(stop) > 32768) || (MICROPY_FLOAT_C_FUN(fabs)(start) > 32768) || (MICROPY_FLOAT_C_FUN(fabs)(step) > 32768)) { + dtype = NDARRAY_FLOAT; + } + if(args[3].u_obj != mp_const_none) { + dtype = (uint8_t)mp_obj_get_int(args[3].u_obj); + } + ndarray_obj_t *ndarray; + if((stop - start)/step < 0) { + ndarray = ndarray_new_linear_array(0, dtype); + } else { + size_t len = (size_t)(MICROPY_FLOAT_C_FUN(ceil)((stop - start) / step)); + stop = start + (len - 1) * step; + ndarray = create_linspace_arange(start, step, stop, len, dtype); + } + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_arange_obj, 1, create_arange); +#endif + + +#if ULAB_NUMPY_HAS_ASARRAY +mp_obj_t create_asarray(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_obj = mp_const_none } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t _dtype; + #if ULAB_HAS_DTYPE_OBJECT + if(mp_obj_is_type(args[1].u_obj, &ulab_dtype_type)) { + dtype_obj_t *dtype = MP_OBJ_TO_PTR(args[1].u_obj); + _dtype = dtype->dtype; + } else { // this must be an integer defined as a class constant (ulab.numpy.uint8 etc.) + if(args[1].u_obj == mp_const_none) { + _dtype = 0; + } else { + _dtype = mp_obj_get_int(args[1].u_obj); + } + } + #else + if(args[1].u_obj == mp_const_none) { + _dtype = 0; + } else { + _dtype = mp_obj_get_int(args[1].u_obj); + } + #endif + + if(ulab_tools_mp_obj_is_scalar(args[0].u_obj)) { + return args[0].u_obj; + } else if(mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + if((_dtype == ndarray->dtype) || (_dtype == 0)) { + return args[0].u_obj; + } else { + return MP_OBJ_FROM_PTR(ndarray_copy_view_convert_type(ndarray, _dtype)); + } + } else if(ndarray_object_is_array_like(args[0].u_obj)) { + if(_dtype == 0) { + _dtype = NDARRAY_FLOAT; + } + return MP_OBJ_FROM_PTR(ndarray_from_iterable(args[0].u_obj, _dtype)); + } else { + mp_raise_TypeError(translate("wrong input type")); + } + return mp_const_none; // this should never happen +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_asarray_obj, 1, create_asarray); +#endif + +#if ULAB_NUMPY_HAS_CONCATENATE +//| def concatenate(arrays: Tuple[ulab.numpy.ndarray], *, axis: int = 0) -> ulab.numpy.ndarray: +//| """ +//| .. param: arrays +//| tuple of ndarrays +//| .. param: axis +//| axis along which the arrays will be joined +//| +//| Join a sequence of arrays along an existing axis.""" +//| ... +//| + +mp_obj_t create_concatenate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 0 } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_type(args[0].u_obj, &mp_type_tuple)) { + mp_raise_TypeError(translate("first argument must be a tuple of ndarrays")); + } + int8_t axis = (int8_t)args[1].u_int; + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + mp_obj_tuple_t *ndarrays = MP_OBJ_TO_PTR(args[0].u_obj); + + // first check, whether the arrays are compatible + ndarray_obj_t *_ndarray = MP_OBJ_TO_PTR(ndarrays->items[0]); + uint8_t dtype = _ndarray->dtype; + uint8_t ndim = _ndarray->ndim; + if(axis < 0) { + axis += ndim; + } + if((axis < 0) || (axis >= ndim)) { + mp_raise_ValueError(translate("wrong axis specified")); + } + // shift axis + axis = ULAB_MAX_DIMS - ndim + axis; + for(uint8_t j=0; j < ULAB_MAX_DIMS; j++) { + shape[j] = _ndarray->shape[j]; + } + + for(uint8_t i=1; i < ndarrays->len; i++) { + _ndarray = MP_OBJ_TO_PTR(ndarrays->items[i]); + // check, whether the arrays are compatible + if((dtype != _ndarray->dtype) || (ndim != _ndarray->ndim)) { + mp_raise_ValueError(translate("input arrays are not compatible")); + } + for(uint8_t j=0; j < ULAB_MAX_DIMS; j++) { + if(j == axis) { + shape[j] += _ndarray->shape[j]; + } else { + if(shape[j] != _ndarray->shape[j]) { + mp_raise_ValueError(translate("input arrays are not compatible")); + } + } + } + } + + ndarray_obj_t *target = ndarray_new_dense_ndarray(ndim, shape, dtype); + uint8_t *tpos = (uint8_t *)target->array; + uint8_t *tarray; + + for(uint8_t p=0; p < ndarrays->len; p++) { + // reset the pointer along the axis + ndarray_obj_t *source = MP_OBJ_TO_PTR(ndarrays->items[p]); + uint8_t *sarray = (uint8_t *)source->array; + tarray = tpos; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + memcpy(tarray, sarray, source->itemsize); + tarray += target->strides[ULAB_MAX_DIMS - 1]; + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + tarray -= target->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + tarray += target->strides[ULAB_MAX_DIMS - 2]; + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + tarray -= target->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + tarray += target->strides[ULAB_MAX_DIMS - 3]; + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + tarray -= target->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + tarray += target->strides[ULAB_MAX_DIMS - 4]; + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif + if(p < ndarrays->len - 1) { + tpos += target->strides[axis] * source->shape[axis]; + } + } + return MP_OBJ_FROM_PTR(target); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_concatenate_obj, 1, create_concatenate); +#endif + +#if ULAB_MAX_DIMS > 1 +#if ULAB_NUMPY_HAS_DIAG +//| def diag(a: ulab.numpy.ndarray, *, k: int = 0) -> ulab.numpy.ndarray: +//| """ +//| .. param: a +//| an ndarray +//| .. param: k +//| Offset of the diagonal from the main diagonal. Can be positive or negative. +//| +//| Return specified diagonals.""" +//| ... +//| + +mp_obj_t create_diag(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_k, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 0 } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + ndarray_obj_t *source = ndarray_from_iterable(args[0].u_obj, NDARRAY_FLOAT); + ndarray_obj_t *target = NULL; + + int32_t k = args[1].u_int; + size_t k_abs = k >= 0 ? (size_t)k : (size_t)(-k); + if(source->ndim == 2) { // return the diagonal + size_t len; + if(k >= 0) { + len = (k_abs <= source->shape[ULAB_MAX_DIMS - 1]) ? source->shape[ULAB_MAX_DIMS - 1] - k_abs : 0; + } else { + len = (k_abs <= source->shape[ULAB_MAX_DIMS - 2]) ? source->shape[ULAB_MAX_DIMS - 2] - k_abs : 0; + } + target = ndarray_new_linear_array(len, source->dtype); + + if(len == 0) { + return MP_OBJ_FROM_PTR(target); + } + + uint8_t *sarray = (uint8_t *)source->array; + uint8_t *tarray = (uint8_t *)target->array; + if(k >= 0) { + sarray += source->strides[ULAB_MAX_DIMS - 1] * k; + } else { + sarray += source->strides[ULAB_MAX_DIMS - 2] * k_abs; + } + for(size_t i=0; i < len; i++) { + memcpy(tarray, sarray, source->itemsize); + sarray += (source->strides[ULAB_MAX_DIMS - 1] + source->strides[ULAB_MAX_DIMS - 2]); + tarray += target->itemsize; + } + } else if(source->ndim == 1) { // return a rank-2 tensor with the prescribed diagonal + size_t len = source->len + k_abs; + target = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, len, len), source->dtype); + uint8_t *sarray = (uint8_t *)source->array; + uint8_t *tarray = (uint8_t *)target->array; + + if(k < 0) { + tarray += len * k_abs * target->itemsize; + } else { + tarray += k_abs * target->itemsize; + } + for(size_t i = 0; i < source->len; i++) { + memcpy(tarray, sarray, source->itemsize); + sarray += source->strides[ULAB_MAX_DIMS - 1]; + tarray += (len + 1) * target->itemsize; + } + } + #if ULAB_MAX_DIMS > 2 + else { + mp_raise_ValueError(translate("input must be 1- or 2-d")); + } + #endif + + return MP_OBJ_FROM_PTR(target); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_diag_obj, 1, create_diag); +#endif /* ULAB_NUMPY_HAS_DIAG */ + +#if ULAB_NUMPY_HAS_EMPTY +// This function is bound in numpy.c to numpy.zeros(), and is simply an alias for that + +//| def empty(shape: Union[int, Tuple[int, ...]], *, dtype: _DType = ulab.numpy.float) -> ulab.numpy.ndarray: +//| """ +//| .. param: shape +//| Shape of the array, either an integer (for a 1-D array) or a tuple of 2 integers (for a 2-D array) +//| .. param: dtype +//| Type of values in the array +//| +//| Return a new array of the given shape with all elements set to 0. An alias for numpy.zeros.""" +//| ... +//| +#endif + +#if ULAB_NUMPY_HAS_EYE +//| def eye(size: int, *, M: Optional[int] = None, k: int = 0, dtype: _DType = ulab.numpy.float) -> ulab.numpy.ndarray: +//| """Return a new square array of size, with the diagonal elements set to 1 +//| and the other elements set to 0. If k is given, the diagonal is shifted by the specified amount.""" +//| ... +//| + +mp_obj_t create_eye(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_INT, { .u_int = 0 } }, + { MP_QSTR_M, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_k, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 0 } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + size_t n = args[0].u_int, m; + size_t k = args[2].u_int > 0 ? (size_t)args[2].u_int : (size_t)(-args[2].u_int); + uint8_t dtype = args[3].u_int; + if(args[1].u_rom_obj == mp_const_none) { + m = n; + } else { + m = mp_obj_get_int(args[1].u_rom_obj); + } + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, n, m), dtype); + if(dtype == NDARRAY_BOOL) { + dtype = NDARRAY_UINT8; + } + mp_obj_t one = mp_obj_new_int(1); + size_t i = 0; + if((args[2].u_int >= 0)) { + while(k < m) { + ndarray_set_value(dtype, ndarray->array, i*m+k, one); + k++; + i++; + } + } else { + while(k < n) { + ndarray_set_value(dtype, ndarray->array, k*m+i, one); + k++; + i++; + } + } + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_eye_obj, 1, create_eye); +#endif /* ULAB_NUMPY_HAS_EYE */ +#endif /* ULAB_MAX_DIMS > 1 */ + +#if ULAB_NUMPY_HAS_FULL +//| def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[_float, _bool], *, dtype: _DType = ulab.numpy.float) -> ulab.numpy.ndarray: +//| """ +//| .. param: shape +//| Shape of the array, either an integer (for a 1-D array) or a tuple of integers (for tensors of higher rank) +//| .. param: fill_value +//| scalar, the value with which the array is filled +//| .. param: dtype +//| Type of values in the array +//| +//| Return a new array of the given shape with all elements set to 0.""" +//| ... +//| + +mp_obj_t create_full(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t dtype = args[2].u_int; + + return create_zeros_ones_full(args[0].u_obj, dtype, args[1].u_obj); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_full_obj, 0, create_full); +#endif + + +#if ULAB_NUMPY_HAS_LINSPACE +//| def linspace( +//| start: _float, +//| stop: _float, +//| *, +//| dtype: _DType = ulab.numpy.float, +//| num: int = 50, +//| endpoint: _bool = True, +//| retstep: _bool = False +//| ) -> ulab.numpy.ndarray: +//| """ +//| .. param: start +//| First value in the array +//| .. param: stop +//| Final value in the array +//| .. param int: num +//| Count of values in the array. +//| .. param: dtype +//| Type of values in the array +//| .. param bool: endpoint +//| Whether the ``stop`` value is included. Note that even when +//| endpoint=True, the exact ``stop`` value may not be included due to the +//| inaccuracy of floating point arithmetic. +//| .. param bool: retstep, +//| If True, return (`samples`, `step`), where `step` is the spacing between samples. +//| +//| Return a new 1-D array with ``num`` elements ranging from ``start`` to ``stop`` linearly.""" +//| ... +//| + +mp_obj_t create_linspace(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_num, MP_ARG_INT, { .u_int = 50 } }, + { MP_QSTR_endpoint, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_true } }, + { MP_QSTR_retstep, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_false } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(args[2].u_int < 2) { + mp_raise_ValueError(translate("number of points must be at least 2")); + } + size_t len = (size_t)args[2].u_int; + mp_float_t start, step, stop; + + ndarray_obj_t *ndarray = NULL; + + #if ULAB_SUPPORTS_COMPLEX + mp_float_t step_real, step_imag; + bool complex_out = false; + + if(mp_obj_is_type(args[0].u_obj, &mp_type_complex) || mp_obj_is_type(args[1].u_obj, &mp_type_complex)) { + complex_out = true; + ndarray = ndarray_new_linear_array(len, NDARRAY_COMPLEX); + mp_float_t *array = (mp_float_t *)ndarray->array; + mp_float_t start_real, start_imag; + mp_float_t stop_real, stop_imag; + + mp_obj_get_complex(args[0].u_obj, &start_real, &start_imag); + mp_obj_get_complex(args[1].u_obj, &stop_real, &stop_imag); + if(args[3].u_obj == mp_const_true) { + step_real = (stop_real - start_real) / (len - 1); + step_imag = (stop_imag - start_imag) / (len - 1); + } else { + step_real = (stop_real - start_real) / len; + step_imag = (stop_imag - start_imag) / len; + } + + for(size_t i = 0; i < len; i++) { + *array++ = start_real; + *array++ = start_imag; + start_real += step_real; + start_imag += step_imag; + } + } else { + #endif + start = mp_obj_get_float(args[0].u_obj); + stop = mp_obj_get_float(args[1].u_obj); + + uint8_t typecode = args[5].u_int; + + if(args[3].u_obj == mp_const_true) { + step = (stop - start) / (len - 1); + } else { + step = (stop - start) / len; + stop = start + step * (len - 1); + } + + ndarray = create_linspace_arange(start, step, stop, len, typecode); + #if ULAB_SUPPORTS_COMPLEX + } + #endif + + if(args[4].u_obj == mp_const_false) { + return MP_OBJ_FROM_PTR(ndarray); + } else { + mp_obj_t tuple[2]; + tuple[0] = ndarray; + #if ULAB_SUPPORTS_COMPLEX + if(complex_out) { + tuple[1] = mp_obj_new_complex(step_real, step_imag); + } else { + tuple[1] = mp_obj_new_float(step); + } + #else /* ULAB_SUPPORTS_COMPLEX */ + tuple[1] = mp_obj_new_float(step); + #endif + + return mp_obj_new_tuple(2, tuple); + } +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_linspace_obj, 2, create_linspace); +#endif + +#if ULAB_NUMPY_HAS_LOGSPACE +//| def logspace( +//| start: _float, +//| stop: _float, +//| *, +//| dtype: _DType = ulab.numpy.float, +//| num: int = 50, +//| endpoint: _bool = True, +//| base: _float = 10.0 +//| ) -> ulab.numpy.ndarray: +//| """ +//| .. param: start +//| First value in the array +//| .. param: stop +//| Final value in the array +//| .. param int: num +//| Count of values in the array. Defaults to 50. +//| .. param: base +//| The base of the log space. The step size between the elements in +//| ``ln(samples) / ln(base)`` (or ``log_base(samples)``) is uniform. Defaults to 10.0. +//| .. param: dtype +//| Type of values in the array +//| .. param bool: endpoint +//| Whether the ``stop`` value is included. Note that even when +//| endpoint=True, the exact ``stop`` value may not be included due to the +//| inaccuracy of floating point arithmetic. Defaults to True. +//| +//| Return a new 1-D array with ``num`` evenly spaced elements on a log scale. +//| The sequence starts at ``base ** start``, and ends with ``base ** stop``.""" +//| ... +//| + +const mp_obj_float_t create_float_const_ten = {{&mp_type_float}, MICROPY_FLOAT_CONST(10.0)}; + +mp_obj_t create_logspace(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_num, MP_ARG_INT, { .u_int = 50 } }, + { MP_QSTR_base, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_PTR(&create_float_const_ten) } }, + { MP_QSTR_endpoint, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_true } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(args[2].u_int < 2) { + mp_raise_ValueError(translate("number of points must be at least 2")); + } + size_t len = (size_t)args[2].u_int; + mp_float_t start, step, quotient; + start = mp_obj_get_float(args[0].u_obj); + uint8_t dtype = args[5].u_int; + mp_float_t base = mp_obj_get_float(args[3].u_obj); + if(args[4].u_obj == mp_const_true) step = (mp_obj_get_float(args[1].u_obj) - start)/(len - 1); + else step = (mp_obj_get_float(args[1].u_obj) - start) / len; + quotient = MICROPY_FLOAT_C_FUN(pow)(base, step); + ndarray_obj_t *ndarray = ndarray_new_linear_array(len, dtype); + + mp_float_t value = MICROPY_FLOAT_C_FUN(pow)(base, start); + if(ndarray->dtype == NDARRAY_UINT8) { + uint8_t *array = (uint8_t *)ndarray->array; + if(ndarray->boolean) { + memset(array, 1, len); + } else { + for(size_t i=0; i < len; i++, value *= quotient) *array++ = (uint8_t)value; + } + } else if(ndarray->dtype == NDARRAY_INT8) { + int8_t *array = (int8_t *)ndarray->array; + for(size_t i=0; i < len; i++, value *= quotient) *array++ = (int8_t)value; + } else if(ndarray->dtype == NDARRAY_UINT16) { + uint16_t *array = (uint16_t *)ndarray->array; + for(size_t i=0; i < len; i++, value *= quotient) *array++ = (uint16_t)value; + } else if(ndarray->dtype == NDARRAY_INT16) { + int16_t *array = (int16_t *)ndarray->array; + for(size_t i=0; i < len; i++, value *= quotient) *array++ = (int16_t)value; + } else { + mp_float_t *array = (mp_float_t *)ndarray->array; + for(size_t i=0; i < len; i++, value *= quotient) *array++ = value; + } + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_logspace_obj, 2, create_logspace); +#endif + +#if ULAB_NUMPY_HAS_ONES +//| def ones(shape: Union[int, Tuple[int, ...]], *, dtype: _DType = ulab.numpy.float) -> ulab.numpy.ndarray: +//| """ +//| .. param: shape +//| Shape of the array, either an integer (for a 1-D array) or a tuple of 2 integers (for a 2-D array) +//| .. param: dtype +//| Type of values in the array +//| +//| Return a new array of the given shape with all elements set to 1.""" +//| ... +//| + +mp_obj_t create_ones(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t dtype = args[1].u_int; + mp_obj_t one = mp_obj_new_int(1); + return create_zeros_ones_full(args[0].u_obj, dtype, one); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_ones_obj, 0, create_ones); +#endif + +#if ULAB_NUMPY_HAS_ZEROS +//| def zeros(shape: Union[int, Tuple[int, ...]], *, dtype: _DType = ulab.numpy.float) -> ulab.numpy.ndarray: +//| """ +//| .. param: shape +//| Shape of the array, either an integer (for a 1-D array) or a tuple of 2 integers (for a 2-D array) +//| .. param: dtype +//| Type of values in the array +//| +//| Return a new array of the given shape with all elements set to 0.""" +//| ... +//| + +mp_obj_t create_zeros(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t dtype = args[1].u_int; + return create_zeros_ones_full(args[0].u_obj, dtype, mp_const_none); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_zeros_obj, 0, create_zeros); +#endif + +#if ULAB_NUMPY_HAS_FROMBUFFER +mp_obj_t create_frombuffer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_INT(NDARRAY_FLOAT) } }, + { MP_QSTR_count, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_INT(-1) } }, + { MP_QSTR_offset, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_INT(0) } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t dtype = mp_obj_get_int(args[1].u_obj); + size_t offset = mp_obj_get_int(args[3].u_obj); + + mp_buffer_info_t bufinfo; + if(mp_get_buffer(args[0].u_obj, &bufinfo, MP_BUFFER_READ)) { + size_t sz = ulab_binary_get_size(dtype); + + if(bufinfo.len < offset) { + mp_raise_ValueError(translate("offset must be non-negative and no greater than buffer length")); + } + size_t len = (bufinfo.len - offset) / sz; + if((len * sz) != (bufinfo.len - offset)) { + mp_raise_ValueError(translate("buffer size must be a multiple of element size")); + } + if(mp_obj_get_int(args[2].u_obj) > 0) { + size_t count = mp_obj_get_int(args[2].u_obj); + if(len < count) { + mp_raise_ValueError(translate("buffer is smaller than requested size")); + } else { + len = count; + } + } + ndarray_obj_t *ndarray = m_new_obj(ndarray_obj_t); + ndarray->base.type = &ulab_ndarray_type; + ndarray->dtype = dtype == NDARRAY_BOOL ? NDARRAY_UINT8 : dtype; + ndarray->boolean = dtype == NDARRAY_BOOL ? NDARRAY_BOOLEAN : NDARRAY_NUMERIC; + ndarray->ndim = 1; + ndarray->len = len; + ndarray->itemsize = sz; + ndarray->shape[ULAB_MAX_DIMS - 1] = len; + ndarray->strides[ULAB_MAX_DIMS - 1] = sz; + + uint8_t *buffer = bufinfo.buf; + ndarray->array = buffer + offset; + return MP_OBJ_FROM_PTR(ndarray); + } + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_frombuffer_obj, 1, create_frombuffer); +#endif diff --git a/python/port/mod/ulab/numpy/create.h b/python/port/mod/ulab/numpy/create.h new file mode 100644 index 00000000000..6e54b10e7e2 --- /dev/null +++ b/python/port/mod/ulab/numpy/create.h @@ -0,0 +1,84 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2019-2021 Zoltán Vörös +*/ + +#ifndef _CREATE_ +#define _CREATE_ + +#include "../ulab.h" +#include "../ndarray.h" + +#if ULAB_NUMPY_HAS_ARANGE +mp_obj_t create_arange(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_arange_obj); +#endif + +#if ULAB_NUMPY_HAS_ASARRAY +mp_obj_t create_arange(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_asarray_obj); +#endif + +#if ULAB_NUMPY_HAS_CONCATENATE +mp_obj_t create_concatenate(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_concatenate_obj); +#endif + +#if ULAB_NUMPY_HAS_DIAG +mp_obj_t create_diag(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_diag_obj); +#endif + +#if ULAB_MAX_DIMS > 1 +#if ULAB_NUMPY_HAS_EYE +mp_obj_t create_eye(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_eye_obj); +#endif +#endif + +#if ULAB_NUMPY_HAS_FULL +mp_obj_t create_full(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_full_obj); +#endif + +#if ULAB_NUMPY_HAS_LINSPACE +mp_obj_t create_linspace(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_linspace_obj); +#endif + +#if ULAB_NUMPY_HAS_LOGSPACE +mp_obj_t create_logspace(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_logspace_obj); +#endif + +#if ULAB_NUMPY_HAS_ONES +mp_obj_t create_ones(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_ones_obj); +#endif + +#if ULAB_NUMPY_HAS_ZEROS +mp_obj_t create_zeros(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_zeros_obj); +#endif + +#if ULAB_NUMPY_HAS_FROMBUFFER +mp_obj_t create_frombuffer(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_frombuffer_obj); +#endif + +#define ARANGE_LOOP(type_, ndarray, len, step, stop) \ +({\ + type_ *array = (type_ *)(ndarray)->array;\ + for (size_t i = 0; i < (len) - 1; i++, (value) += (step)) {\ + *array++ = (type_)(value);\ + }\ + *array = (type_)(stop);\ +}) + +#endif diff --git a/python/port/mod/ulab/numpy/fft/fft.c b/python/port/mod/ulab/numpy/fft/fft.c index 5c6af832d2d..31a8712eb79 100644 --- a/python/port/mod/ulab/numpy/fft/fft.c +++ b/python/port/mod/ulab/numpy/fft/fft.c @@ -20,11 +20,13 @@ #include "py/obj.h" #include "py/objarray.h" +#include "../carray/carray_tools.h" #include "fft.h" //| """Frequency-domain functions""" //| //| import ulab.numpy +//| import ulab.utils //| def fft(r: ulab.numpy.ndarray, c: Optional[ulab.numpy.ndarray] = None) -> Tuple[ulab.numpy.ndarray, ulab.numpy.ndarray]: @@ -35,10 +37,17 @@ //| //| Perform a Fast Fourier Transform from the time domain into the frequency domain //| -//| See also ~ulab.extras.spectrum, which computes the magnitude of the fft, +//| See also `ulab.utils.spectrogram`, which computes the magnitude of the fft, //| rather than separately returning its real and imaginary parts.""" //| ... //| +#if ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE +static mp_obj_t fft_fft(mp_obj_t arg) { + return fft_fft_ifft_spectrogram(arg, FFT_FFT); +} + +MP_DEFINE_CONST_FUN_OBJ_1(fft_fft_obj, fft_fft); +#else static mp_obj_t fft_fft(size_t n_args, const mp_obj_t *args) { if(n_args == 2) { return fft_fft_ifft_spectrogram(n_args, args[0], args[1], FFT_FFT); @@ -48,6 +57,7 @@ static mp_obj_t fft_fft(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_fft_obj, 1, 2, fft_fft); +#endif //| def ifft(r: ulab.numpy.ndarray, c: Optional[ulab.numpy.ndarray] = None) -> Tuple[ulab.numpy.ndarray, ulab.numpy.ndarray]: //| """ @@ -55,11 +65,19 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_fft_obj, 1, 2, fft_fft); //| :param ulab.numpy.ndarray c: An optional 1-dimension array of values whose size is a power of 2, giving the complex part of the value //| :return tuple (r, c): The real and complex parts of the inverse FFT //| -//| Perform an Inverse Fast Fourier Transform from the frequency domain into the time domain""" +//| Perform an Inverse Fast Fourier Transform from the frequeny domain into the time domain""" //| ... //| +#if ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE +static mp_obj_t fft_ifft(mp_obj_t arg) { + return fft_fft_ifft_spectrogram(arg, FFT_IFFT); +} + +MP_DEFINE_CONST_FUN_OBJ_1(fft_ifft_obj, fft_ifft); +#else static mp_obj_t fft_ifft(size_t n_args, const mp_obj_t *args) { + NOT_IMPLEMENTED_FOR_COMPLEX() if(n_args == 2) { return fft_fft_ifft_spectrogram(n_args, args[0], args[1], FFT_IFFT); } else { @@ -68,6 +86,7 @@ static mp_obj_t fft_ifft(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_ifft_obj, 1, 2, fft_ifft); +#endif STATIC const mp_rom_map_elem_t ulab_fft_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_fft) }, diff --git a/python/port/mod/ulab/numpy/fft/fft.h b/python/port/mod/ulab/numpy/fft/fft.h index 66acafe1151..7a16698416d 100644 --- a/python/port/mod/ulab/numpy/fft/fft.h +++ b/python/port/mod/ulab/numpy/fft/fft.h @@ -19,6 +19,12 @@ extern mp_obj_module_t ulab_fft_module; +#if ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE +MP_DECLARE_CONST_FUN_OBJ_3(fft_fft_obj); +MP_DECLARE_CONST_FUN_OBJ_3(fft_ifft_obj); +#else MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(fft_fft_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(fft_ifft_obj); #endif + +#endif diff --git a/python/port/mod/ulab/numpy/fft/fft_tools.c b/python/port/mod/ulab/numpy/fft/fft_tools.c index 6dd2ca47cad..8a55927e31a 100644 --- a/python/port/mod/ulab/numpy/fft/fft_tools.c +++ b/python/port/mod/ulab/numpy/fft/fft_tools.c @@ -9,10 +9,12 @@ */ #include +#include #include "py/runtime.h" #include "../../ndarray.h" #include "../../ulab_tools.h" +#include "../carray/carray_tools.h" #include "fft_tools.h" #ifndef MP_PI @@ -22,7 +24,8 @@ #define MP_E MICROPY_FLOAT_CONST(2.71828182845904523536) #endif -/* +/* Kernel implementation for the case, when ulab has no complex support + * The following function takes two arrays, namely, the real and imaginary * parts of a complex array, and calculates the Fourier transform in place. * @@ -31,6 +34,128 @@ * and can be used independent of ulab. */ +#if ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE +/* Kernel implementation for the complex case. Data are contained in data as + + data[0], data[1], data[2], data[3], .... , data[2n - 2], data[2n-1] + real[0], imag[0], real[1], imag[1], .... , real[n-1], imag[n-1] + + In general + real[i] = data[2i] + imag[i] = data[2i+1] + +*/ +void fft_kernel_complex(mp_float_t *data, size_t n, int isign) { + size_t j, m, mmax, istep; + mp_float_t tempr, tempi; + mp_float_t wtemp, wr, wpr, wpi, wi, theta; + + j = 0; + for(size_t i = 0; i < n; i++) { + if (j > i) { + SWAP(mp_float_t, data[2*i], data[2*j]); + SWAP(mp_float_t, data[2*i+1], data[2*j+1]); + } + m = n >> 1; + while (j >= m && m > 0) { + j -= m; + m >>= 1; + } + j += m; + } + + mmax = 1; + while (n > mmax) { + istep = mmax << 1; + theta = MICROPY_FLOAT_CONST(-2.0)*isign*MP_PI/istep; + wtemp = MICROPY_FLOAT_C_FUN(sin)(MICROPY_FLOAT_CONST(0.5) * theta); + wpr = MICROPY_FLOAT_CONST(-2.0) * wtemp * wtemp; + wpi = MICROPY_FLOAT_C_FUN(sin)(theta); + wr = MICROPY_FLOAT_CONST(1.0); + wi = MICROPY_FLOAT_CONST(0.0); + for(m = 0; m < mmax; m++) { + for(size_t i = m; i < n; i += istep) { + j = i + mmax; + tempr = wr * data[2*j] - wi * data[2*j+1]; + tempi = wr * data[2*j+1] + wi * data[2*j]; + data[2*j] = data[2*i] - tempr; + data[2*j+1] = data[2*i+1] - tempi; + data[2*i] += tempr; + data[2*i+1] += tempi; + } + wtemp = wr; + wr = wr*wpr - wi*wpi + wr; + wi = wi*wpr + wtemp*wpi + wi; + } + mmax = istep; + } +} + +/* + * The following function is a helper interface to the python side. + * It has been factored out from fft.c, so that the same argument parsing + * routine can be called from scipy.signal.spectrogram. + */ +mp_obj_t fft_fft_ifft_spectrogram(mp_obj_t data_in, uint8_t type) { + if(!mp_obj_is_type(data_in, &ulab_ndarray_type)) { + mp_raise_NotImplementedError(translate("FFT is defined for ndarrays only")); + } + ndarray_obj_t *in = MP_OBJ_TO_PTR(data_in); + #if ULAB_MAX_DIMS > 1 + if(in->ndim != 1) { + mp_raise_TypeError(translate("FFT is implemented for linear arrays only")); + } + #endif + size_t len = in->len; + // Check if input is of length of power of 2 + if((len & (len-1)) != 0) { + mp_raise_ValueError(translate("input array length must be power of 2")); + } + + ndarray_obj_t *out = ndarray_new_linear_array(len, NDARRAY_COMPLEX); + mp_float_t *data = (mp_float_t *)out->array; + uint8_t *array = (uint8_t *)in->array; + + if(in->dtype == NDARRAY_COMPLEX) { + uint8_t sz = 2 * sizeof(mp_float_t); + uint8_t *data_ = (uint8_t *)out->array; + for(size_t i = 0; i < len; i++) { + memcpy(data_, array, sz); + array += in->strides[ULAB_MAX_DIMS - 1]; + } + } else { + mp_float_t (*func)(void *) = ndarray_get_float_function(in->dtype); + for(size_t i = 0; i < len; i++) { + // real part; the imaginary part is 0, no need to assign + *data = func(array); + data += 2; + array += in->strides[ULAB_MAX_DIMS - 1]; + } + } + data -= 2 * len; + + if((type == FFT_FFT) || (type == FFT_SPECTROGRAM)) { + fft_kernel_complex(data, len, 1); + if(type == FFT_SPECTROGRAM) { + ndarray_obj_t *spectrum = ndarray_new_linear_array(len, NDARRAY_FLOAT); + mp_float_t *sarray = (mp_float_t *)spectrum->array; + for(size_t i = 0; i < len; i++) { + *sarray++ = MICROPY_FLOAT_C_FUN(sqrt)(data[0] * data[0] + data[1] * data[1]); + data += 2; + } + m_del(mp_float_t, data, 2 * len); + return MP_OBJ_FROM_PTR(spectrum); + } + } else { // inverse transform + fft_kernel_complex(data, len, -1); + // TODO: numpy accepts the norm keyword argument + for(size_t i = 0; i < len; i++) { + *data++ /= len; + } + } + return MP_OBJ_FROM_PTR(out); +} +#else /* ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE */ void fft_kernel(mp_float_t *real, mp_float_t *imag, size_t n, int isign) { size_t j, m, mmax, istep; mp_float_t tempr, tempi; @@ -77,12 +202,6 @@ void fft_kernel(mp_float_t *real, mp_float_t *imag, size_t n, int isign) { } } -/* - * The following function is a helper interface to the python side. - * It has been factored out from fft.c, so that the same argument parsing - * routine can be called from scipy.signal.spectrogram. - */ - mp_obj_t fft_fft_ifft_spectrogram(size_t n_args, mp_obj_t arg_re, mp_obj_t arg_im, uint8_t type) { if(!mp_obj_is_type(arg_re, &ulab_ndarray_type)) { mp_raise_NotImplementedError(translate("FFT is defined for ndarrays only")); @@ -95,6 +214,7 @@ mp_obj_t fft_fft_ifft_spectrogram(size_t n_args, mp_obj_t arg_re, mp_obj_t arg_i ndarray_obj_t *re = MP_OBJ_TO_PTR(arg_re); #if ULAB_MAX_DIMS > 1 if(re->ndim != 1) { + COMPLEX_DTYPE_NOT_IMPLEMENTED(re->dtype) mp_raise_TypeError(translate("FFT is implemented for linear arrays only")); } #endif @@ -122,6 +242,7 @@ mp_obj_t fft_fft_ifft_spectrogram(size_t n_args, mp_obj_t arg_re, mp_obj_t arg_i ndarray_obj_t *im = MP_OBJ_TO_PTR(arg_im); #if ULAB_MAX_DIMS > 1 if(im->ndim != 1) { + COMPLEX_DTYPE_NOT_IMPLEMENTED(im->dtype) mp_raise_TypeError(translate("FFT is implemented for linear arrays only")); } #endif @@ -163,3 +284,4 @@ mp_obj_t fft_fft_ifft_spectrogram(size_t n_args, mp_obj_t arg_re, mp_obj_t arg_i return mp_obj_new_tuple(2, tuple); } } +#endif /* ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE */ diff --git a/python/port/mod/ulab/numpy/fft/fft_tools.h b/python/port/mod/ulab/numpy/fft/fft_tools.h index d3b856d0721..9444232f64d 100644 --- a/python/port/mod/ulab/numpy/fft/fft_tools.h +++ b/python/port/mod/ulab/numpy/fft/fft_tools.h @@ -17,7 +17,12 @@ enum FFT_TYPE { FFT_SPECTROGRAM, }; +#if ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE +void fft_kernel(mp_float_t *, size_t , int ); +mp_obj_t fft_fft_ifft_spectrogram(mp_obj_t , uint8_t ); +#else void fft_kernel(mp_float_t *, mp_float_t *, size_t , int ); mp_obj_t fft_fft_ifft_spectrogram(size_t , mp_obj_t , mp_obj_t , uint8_t ); +#endif /* ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE */ #endif /* _FFT_TOOLS_ */ diff --git a/python/port/mod/ulab/numpy/filter.c b/python/port/mod/ulab/numpy/filter.c index bf2d16cd40c..057cd6dc467 100644 --- a/python/port/mod/ulab/numpy/filter.c +++ b/python/port/mod/ulab/numpy/filter.c @@ -21,6 +21,7 @@ #include "../ulab.h" #include "../scipy/signal/signal.h" +#include "carray/carray_tools.h" #include "filter.h" #if ULAB_NUMPY_HAS_CONVOLVE @@ -53,30 +54,77 @@ mp_obj_t filter_convolve(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_a } int len = len_a + len_c - 1; // convolve mode "full" - ndarray_obj_t *out = ndarray_new_linear_array(len, NDARRAY_FLOAT); - mp_float_t *outptr = (mp_float_t *)out->array; + int32_t off = len_c - 1; + uint8_t dtype = NDARRAY_FLOAT; + + #if ULAB_SUPPORTS_COMPLEX + if((a->dtype == NDARRAY_COMPLEX) || (c->dtype == NDARRAY_COMPLEX)) { + dtype = NDARRAY_COMPLEX; + } + #endif + ndarray_obj_t *ndarray = ndarray_new_linear_array(len, dtype); + mp_float_t *array = (mp_float_t *)ndarray->array; + uint8_t *aarray = (uint8_t *)a->array; uint8_t *carray = (uint8_t *)c->array; - int32_t off = len_c - 1; int32_t as = a->strides[ULAB_MAX_DIMS - 1] / a->itemsize; int32_t cs = c->strides[ULAB_MAX_DIMS - 1] / c->itemsize; - for(int32_t k=-off; k < len-off; k++) { - mp_float_t accum = (mp_float_t)0.0; + + #if ULAB_SUPPORTS_COMPLEX + if(dtype == NDARRAY_COMPLEX) { + mp_float_t a_real, a_imag; + mp_float_t c_real, c_imag = MICROPY_FLOAT_CONST(0.0); + for(int32_t k = -off; k < len-off; k++) { + mp_float_t accum_real = MICROPY_FLOAT_CONST(0.0); + mp_float_t accum_imag = MICROPY_FLOAT_CONST(0.0); + + int32_t top_n = MIN(len_c, len_a - k); + int32_t bot_n = MAX(-k, 0); + + for(int32_t n = bot_n; n < top_n; n++) { + int32_t idx_c = (len_c - n - 1) * cs; + int32_t idx_a = (n + k) * as; + if(a->dtype != NDARRAY_COMPLEX) { + a_real = ndarray_get_float_index(aarray, a->dtype, idx_a); + a_imag = MICROPY_FLOAT_CONST(0.0); + } else { + a_real = ndarray_get_float_index(aarray, NDARRAY_FLOAT, 2 * idx_a); + a_imag = ndarray_get_float_index(aarray, NDARRAY_FLOAT, 2 * idx_a + 1); + } + + if(c->dtype != NDARRAY_COMPLEX) { + c_real = ndarray_get_float_index(carray, c->dtype, idx_c); + c_imag = MICROPY_FLOAT_CONST(0.0); + } else { + c_real = ndarray_get_float_index(carray, NDARRAY_FLOAT, 2 * idx_c); + c_imag = ndarray_get_float_index(carray, NDARRAY_FLOAT, 2 * idx_c + 1); + } + accum_real += a_real * c_real - a_imag * c_imag; + accum_imag += a_real * c_imag + a_imag * c_real; + } + *array++ = accum_real; + *array++ = accum_imag; + } + return MP_OBJ_FROM_PTR(ndarray); + } + #endif + + for(int32_t k = -off; k < len-off; k++) { + mp_float_t accum = MICROPY_FLOAT_CONST(0.0); int32_t top_n = MIN(len_c, len_a - k); int32_t bot_n = MAX(-k, 0); - for(int32_t n=bot_n; n < top_n; n++) { + for(int32_t n = bot_n; n < top_n; n++) { int32_t idx_c = (len_c - n - 1) * cs; int32_t idx_a = (n + k) * as; mp_float_t ai = ndarray_get_float_index(aarray, a->dtype, idx_a); mp_float_t ci = ndarray_get_float_index(carray, c->dtype, idx_c); accum += ai * ci; } - *outptr++ = accum; + *array++ = accum; } - - return out; + return MP_OBJ_FROM_PTR(ndarray); } MP_DEFINE_CONST_FUN_OBJ_KW(filter_convolve_obj, 2, filter_convolve); diff --git a/python/port/mod/ulab/numpy/io/io.c b/python/port/mod/ulab/numpy/io/io.c new file mode 100644 index 00000000000..0d02945713f --- /dev/null +++ b/python/port/mod/ulab/numpy/io/io.c @@ -0,0 +1,817 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Zoltán Vörös +*/ + +#include +#include + +#include "py/builtin.h" +#include "py/formatfloat.h" +#include "py/obj.h" +#include "py/parsenum.h" +#include "py/runtime.h" +#include "py/stream.h" +#include "extmod/vfs.h" + +#include "../../ndarray.h" +#include "../../ulab_tools.h" +#include "io.h" + +#define ULAB_IO_BUFFER_SIZE 128 +#define ULAB_IO_CLIPBOARD_SIZE 32 +#define ULAB_IO_MAX_ROWS 65535 + +#define ULAB_IO_NULL_ENDIAN 0 +#define ULAB_IO_LITTLE_ENDIAN 1 +#define ULAB_IO_BIG_ENDIAN 2 + +#if ULAB_NUMPY_HAS_LOAD +static void io_read_(mp_obj_t stream, const mp_stream_p_t *stream_p, char *buffer, char *string, uint16_t len, int *error) { + size_t read = stream_p->read(stream, buffer, len, error); + bool fail = false; + if(read == len) { + if(string != NULL) { + if(memcmp(buffer, string, len) != 0) { + fail = true; + } + } + } else { + fail = true; + } + if(fail) { + stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, error); + mp_raise_msg(&mp_type_RuntimeError, translate("corrupted file")); + } +} + +static mp_obj_t io_load(mp_obj_t file) { + if(!mp_obj_is_str(file)) { + mp_raise_TypeError(translate("wrong input type")); + } + + int error; + char *buffer = m_new(char, ULAB_IO_BUFFER_SIZE); + + // test for endianness + uint16_t x = 1; + int8_t native_endianness = (x >> 8) == 1 ? ULAB_IO_BIG_ENDIAN : ULAB_IO_LITTLE_ENDIAN; + + mp_obj_t open_args[2] = { + file, + MP_OBJ_NEW_QSTR(MP_QSTR_rb) + }; + + mp_obj_t stream = mp_builtin_open_obj.fun.kw(2, open_args, (mp_map_t *)&mp_const_empty_map); + const mp_stream_p_t *stream_p = mp_get_stream(stream); + + // read header + // magic string + io_read_(stream, stream_p, buffer, "\x93NUMPY", 6, &error); + // simply discard the version number + io_read_(stream, stream_p, buffer, NULL, 2, &error); + // header length, represented as a little endian uint16 (0x76, 0x00) + io_read_(stream, stream_p, buffer, NULL, 2, &error); + + uint16_t header_length = buffer[1]; + header_length <<= 8; + header_length += buffer[0]; + + // beginning of the dictionary describing the array + io_read_(stream, stream_p, buffer, "{'descr': '", 11, &error); + uint8_t dtype; + + io_read_(stream, stream_p, buffer, NULL, 1, &error); + uint8_t endianness = ULAB_IO_NULL_ENDIAN; + if(*buffer == '<') { + endianness = ULAB_IO_LITTLE_ENDIAN; + } else if(*buffer == '>') { + endianness = ULAB_IO_BIG_ENDIAN; + } + + io_read_(stream, stream_p, buffer, NULL, 2, &error); + if(memcmp(buffer, "u1", 2) == 0) { + dtype = NDARRAY_UINT8; + } else if(memcmp(buffer, "i1", 2) == 0) { + dtype = NDARRAY_INT8; + } else if(memcmp(buffer, "u2", 2) == 0) { + dtype = NDARRAY_UINT16; + } else if(memcmp(buffer, "i2", 2) == 0) { + dtype = NDARRAY_INT16; + } + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + else if(memcmp(buffer, "f4", 2) == 0) { + dtype = NDARRAY_FLOAT; + } + #else + else if(memcmp(buffer, "f8", 2) == 0) { + dtype = NDARRAY_FLOAT; + } + #endif + #if ULAB_SUPPORTS_COMPLEX + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + else if(memcmp(buffer, "c8", 2) == 0) { + dtype = NDARRAY_COMPLEX; + } + #else + else if(memcmp(buffer, "c16", 3) == 0) { + dtype = NDARRAY_COMPLEX; + } + #endif + #endif /* ULAB_SUPPORT_COPMLEX */ + else { + stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error); + mp_raise_TypeError(translate("wrong dtype")); + } + + io_read_(stream, stream_p, buffer, "', 'fortran_order': False, 'shape': (", 37, &error); + + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + + uint16_t bytes_to_read = MIN(ULAB_IO_BUFFER_SIZE, header_length - 51); + // bytes_to_read is 128 at most. This should be enough to contain a + // maximum of 4 size_t numbers plus the delimiters + io_read_(stream, stream_p, buffer, NULL, bytes_to_read, &error); + char *needle = buffer; + uint8_t ndim = 0; + + // find out the number of dimensions by counting the commas in the string + while(1) { + if(*needle == ',') { + ndim++; + if(needle[1] == ')') { + break; + } + } else if((*needle == ')') && (ndim > 0)) { + ndim++; + break; + } + needle++; + } + + needle = buffer; + for(uint8_t i = 0; i < ndim; i++) { + size_t number = 0; + // trivial number parsing here + while(1) { + if((*needle == ' ') || (*needle == '\t')) { + needle++; + } + if((*needle > 47) && (*needle < 58)) { + number = number * 10 + (*needle - 48); + } else if((*needle == ',') || (*needle == ')')) { + break; + } + else { + stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error); + mp_raise_msg(&mp_type_RuntimeError, translate("corrupted file")); + } + needle++; + } + needle++; + shape[ULAB_MAX_DIMS - ndim + i] = number; + } + + // strip the rest of the header + if((bytes_to_read + 51) < header_length) { + io_read_(stream, stream_p, buffer, NULL, header_length - (bytes_to_read + 51), &error); + } + + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(ndim, shape, dtype); + char *array = (char *)ndarray->array; + + size_t read = stream_p->read(stream, array, ndarray->len * ndarray->itemsize, &error); + if(read != ndarray->len * ndarray->itemsize) { + stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error); + mp_raise_msg(&mp_type_RuntimeError, translate("corrupted file")); + } + + stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error); + m_del(char, buffer, ULAB_IO_BUFFER_SIZE); + + // swap the bytes, if necessary + if((native_endianness != endianness) && (dtype != NDARRAY_UINT8) && (dtype != NDARRAY_INT8)) { + uint8_t sz = ndarray->itemsize; + char *tmpbuff = NULL; + + #if ULAB_SUPPORTS_COMPLEX + if(dtype == NDARRAY_COMPLEX) { + // work with the floating point real and imaginary parts + sz /= 2; + tmpbuff = m_new(char, sz); + for(size_t i = 0; i < ndarray->len; i++) { + for(uint8_t k = 0; k < 2; k++) { + tmpbuff += sz; + for(uint8_t j = 0; j < sz; j++) { + memcpy(--tmpbuff, array++, 1); + } + memcpy(array-sz, tmpbuff, sz); + } + } + } else { + #endif + tmpbuff = m_new(char, sz); + for(size_t i = 0; i < ndarray->len; i++) { + tmpbuff += sz; + for(uint8_t j = 0; j < sz; j++) { + memcpy(--tmpbuff, array++, 1); + } + memcpy(array-sz, tmpbuff, sz); + } + #if ULAB_SUPPORTS_COMPLEX + } + #endif + m_del(char, tmpbuff, sz); + } + + m_del(size_t, shape, ULAB_MAX_DIMS); + + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_1(io_load_obj, io_load); +#endif /* ULAB_NUMPY_HAS_LOAD */ + +#if ULAB_NUMPY_HAS_LOADTXT +static void io_assign_value(const char *clipboard, uint8_t len, ndarray_obj_t *ndarray, size_t *idx, uint8_t dtype) { + mp_obj_t value = mp_parse_num_decimal(clipboard, len, false, false, NULL); + if(dtype != NDARRAY_FLOAT) { + mp_float_t _value = mp_obj_get_float(value); + value = mp_obj_new_int((int32_t)MICROPY_FLOAT_C_FUN(round)(_value)); + } + ndarray_set_value(dtype, ndarray->array, (*idx)++, value); +} + +static mp_obj_t io_loadtxt(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_delimiter, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_comments, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_max_rows, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = -1 } }, + { MP_QSTR_usecols, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + { MP_QSTR_skiprows, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 0 } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t open_args[2] = { + args[0].u_obj, + MP_OBJ_NEW_QSTR(MP_QSTR_r) + }; + + mp_obj_t stream = mp_builtin_open_obj.fun.kw(2, open_args, (mp_map_t *)&mp_const_empty_map); + const mp_stream_p_t *stream_p = mp_get_stream(stream); + + char *buffer = m_new(char, ULAB_IO_BUFFER_SIZE); + int error; + + char delimiter = ' '; + if(args[1].u_obj != mp_const_none) { + size_t _len; + char *_delimiter = m_new(char, 8); + _delimiter = (char *)mp_obj_str_get_data(args[1].u_obj, &_len); + delimiter = _delimiter[0]; + } + + char comment_char = '#'; + if(args[2].u_obj != mp_const_none) { + size_t _len; + char *_comment_char = m_new(char, 8); + _comment_char = (char *)mp_obj_str_get_data(args[2].u_obj, &_len); + comment_char = _comment_char[0]; + } + + uint16_t skiprows = args[6].u_int; + uint16_t max_rows = ULAB_IO_MAX_ROWS; + if((args[3].u_int > 0) && (args[3].u_int < ULAB_IO_MAX_ROWS)) { + max_rows = args[3].u_int + skiprows; + } + + uint16_t *cols = NULL; + uint8_t used_columns = 0; + if(args[4].u_obj != mp_const_none) { + if(mp_obj_is_int(args[4].u_obj)) { + used_columns = 1; + cols = m_new(uint16_t, used_columns); + cols[0] = (uint16_t)mp_obj_get_int(args[4].u_obj); + } else { + #if ULAB_MAX_DIMS == 1 + mp_raise_ValueError(translate("usecols keyword must be specified")); + #else + // assume that the argument is an iterable + used_columns = (uint16_t)mp_obj_get_int(mp_obj_len(args[4].u_obj)); + cols = m_new(uint16_t, used_columns); + mp_obj_iter_buf_t iter_buf; + mp_obj_t item, iterable = mp_getiter(args[4].u_obj, &iter_buf); + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + *cols++ = (uint16_t)mp_obj_get_int(item); + } + cols -= used_columns; + #endif + } + } + + uint8_t dtype = args[5].u_int; + + // count the columns and rows + // we actually count only the rows and the items, and assume that + // the number of columns can be gotten by means of a simple division, + // i.e., that each row has the same number of columns + char *offset; + uint16_t rows = 0, items = 0, all_rows = 0; + uint8_t read; + uint8_t len = 0; + + do { + read = (uint8_t)stream_p->read(stream, buffer, ULAB_IO_BUFFER_SIZE - 1, &error); + buffer[read] = '\0'; + offset = buffer; + while(*offset != '\0') { + if(*offset == comment_char) { + // clear the line till the end, or the buffer's end + while((*offset != '\0')) { + offset++; + if(*offset == '\n') { + offset++; + all_rows++; + break; + } + } + } + + // catch whitespaces here: if these are not on a comment line, then they delimit a number + if(*offset == '\n') { + all_rows++; + if(all_rows > skiprows) { + rows++; + items++; + len = 0; + } + if(all_rows == max_rows) { + break; + } + } + + if((*offset == ' ') || (*offset == '\t') || (*offset == '\v') || + (*offset == '\f') || (*offset == '\r') || (*offset == delimiter)) { + offset++; + while((*offset == ' ') || (*offset == '\t') || (*offset == '\v') || (*offset == '\f') || (*offset == '\r')) { + offset++; + } + if(len > 0) { + if(all_rows >= skiprows) { + items++; + } + len = 0; + } + } else { + offset++; + len++; + } + } + } while((read > 0) && (all_rows < max_rows)); + + if(rows == 0) { + mp_raise_ValueError(translate("empty file")); + } + uint16_t columns = items / rows; + + if(columns < used_columns) { + mp_raise_ValueError(translate("usecols is too high")); + } + + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + + #if ULAB_MAX_DIMS == 1 + shape[0] = rows; + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(1, shape, dtype); + #else + if(args[4].u_obj == mp_const_none) { + shape[ULAB_MAX_DIMS - 1] = columns; + } else { + shape[ULAB_MAX_DIMS - 1] = used_columns; + } + shape[ULAB_MAX_DIMS - 2] = rows; + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(2, shape, dtype); + #endif + + struct mp_stream_seek_t seek_s; + seek_s.offset = 0; + seek_s.whence = MP_SEEK_SET; + stream_p->ioctl(stream, MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error); + + char *clipboard = m_new(char, ULAB_IO_CLIPBOARD_SIZE); + char *clipboard_origin = clipboard; + + rows = 0; + columns = 0; + len = 0; + + size_t idx = 0; + do { + read = stream_p->read(stream, buffer, ULAB_IO_BUFFER_SIZE - 1, &error); + buffer[read] = '\0'; + offset = buffer; + + while(*offset != '\0') { + if(*offset == comment_char) { + // clear the line till the end, or the buffer's end + while((*offset != '\0')) { + offset++; + if(*offset == '\n') { + rows++; + offset++; + break; + } + } + } + + if(rows == max_rows) { + break; + } + + if((*offset == ' ') || (*offset == '\t') || (*offset == '\v') || + (*offset == '\f') || (*offset == '\r') || (*offset == '\n') || (*offset == delimiter)) { + offset++; + while((*offset == ' ') || (*offset == '\t') || (*offset == '\v') || + (*offset == '\f') || (*offset == '\r') || (*offset == '\n')) { + offset++; + } + if(len > 0) { + clipboard = clipboard_origin; + if(rows >= skiprows) { + #if ULAB_MAX_DIMS == 1 + if(columns == cols[0]) { + io_assign_value(clipboard, len, ndarray, &idx, dtype); + } + #else + if(args[4].u_obj == mp_const_none) { + io_assign_value(clipboard, len, ndarray, &idx, dtype); + } else { + for(uint8_t c = 0; c < used_columns; c++) { + if(columns == cols[c]) { + io_assign_value(clipboard, len, ndarray, &idx, dtype); + break; + } + } + } + #endif + } + columns++; + len = 0; + + if(offset[-1] == '\n') { + columns = 0; + rows++; + } + } + } else { + *clipboard++ = *offset++; + len++; + } + } + } while((read > 0) && (rows < max_rows)); + + stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error); + + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(char, buffer, ULAB_IO_BUFFER_SIZE); + m_del(char, clipboard, ULAB_IO_CLIPBOARD_SIZE); + m_del(uint16_t, cols, used_columns); + + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(io_loadtxt_obj, 1, io_loadtxt); +#endif /* ULAB_NUMPY_HAS_LOADTXT */ + + +#if ULAB_NUMPY_HAS_SAVE +static uint8_t io_sprintf(char *buffer, const char *comma, size_t x) { + uint8_t offset = 1; + char *buf = buffer; + // our own minimal implementation of sprintf for size_t types + // this is required on systems, where sprintf is not available + + // find out, how many characters are required + // we could call log10 here... + for(size_t i = 10; i < 100000000; i *= 10) { + if(x < i) { + break; + } + buf++; + } + + while(x > 0) { + uint8_t rem = x % 10; + *buf-- = '0' + rem; + x /= 10; + offset++; + } + + buf += offset; + while(*comma != '\0') { + *buf++ = *comma++; + offset++; + } + return offset - 1; +} + +static mp_obj_t io_save(mp_obj_t file, mp_obj_t ndarray_) { + if(!mp_obj_is_str(file) || !mp_obj_is_type(ndarray_, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("wrong input type")); + } + + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(ndarray_); + int error; + char *buffer = m_new(char, ULAB_IO_BUFFER_SIZE); + uint8_t offset = 0; + + // test for endianness + uint16_t x = 1; + int8_t native_endianness = (x >> 8) == 1 ? '>' : '<'; + + mp_obj_t open_args[2] = { + file, + MP_OBJ_NEW_QSTR(MP_QSTR_wb) + }; + + mp_obj_t stream = mp_builtin_open_obj.fun.kw(2, open_args, (mp_map_t *)&mp_const_empty_map); + const mp_stream_p_t *stream_p = mp_get_stream(stream); + + // write header; + // magic string + header length, which is always 128 - 10 = 118, represented as a little endian uint16 (0x76, 0x00) + // + beginning of the dictionary describing the array + memcpy(buffer, "\x93NUMPY\x01\x00\x76\x00{'descr': '", 21); + offset += 21; + + buffer[offset] = native_endianness; + if((ndarray->dtype == NDARRAY_UINT8) || (ndarray->dtype == NDARRAY_INT8)) { + // for single-byte data, the endianness doesn't matter + buffer[offset] = '|'; + } + offset++; + switch(ndarray->dtype) { + case NDARRAY_UINT8: + memcpy(buffer+offset, "u1", 2); + break; + case NDARRAY_INT8: + memcpy(buffer+offset, "i1", 2); + break; + case NDARRAY_UINT16: + memcpy(buffer+offset, "u2", 2); + break; + case NDARRAY_INT16: + memcpy(buffer+offset, "i2", 2); + break; + case NDARRAY_FLOAT: + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + memcpy(buffer+offset, "f4", 2); + #else + memcpy(buffer+offset, "f8", 2); + #endif + break; + #if ULAB_SUPPORTS_COMPLEX + case NDARRAY_COMPLEX: + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + memcpy(buffer+offset, "c8", 2); + #else + memcpy(buffer+offset, "c16", 3); + offset++; + #endif + break; + #endif + } + + offset += 2; + memcpy(buffer+offset, "', 'fortran_order': False, 'shape': (", 37); + offset += 37; + + if(ndarray->ndim == 1) { + offset += io_sprintf(buffer+offset, ",\0", ndarray->shape[ULAB_MAX_DIMS - 1]); + } else { + for(uint8_t i = ndarray->ndim; i > 1; i--) { + offset += io_sprintf(buffer+offset, ", \0", ndarray->shape[ULAB_MAX_DIMS - i]); + } + offset += io_sprintf(buffer+offset, "\0", ndarray->shape[ULAB_MAX_DIMS - 1]); + } + memcpy(buffer+offset, "), }", 4); + offset += 4; + // pad with space till the very end + memset(buffer+offset, 32, ULAB_IO_BUFFER_SIZE - offset - 1); + buffer[ULAB_IO_BUFFER_SIZE - 1] = '\n'; + stream_p->write(stream, buffer, ULAB_IO_BUFFER_SIZE, &error); + + // write the array data + uint8_t sz = ndarray->itemsize; + offset = 0; + + uint8_t *array = (uint8_t *)ndarray->array; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + memcpy(buffer+offset, array, sz); + offset += sz; + if(offset == ULAB_IO_BUFFER_SIZE) { + stream_p->write(stream, buffer, offset, &error); + offset = 0; + } + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < ndarray->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * ndarray->shape[ULAB_MAX_DIMS-1]; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < ndarray->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= ndarray->strides[ULAB_MAX_DIMS - 2] * ndarray->shape[ULAB_MAX_DIMS-2]; + array += ndarray->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < ndarray->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= ndarray->strides[ULAB_MAX_DIMS - 3] * ndarray->shape[ULAB_MAX_DIMS-3]; + array += ndarray->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < ndarray->shape[ULAB_MAX_DIMS - 4]); + #endif + + stream_p->write(stream, buffer, offset, &error); + stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error); + + m_del(char, buffer, ULAB_IO_BUFFER_SIZE); + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_2(io_save_obj, io_save); +#endif /* ULAB_NUMPY_HAS_SAVE */ + +#if ULAB_NUMPY_HAS_SAVETXT +static int8_t io_format_float(ndarray_obj_t *ndarray, mp_float_t (*func)(void *), uint8_t *array, char *buffer, char *delimiter) { + // own implementation of float formatting for platforms that don't have sprintf + int8_t offset = 0; + + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C + const int precision = 6; + #else + const int precision = 7; + #endif + #else + const int precision = 16; + #endif + + #if ULAB_SUPPORTS_COMPLEX + if(ndarray->dtype == NDARRAY_COMPLEX) { + mp_float_t real = func(array); + mp_float_t imag = func(array + ndarray->itemsize / 2); + offset = mp_format_float(real, buffer, ULAB_IO_BUFFER_SIZE, 'f', precision, 'j'); + if(imag >= MICROPY_FLOAT_CONST(0.0)) { + buffer[offset++] = '+'; + } else { + buffer[offset++] = '-'; + } + offset += mp_format_float(-imag, &buffer[offset], ULAB_IO_BUFFER_SIZE, 'f', precision, 'j'); + } + #endif + offset = (uint8_t)mp_format_float(func(array), buffer, ULAB_IO_BUFFER_SIZE, 'f', precision, '\0'); + + #if ULAB_SUPPORTS_COMPLEX + if(ndarray->dtype != NDARRAY_COMPLEX) { + // complexes end with a 'j', floats with a '\0', so we have to wind back by one character + offset--; + } + #endif + + while(*delimiter != '\0') { + buffer[offset++] = *delimiter++; + } + + return offset; +} + +static mp_obj_t io_savetxt(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_delimiter, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_header, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_footer, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_comments, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_str(args[0].u_obj) || !mp_obj_is_type(args[1].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("wrong input type")); + } + + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[1].u_obj); + + #if ULAB_MAX_DIMS > 2 + if(ndarray->ndim > 2) { + mp_raise_ValueError(translate("array has too many dimensions")); + } + #endif + + mp_obj_t open_args[2] = { + args[0].u_obj, + MP_OBJ_NEW_QSTR(MP_QSTR_w) + }; + + mp_obj_t stream = mp_builtin_open_obj.fun.kw(2, open_args, (mp_map_t *)&mp_const_empty_map); + const mp_stream_p_t *stream_p = mp_get_stream(stream); + + char *buffer = m_new(char, ULAB_IO_BUFFER_SIZE); + int error; + + if(mp_obj_is_str(args[3].u_obj)) { + size_t _len; + if(mp_obj_is_str(args[5].u_obj)) { + const char *comments = mp_obj_str_get_data(args[5].u_obj, &_len); + stream_p->write(stream, comments, _len, &error); + } else { + stream_p->write(stream, "# ", 2, &error); + } + const char *header = mp_obj_str_get_data(args[3].u_obj, &_len); + stream_p->write(stream, header, _len, &error); + stream_p->write(stream, "\n", 1, &error); + } + + uint8_t *array = (uint8_t *)ndarray->array; + mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype); + char *delimiter = m_new(char, 8); + + if(ndarray->ndim == 1) { + delimiter[0] = '\n'; + delimiter[1] = '\0'; + } else if(args[2].u_obj == mp_const_none) { + delimiter[0] = ' '; + delimiter[1] = '\0'; + } else { + size_t delimiter_len; + delimiter = (char *)mp_obj_str_get_data(args[2].u_obj, &delimiter_len); + } + + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + int8_t chars = io_format_float(ndarray, func, array, buffer, l == ndarray->shape[ULAB_MAX_DIMS - 1] - 1 ? "\n" : delimiter); + if(chars > 0) { + stream_p->write(stream, buffer, chars, &error); + } + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < ndarray->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * ndarray->shape[ULAB_MAX_DIMS-1]; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < ndarray->shape[ULAB_MAX_DIMS - 2]); + #endif + + if(mp_obj_is_str(args[4].u_obj)) { + size_t _len; + if(mp_obj_is_str(args[5].u_obj)) { + const char *comments = mp_obj_str_get_data(args[5].u_obj, &_len); + stream_p->write(stream, comments, _len, &error); + } else { + stream_p->write(stream, "# ", 2, &error); + } + const char *footer = mp_obj_str_get_data(args[4].u_obj, &_len); + stream_p->write(stream, footer, _len, &error); + stream_p->write(stream, "\n", 1, &error); + } + + stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error); + + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(io_savetxt_obj, 2, io_savetxt); +#endif /* ULAB_NUMPY_HAS_SAVETXT */ \ No newline at end of file diff --git a/python/port/mod/ulab/numpy/io/io.h b/python/port/mod/ulab/numpy/io/io.h new file mode 100644 index 00000000000..d0141e78d9c --- /dev/null +++ b/python/port/mod/ulab/numpy/io/io.h @@ -0,0 +1,19 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Zoltán Vörös +*/ + +#ifndef _ULAB_IO_ +#define _ULAB_IO_ + +MP_DECLARE_CONST_FUN_OBJ_1(io_load_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(io_loadtxt_obj); +MP_DECLARE_CONST_FUN_OBJ_2(io_save_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(io_savetxt_obj); + +#endif \ No newline at end of file diff --git a/python/port/mod/ulab/numpy/linalg/linalg.c b/python/port/mod/ulab/numpy/linalg/linalg.c index 596280feaad..478503cf685 100644 --- a/python/port/mod/ulab/numpy/linalg/linalg.c +++ b/python/port/mod/ulab/numpy/linalg/linalg.c @@ -22,6 +22,7 @@ #include "../../ulab.h" #include "../../ulab_tools.h" +#include "../carray/carray_tools.h" #include "linalg.h" #if ULAB_NUMPY_HAS_LINALG_MODULE @@ -44,6 +45,7 @@ static mp_obj_t linalg_cholesky(mp_obj_t oin) { ndarray_obj_t *ndarray = tools_object_is_square(oin); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) ndarray_obj_t *L = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, ndarray->shape[ULAB_MAX_DIMS - 1], ndarray->shape[ULAB_MAX_DIMS - 1]), NDARRAY_FLOAT); mp_float_t *Larray = (mp_float_t *)L->array; @@ -110,6 +112,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(linalg_cholesky_obj, linalg_cholesky); static mp_obj_t linalg_det(mp_obj_t oin) { ndarray_obj_t *ndarray = tools_object_is_square(oin); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) uint8_t *array = (uint8_t *)ndarray->array; size_t N = ndarray->shape[ULAB_MAX_DIMS - 1]; mp_float_t *tmp = m_new(mp_float_t, N * N); @@ -182,6 +185,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(linalg_det_obj, linalg_det); static mp_obj_t linalg_eig(mp_obj_t oin) { ndarray_obj_t *in = tools_object_is_square(oin); + COMPLEX_DTYPE_NOT_IMPLEMENTED(in->dtype) uint8_t *iarray = (uint8_t *)in->array; size_t S = in->shape[ULAB_MAX_DIMS - 1]; mp_float_t *array = m_new(mp_float_t, S*S); @@ -243,6 +247,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(linalg_eig_obj, linalg_eig); //| static mp_obj_t linalg_inv(mp_obj_t o_in) { ndarray_obj_t *ndarray = tools_object_is_square(o_in); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) uint8_t *array = (uint8_t *)ndarray->array; size_t N = ndarray->shape[ULAB_MAX_DIMS - 1]; ndarray_obj_t *inverted = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, N, N), NDARRAY_FLOAT); @@ -305,6 +310,7 @@ static mp_obj_t linalg_norm(size_t n_args, const mp_obj_t *pos_args, mp_map_t *k return mp_obj_new_float(MICROPY_FLOAT_C_FUN(sqrt)(dot * (count - 1))); } else if(mp_obj_is_type(x, &ulab_ndarray_type)) { ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(x); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) uint8_t *array = (uint8_t *)ndarray->array; // always get a float, so that we don't have to resolve the dtype later mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype); @@ -429,22 +435,22 @@ static mp_obj_t linalg_qr(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ // [[c s], // [s -c]] if(MICROPY_FLOAT_C_FUN(fabs)(rarray[i * n + j]) < LINALG_EPSILON) { // r[i, j] - c = (rarray[(i - 1) * n + j] >= 0.0) ? 1.0 : -1.0; // r[i-1, j] + c = (rarray[(i - 1) * n + j] >= MICROPY_FLOAT_CONST(0.0)) ? MICROPY_FLOAT_CONST(1.0) : MICROPY_FLOAT_CONST(-1.0); // r[i-1, j] s = 0.0; } else if(MICROPY_FLOAT_C_FUN(fabs)(rarray[(i - 1) * n + j]) < LINALG_EPSILON) { // r[i-1, j] c = 0.0; - s = (rarray[i * n + j] >= 0.0) ? -1.0 : 1.0; // r[i, j] + s = (rarray[i * n + j] >= MICROPY_FLOAT_CONST(0.0)) ? MICROPY_FLOAT_CONST(-1.0) : MICROPY_FLOAT_CONST(1.0); // r[i, j] } else { mp_float_t t, u; if(MICROPY_FLOAT_C_FUN(fabs)(rarray[(i - 1) * n + j]) > MICROPY_FLOAT_C_FUN(fabs)(rarray[i * n + j])) { // r[i-1, j], r[i, j] t = rarray[i * n + j] / rarray[(i - 1) * n + j]; // r[i, j]/r[i-1, j] u = MICROPY_FLOAT_C_FUN(sqrt)(1 + t * t); - c = -1.0 / u; + c = MICROPY_FLOAT_CONST(-1.0) / u; s = c * t; } else { t = rarray[(i - 1) * n + j] / rarray[i * n + j]; // r[i-1, j]/r[i, j] u = MICROPY_FLOAT_C_FUN(sqrt)(1 + t * t); - s = -1.0 / u; + s = MICROPY_FLOAT_CONST(-1.0) / u; c = s * t; } } diff --git a/python/port/mod/ulab/numpy/linalg/linalg_tools.c b/python/port/mod/ulab/numpy/linalg/linalg_tools.c index 5e03a50ab59..7ae97d211cb 100644 --- a/python/port/mod/ulab/numpy/linalg/linalg_tools.c +++ b/python/port/mod/ulab/numpy/linalg/linalg_tools.c @@ -14,8 +14,8 @@ #include "linalg_tools.h" -/* - * The following function inverts a matrix, whose entries are given in the input array +/* + * The following function inverts a matrix, whose entries are given in the input array * The function has no dependencies beyond micropython itself (for the definition of mp_float_t), * and can be used independent of ulab. */ @@ -26,10 +26,9 @@ bool linalg_invert_matrix(mp_float_t *data, size_t N) { // initially, this is the unit matrix: the contents of this matrix is what // will be returned after all the transformations - mp_float_t *unit = m_new(mp_float_t, N*N); + mp_float_t *unit = m_new0(mp_float_t, N*N); mp_float_t elem = 1.0; - // initialise the unit matrix - memset(unit, 0, sizeof(mp_float_t)*N*N); + for(size_t m=0; m < N; m++) { memcpy(&unit[m * (N+1)], &elem, sizeof(mp_float_t)); } @@ -78,9 +77,9 @@ bool linalg_invert_matrix(mp_float_t *data, size_t N) { return true; } -/* - * The following function calculates the eigenvalues and eigenvectors of a symmetric - * real matrix, whose entries are given in the input array. +/* + * The following function calculates the eigenvalues and eigenvectors of a symmetric + * real matrix, whose entries are given in the input array. * The function has no dependencies beyond micropython itself (for the definition of mp_float_t), * and can be used independent of ulab. */ @@ -166,6 +165,6 @@ size_t linalg_jacobi_rotations(mp_float_t *array, mp_float_t *eigvectors, size_t eigvectors[m * S + N] = s * vm + c * vn; } } while(iterations > 0); - + return iterations; } diff --git a/python/port/mod/ulab/numpy/numerical.c b/python/port/mod/ulab/numpy/numerical.c index 39a5f979147..b33d0262be1 100644 --- a/python/port/mod/ulab/numpy/numerical.c +++ b/python/port/mod/ulab/numpy/numerical.c @@ -22,6 +22,7 @@ #include "../ulab.h" #include "../ulab_tools.h" +#include "./carray/carray_tools.h" #include "numerical.h" enum NUMERICAL_FUNCTION_TYPE { @@ -130,33 +131,71 @@ static mp_obj_t numerical_all_any(mp_obj_t oin, mp_obj_t axis, uint8_t optype) { size_t l = 0; if(axis == mp_const_none) { do { - mp_float_t value = func(array); - if((value != MICROPY_FLOAT_CONST(0.0)) & !anytype) { - // optype = NUMERICAL_ANY - return mp_const_true; - } else if((value == MICROPY_FLOAT_CONST(0.0)) & anytype) { - // optype == NUMERICAL_ALL - return mp_const_false; + #if ULAB_SUPPORTS_COMPLEX + if(ndarray->dtype == NDARRAY_COMPLEX) { + mp_float_t real = *((mp_float_t *)array); + mp_float_t imag = *((mp_float_t *)(array + sizeof(mp_float_t))); + if(((real != MICROPY_FLOAT_CONST(0.0)) | (imag != MICROPY_FLOAT_CONST(0.0))) & !anytype) { + // optype = NUMERICAL_ANY + return mp_const_true; + } else if(((real == MICROPY_FLOAT_CONST(0.0)) & (imag == MICROPY_FLOAT_CONST(0.0))) & anytype) { + // optype == NUMERICAL_ALL + return mp_const_false; + } + } else { + #endif + mp_float_t value = func(array); + if((value != MICROPY_FLOAT_CONST(0.0)) & !anytype) { + // optype = NUMERICAL_ANY + return mp_const_true; + } else if((value == MICROPY_FLOAT_CONST(0.0)) & anytype) { + // optype == NUMERICAL_ALL + return mp_const_false; + } + #if ULAB_SUPPORTS_COMPLEX } + #endif array += _shape_strides.strides[0]; l++; } while(l < _shape_strides.shape[0]); } else { // a scalar axis keyword was supplied do { - mp_float_t value = func(array); - if((value != MICROPY_FLOAT_CONST(0.0)) & !anytype) { - // optype == NUMERICAL_ANY - *rarray = 1; - // since we are breaking out of the loop, move the pointer forward - array += _shape_strides.strides[0] * (_shape_strides.shape[0] - l); - break; - } else if((value == MICROPY_FLOAT_CONST(0.0)) & anytype) { - // optype == NUMERICAL_ALL - *rarray = 0; - // since we are breaking out of the loop, move the pointer forward - array += _shape_strides.strides[0] * (_shape_strides.shape[0] - l); - break; + #if ULAB_SUPPORTS_COMPLEX + if(ndarray->dtype == NDARRAY_COMPLEX) { + mp_float_t real = *((mp_float_t *)array); + mp_float_t imag = *((mp_float_t *)(array + sizeof(mp_float_t))); + if(((real != MICROPY_FLOAT_CONST(0.0)) | (imag != MICROPY_FLOAT_CONST(0.0))) & !anytype) { + // optype = NUMERICAL_ANY + *rarray = 1; + // since we are breaking out of the loop, move the pointer forward + array += _shape_strides.strides[0] * (_shape_strides.shape[0] - l); + break; + } else if(((real == MICROPY_FLOAT_CONST(0.0)) & (imag == MICROPY_FLOAT_CONST(0.0))) & anytype) { + // optype == NUMERICAL_ALL + *rarray = 0; + // since we are breaking out of the loop, move the pointer forward + array += _shape_strides.strides[0] * (_shape_strides.shape[0] - l); + break; + } + } else { + #endif + mp_float_t value = func(array); + if((value != MICROPY_FLOAT_CONST(0.0)) & !anytype) { + // optype == NUMERICAL_ANY + *rarray = 1; + // since we are breaking out of the loop, move the pointer forward + array += _shape_strides.strides[0] * (_shape_strides.shape[0] - l); + break; + } else if((value == MICROPY_FLOAT_CONST(0.0)) & anytype) { + // optype == NUMERICAL_ALL + *rarray = 0; + // since we are breaking out of the loop, move the pointer forward + array += _shape_strides.strides[0] * (_shape_strides.shape[0] - l); + break; + } + #if ULAB_SUPPORTS_COMPLEX } + #endif array += _shape_strides.strides[0]; l++; } while(l < _shape_strides.shape[0]); @@ -234,6 +273,7 @@ static mp_obj_t numerical_sum_mean_std_iterable(mp_obj_t oin, uint8_t optype, si } static mp_obj_t numerical_sum_mean_std_ndarray(ndarray_obj_t *ndarray, mp_obj_t axis, uint8_t optype, size_t ddof) { + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) uint8_t *array = (uint8_t *)ndarray->array; shape_strides _shape_strides = tools_reduce_axes(ndarray, axis); @@ -244,7 +284,7 @@ static mp_obj_t numerical_sum_mean_std_ndarray(ndarray_obj_t *ndarray, mp_obj_t return mp_obj_new_float(MICROPY_FLOAT_CONST(0.0)); } mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype); - mp_float_t M =MICROPY_FLOAT_CONST(0.0); + mp_float_t M = MICROPY_FLOAT_CONST(0.0); mp_float_t m = MICROPY_FLOAT_CONST(0.0); mp_float_t S = MICROPY_FLOAT_CONST(0.0); mp_float_t s = MICROPY_FLOAT_CONST(0.0); @@ -472,17 +512,12 @@ static mp_obj_t numerical_argmin_argmax_ndarray(ndarray_obj_t *ndarray, mp_obj_t } } } else { - int8_t ax = mp_obj_get_int(axis); - if(ax < 0) ax += ndarray->ndim; - if((ax < 0) || (ax > ndarray->ndim - 1)) { - mp_raise_ValueError(translate("axis is out of bounds")); - } + int8_t ax = tools_get_axis(axis, ndarray->ndim); uint8_t *array = (uint8_t *)ndarray->array; - size_t *shape = m_new(size_t, ULAB_MAX_DIMS); - memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); - int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); - memset(strides, 0, sizeof(uint32_t)*ULAB_MAX_DIMS); + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *strides = m_new0(int32_t, ULAB_MAX_DIMS); + numerical_reduce_axes(ndarray, ax, shape, strides); uint8_t index = ULAB_MAX_DIMS - ndarray->ndim + ax; @@ -507,6 +542,9 @@ static mp_obj_t numerical_argmin_argmax_ndarray(ndarray_obj_t *ndarray, mp_obj_t } else { RUN_ARGMIN(ndarray, mp_float_t, array, results, rarray, shape, strides, index, optype); } + + m_del(int32_t, strides, ULAB_MAX_DIMS); + if(results->len == 1) { return mp_binary_get_val_array(results->dtype, results->array, 0); } @@ -555,9 +593,11 @@ static mp_obj_t numerical_function(size_t n_args, const mp_obj_t *pos_args, mp_m case NUMERICAL_MAX: case NUMERICAL_ARGMIN: case NUMERICAL_ARGMAX: + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) return numerical_argmin_argmax_ndarray(ndarray, axis, optype); case NUMERICAL_SUM: case NUMERICAL_MEAN: + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) return numerical_sum_mean_std_ndarray(ndarray, axis, optype, 0); default: mp_raise_NotImplementedError(translate("operation is not implemented on ndarrays")); @@ -580,6 +620,7 @@ static mp_obj_t numerical_sort_helper(mp_obj_t oin, mp_obj_t axis, uint8_t inpla } else { ndarray = ndarray_copy_view(MP_OBJ_TO_PTR(oin)); } + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) int8_t ax = 0; if(axis == mp_const_none) { @@ -594,30 +635,30 @@ static mp_obj_t numerical_sort_helper(mp_obj_t oin, mp_obj_t axis, uint8_t inpla ndarray->ndim = 1; #endif } else { - ax = mp_obj_get_int(axis); - if(ax < 0) ax += ndarray->ndim; - if((ax < 0) || (ax > ndarray->ndim - 1)) { - mp_raise_ValueError(translate("index out of range")); - } + ax = tools_get_axis(axis, ndarray->ndim); } - size_t *shape = m_new(size_t, ULAB_MAX_DIMS); - memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); - int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); - memset(strides, 0, sizeof(uint32_t)*ULAB_MAX_DIMS); + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *strides = m_new0(int32_t, ULAB_MAX_DIMS); + numerical_reduce_axes(ndarray, ax, shape, strides); ax = ULAB_MAX_DIMS - ndarray->ndim + ax; // we work with the typed array, so re-scale the stride int32_t increment = ndarray->strides[ax] / ndarray->itemsize; uint8_t *array = (uint8_t *)ndarray->array; - if((ndarray->dtype == NDARRAY_UINT8) || (ndarray->dtype == NDARRAY_INT8)) { - HEAPSORT(ndarray, uint8_t, array, shape, strides, ax, increment, ndarray->shape[ax]); - } else if((ndarray->dtype == NDARRAY_INT16) || (ndarray->dtype == NDARRAY_INT16)) { - HEAPSORT(ndarray, uint16_t, array, shape, strides, ax, increment, ndarray->shape[ax]); - } else { - HEAPSORT(ndarray, mp_float_t, array, shape, strides, ax, increment, ndarray->shape[ax]); + if(ndarray->shape[ax]) { + if((ndarray->dtype == NDARRAY_UINT8) || (ndarray->dtype == NDARRAY_INT8)) { + HEAPSORT(ndarray, uint8_t, array, shape, strides, ax, increment, ndarray->shape[ax]); + } else if((ndarray->dtype == NDARRAY_INT16) || (ndarray->dtype == NDARRAY_INT16)) { + HEAPSORT(ndarray, uint16_t, array, shape, strides, ax, increment, ndarray->shape[ax]); + } else { + HEAPSORT(ndarray, mp_float_t, array, shape, strides, ax, increment, ndarray->shape[ax]); + } } + + m_del(int32_t, strides, ULAB_MAX_DIMS); + if(inplace == 1) { return mp_const_none; } else { @@ -682,6 +723,7 @@ mp_obj_t numerical_argsort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw } ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) if(args[1].u_obj == mp_const_none) { // bail out, though dense arrays could still be sorted mp_raise_NotImplementedError(translate("argsort is not implemented for flattened arrays")); @@ -693,22 +735,17 @@ mp_obj_t numerical_argsort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw mp_raise_ValueError(translate("axis too long")); } } - int8_t ax = mp_obj_get_int(args[1].u_obj); - if(ax < 0) ax += ndarray->ndim; - if((ax < 0) || (ax > ndarray->ndim - 1)) { - mp_raise_ValueError(translate("index out of range")); - } - size_t *shape = m_new(size_t, ULAB_MAX_DIMS); - memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); - int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); - memset(strides, 0, sizeof(uint32_t)*ULAB_MAX_DIMS); + int8_t ax = tools_get_axis(args[1].u_obj, ndarray->ndim); + + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *strides = m_new0(int32_t, ULAB_MAX_DIMS); numerical_reduce_axes(ndarray, ax, shape, strides); // We could return an NDARRAY_UINT8 array, if all lengths are shorter than 256 ndarray_obj_t *indices = ndarray_new_ndarray(ndarray->ndim, ndarray->shape, NULL, NDARRAY_UINT16); - int32_t *istrides = m_new(int32_t, ULAB_MAX_DIMS); - memset(istrides, 0, sizeof(uint32_t)*ULAB_MAX_DIMS); + int32_t *istrides = m_new0(int32_t, ULAB_MAX_DIMS); numerical_reduce_axes(indices, ax, shape, istrides); + for(uint8_t i=0; i < ULAB_MAX_DIMS; i++) { istrides[i] /= sizeof(uint16_t); } @@ -760,13 +797,20 @@ mp_obj_t numerical_argsort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw // reset the array iarray = indices->array; - if((ndarray->dtype == NDARRAY_UINT8) || (ndarray->dtype == NDARRAY_INT8)) { - HEAP_ARGSORT(ndarray, uint8_t, array, shape, strides, ax, increment, ndarray->shape[ax], iarray, istrides, iincrement); - } else if((ndarray->dtype == NDARRAY_UINT16) || (ndarray->dtype == NDARRAY_INT16)) { - HEAP_ARGSORT(ndarray, uint16_t, array, shape, strides, ax, increment, ndarray->shape[ax], iarray, istrides, iincrement); - } else { - HEAP_ARGSORT(ndarray, mp_float_t, array, shape, strides, ax, increment, ndarray->shape[ax], iarray, istrides, iincrement); + if(ndarray->shape[ax]) { + if((ndarray->dtype == NDARRAY_UINT8) || (ndarray->dtype == NDARRAY_INT8)) { + HEAP_ARGSORT(ndarray, uint8_t, array, shape, strides, ax, increment, ndarray->shape[ax], iarray, istrides, iincrement); + } else if((ndarray->dtype == NDARRAY_UINT16) || (ndarray->dtype == NDARRAY_INT16)) { + HEAP_ARGSORT(ndarray, uint16_t, array, shape, strides, ax, increment, ndarray->shape[ax], iarray, istrides, iincrement); + } else { + HEAP_ARGSORT(ndarray, mp_float_t, array, shape, strides, ax, increment, ndarray->shape[ax], iarray, istrides, iincrement); + } } + + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(int32_t, strides, ULAB_MAX_DIMS); + m_del(int32_t, istrides, ULAB_MAX_DIMS); + return MP_OBJ_FROM_PTR(indices); } @@ -785,6 +829,8 @@ static mp_obj_t numerical_cross(mp_obj_t _a, mp_obj_t _b) { } ndarray_obj_t *a = MP_OBJ_TO_PTR(_a); ndarray_obj_t *b = MP_OBJ_TO_PTR(_b); + COMPLEX_DTYPE_NOT_IMPLEMENTED(a->dtype) + COMPLEX_DTYPE_NOT_IMPLEMENTED(b->dtype) if((a->ndim != 1) || (b->ndim != 1) || (a->len != b->len) || (a->len != 3)) { mp_raise_ValueError(translate("cross is defined for 1D arrays of length 3")); } @@ -873,6 +919,7 @@ mp_obj_t numerical_diff(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar } ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) int8_t ax = args[2].u_int; if(ax < 0) ax += ndarray->ndim; @@ -891,13 +938,12 @@ mp_obj_t numerical_diff(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar int8_t *stencil = m_new(int8_t, N+1); stencil[0] = 1; - for(uint8_t i=1; i < N+1; i++) { + for(uint8_t i = 1; i < N+1; i++) { stencil[i] = -stencil[i-1]*(N-i+1)/i; } - size_t *shape = m_new(size_t, ULAB_MAX_DIMS); - memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); - for(uint8_t i=0; i < ULAB_MAX_DIMS; i++) { + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + for(uint8_t i = 0; i < ULAB_MAX_DIMS; i++) { shape[i] = ndarray->shape[i]; if(i == index) { shape[i] -= N; @@ -908,8 +954,7 @@ mp_obj_t numerical_diff(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar uint8_t *rarray = (uint8_t *)results->array; memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); - int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); - memset(strides, 0, sizeof(int32_t)*ULAB_MAX_DIMS); + int32_t *strides = m_new0(int32_t, ULAB_MAX_DIMS); numerical_reduce_axes(ndarray, ax, shape, strides); if(ndarray->dtype == NDARRAY_UINT8) { @@ -956,17 +1001,14 @@ mp_obj_t numerical_flip(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); if(args[1].u_obj == mp_const_none) { // flip the flattened array results = ndarray_new_linear_array(ndarray->len, ndarray->dtype); - ndarray_copy_array(ndarray, results); + ndarray_copy_array(ndarray, results, 0); uint8_t *rarray = (uint8_t *)results->array; rarray += (results->len - 1) * results->itemsize; results->array = rarray; results->strides[ULAB_MAX_DIMS - 1] = -results->strides[ULAB_MAX_DIMS - 1]; } else if(mp_obj_is_int(args[1].u_obj)){ - int8_t ax = mp_obj_get_int(args[1].u_obj); - if(ax < 0) ax += ndarray->ndim; - if((ax < 0) || (ax > ndarray->ndim - 1)) { - mp_raise_ValueError(translate("index out of range")); - } + int8_t ax = tools_get_axis(args[1].u_obj, ndarray->ndim); + ax = ULAB_MAX_DIMS - ndarray->ndim + ax; int32_t offset = (ndarray->shape[ax] - 1) * ndarray->strides[ax]; results = ndarray_new_view(ndarray, ndarray->ndim, ndarray->shape, ndarray->strides, offset); @@ -1044,17 +1086,16 @@ mp_obj_t numerical_median(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ } return mp_obj_new_float(median); } else { - int8_t ax = mp_obj_get_int(args[1].u_obj); - if(ax < 0) ax += ndarray->ndim; - // here we can save the exception, because if the axis is out of range, - // then numerical_sort_helper has already taken care of the issue - size_t *shape = m_new(size_t, ULAB_MAX_DIMS); - memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); - int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); - memset(strides, 0, sizeof(uint32_t)*ULAB_MAX_DIMS); + int8_t ax = tools_get_axis(args[1].u_obj, ndarray->ndim); + + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *strides = m_new0(int32_t, ULAB_MAX_DIMS); numerical_reduce_axes(ndarray, ax, shape, strides); + ax = ULAB_MAX_DIMS - ndarray->ndim + ax; ndarray_obj_t *results = ndarray_new_dense_ndarray(ndarray->ndim-1, shape, NDARRAY_FLOAT); + m_del(size_t, shape, ULAB_MAX_DIMS); + mp_float_t *rarray = (mp_float_t *)results->array; uint8_t *array = (uint8_t *)ndarray->array; @@ -1200,21 +1241,14 @@ mp_obj_t numerical_roll(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar } while(i < ndarray->shape[ULAB_MAX_DIMS - 4]); #endif } else if(mp_obj_is_int(args[2].u_obj)){ - int8_t ax = mp_obj_get_int(args[2].u_obj); - if(ax < 0) ax += ndarray->ndim; - if((ax < 0) || (ax > ndarray->ndim - 1)) { - mp_raise_ValueError(translate("index out of range")); - } - size_t *shape = m_new(size_t, ULAB_MAX_DIMS); - memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); - int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); - memset(strides, 0, sizeof(int32_t)*ULAB_MAX_DIMS); + int8_t ax = tools_get_axis(args[2].u_obj, ndarray->ndim); + + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *strides = m_new0(int32_t, ULAB_MAX_DIMS); numerical_reduce_axes(ndarray, ax, shape, strides); - size_t *rshape = m_new(size_t, ULAB_MAX_DIMS); - memset(rshape, 0, sizeof(size_t)*ULAB_MAX_DIMS); - int32_t *rstrides = m_new(int32_t, ULAB_MAX_DIMS); - memset(rstrides, 0, sizeof(int32_t)*ULAB_MAX_DIMS); + size_t *rshape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *rstrides = m_new0(int32_t, ULAB_MAX_DIMS); numerical_reduce_axes(results, ax, rshape, rstrides); ax = ULAB_MAX_DIMS - ndarray->ndim + ax; @@ -1275,9 +1309,16 @@ mp_obj_t numerical_roll(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar i++; } while(i < shape[ULAB_MAX_DIMS - 3]); #endif + + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(int32_t, strides, ULAB_MAX_DIMS); + m_del(size_t, rshape, ULAB_MAX_DIMS); + m_del(int32_t, rstrides, ULAB_MAX_DIMS); + } else { mp_raise_TypeError(translate("wrong axis index")); } + return results; } diff --git a/python/port/mod/ulab/numpy/numerical.h b/python/port/mod/ulab/numpy/numerical.h index 8d2971cd43e..186c817b0a4 100644 --- a/python/port/mod/ulab/numpy/numerical.h +++ b/python/port/mod/ulab/numpy/numerical.h @@ -155,6 +155,7 @@ type *_array = (type *)array;\ type tmp;\ uint16_t itmp, c, q = (N), p, r = (N) >> 1;\ + assert(N);\ for (;;) {\ if (r > 0) {\ r--;\ diff --git a/python/port/mod/ulab/numpy/numpy.c b/python/port/mod/ulab/numpy/numpy.c index a6559ff8ac5..e1de9e6ddf2 100644 --- a/python/port/mod/ulab/numpy/numpy.c +++ b/python/port/mod/ulab/numpy/numpy.c @@ -8,7 +8,7 @@ * * Copyright (c) 2020 Jeff Epler for Adafruit Industries * 2020 Scott Shawcroft for Adafruit Industries - * 2020-2021 Zoltán Vörös + * 2020-2022 Zoltán Vörös * 2020 Taku Fukada */ @@ -17,11 +17,13 @@ #include "py/runtime.h" #include "numpy.h" -#include "../ulab_create.h" #include "approx.h" +#include "carray/carray.h" #include "compare.h" +#include "create.h" #include "fft/fft.h" #include "filter.h" +#include "io/io.h" #include "linalg/linalg.h" #include "numerical.h" #include "stats.h" @@ -125,6 +127,9 @@ static const mp_rom_map_elem_t ulab_numpy_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_uint16), MP_ROM_INT(NDARRAY_UINT16) }, { MP_ROM_QSTR(MP_QSTR_int16), MP_ROM_INT(NDARRAY_INT16) }, { MP_ROM_QSTR(MP_QSTR_float), MP_ROM_INT(NDARRAY_FLOAT) }, + #if ULAB_SUPPORTS_COMPLEX + { MP_ROM_QSTR(MP_QSTR_complex), MP_ROM_INT(NDARRAY_COMPLEX) }, + #endif // modules of numpy #if ULAB_NUMPY_HAS_FFT_MODULE { MP_ROM_QSTR(MP_QSTR_fft), MP_ROM_PTR(&ulab_fft_module) }, @@ -142,9 +147,15 @@ static const mp_rom_map_elem_t ulab_numpy_globals_table[] = { #if ULAB_NUMPY_HAS_ARANGE { MP_ROM_QSTR(MP_QSTR_arange), (mp_obj_t)&create_arange_obj }, #endif + #if ULAB_NUMPY_HAS_COMPRESS + { MP_ROM_QSTR(MP_QSTR_compress), (mp_obj_t)&transform_compress_obj }, + #endif #if ULAB_NUMPY_HAS_CONCATENATE { MP_ROM_QSTR(MP_QSTR_concatenate), (mp_obj_t)&create_concatenate_obj }, #endif + #if ULAB_NUMPY_HAS_DELETE + { MP_ROM_QSTR(MP_QSTR_delete), (mp_obj_t)&transform_delete_obj }, + #endif #if ULAB_NUMPY_HAS_DIAG #if ULAB_MAX_DIMS > 1 { MP_ROM_QSTR(MP_QSTR_diag), (mp_obj_t)&create_diag_obj }, @@ -224,6 +235,9 @@ static const mp_rom_map_elem_t ulab_numpy_globals_table[] = { #if ULAB_NUMPY_HAS_ARGSORT { MP_OBJ_NEW_QSTR(MP_QSTR_argsort), (mp_obj_t)&numerical_argsort_obj }, #endif + #if ULAB_NUMPY_HAS_ASARRAY + { MP_OBJ_NEW_QSTR(MP_QSTR_asarray), (mp_obj_t)&create_asarray_obj }, + #endif #if ULAB_NUMPY_HAS_CROSS { MP_OBJ_NEW_QSTR(MP_QSTR_cross), (mp_obj_t)&numerical_cross_obj }, #endif @@ -243,6 +257,12 @@ static const mp_rom_map_elem_t ulab_numpy_globals_table[] = { #if ULAB_NUMPY_HAS_FLIP { MP_OBJ_NEW_QSTR(MP_QSTR_flip), (mp_obj_t)&numerical_flip_obj }, #endif + #if ULAB_NUMPY_HAS_LOAD + { MP_OBJ_NEW_QSTR(MP_QSTR_load), (mp_obj_t)&io_load_obj }, + #endif + #if ULAB_NUMPY_HAS_LOADTXT + { MP_OBJ_NEW_QSTR(MP_QSTR_loadtxt), (mp_obj_t)&io_loadtxt_obj }, + #endif #if ULAB_NUMPY_HAS_MINMAX { MP_OBJ_NEW_QSTR(MP_QSTR_max), (mp_obj_t)&numerical_max_obj }, #endif @@ -258,6 +278,15 @@ static const mp_rom_map_elem_t ulab_numpy_globals_table[] = { #if ULAB_NUMPY_HAS_ROLL { MP_OBJ_NEW_QSTR(MP_QSTR_roll), (mp_obj_t)&numerical_roll_obj }, #endif + #if ULAB_NUMPY_HAS_SAVE + { MP_OBJ_NEW_QSTR(MP_QSTR_save), (mp_obj_t)&io_save_obj }, + #endif + #if ULAB_NUMPY_HAS_SAVETXT + { MP_OBJ_NEW_QSTR(MP_QSTR_savetxt), (mp_obj_t)&io_savetxt_obj }, + #endif + #if ULAB_NUMPY_HAS_SIZE + { MP_OBJ_NEW_QSTR(MP_QSTR_size), (mp_obj_t)&transform_size_obj }, + #endif #if ULAB_NUMPY_HAS_SORT { MP_OBJ_NEW_QSTR(MP_QSTR_sort), (mp_obj_t)&numerical_sort_obj }, #endif @@ -276,81 +305,94 @@ static const mp_rom_map_elem_t ulab_numpy_globals_table[] = { #endif // functions of the vector sub-module #if ULAB_NUMPY_HAS_ACOS - { MP_OBJ_NEW_QSTR(MP_QSTR_acos), (mp_obj_t)&vectorise_acos_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_acos), (mp_obj_t)&vector_acos_obj }, #endif #if ULAB_NUMPY_HAS_ACOSH - { MP_OBJ_NEW_QSTR(MP_QSTR_acosh), (mp_obj_t)&vectorise_acosh_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_acosh), (mp_obj_t)&vector_acosh_obj }, #endif #if ULAB_NUMPY_HAS_ARCTAN2 - { MP_OBJ_NEW_QSTR(MP_QSTR_arctan2), (mp_obj_t)&vectorise_arctan2_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_arctan2), (mp_obj_t)&vector_arctan2_obj }, #endif #if ULAB_NUMPY_HAS_AROUND - { MP_OBJ_NEW_QSTR(MP_QSTR_around), (mp_obj_t)&vectorise_around_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_around), (mp_obj_t)&vector_around_obj }, #endif #if ULAB_NUMPY_HAS_ASIN - { MP_OBJ_NEW_QSTR(MP_QSTR_asin), (mp_obj_t)&vectorise_asin_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_asin), (mp_obj_t)&vector_asin_obj }, #endif #if ULAB_NUMPY_HAS_ASINH - { MP_OBJ_NEW_QSTR(MP_QSTR_asinh), (mp_obj_t)&vectorise_asinh_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_asinh), (mp_obj_t)&vector_asinh_obj }, #endif #if ULAB_NUMPY_HAS_ATAN - { MP_OBJ_NEW_QSTR(MP_QSTR_atan), (mp_obj_t)&vectorise_atan_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_atan), (mp_obj_t)&vector_atan_obj }, #endif #if ULAB_NUMPY_HAS_ATANH - { MP_OBJ_NEW_QSTR(MP_QSTR_atanh), (mp_obj_t)&vectorise_atanh_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_atanh), (mp_obj_t)&vector_atanh_obj }, #endif #if ULAB_NUMPY_HAS_CEIL - { MP_OBJ_NEW_QSTR(MP_QSTR_ceil), (mp_obj_t)&vectorise_ceil_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_ceil), (mp_obj_t)&vector_ceil_obj }, #endif #if ULAB_NUMPY_HAS_COS - { MP_OBJ_NEW_QSTR(MP_QSTR_cos), (mp_obj_t)&vectorise_cos_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_cos), (mp_obj_t)&vector_cos_obj }, #endif #if ULAB_NUMPY_HAS_COSH - { MP_OBJ_NEW_QSTR(MP_QSTR_cosh), (mp_obj_t)&vectorise_cosh_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_cosh), (mp_obj_t)&vector_cosh_obj }, #endif #if ULAB_NUMPY_HAS_DEGREES - { MP_OBJ_NEW_QSTR(MP_QSTR_degrees), (mp_obj_t)&vectorise_degrees_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_degrees), (mp_obj_t)&vector_degrees_obj }, #endif #if ULAB_NUMPY_HAS_EXP - { MP_OBJ_NEW_QSTR(MP_QSTR_exp), (mp_obj_t)&vectorise_exp_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_exp), (mp_obj_t)&vector_exp_obj }, #endif #if ULAB_NUMPY_HAS_EXPM1 - { MP_OBJ_NEW_QSTR(MP_QSTR_expm1), (mp_obj_t)&vectorise_expm1_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_expm1), (mp_obj_t)&vector_expm1_obj }, #endif #if ULAB_NUMPY_HAS_FLOOR - { MP_OBJ_NEW_QSTR(MP_QSTR_floor), (mp_obj_t)&vectorise_floor_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_floor), (mp_obj_t)&vector_floor_obj }, #endif #if ULAB_NUMPY_HAS_LOG - { MP_OBJ_NEW_QSTR(MP_QSTR_log), (mp_obj_t)&vectorise_log_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_log), (mp_obj_t)&vector_log_obj }, #endif #if ULAB_NUMPY_HAS_LOG10 - { MP_OBJ_NEW_QSTR(MP_QSTR_log10), (mp_obj_t)&vectorise_log10_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_log10), (mp_obj_t)&vector_log10_obj }, #endif #if ULAB_NUMPY_HAS_LOG2 - { MP_OBJ_NEW_QSTR(MP_QSTR_log2), (mp_obj_t)&vectorise_log2_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_log2), (mp_obj_t)&vector_log2_obj }, #endif #if ULAB_NUMPY_HAS_RADIANS - { MP_OBJ_NEW_QSTR(MP_QSTR_radians), (mp_obj_t)&vectorise_radians_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_radians), (mp_obj_t)&vector_radians_obj }, #endif #if ULAB_NUMPY_HAS_SIN - { MP_OBJ_NEW_QSTR(MP_QSTR_sin), (mp_obj_t)&vectorise_sin_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_sin), (mp_obj_t)&vector_sin_obj }, #endif #if ULAB_NUMPY_HAS_SINH - { MP_OBJ_NEW_QSTR(MP_QSTR_sinh), (mp_obj_t)&vectorise_sinh_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_sinh), (mp_obj_t)&vector_sinh_obj }, #endif #if ULAB_NUMPY_HAS_SQRT - { MP_OBJ_NEW_QSTR(MP_QSTR_sqrt), (mp_obj_t)&vectorise_sqrt_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_sqrt), (mp_obj_t)&vector_sqrt_obj }, #endif #if ULAB_NUMPY_HAS_TAN - { MP_OBJ_NEW_QSTR(MP_QSTR_tan), (mp_obj_t)&vectorise_tan_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_tan), (mp_obj_t)&vector_tan_obj }, #endif #if ULAB_NUMPY_HAS_TANH - { MP_OBJ_NEW_QSTR(MP_QSTR_tanh), (mp_obj_t)&vectorise_tanh_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_tanh), (mp_obj_t)&vector_tanh_obj }, #endif #if ULAB_NUMPY_HAS_VECTORIZE - { MP_OBJ_NEW_QSTR(MP_QSTR_vectorize), (mp_obj_t)&vectorise_vectorize_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_vectorize), (mp_obj_t)&vector_vectorize_obj }, + #endif + #if ULAB_SUPPORTS_COMPLEX + #if ULAB_NUMPY_HAS_REAL + { MP_OBJ_NEW_QSTR(MP_QSTR_real), (mp_obj_t)&carray_real_obj }, + #endif + #if ULAB_NUMPY_HAS_IMAG + { MP_OBJ_NEW_QSTR(MP_QSTR_imag), (mp_obj_t)&carray_imag_obj }, + #endif + #if ULAB_NUMPY_HAS_CONJUGATE + { MP_ROM_QSTR(MP_QSTR_conjugate), (mp_obj_t)&carray_conjugate_obj }, + #endif + #if ULAB_NUMPY_HAS_SORT_COMPLEX + { MP_ROM_QSTR(MP_QSTR_sort_complex), (mp_obj_t)&carray_sort_complex_obj }, + #endif #endif - }; static MP_DEFINE_CONST_DICT(mp_module_ulab_numpy_globals, ulab_numpy_globals_table); diff --git a/python/port/mod/ulab/numpy/poly.c b/python/port/mod/ulab/numpy/poly.c index 7ea7feb1a02..97ee5c75fe5 100644 --- a/python/port/mod/ulab/numpy/poly.c +++ b/python/port/mod/ulab/numpy/poly.c @@ -19,6 +19,7 @@ #include "../ulab.h" #include "linalg/linalg_tools.h" #include "../ulab_tools.h" +#include "carray/carray_tools.h" #include "poly.h" #if ULAB_NUMPY_HAS_POLYFIT @@ -27,6 +28,12 @@ mp_obj_t poly_polyfit(size_t n_args, const mp_obj_t *args) { if(!ndarray_object_is_array_like(args[0])) { mp_raise_ValueError(translate("input data must be an iterable")); } + #if ULAB_SUPPORTS_COMPLEX + if(mp_obj_is_type(args[0], &ulab_ndarray_type)) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0]); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) + } + #endif size_t lenx = 0, leny = 0; uint8_t deg = 0; mp_float_t *x, *XT, *y, *prod; @@ -142,6 +149,17 @@ mp_obj_t poly_polyval(mp_obj_t o_p, mp_obj_t o_x) { if(!ndarray_object_is_array_like(o_p) || !ndarray_object_is_array_like(o_x)) { mp_raise_TypeError(translate("inputs are not iterable")); } + #if ULAB_SUPPORTS_COMPLEX + ndarray_obj_t *input; + if(mp_obj_is_type(o_p, &ulab_ndarray_type)) { + input = MP_OBJ_TO_PTR(o_p); + COMPLEX_DTYPE_NOT_IMPLEMENTED(input->dtype) + } + if(mp_obj_is_type(o_x, &ulab_ndarray_type)) { + input = MP_OBJ_TO_PTR(o_x); + COMPLEX_DTYPE_NOT_IMPLEMENTED(input->dtype) + } + #endif // p had better be a one-dimensional standard iterable uint8_t plen = mp_obj_get_int(mp_obj_len_maybe(o_p)); mp_float_t *p = m_new(mp_float_t, plen); @@ -164,7 +182,7 @@ mp_obj_t poly_polyval(mp_obj_t o_p, mp_obj_t o_x) { mp_float_t (*func)(void *) = ndarray_get_float_function(source->dtype); - // TODO: these loops are really nothing, but the re-implementation of + // TODO: these loops are really nothing, but the re-impplementation of // ITERATE_VECTOR from vectorise.c. We could pass a function pointer here #if ULAB_MAX_DIMS > 3 size_t i = 0; diff --git a/python/port/mod/ulab/numpy/stats.c b/python/port/mod/ulab/numpy/stats.c index a63964fea0a..2d348893737 100644 --- a/python/port/mod/ulab/numpy/stats.c +++ b/python/port/mod/ulab/numpy/stats.c @@ -21,6 +21,7 @@ #include "../ulab.h" #include "../ulab_tools.h" +#include "carray/carray_tools.h" #include "stats.h" #if ULAB_MAX_DIMS > 1 @@ -36,6 +37,7 @@ static mp_obj_t stats_trace(mp_obj_t oin) { ndarray_obj_t *ndarray = tools_object_is_square(oin); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) mp_float_t trace = 0.0; for(size_t i=0; i < ndarray->shape[ULAB_MAX_DIMS - 1]; i++) { int32_t pos = i * (ndarray->strides[ULAB_MAX_DIMS - 1] + ndarray->strides[ULAB_MAX_DIMS - 2]); diff --git a/python/port/mod/ulab/numpy/transform.c b/python/port/mod/ulab/numpy/transform.c index 2c2d2dbddb0..4f27ef3462d 100644 --- a/python/port/mod/ulab/numpy/transform.c +++ b/python/port/mod/ulab/numpy/transform.c @@ -9,17 +9,337 @@ * */ +#include +#include #include #include -#include #include "py/obj.h" #include "py/runtime.h" #include "py/misc.h" #include "../ulab.h" #include "../ulab_tools.h" +#include "carray/carray_tools.h" +#include "numerical.h" #include "transform.h" +#if ULAB_NUMPY_HAS_COMPRESS +static mp_obj_t transform_compress(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t condition = args[0].u_obj; + + if(!mp_obj_is_type(args[1].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("wrong input type")); + } + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[1].u_obj); + uint8_t *array = (uint8_t *)ndarray->array; + + mp_obj_t axis = args[2].u_obj; + + size_t len = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(condition)); + int8_t ax, shift_ax = 0; + + if(axis != mp_const_none) { + ax = tools_get_axis(axis, ndarray->ndim); + shift_ax = ULAB_MAX_DIMS - ndarray->ndim + ax; + } + + if(((axis == mp_const_none) && (len != ndarray->len)) || + ((axis != mp_const_none) && (len != ndarray->shape[shift_ax]))) { + mp_raise_ValueError(translate("wrong length of condition array")); + } + + size_t true_count = 0; + mp_obj_iter_buf_t iter_buf; + mp_obj_t item, iterable = mp_getiter(condition, &iter_buf); + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + if(mp_obj_is_true(item)) { + true_count++; + } + } + + iterable = mp_getiter(condition, &iter_buf); + + ndarray_obj_t *result = NULL; + + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + memcpy(shape, ndarray->shape, ULAB_MAX_DIMS * sizeof(size_t)); + + size_t *rshape = m_new(size_t, ULAB_MAX_DIMS); + memcpy(rshape, ndarray->shape, ULAB_MAX_DIMS * sizeof(size_t)); + + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + memcpy(strides, ndarray->strides, ULAB_MAX_DIMS * sizeof(int32_t)); + + int32_t *rstrides = m_new0(int32_t, ULAB_MAX_DIMS); + + if(axis == mp_const_none) { + result = ndarray_new_linear_array(true_count, ndarray->dtype); + + rstrides[ULAB_MAX_DIMS - 1] = ndarray->itemsize; + rshape[ULAB_MAX_DIMS - 1] = 0; + } else { + rshape[shift_ax] = true_count; + + result = ndarray_new_dense_ndarray(ndarray->ndim, rshape, ndarray->dtype); + + SWAP(size_t, shape[shift_ax], shape[ULAB_MAX_DIMS - 1]); + SWAP(size_t, rshape[shift_ax], rshape[ULAB_MAX_DIMS - 1]); + SWAP(int32_t, strides[shift_ax], strides[ULAB_MAX_DIMS - 1]); + + memcpy(rstrides, result->strides, ULAB_MAX_DIMS * sizeof(int32_t)); + SWAP(int32_t, rstrides[shift_ax], rstrides[ULAB_MAX_DIMS - 1]); + } + + uint8_t *rarray = (uint8_t *)result->array; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + if(axis != mp_const_none) { + iterable = mp_getiter(condition, &iter_buf); + } + do { + item = mp_iternext(iterable); + if(mp_obj_is_true(item)) { + memcpy(rarray, array, ndarray->itemsize); + rarray += rstrides[ULAB_MAX_DIMS - 1]; + } + array += strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + array -= strides[ULAB_MAX_DIMS - 1] * shape[ULAB_MAX_DIMS - 1]; + array += strides[ULAB_MAX_DIMS - 2]; + rarray -= rstrides[ULAB_MAX_DIMS - 1] * rshape[ULAB_MAX_DIMS - 1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= strides[ULAB_MAX_DIMS - 2] * shape[ULAB_MAX_DIMS - 2]; + array += strides[ULAB_MAX_DIMS - 3]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * rshape[ULAB_MAX_DIMS - 2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= strides[ULAB_MAX_DIMS - 3] * shape[ULAB_MAX_DIMS - 3]; + array += strides[ULAB_MAX_DIMS - 4]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * rshape[ULAB_MAX_DIMS - 2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + i++; + } while(i < shape[ULAB_MAX_DIMS - 4]); + #endif + + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(size_t, rshape, ULAB_MAX_DIMS); + m_del(int32_t, strides, ULAB_MAX_DIMS); + m_del(int32_t, rstrides, ULAB_MAX_DIMS); + + return result; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(transform_compress_obj, 2, transform_compress); +#endif /* ULAB_NUMPY_HAS_COMPRESS */ + +#if ULAB_NUMPY_HAS_DELETE +static mp_obj_t transform_delete(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("first argument must be an ndarray")); + } + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + uint8_t *array = (uint8_t *)ndarray->array; + + mp_obj_t indices = args[1].u_obj; + + mp_obj_t axis = args[2].u_obj; + + int8_t shift_ax; + + size_t axis_len; + + if(axis != mp_const_none) { + int8_t ax = tools_get_axis(axis, ndarray->ndim); + shift_ax = ULAB_MAX_DIMS - ndarray->ndim + ax; + axis_len = ndarray->shape[shift_ax]; + } else { + axis_len = ndarray->len; + } + + size_t index_len; + if(mp_obj_is_int(indices)) { + index_len = 1; + } else { + if(mp_obj_len_maybe(indices) == MP_OBJ_NULL) { + mp_raise_TypeError(translate("wrong index type")); + } + index_len = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(indices)); + } + + if(index_len > axis_len) { + mp_raise_ValueError(translate("wrong length of index array")); + } + + size_t *index_array = m_new(size_t, index_len); + + if(mp_obj_is_int(indices)) { + ssize_t value = (ssize_t)mp_obj_get_int(indices); + if(value < 0) { + value += axis_len; + } + if((value < 0) || (value > (ssize_t)axis_len)) { + mp_raise_ValueError(translate("index is out of bounds")); + } else { + *index_array++ = (size_t)value; + } + } else { + mp_obj_iter_buf_t iter_buf; + mp_obj_t item, iterable = mp_getiter(indices, &iter_buf); + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + ssize_t value = (ssize_t)mp_obj_get_int(item); + if(value < 0) { + value += axis_len; + } + if((value < 0) || (value > (ssize_t)axis_len)) { + mp_raise_ValueError(translate("index is out of bounds")); + } else { + *index_array++ = (size_t)value; + } + } + } + + // sort the array, since it is not guaranteed that the input is sorted + HEAPSORT1(size_t, index_array, 1, index_len); + + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + memcpy(shape, ndarray->shape, ULAB_MAX_DIMS * sizeof(size_t)); + + size_t *rshape = m_new(size_t, ULAB_MAX_DIMS); + memcpy(rshape, ndarray->shape, ULAB_MAX_DIMS * sizeof(size_t)); + + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + memcpy(strides, ndarray->strides, ULAB_MAX_DIMS * sizeof(int32_t)); + + int32_t *rstrides = m_new0(int32_t, ULAB_MAX_DIMS); + + ndarray_obj_t *result = NULL; + + if(axis == mp_const_none) { + result = ndarray_new_linear_array(ndarray->len - index_len, ndarray->dtype); + rstrides[ULAB_MAX_DIMS - 1] = ndarray->itemsize; + memset(rshape, 0, sizeof(size_t) * ULAB_MAX_DIMS); + } else { + rshape[shift_ax] = shape[shift_ax] - index_len; + + result = ndarray_new_dense_ndarray(ndarray->ndim, rshape, ndarray->dtype); + + SWAP(size_t, shape[shift_ax], shape[ULAB_MAX_DIMS - 1]); + SWAP(size_t, rshape[shift_ax], rshape[ULAB_MAX_DIMS - 1]); + SWAP(int32_t, strides[shift_ax], strides[ULAB_MAX_DIMS - 1]); + + memcpy(rstrides, result->strides, ULAB_MAX_DIMS * sizeof(int32_t)); + SWAP(int32_t, rstrides[shift_ax], rstrides[ULAB_MAX_DIMS - 1]); + } + + uint8_t *rarray = (uint8_t *)result->array; + index_array -= index_len; + size_t count = 0; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + if(count == *index_array) { + index_array++; + } else { + memcpy(rarray, array, ndarray->itemsize); + rarray += rstrides[ULAB_MAX_DIMS - 1]; + } + array += strides[ULAB_MAX_DIMS - 1]; + l++; + count++; + } while(l < shape[ULAB_MAX_DIMS - 1]); + if(axis != mp_const_none) { + index_array -= index_len; + count = 0; + } + #if ULAB_MAX_DIMS > 1 + array -= strides[ULAB_MAX_DIMS - 1] * shape[ULAB_MAX_DIMS - 1]; + array += strides[ULAB_MAX_DIMS - 2]; + rarray -= rstrides[ULAB_MAX_DIMS - 1] * rshape[ULAB_MAX_DIMS - 1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= strides[ULAB_MAX_DIMS - 2] * shape[ULAB_MAX_DIMS - 2]; + array += strides[ULAB_MAX_DIMS - 3]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * rshape[ULAB_MAX_DIMS - 2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= strides[ULAB_MAX_DIMS - 3] * shape[ULAB_MAX_DIMS - 3]; + array += strides[ULAB_MAX_DIMS - 4]; + rarray -= rstrides[ULAB_MAX_DIMS - 3] * rshape[ULAB_MAX_DIMS - 3]; + rarray += rstrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < shape[ULAB_MAX_DIMS - 4]); + #endif + + // TODO: deleting shape generates a seg fault + // m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(size_t, rshape, ULAB_MAX_DIMS); + m_del(int32_t, strides, ULAB_MAX_DIMS); + m_del(int32_t, rstrides, ULAB_MAX_DIMS); + + return MP_OBJ_FROM_PTR(result); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(transform_delete_obj, 2, transform_delete); +#endif /* ULAB_NUMPY_HAS_DELETE */ + + #if ULAB_MAX_DIMS > 1 #if ULAB_NUMPY_HAS_DOT //| def dot(m1: ulab.numpy.ndarray, m2: ulab.numpy.ndarray) -> Union[ulab.numpy.ndarray, _float]: @@ -39,6 +359,9 @@ mp_obj_t transform_dot(mp_obj_t _m1, mp_obj_t _m2) { } ndarray_obj_t *m1 = MP_OBJ_TO_PTR(_m1); ndarray_obj_t *m2 = MP_OBJ_TO_PTR(_m2); + COMPLEX_DTYPE_NOT_IMPLEMENTED(m1->dtype) + COMPLEX_DTYPE_NOT_IMPLEMENTED(m2->dtype) + uint8_t *array1 = (uint8_t *)m1->array; uint8_t *array2 = (uint8_t *)m2->array; @@ -86,5 +409,42 @@ mp_obj_t transform_dot(mp_obj_t _m1, mp_obj_t _m2) { } MP_DEFINE_CONST_FUN_OBJ_2(transform_dot_obj, transform_dot); +#endif /* ULAB_NUMPY_HAS_DOT */ +#endif /* ULAB_MAX_DIMS > 1 */ + +#if ULAB_NUMPY_HAS_SIZE +static mp_obj_t transform_size(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(ulab_tools_mp_obj_is_scalar(args[0].u_obj)) { + return mp_obj_new_int(1); + } + + if(!ndarray_object_is_array_like(args[0].u_obj)) { + mp_raise_TypeError(translate("first argument must be an ndarray")); + } + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + return mp_obj_len_maybe(args[0].u_obj); + } + + // at this point, the args[0] is most certainly an ndarray + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + mp_obj_t axis = args[1].u_obj; + size_t len; + if(axis != mp_const_none) { + int8_t ax = tools_get_axis(axis, ndarray->ndim); + len = ndarray->shape[ULAB_MAX_DIMS - ndarray->ndim + ax]; + } else { + len = ndarray->len; + } + + return mp_obj_new_int(len); +} +MP_DEFINE_CONST_FUN_OBJ_KW(transform_size_obj, 1, transform_size); #endif -#endif \ No newline at end of file diff --git a/python/port/mod/ulab/numpy/transform.h b/python/port/mod/ulab/numpy/transform.h index f4a09b8ef81..bfb4482cce2 100644 --- a/python/port/mod/ulab/numpy/transform.h +++ b/python/port/mod/ulab/numpy/transform.h @@ -21,8 +21,10 @@ #include "../ulab.h" #include "../ulab_tools.h" -#include "transform.h" +MP_DECLARE_CONST_FUN_OBJ_KW(transform_compress_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(transform_delete_obj); MP_DECLARE_CONST_FUN_OBJ_2(transform_dot_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(transform_size_obj); #endif diff --git a/python/port/mod/ulab/numpy/vector.c b/python/port/mod/ulab/numpy/vector.c index ceba255989b..97ab66d2116 100644 --- a/python/port/mod/ulab/numpy/vector.c +++ b/python/port/mod/ulab/numpy/vector.c @@ -22,6 +22,7 @@ #include "../ulab.h" #include "../ulab_tools.h" +#include "carray/carray_tools.h" #include "vector.h" //| """Element-by-element functions @@ -31,7 +32,7 @@ //| much more efficient than expressing the same operation as a Python loop.""" //| -static mp_obj_t vectorise_generic_vector(mp_obj_t o_in, mp_float_t (*f)(mp_float_t)) { +static mp_obj_t vector_generic_vector(mp_obj_t o_in, mp_float_t (*f)(mp_float_t)) { // Return a single value, if o_in is not iterable if(mp_obj_is_float(o_in) || mp_obj_is_int(o_in)) { return mp_obj_new_float(f(mp_obj_get_float(o_in))); @@ -39,6 +40,7 @@ static mp_obj_t vectorise_generic_vector(mp_obj_t o_in, mp_float_t (*f)(mp_float ndarray_obj_t *ndarray = NULL; if(mp_obj_is_type(o_in, &ulab_ndarray_type)) { ndarray_obj_t *source = MP_OBJ_TO_PTR(o_in); + COMPLEX_DTYPE_NOT_IMPLEMENTED(source->dtype) uint8_t *sarray = (uint8_t *)source->array; ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_FLOAT); mp_float_t *array = (mp_float_t *)ndarray->array; @@ -99,10 +101,10 @@ static mp_obj_t vectorise_generic_vector(mp_obj_t o_in, mp_float_t (*f)(mp_float #endif /* ULAB_VECTORISE_USES_FUN_POINTER */ } else { ndarray = ndarray_from_mp_obj(o_in, 0); - mp_float_t *array = (mp_float_t *)ndarray->array; + mp_float_t *narray = (mp_float_t *)ndarray->array; for(size_t i = 0; i < ndarray->len; i++) { - *array = f(*array); - array++; + *narray = f(*narray); + narray++; } } return MP_OBJ_FROM_PTR(ndarray); @@ -115,7 +117,7 @@ static mp_obj_t vectorise_generic_vector(mp_obj_t o_in, mp_float_t (*f)(mp_float //| MATH_FUN_1(acos, acos); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_acos_obj, vectorise_acos); +MP_DEFINE_CONST_FUN_OBJ_1(vector_acos_obj, vector_acos); #endif #if ULAB_NUMPY_HAS_ACOSH @@ -125,7 +127,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_acos_obj, vectorise_acos); //| MATH_FUN_1(acosh, acosh); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_acosh_obj, vectorise_acosh); +MP_DEFINE_CONST_FUN_OBJ_1(vector_acosh_obj, vector_acosh); #endif #if ULAB_NUMPY_HAS_ASIN @@ -135,7 +137,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_acosh_obj, vectorise_acosh); //| MATH_FUN_1(asin, asin); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_asin_obj, vectorise_asin); +MP_DEFINE_CONST_FUN_OBJ_1(vector_asin_obj, vector_asin); #endif #if ULAB_NUMPY_HAS_ASINH @@ -145,7 +147,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_asin_obj, vectorise_asin); //| MATH_FUN_1(asinh, asinh); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_asinh_obj, vectorise_asinh); +MP_DEFINE_CONST_FUN_OBJ_1(vector_asinh_obj, vector_asinh); #endif #if ULAB_NUMPY_HAS_AROUND @@ -155,7 +157,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_asinh_obj, vectorise_asinh); //| ... //| -mp_obj_t vectorise_around(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { +mp_obj_t vector_around(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} }, { MP_QSTR_decimals, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0 } } @@ -169,6 +171,7 @@ mp_obj_t vectorise_around(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ int8_t n = args[1].u_int; mp_float_t mul = MICROPY_FLOAT_C_FUN(pow)(10.0, n); ndarray_obj_t *source = MP_OBJ_TO_PTR(args[0].u_obj); + COMPLEX_DTYPE_NOT_IMPLEMENTED(source->dtype) ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_FLOAT); mp_float_t *narray = (mp_float_t *)ndarray->array; uint8_t *sarray = (uint8_t *)source->array; @@ -215,7 +218,7 @@ mp_obj_t vectorise_around(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ return MP_OBJ_FROM_PTR(ndarray); } -MP_DEFINE_CONST_FUN_OBJ_KW(vectorise_around_obj, 1, vectorise_around); +MP_DEFINE_CONST_FUN_OBJ_KW(vector_around_obj, 1, vector_around); #endif #if ULAB_NUMPY_HAS_ATAN @@ -226,7 +229,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(vectorise_around_obj, 1, vectorise_around); //| MATH_FUN_1(atan, atan); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_atan_obj, vectorise_atan); +MP_DEFINE_CONST_FUN_OBJ_1(vector_atan_obj, vector_atan); #endif #if ULAB_NUMPY_HAS_ARCTAN2 @@ -236,9 +239,12 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_atan_obj, vectorise_atan); //| ... //| -mp_obj_t vectorise_arctan2(mp_obj_t y, mp_obj_t x) { +mp_obj_t vector_arctan2(mp_obj_t y, mp_obj_t x) { ndarray_obj_t *ndarray_x = ndarray_from_mp_obj(x, 0); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray_x->dtype) + ndarray_obj_t *ndarray_y = ndarray_from_mp_obj(y, 0); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray_y->dtype) uint8_t ndim = 0; size_t *shape = m_new(size_t, ULAB_MAX_DIMS); @@ -309,7 +315,7 @@ mp_obj_t vectorise_arctan2(mp_obj_t y, mp_obj_t x) { return MP_OBJ_FROM_PTR(results); } -MP_DEFINE_CONST_FUN_OBJ_2(vectorise_arctan2_obj, vectorise_arctan2); +MP_DEFINE_CONST_FUN_OBJ_2(vector_arctan2_obj, vector_arctan2); #endif /* ULAB_VECTORISE_HAS_ARCTAN2 */ #if ULAB_NUMPY_HAS_ATANH @@ -319,7 +325,7 @@ MP_DEFINE_CONST_FUN_OBJ_2(vectorise_arctan2_obj, vectorise_arctan2); //| MATH_FUN_1(atanh, atanh); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_atanh_obj, vectorise_atanh); +MP_DEFINE_CONST_FUN_OBJ_1(vector_atanh_obj, vector_atanh); #endif #if ULAB_NUMPY_HAS_CEIL @@ -329,7 +335,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_atanh_obj, vectorise_atanh); //| MATH_FUN_1(ceil, ceil); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_ceil_obj, vectorise_ceil); +MP_DEFINE_CONST_FUN_OBJ_1(vector_ceil_obj, vector_ceil); #endif #if ULAB_NUMPY_HAS_COS @@ -339,7 +345,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_ceil_obj, vectorise_ceil); //| MATH_FUN_1(cos, cos); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_cos_obj, vectorise_cos); +MP_DEFINE_CONST_FUN_OBJ_1(vector_cos_obj, vector_cos); #endif #if ULAB_NUMPY_HAS_COSH @@ -349,7 +355,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_cos_obj, vectorise_cos); //| MATH_FUN_1(cosh, cosh); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_cosh_obj, vectorise_cosh); +MP_DEFINE_CONST_FUN_OBJ_1(vector_cosh_obj, vector_cosh); #endif #if ULAB_NUMPY_HAS_DEGREES @@ -358,15 +364,15 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_cosh_obj, vectorise_cosh); //| ... //| -static mp_float_t vectorise_degrees_(mp_float_t value) { +static mp_float_t vector_degrees_(mp_float_t value) { return value * MICROPY_FLOAT_CONST(180.0) / MP_PI; } -static mp_obj_t vectorise_degrees(mp_obj_t x_obj) { - return vectorise_generic_vector(x_obj, vectorise_degrees_); +static mp_obj_t vector_degrees(mp_obj_t x_obj) { + return vector_generic_vector(x_obj, vector_degrees_); } -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_degrees_obj, vectorise_degrees); +MP_DEFINE_CONST_FUN_OBJ_1(vector_degrees_obj, vector_degrees); #endif #if ULAB_SCIPY_SPECIAL_HAS_ERF @@ -376,7 +382,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_degrees_obj, vectorise_degrees); //| MATH_FUN_1(erf, erf); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_erf_obj, vectorise_erf); +MP_DEFINE_CONST_FUN_OBJ_1(vector_erf_obj, vector_erf); #endif #if ULAB_SCIPY_SPECIAL_HAS_ERFC @@ -386,7 +392,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_erf_obj, vectorise_erf); //| MATH_FUN_1(erfc, erfc); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_erfc_obj, vectorise_erfc); +MP_DEFINE_CONST_FUN_OBJ_1(vector_erfc_obj, vector_erfc); #endif #if ULAB_NUMPY_HAS_EXP @@ -395,8 +401,69 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_erfc_obj, vectorise_erfc); //| ... //| -MATH_FUN_1(exp, exp); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_exp_obj, vectorise_exp); +static mp_obj_t vector_exp(mp_obj_t o_in) { + #if ULAB_SUPPORTS_COMPLEX + if(mp_obj_is_type(o_in, &mp_type_complex)) { + mp_float_t real, imag; + mp_obj_get_complex(o_in, &real, &imag); + mp_float_t exp_real = MICROPY_FLOAT_C_FUN(exp)(real); + return mp_obj_new_complex(exp_real * MICROPY_FLOAT_C_FUN(cos)(imag), exp_real * MICROPY_FLOAT_C_FUN(sin)(imag)); + } else if(mp_obj_is_type(o_in, &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(o_in); + if(source->dtype == NDARRAY_COMPLEX) { + uint8_t *sarray = (uint8_t *)source->array; + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_COMPLEX); + mp_float_t *array = (mp_float_t *)ndarray->array; + uint8_t itemsize = sizeof(mp_float_t); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t real = *(mp_float_t *)sarray; + mp_float_t imag = *(mp_float_t *)(sarray + itemsize); + mp_float_t exp_real = MICROPY_FLOAT_C_FUN(exp)(real); + *array++ = exp_real * MICROPY_FLOAT_C_FUN(cos)(imag); + *array++ = exp_real * MICROPY_FLOAT_C_FUN(sin)(imag); + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + return MP_OBJ_FROM_PTR(ndarray); + } + } + #endif + return vector_generic_vector(o_in, MICROPY_FLOAT_C_FUN(exp)); +} + +MP_DEFINE_CONST_FUN_OBJ_1(vector_exp_obj, vector_exp); #endif #if ULAB_NUMPY_HAS_EXPM1 @@ -406,7 +473,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_exp_obj, vectorise_exp); //| MATH_FUN_1(expm1, expm1); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_expm1_obj, vectorise_expm1); +MP_DEFINE_CONST_FUN_OBJ_1(vector_expm1_obj, vector_expm1); #endif #if ULAB_NUMPY_HAS_FLOOR @@ -416,7 +483,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_expm1_obj, vectorise_expm1); //| MATH_FUN_1(floor, floor); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_floor_obj, vectorise_floor); +MP_DEFINE_CONST_FUN_OBJ_1(vector_floor_obj, vector_floor); #endif #if ULAB_SCIPY_SPECIAL_HAS_GAMMA @@ -426,7 +493,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_floor_obj, vectorise_floor); //| MATH_FUN_1(gamma, tgamma); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_gamma_obj, vectorise_gamma); +MP_DEFINE_CONST_FUN_OBJ_1(vector_gamma_obj, vector_gamma); #endif #if ULAB_SCIPY_SPECIAL_HAS_GAMMALN @@ -436,7 +503,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_gamma_obj, vectorise_gamma); //| MATH_FUN_1(lgamma, lgamma); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_lgamma_obj, vectorise_lgamma); +MP_DEFINE_CONST_FUN_OBJ_1(vector_lgamma_obj, vector_lgamma); #endif #if ULAB_NUMPY_HAS_LOG @@ -446,7 +513,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_lgamma_obj, vectorise_lgamma); //| MATH_FUN_1(log, log); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log_obj, vectorise_log); +MP_DEFINE_CONST_FUN_OBJ_1(vector_log_obj, vector_log); #endif #if ULAB_NUMPY_HAS_LOG10 @@ -456,7 +523,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log_obj, vectorise_log); //| MATH_FUN_1(log10, log10); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log10_obj, vectorise_log10); +MP_DEFINE_CONST_FUN_OBJ_1(vector_log10_obj, vector_log10); #endif #if ULAB_NUMPY_HAS_LOG2 @@ -466,7 +533,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log10_obj, vectorise_log10); //| MATH_FUN_1(log2, log2); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log2_obj, vectorise_log2); +MP_DEFINE_CONST_FUN_OBJ_1(vector_log2_obj, vector_log2); #endif #if ULAB_NUMPY_HAS_RADIANS @@ -475,15 +542,15 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log2_obj, vectorise_log2); //| ... //| -static mp_float_t vectorise_radians_(mp_float_t value) { +static mp_float_t vector_radians_(mp_float_t value) { return value * MP_PI / MICROPY_FLOAT_CONST(180.0); } -static mp_obj_t vectorise_radians(mp_obj_t x_obj) { - return vectorise_generic_vector(x_obj, vectorise_radians_); +static mp_obj_t vector_radians(mp_obj_t x_obj) { + return vector_generic_vector(x_obj, vector_radians_); } -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_radians_obj, vectorise_radians); +MP_DEFINE_CONST_FUN_OBJ_1(vector_radians_obj, vector_radians); #endif #if ULAB_NUMPY_HAS_SIN @@ -493,7 +560,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_radians_obj, vectorise_radians); //| MATH_FUN_1(sin, sin); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_sin_obj, vectorise_sin); +MP_DEFINE_CONST_FUN_OBJ_1(vector_sin_obj, vector_sin); #endif #if ULAB_NUMPY_HAS_SINH @@ -503,18 +570,158 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_sin_obj, vectorise_sin); //| MATH_FUN_1(sinh, sinh); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_sinh_obj, vectorise_sinh); +MP_DEFINE_CONST_FUN_OBJ_1(vector_sinh_obj, vector_sinh); #endif + #if ULAB_NUMPY_HAS_SQRT //| def sqrt(a: _ArrayLike) -> ulab.numpy.ndarray: //| """Computes the square root""" //| ... //| +#if ULAB_SUPPORTS_COMPLEX +mp_obj_t vector_sqrt(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = mp_const_none } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_INT(NDARRAY_FLOAT) } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t o_in = args[0].u_obj; + uint8_t dtype = mp_obj_get_int(args[1].u_obj); + if((dtype != NDARRAY_FLOAT) && (dtype != NDARRAY_COMPLEX)) { + mp_raise_TypeError(translate("dtype must be float, or complex")); + } + + if(mp_obj_is_type(o_in, &mp_type_complex)) { + mp_float_t real, imag; + mp_obj_get_complex(o_in, &real, &imag); + mp_float_t sqrt_abs = MICROPY_FLOAT_C_FUN(sqrt)(real * real + imag * imag); + sqrt_abs = MICROPY_FLOAT_C_FUN(sqrt)(sqrt_abs); + mp_float_t theta = MICROPY_FLOAT_CONST(0.5) * MICROPY_FLOAT_C_FUN(atan2)(imag, real); + return mp_obj_new_complex(sqrt_abs * MICROPY_FLOAT_C_FUN(cos)(theta), sqrt_abs * MICROPY_FLOAT_C_FUN(sin)(theta)); + } else if(mp_obj_is_type(o_in, &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(o_in); + if((source->dtype == NDARRAY_COMPLEX) && (dtype == NDARRAY_FLOAT)) { + mp_raise_TypeError(translate("can't convert complex to float")); + } + + if(dtype == NDARRAY_COMPLEX) { + if(source->dtype == NDARRAY_COMPLEX) { + uint8_t *sarray = (uint8_t *)source->array; + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_COMPLEX); + mp_float_t *array = (mp_float_t *)ndarray->array; + uint8_t itemsize = sizeof(mp_float_t); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t real = *(mp_float_t *)sarray; + mp_float_t imag = *(mp_float_t *)(sarray + itemsize); + mp_float_t sqrt_abs = MICROPY_FLOAT_C_FUN(sqrt)(real * real + imag * imag); + sqrt_abs = MICROPY_FLOAT_C_FUN(sqrt)(sqrt_abs); + mp_float_t theta = MICROPY_FLOAT_CONST(0.5) * MICROPY_FLOAT_C_FUN(atan2)(imag, real); + *array++ = sqrt_abs * MICROPY_FLOAT_C_FUN(cos)(theta); + *array++ = sqrt_abs * MICROPY_FLOAT_C_FUN(sin)(theta); + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + return MP_OBJ_FROM_PTR(ndarray); + } else if(source->dtype == NDARRAY_FLOAT) { + uint8_t *sarray = (uint8_t *)source->array; + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_COMPLEX); + mp_float_t *array = (mp_float_t *)ndarray->array; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t value = *(mp_float_t *)sarray; + if(value >= MICROPY_FLOAT_CONST(0.0)) { + *array++ = MICROPY_FLOAT_C_FUN(sqrt)(value); + array++; + } else { + array++; + *array++ = MICROPY_FLOAT_C_FUN(sqrt)(-value); + } + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + return MP_OBJ_FROM_PTR(ndarray); + } else { + mp_raise_TypeError(translate("input dtype must be float or complex")); + } + } + } + return vector_generic_vector(o_in, MICROPY_FLOAT_C_FUN(sqrt)); +} +MP_DEFINE_CONST_FUN_OBJ_KW(vector_sqrt_obj, 1, vector_sqrt); +#else MATH_FUN_1(sqrt, sqrt); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_sqrt_obj, vectorise_sqrt); -#endif +MP_DEFINE_CONST_FUN_OBJ_1(vector_sqrt_obj, vector_sqrt); +#endif /* ULAB_SUPPORTS_COMPLEX */ + +#endif /* ULAB_NUMPY_HAS_SQRT */ #if ULAB_NUMPY_HAS_TAN //| def tan(a: _ArrayLike) -> ulab.numpy.ndarray: @@ -523,7 +730,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_sqrt_obj, vectorise_sqrt); //| MATH_FUN_1(tan, tan); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_tan_obj, vectorise_tan); +MP_DEFINE_CONST_FUN_OBJ_1(vector_tan_obj, vector_tan); #endif #if ULAB_NUMPY_HAS_TANH @@ -532,11 +739,11 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_tan_obj, vectorise_tan); //| ... MATH_FUN_1(tanh, tanh); -MP_DEFINE_CONST_FUN_OBJ_1(vectorise_tanh_obj, vectorise_tanh); +MP_DEFINE_CONST_FUN_OBJ_1(vector_tanh_obj, vector_tanh); #endif #if ULAB_NUMPY_HAS_VECTORIZE -static mp_obj_t vectorise_vectorized_function_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { +static mp_obj_t vector_vectorized_function_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void) n_args; (void) n_kw; vectorized_function_obj_t *self = MP_OBJ_TO_PTR(self_in); @@ -544,6 +751,7 @@ static mp_obj_t vectorise_vectorized_function_call(mp_obj_t self_in, size_t n_ar mp_obj_t fvalue; if(mp_obj_is_type(args[0], &ulab_ndarray_type)) { ndarray_obj_t *source = MP_OBJ_TO_PTR(args[0]); + COMPLEX_DTYPE_NOT_IMPLEMENTED(source->dtype) ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, self->otypes); for(size_t i=0; i < source->len; i++) { avalue[0] = mp_binary_get_val_array(source->dtype, source->array, i); @@ -575,12 +783,12 @@ static mp_obj_t vectorise_vectorized_function_call(mp_obj_t self_in, size_t n_ar return mp_const_none; } -const mp_obj_type_t vectorise_function_type = { +const mp_obj_type_t vector_function_type = { { &mp_type_type }, .flags = MP_TYPE_FLAG_EXTENDED, .name = MP_QSTR_, MP_TYPE_EXTENDED_FIELDS( - .call = vectorise_vectorized_function_call, + .call = vector_vectorized_function_call, ) }; @@ -598,7 +806,7 @@ const mp_obj_type_t vectorise_function_type = { //| ... //| -static mp_obj_t vectorise_vectorize(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { +static mp_obj_t vector_vectorize(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} }, { MP_QSTR_otypes, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} } @@ -625,12 +833,12 @@ static mp_obj_t vectorise_vectorize(size_t n_args, const mp_obj_t *pos_args, mp_ mp_raise_ValueError(translate("wrong output type")); } vectorized_function_obj_t *function = m_new_obj(vectorized_function_obj_t); - function->base.type = &vectorise_function_type; + function->base.type = &vector_function_type; function->otypes = otypes; function->fun = args[0].u_obj; function->type = type; return MP_OBJ_FROM_PTR(function); } -MP_DEFINE_CONST_FUN_OBJ_KW(vectorise_vectorize_obj, 1, vectorise_vectorize); +MP_DEFINE_CONST_FUN_OBJ_KW(vector_vectorize_obj, 1, vector_vectorize); #endif diff --git a/python/port/mod/ulab/numpy/vector.h b/python/port/mod/ulab/numpy/vector.h index dbd0b33ea7e..ea38b0fdcc7 100644 --- a/python/port/mod/ulab/numpy/vector.h +++ b/python/port/mod/ulab/numpy/vector.h @@ -15,35 +15,39 @@ #include "../ulab.h" #include "../ndarray.h" -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_acos_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_acosh_obj); -MP_DECLARE_CONST_FUN_OBJ_2(vectorise_arctan2_obj); -MP_DECLARE_CONST_FUN_OBJ_KW(vectorise_around_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_asin_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_asinh_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_atan_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_atanh_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_ceil_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_cos_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_cosh_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_degrees_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_erf_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_erfc_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_exp_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_expm1_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_floor_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_gamma_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_lgamma_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_log_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_log10_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_log2_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_radians_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_sin_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_sinh_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_sqrt_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_tan_obj); -MP_DECLARE_CONST_FUN_OBJ_1(vectorise_tanh_obj); -MP_DECLARE_CONST_FUN_OBJ_KW(vectorise_vectorize_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_acos_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_acosh_obj); +MP_DECLARE_CONST_FUN_OBJ_2(vector_arctan2_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(vector_around_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_asin_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_asinh_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_atan_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_atanh_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_ceil_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_cos_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_cosh_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_degrees_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_erf_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_erfc_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_exp_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_expm1_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_floor_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_gamma_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_lgamma_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_log_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_log10_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_log2_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_radians_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_sin_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_sinh_obj); +#if ULAB_SUPPORTS_COMPLEX +MP_DECLARE_CONST_FUN_OBJ_KW(vector_sqrt_obj); +#else +MP_DECLARE_CONST_FUN_OBJ_1(vector_sqrt_obj); +#endif +MP_DECLARE_CONST_FUN_OBJ_1(vector_tan_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_tanh_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(vector_vectorize_obj); typedef struct _vectorized_function_obj_t { mp_obj_base_t base; @@ -53,12 +57,13 @@ typedef struct _vectorized_function_obj_t { } vectorized_function_obj_t; #if ULAB_HAS_FUNCTION_ITERATOR -#define ITERATE_VECTOR(type, array, source, sarray)\ +#define ITERATE_VECTOR(type, array, source, sarray, shift)\ ({\ size_t *scoords = ndarray_new_coords((source)->ndim);\ for(size_t i=0; i < (source)->len/(source)->shape[ULAB_MAX_DIMS -1]; i++) {\ for(size_t l=0; l < (source)->shape[ULAB_MAX_DIMS - 1]; l++) {\ - *(array)++ = f(*((type *)(sarray)));\ + *(array) = f(*((type *)(sarray)));\ + (array) += (shift);\ (sarray) += (source)->strides[ULAB_MAX_DIMS - 1];\ }\ ndarray_rewind_array((source)->ndim, sarray, (source)->shape, (source)->strides, scoords);\ @@ -149,8 +154,8 @@ typedef struct _vectorized_function_obj_t { #endif /* ULAB_HAS_FUNCTION_ITERATOR */ #define MATH_FUN_1(py_name, c_name) \ - static mp_obj_t vectorise_ ## py_name(mp_obj_t x_obj) { \ - return vectorise_generic_vector(x_obj, MICROPY_FLOAT_C_FUN(c_name)); \ + static mp_obj_t vector_ ## py_name(mp_obj_t x_obj) { \ + return vector_generic_vector(x_obj, MICROPY_FLOAT_C_FUN(c_name)); \ } #endif /* _VECTOR_ */ diff --git a/python/port/mod/ulab/scipy/optimize/optimize.c b/python/port/mod/ulab/scipy/optimize/optimize.c index 30b61283de6..c2ed6fff86b 100644 --- a/python/port/mod/ulab/scipy/optimize/optimize.c +++ b/python/port/mod/ulab/scipy/optimize/optimize.c @@ -121,7 +121,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(optimize_bisect_obj, 3, optimize_bisect); //| Find a minimum of the function ``f(x)`` using the downhill simplex method. //| The located ``x`` is within ``fxtol`` of the actual minimum, and ``f(x)`` //| is within ``fatol`` of the actual minimum unless more than ``maxiter`` -//| steps are required.""" +//| steps are requried.""" //| ... //| @@ -344,7 +344,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(optimize_curve_fit_obj, 2, optimize_curve_fit); //| //| Find a solution (zero) of the function ``f(x)`` using Newton's Method. //| The result is accurate to within ``xtol * rtol * |f(x)|`` unless more than -//| ``maxiter`` steps are required.""" +//| ``maxiter`` steps are requried.""" //| ... //| diff --git a/python/port/mod/ulab/scipy/scipy.c b/python/port/mod/ulab/scipy/scipy.c index c37aa4ee8ea..a7683e2d344 100644 --- a/python/port/mod/ulab/scipy/scipy.c +++ b/python/port/mod/ulab/scipy/scipy.c @@ -48,4 +48,4 @@ mp_obj_module_t ulab_scipy_module = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t*)&mp_module_ulab_scipy_globals, }; -#endif +#endif /* ULAB_HAS_SCIPY */ diff --git a/python/port/mod/ulab/scipy/signal/signal.c b/python/port/mod/ulab/scipy/signal/signal.c index cc559b5981c..60dbad073f1 100644 --- a/python/port/mod/ulab/scipy/signal/signal.c +++ b/python/port/mod/ulab/scipy/signal/signal.c @@ -18,32 +18,9 @@ #include "../../ulab.h" #include "../../ndarray.h" -#include "../../numpy/fft/fft_tools.h" +#include "../../numpy/carray/carray_tools.h" -#if ULAB_SCIPY_SIGNAL_HAS_SPECTROGRAM -//| import ulab.numpy -//| -//| def spectrogram(r: ulab.numpy.ndarray) -> ulab.numpy.ndarray: -//| """ -//| :param ulab.numpy.ndarray r: A 1-dimension array of values whose size is a power of 2 -//| -//| Computes the spectrum of the input signal. This is the absolute value of the (complex-valued) fft of the signal. -//| This function is similar to scipy's ``scipy.signal.spectrogram``.""" -//| ... -//| - -mp_obj_t signal_spectrogram(size_t n_args, const mp_obj_t *args) { - if(n_args == 2) { - return fft_fft_ifft_spectrogram(n_args, args[0], args[1], FFT_SPECTROGRAM); - } else { - return fft_fft_ifft_spectrogram(n_args, args[0], mp_const_none, FFT_SPECTROGRAM); - } -} - -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(signal_spectrogram_obj, 1, 2, signal_spectrogram); -#endif /* ULAB_SCIPY_SIGNAL_HAS_SPECTROGRAM */ - -#if ULAB_SCIPY_SIGNAL_HAS_SOSFILT +#if ULAB_SCIPY_SIGNAL_HAS_SOSFILT & ULAB_MAX_DIMS > 1 static void signal_sosfilt_array(mp_float_t *x, const mp_float_t *coeffs, mp_float_t *zf, const size_t len) { for(size_t i=0; i < len; i++) { mp_float_t xn = *x; @@ -68,6 +45,12 @@ mp_obj_t signal_sosfilt(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar if(!ndarray_object_is_array_like(args[0].u_obj) || !ndarray_object_is_array_like(args[1].u_obj)) { mp_raise_TypeError(translate("sosfilt requires iterable arguments")); } + #if ULAB_SUPPORTS_COMPLEX + if(mp_obj_is_type(args[1].u_obj, &ulab_ndarray_type)) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[1].u_obj); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) + } + #endif size_t lenx = (size_t)mp_obj_get_int(mp_obj_len_maybe(args[1].u_obj)); ndarray_obj_t *y = ndarray_new_linear_array(lenx, NDARRAY_FLOAT); mp_float_t *yarray = (mp_float_t *)y->array; @@ -102,7 +85,7 @@ mp_obj_t signal_sosfilt(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar mp_raise_TypeError(translate("zi must be an ndarray")); } else { ndarray_obj_t *zi = MP_OBJ_TO_PTR(args[2].u_obj); - if((zi->shape[ULAB_MAX_DIMS - 1] != lensos) || (zi->shape[ULAB_MAX_DIMS - 1] != 2)) { + if((zi->shape[ULAB_MAX_DIMS - 2] != lensos) || (zi->shape[ULAB_MAX_DIMS - 1] != 2)) { mp_raise_ValueError(translate("zi must be of shape (n_section, 2)")); } if(zi->dtype != NDARRAY_FLOAT) { @@ -139,10 +122,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(signal_sosfilt_obj, 2, signal_sosfilt); static const mp_rom_map_elem_t ulab_scipy_signal_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_signal) }, - #if ULAB_SCIPY_SIGNAL_HAS_SPECTROGRAM - { MP_OBJ_NEW_QSTR(MP_QSTR_spectrogram), (mp_obj_t)&signal_spectrogram_obj }, - #endif - #if ULAB_SCIPY_SIGNAL_HAS_SOSFILT + #if ULAB_SCIPY_SIGNAL_HAS_SOSFILT & ULAB_MAX_DIMS > 1 { MP_OBJ_NEW_QSTR(MP_QSTR_sosfilt), (mp_obj_t)&signal_sosfilt_obj }, #endif }; diff --git a/python/port/mod/ulab/scipy/signal/signal.h b/python/port/mod/ulab/scipy/signal/signal.h index d33220e6299..3c2343a82c7 100644 --- a/python/port/mod/ulab/scipy/signal/signal.h +++ b/python/port/mod/ulab/scipy/signal/signal.h @@ -18,7 +18,6 @@ extern mp_obj_module_t ulab_scipy_signal_module; -MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(signal_spectrogram_obj); MP_DECLARE_CONST_FUN_OBJ_KW(signal_sosfilt_obj); #endif /* _SCIPY_SIGNAL_ */ diff --git a/python/port/mod/ulab/scipy/special/special.c b/python/port/mod/ulab/scipy/special/special.c index 82b53247d3a..79d9b77f34d 100644 --- a/python/port/mod/ulab/scipy/special/special.c +++ b/python/port/mod/ulab/scipy/special/special.c @@ -21,16 +21,16 @@ static const mp_rom_map_elem_t ulab_scipy_special_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_special) }, #if ULAB_SCIPY_SPECIAL_HAS_ERF - { MP_OBJ_NEW_QSTR(MP_QSTR_erf), (mp_obj_t)&vectorise_erf_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_erf), (mp_obj_t)&vector_erf_obj }, #endif #if ULAB_SCIPY_SPECIAL_HAS_ERFC - { MP_OBJ_NEW_QSTR(MP_QSTR_erfc), (mp_obj_t)&vectorise_erfc_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_erfc), (mp_obj_t)&vector_erfc_obj }, #endif #if ULAB_SCIPY_SPECIAL_HAS_GAMMA - { MP_OBJ_NEW_QSTR(MP_QSTR_gamma), (mp_obj_t)&vectorise_gamma_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_gamma), (mp_obj_t)&vector_gamma_obj }, #endif #if ULAB_SCIPY_SPECIAL_HAS_GAMMALN - { MP_OBJ_NEW_QSTR(MP_QSTR_gammaln), (mp_obj_t)&vectorise_lgamma_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_gammaln), (mp_obj_t)&vector_lgamma_obj }, #endif }; diff --git a/python/port/mod/ulab/ulab.c b/python/port/mod/ulab/ulab.c index 2b9ebd73f62..9785a8dd201 100644 --- a/python/port/mod/ulab/ulab.c +++ b/python/port/mod/ulab/ulab.c @@ -20,9 +20,9 @@ #include "py/objarray.h" #include "ulab.h" -#include "ulab_create.h" #include "ndarray.h" #include "ndarray_properties.h" +#include "numpy/create.h" #include "numpy/ndarray/ndarray_iter.h" #include "numpy/numpy.h" @@ -33,13 +33,21 @@ #include "user/user.h" #include "utils/utils.h" -#define ULAB_VERSION 3.3.8 +#define ULAB_VERSION 5.0.7 #define xstr(s) str(s) #define str(s) #s + +#if ULAB_SUPPORTS_COMPLEX +#define ULAB_VERSION_STRING xstr(ULAB_VERSION) xstr(-) xstr(ULAB_MAX_DIMS) xstr(D-c) +#else #define ULAB_VERSION_STRING xstr(ULAB_VERSION) xstr(-) xstr(ULAB_MAX_DIMS) xstr(D) +#endif STATIC MP_DEFINE_STR_OBJ(ulab_version_obj, ULAB_VERSION_STRING); +#ifdef ULAB_HASH +STATIC MP_DEFINE_STR_OBJ(ulab_sha_obj, xstr(ULAB_HASH)); +#endif STATIC const mp_rom_map_elem_t ulab_ndarray_locals_dict_table[] = { #if ULAB_MAX_DIMS > 1 @@ -62,6 +70,9 @@ STATIC const mp_rom_map_elem_t ulab_ndarray_locals_dict_table[] = { #if NDARRAY_HAS_TOBYTES { MP_ROM_QSTR(MP_QSTR_tobytes), MP_ROM_PTR(&ndarray_tobytes_obj) }, #endif + #if NDARRAY_HAS_TOLIST + { MP_ROM_QSTR(MP_QSTR_tolist), MP_ROM_PTR(&ndarray_tolist_obj) }, + #endif #if NDARRAY_HAS_SORT { MP_ROM_QSTR(MP_QSTR_sort), MP_ROM_PTR(&numerical_sort_inplace_obj) }, #endif @@ -141,6 +152,9 @@ const mp_obj_type_t ndarray_flatiter_type = { STATIC const mp_map_elem_t ulab_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_ulab) }, { MP_ROM_QSTR(MP_QSTR___version__), MP_ROM_PTR(&ulab_version_obj) }, + #ifdef ULAB_HASH + { MP_ROM_QSTR(MP_QSTR___sha__), MP_ROM_PTR(&ulab_sha_obj) }, + #endif #if ULAB_HAS_DTYPE_OBJECT { MP_OBJ_NEW_QSTR(MP_QSTR_dtype), (mp_obj_t)&ulab_dtype_type }, #else @@ -174,4 +188,10 @@ const mp_obj_module_t ulab_user_cmodule = { .globals = (mp_obj_dict_t*)&mp_module_ulab_globals, }; +// Use old three-argument MP_REGISTER_MODULE for +// MicroPython <= v1.18.0: (1 << 16) | (18 << 8) | 0 +#if MICROPY_VERSION <= 70144 MP_REGISTER_MODULE(MP_QSTR_ulab, ulab_user_cmodule, MODULE_ULAB_ENABLED); +#else +MP_REGISTER_MODULE(MP_QSTR_ulab, ulab_user_cmodule); +#endif diff --git a/python/port/mod/ulab/ulab.h b/python/port/mod/ulab/ulab.h index 248047c8528..abeda967f1c 100644 --- a/python/port/mod/ulab/ulab.h +++ b/python/port/mod/ulab/ulab.h @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2021 Zoltán Vörös + * Copyright (c) 2019-2022 Zoltán Vörös */ #ifndef __ULAB__ @@ -18,9 +18,9 @@ // // - how many dimensions ulab can handle // - which functions are included in the compiled firmware -// - whether the python syntax is numpy-like, or modular // - whether arrays can be sliced and iterated over // - which binary/unary operators are supported +// - whether ulab can deal with complex numbers // // A considerable amount of flash space can be saved by removing (setting // the corresponding constants to 0) the unnecessary functions and features. @@ -31,6 +31,10 @@ #include ULAB_CONFIG_FILE #endif +// Adds support for complex ndarrays +#ifndef ULAB_SUPPORTS_COMPLEX +#define ULAB_SUPPORTS_COMPLEX (0) +#endif // Determines, whether scipy is defined in ulab. The sub-modules and functions // of scipy have to be defined separately @@ -228,6 +232,10 @@ #define NDARRAY_HAS_TOBYTES (1) #endif +#ifndef NDARRAY_HAS_TOLIST +#define NDARRAY_HAS_TOLIST (1) +#endif + #ifndef NDARRAY_HAS_TRANSPOSE #define NDARRAY_HAS_TRANSPOSE (1) #endif @@ -385,6 +393,15 @@ #define ULAB_NUMPY_HAS_FFT_MODULE (1) #endif +// By setting this constant to 1, the FFT routine will behave in a +// numpy-compatible way, i.e., it will output a complex array +// This setting has no effect, if ULAB_SUPPORTS_COMPLEX is 0 +// Note that in this case, the input also must be numpythonic, +// i.e., the real an imaginary parts cannot be passed as two arguments +#ifndef ULAB_FFT_IS_NUMPY_COMPATIBLE +#define ULAB_FFT_IS_NUMPY_COMPATIBLE (0) +#endif + #ifndef ULAB_FFT_HAS_FFT #define ULAB_FFT_HAS_FFT (1) #endif @@ -409,6 +426,14 @@ #define ULAB_NUMPY_HAS_ARGSORT (1) #endif +#ifndef ULAB_NUMPY_HAS_ASARRAY +#define ULAB_NUMPY_HAS_ASARRAY (0) +#endif + +#ifndef ULAB_NUMPY_HAS_COMPRESS +#define ULAB_NUMPY_HAS_COMPRESS (1) +#endif + #ifndef ULAB_NUMPY_HAS_CONVOLVE #define ULAB_NUMPY_HAS_CONVOLVE (1) #endif @@ -417,6 +442,10 @@ #define ULAB_NUMPY_HAS_CROSS (1) #endif +#ifndef ULAB_NUMPY_HAS_DELETE +#define ULAB_NUMPY_HAS_DELETE (1) +#endif + #ifndef ULAB_NUMPY_HAS_DIFF #define ULAB_NUMPY_HAS_DIFF (1) #endif @@ -433,6 +462,14 @@ #define ULAB_NUMPY_HAS_INTERP (1) #endif +#ifndef ULAB_NUMPY_HAS_LOAD +#define ULAB_NUMPY_HAS_LOAD (0) +#endif + +#ifndef ULAB_NUMPY_HAS_LOADTXT +#define ULAB_NUMPY_HAS_LOADTXT (0) +#endif + #ifndef ULAB_NUMPY_HAS_MEAN #define ULAB_NUMPY_HAS_MEAN (1) #endif @@ -457,6 +494,18 @@ #define ULAB_NUMPY_HAS_ROLL (1) #endif +#ifndef ULAB_NUMPY_HAS_SAVE +#define ULAB_NUMPY_HAS_SAVE (0) +#endif + +#ifndef ULAB_NUMPY_HAS_SAVETXT +#define ULAB_NUMPY_HAS_SAVETXT (0) +#endif + +#ifndef ULAB_NUMPY_HAS_SIZE +#define ULAB_NUMPY_HAS_SIZE (1) +#endif + #ifndef ULAB_NUMPY_HAS_SORT #define ULAB_NUMPY_HAS_SORT (1) #endif @@ -579,6 +628,25 @@ #define ULAB_NUMPY_HAS_VECTORIZE (1) #endif +// Complex functions. The implementations are compiled into +// the firmware, only if ULAB_SUPPORTS_COMPLEX is set to 1 +#ifndef ULAB_NUMPY_HAS_CONJUGATE +#define ULAB_NUMPY_HAS_CONJUGATE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_IMAG +#define ULAB_NUMPY_HAS_IMAG (1) +#endif + +#ifndef ULAB_NUMPY_HAS_REAL +#define ULAB_NUMPY_HAS_REAL (1) +#endif + +#ifndef ULAB_NUMPY_HAS_SORT_COMPLEX +#define ULAB_NUMPY_HAS_SORT_COMPLEX (0) +#endif + +// scipy modules #ifndef ULAB_SCIPY_HAS_LINALG_MODULE #define ULAB_SCIPY_HAS_LINALG_MODULE (1) #endif @@ -595,10 +663,6 @@ #define ULAB_SCIPY_HAS_SIGNAL_MODULE (1) #endif -#ifndef ULAB_SCIPY_SIGNAL_HAS_SPECTROGRAM -#define ULAB_SCIPY_SIGNAL_HAS_SPECTROGRAM (1) -#endif - #ifndef ULAB_SCIPY_SIGNAL_HAS_SOSFILT #define ULAB_SCIPY_SIGNAL_HAS_SOSFILT (1) #endif @@ -643,12 +707,7 @@ #define ULAB_SCIPY_SPECIAL_HAS_GAMMALN (1) #endif -// user-defined module; source of the module and -// its sub-modules should be placed in code/user/ -#ifndef ULAB_HAS_USER_MODULE -#define ULAB_HAS_USER_MODULE (0) -#endif - +// functions of the utils module #ifndef ULAB_HAS_UTILS_MODULE #define ULAB_HAS_UTILS_MODULE (1) #endif @@ -669,4 +728,14 @@ #define ULAB_UTILS_HAS_FROM_UINT32_BUFFER (1) #endif +#ifndef ULAB_UTILS_HAS_SPECTROGRAM +#define ULAB_UTILS_HAS_SPECTROGRAM (1) +#endif + +// user-defined module; source of the module and +// its sub-modules should be placed in code/user/ +#ifndef ULAB_HAS_USER_MODULE +#define ULAB_HAS_USER_MODULE (0) +#endif + #endif diff --git a/python/port/mod/ulab/ulab_tools.c b/python/port/mod/ulab/ulab_tools.c index acd3d8a586c..68f14bf552e 100644 --- a/python/port/mod/ulab/ulab_tools.c +++ b/python/port/mod/ulab/ulab_tools.c @@ -5,7 +5,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2020-2021 Zoltán Vörös + * Copyright (c) 2020-2022 Zoltán Vörös */ @@ -216,6 +216,14 @@ shape_strides tools_reduce_axes(ndarray_obj_t *ndarray, mp_obj_t axis) { return _shape_strides; } +int8_t tools_get_axis(mp_obj_t axis, uint8_t ndim) { + int8_t ax = mp_obj_get_int(axis); + if(ax < 0) ax += ndim; + if((ax < 0) || (ax > ndim - 1)) { + mp_raise_ValueError(translate("axis is out of bounds")); + } + return ax; +} #if ULAB_MAX_DIMS > 1 ndarray_obj_t *tools_object_is_square(mp_obj_t obj) { @@ -231,3 +239,38 @@ ndarray_obj_t *tools_object_is_square(mp_obj_t obj) { return ndarray; } #endif + +uint8_t ulab_binary_get_size(uint8_t dtype) { + #if ULAB_SUPPORTS_COMPLEX + if(dtype == NDARRAY_COMPLEX) { + return 2 * (uint8_t)sizeof(mp_float_t); + } + #endif + return dtype == NDARRAY_BOOL ? 1 : mp_binary_get_size('@', dtype, NULL); +} + +#if ULAB_SUPPORTS_COMPLEX +void ulab_rescale_float_strides(int32_t *strides) { + // re-scale the strides, so that we can work with floats, when iterating + uint8_t sz = sizeof(mp_float_t); + for(uint8_t i = 0; i < ULAB_MAX_DIMS; i++) { + strides[i] /= sz; + } +} +#endif + +bool ulab_tools_mp_obj_is_scalar(mp_obj_t obj) { + #if ULAB_SUPPORTS_COMPLEX + if(mp_obj_is_int(obj) || mp_obj_is_float(obj) || mp_obj_is_type(obj, &mp_type_complex)) { + return true; + } else { + return false; + } + #else + if(mp_obj_is_int(obj) || mp_obj_is_float(obj)) { + return true; + } else { + return false; + } + #endif +} \ No newline at end of file diff --git a/python/port/mod/ulab/ulab_tools.h b/python/port/mod/ulab/ulab_tools.h index 378e4f0ca62..5ae99df9078 100644 --- a/python/port/mod/ulab/ulab_tools.h +++ b/python/port/mod/ulab/ulab_tools.h @@ -5,7 +5,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2020-2021 Zoltán Vörös + * Copyright (c) 2020-2022 Zoltán Vörös */ #ifndef _TOOLS_ @@ -33,5 +33,14 @@ uint8_t ndarray_upcast_dtype(uint8_t , uint8_t ); void *ndarray_set_float_function(uint8_t ); shape_strides tools_reduce_axes(ndarray_obj_t *, mp_obj_t ); +int8_t tools_get_axis(mp_obj_t , uint8_t ); ndarray_obj_t *tools_object_is_square(mp_obj_t ); + +uint8_t ulab_binary_get_size(uint8_t ); + +#if ULAB_SUPPORTS_COMPLEX +void ulab_rescale_float_strides(int32_t *); +#endif + +bool ulab_tools_mp_obj_is_scalar(mp_obj_t ); #endif diff --git a/python/port/mod/ulab/user/user.c b/python/port/mod/ulab/user/user.c index f69089da5f6..835c091c7c2 100644 --- a/python/port/mod/ulab/user/user.c +++ b/python/port/mod/ulab/user/user.c @@ -74,7 +74,7 @@ static mp_obj_t user_square(mp_obj_t arg) { *rarray++ = (*array) * (*array); } } - // at the end, return a micropython object + // at the end, return a micrppython object return MP_OBJ_FROM_PTR(results); } diff --git a/python/port/mod/ulab/utils/utils.c b/python/port/mod/ulab/utils/utils.c index 2b7dc093c02..31b6893c75d 100644 --- a/python/port/mod/ulab/utils/utils.c +++ b/python/port/mod/ulab/utils/utils.c @@ -16,6 +16,8 @@ #include "py/misc.h" #include "utils.h" +#include "../numpy/fft/fft_tools.h" + #if ULAB_HAS_UTILS_MODULE enum UTILS_BUFFER_TYPE { @@ -187,8 +189,41 @@ static mp_obj_t utils_from_uint32_buffer(size_t n_args, const mp_obj_t *pos_args MP_DEFINE_CONST_FUN_OBJ_KW(utils_from_uint32_buffer_obj, 1, utils_from_uint32_buffer); #endif +#endif /* ULAB_UTILS_HAS_FROM_INT16_BUFFER | ULAB_UTILS_HAS_FROM_UINT16_BUFFER | ULAB_UTILS_HAS_FROM_INT32_BUFFER | ULAB_UTILS_HAS_FROM_UINT32_BUFFER */ + +#if ULAB_UTILS_HAS_SPECTROGRAM +//| import ulab.numpy +//| +//| def spectrogram(r: ulab.numpy.ndarray) -> ulab.numpy.ndarray: +//| """ +//| :param ulab.numpy.ndarray r: A 1-dimension array of values whose size is a power of 2 +//| +//| Computes the spectrum of the input signal. This is the absolute value of the (complex-valued) fft of the signal. +//| This function is similar to scipy's ``scipy.signal.welch`` https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.welch.html.""" +//| ... +//| + +mp_obj_t utils_spectrogram(size_t n_args, const mp_obj_t *args) { + #if ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE + return fft_fft_ifft_spectrogram(args[0], FFT_SPECTROGRAM); + #else + if(n_args == 2) { + return fft_fft_ifft_spectrogram(n_args, args[0], args[1], FFT_SPECTROGRAM); + } else { + return fft_fft_ifft_spectrogram(n_args, args[0], mp_const_none, FFT_SPECTROGRAM); + } + #endif +} + +#if ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(utils_spectrogram_obj, 1, 1, utils_spectrogram); +#else +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(utils_spectrogram_obj, 1, 2, utils_spectrogram); #endif +#endif /* ULAB_UTILS_HAS_SPECTROGRAM */ + + static const mp_rom_map_elem_t ulab_utils_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_utils) }, #if ULAB_UTILS_HAS_FROM_INT16_BUFFER @@ -203,6 +238,9 @@ static const mp_rom_map_elem_t ulab_utils_globals_table[] = { #if ULAB_UTILS_HAS_FROM_UINT32_BUFFER { MP_OBJ_NEW_QSTR(MP_QSTR_from_uint32_buffer), (mp_obj_t)&utils_from_uint32_buffer_obj }, #endif + #if ULAB_UTILS_HAS_SPECTROGRAM + { MP_OBJ_NEW_QSTR(MP_QSTR_spectrogram), (mp_obj_t)&utils_spectrogram_obj }, + #endif }; static MP_DEFINE_CONST_DICT(mp_module_ulab_utils_globals, ulab_utils_globals_table); @@ -212,4 +250,4 @@ mp_obj_module_t ulab_utils_module = { .globals = (mp_obj_dict_t*)&mp_module_ulab_utils_globals, }; -#endif +#endif /* ULAB_HAS_UTILS_MODULE */ diff --git a/python/port/mpconfigport.h b/python/port/mpconfigport.h index 26f813ba9e4..9040132fe8d 100644 --- a/python/port/mpconfigport.h +++ b/python/port/mpconfigport.h @@ -1,6 +1,9 @@ #include #include +// Include helpers when this is not a MicroPython build. +#ifdef EPSILON_VERSION #include "helpers.h" +#endif /* MicroPython configuration options * We're not listing the default options as defined in mpconfig.h */ @@ -16,6 +19,11 @@ * are therefore erased prematurely. */ #define MICROPY_ENABLE_PYSTACK (0) +// Whether to encode None/False/True as immediate objects instead of pointers to +// real objects. Reduces code size by a decent amount without hurting +// performance, for all representations except D on some architectures. +#define MICROPY_OBJ_IMMEDIATE_OBJS 0 + // Maximum length of a path in the filesystem #define MICROPY_ALLOC_PATH_MAX (32) @@ -139,40 +147,6 @@ typedef long mp_off_t; #define MP_STATE_PORT MP_STATE_VM -extern const struct _mp_obj_module_t modion_module; -extern const struct _mp_obj_module_t modkandinsky_module; -extern const struct _mp_obj_module_t modmatplotlib_module; -extern const struct _mp_obj_module_t modpyplot_module; -extern const struct _mp_obj_module_t modtime_module; -extern const struct _mp_obj_module_t modos_module; -extern const struct _mp_obj_module_t modturtle_module; - -#if !defined(INCLUDE_ULAB) - -#define MICROPY_PORT_BUILTIN_MODULES \ - { MP_ROM_QSTR(MP_QSTR_ion), MP_ROM_PTR(&modion_module) }, \ - { MP_ROM_QSTR(MP_QSTR_kandinsky), MP_ROM_PTR(&modkandinsky_module) }, \ - { MP_ROM_QSTR(MP_QSTR_matplotlib), MP_ROM_PTR(&modmatplotlib_module) }, \ - { MP_ROM_QSTR(MP_QSTR_matplotlib_dot_pyplot), MP_ROM_PTR(&modpyplot_module) }, \ - { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&modtime_module) }, \ - { MP_ROM_QSTR(MP_QSTR_os), MP_ROM_PTR(&modos_module) }, \ - { MP_ROM_QSTR(MP_QSTR_turtle), MP_ROM_PTR(&modturtle_module) }, \ - -#else -extern const struct _mp_obj_module_t ulab_user_cmodule; - -#define MICROPY_PORT_BUILTIN_MODULES \ - { MP_ROM_QSTR(MP_QSTR_ion), MP_ROM_PTR(&modion_module) }, \ - { MP_ROM_QSTR(MP_QSTR_kandinsky), MP_ROM_PTR(&modkandinsky_module) }, \ - { MP_ROM_QSTR(MP_QSTR_matplotlib), MP_ROM_PTR(&modmatplotlib_module) }, \ - { MP_ROM_QSTR(MP_QSTR_matplotlib_dot_pyplot), MP_ROM_PTR(&modpyplot_module) }, \ - { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&modtime_module) }, \ - { MP_ROM_QSTR(MP_QSTR_os), MP_ROM_PTR(&modos_module) }, \ - { MP_ROM_QSTR(MP_QSTR_turtle), MP_ROM_PTR(&modturtle_module) }, \ - { MP_ROM_QSTR(MP_QSTR_ulab), MP_ROM_PTR(&ulab_user_cmodule) }, \ - -#endif - // Enable setjmp in debug mode. This is to avoid some optimizations done // specifically for x86_64 using inline assembly, which makes the debug binary diff --git a/python/src/extmod/moduplatform.h b/python/src/extmod/moduplatform.h new file mode 100644 index 00000000000..740a745b619 --- /dev/null +++ b/python/src/extmod/moduplatform.h @@ -0,0 +1,7 @@ +// This file is needed, because MicroPython includes it in the modsys.c file. +// https://github.com/micropython/micropython/commit/402df833fe6da5233c83c58421e81493cda54f67#diff-99822946f0f35edf3fa262e9a3b213da739cfd30f5c8c0c44ef0eb67d7d7b4b0R39 +// It is just a hack to make it work. +// It will be needed if the sys module is enabled. +#if MICROPY_PY_SYS +#warning "To build the sys module, you need to use the real moduplatform module from MicroPython." +#endif diff --git a/python/src/py/asmarm.c b/python/src/py/asmarm.c index 4ba93d0806e..42724e4d4b3 100644 --- a/python/src/py/asmarm.c +++ b/python/src/py/asmarm.c @@ -304,6 +304,11 @@ void asm_arm_ldrh_reg_reg(asm_arm_t *as, uint rd, uint rn) { emit_al(as, 0x1d000b0 | (rn << 16) | (rd << 12)); } +void asm_arm_ldrh_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offset) { + // ldrh rd, [rn, #off] + emit_al(as, 0x1f000b0 | (rn << 16) | (rd << 12) | ((byte_offset & 0xf0) << 4) | (byte_offset & 0xf)); +} + void asm_arm_ldrb_reg_reg(asm_arm_t *as, uint rd, uint rn) { // ldrb rd, [rn] emit_al(as, 0x5d00000 | (rn << 16) | (rd << 12)); diff --git a/python/src/py/asmarm.h b/python/src/py/asmarm.h index 0e029f20e9d..561d69a4b10 100644 --- a/python/src/py/asmarm.h +++ b/python/src/py/asmarm.h @@ -109,6 +109,7 @@ void asm_arm_asr_reg_reg(asm_arm_t *as, uint rd, uint rs); // memory void asm_arm_ldr_reg_reg(asm_arm_t *as, uint rd, uint rn, uint byte_offset); void asm_arm_ldrh_reg_reg(asm_arm_t *as, uint rd, uint rn); +void asm_arm_ldrh_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offset); void asm_arm_ldrb_reg_reg(asm_arm_t *as, uint rd, uint rn); void asm_arm_str_reg_reg(asm_arm_t *as, uint rd, uint rm, uint byte_offset); void asm_arm_strh_reg_reg(asm_arm_t *as, uint rd, uint rm); @@ -203,6 +204,7 @@ void asm_arm_bx_reg(asm_arm_t *as, uint reg_src); #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_arm_ldr_reg_reg((as), (reg_dest), (reg_base), 4 * (word_offset)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_arm_ldrb_reg_reg((as), (reg_dest), (reg_base)) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_arm_ldrh_reg_reg((as), (reg_dest), (reg_base)) +#define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_arm_ldrh_reg_reg_offset((as), (reg_dest), (reg_base), 2 * (uint16_offset)) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_arm_ldr_reg_reg((as), (reg_dest), (reg_base), 0) #define ASM_STORE_REG_REG(as, reg_value, reg_base) asm_arm_str_reg_reg((as), (reg_value), (reg_base), 0) diff --git a/python/src/py/asmbase.c b/python/src/py/asmbase.c index 344e03e7a7a..4a3fd089cb2 100644 --- a/python/src/py/asmbase.c +++ b/python/src/py/asmbase.c @@ -61,7 +61,8 @@ void mp_asm_base_start_pass(mp_asm_base_t *as, int pass) { // all functions must go through this one to emit bytes // if as->pass < MP_ASM_PASS_EMIT, then this function just counts the number // of bytes needed and returns NULL, and callers should not store any data -uint8_t *mp_asm_base_get_cur_to_write_bytes(mp_asm_base_t *as, size_t num_bytes_to_write) { +uint8_t *mp_asm_base_get_cur_to_write_bytes(void *as_in, size_t num_bytes_to_write) { + mp_asm_base_t *as = as_in; uint8_t *c = NULL; if (as->pass == MP_ASM_PASS_EMIT) { assert(as->code_offset + num_bytes_to_write <= as->code_size); diff --git a/python/src/py/asmbase.h b/python/src/py/asmbase.h index 24c3af8679c..960be7685f5 100644 --- a/python/src/py/asmbase.h +++ b/python/src/py/asmbase.h @@ -45,7 +45,7 @@ typedef struct _mp_asm_base_t { void mp_asm_base_init(mp_asm_base_t *as, size_t max_num_labels); void mp_asm_base_deinit(mp_asm_base_t *as, bool free_code); void mp_asm_base_start_pass(mp_asm_base_t *as, int pass); -uint8_t *mp_asm_base_get_cur_to_write_bytes(mp_asm_base_t *as, size_t num_bytes_to_write); +uint8_t *mp_asm_base_get_cur_to_write_bytes(void *as, size_t num_bytes_to_write); void mp_asm_base_label_assign(mp_asm_base_t *as, size_t label); void mp_asm_base_align(mp_asm_base_t *as, unsigned int align); void mp_asm_base_data(mp_asm_base_t *as, unsigned int bytesize, uintptr_t val); diff --git a/python/src/py/asmthumb.c b/python/src/py/asmthumb.c index db4520ce123..49574c43a7f 100644 --- a/python/src/py/asmthumb.c +++ b/python/src/py/asmthumb.c @@ -34,9 +34,25 @@ #if MICROPY_EMIT_THUMB || MICROPY_EMIT_INLINE_THUMB #include "py/mpstate.h" -#include "py/persistentcode.h" #include "py/asmthumb.h" +#ifdef _MSC_VER +#include + +static uint32_t mp_clz(uint32_t x) { + unsigned long lz = 0; + return _BitScanReverse(&lz, x) ? (sizeof(x) * 8 - 1) - lz : 0; +} + +static uint32_t mp_ctz(uint32_t x) { + unsigned long tz = 0; + return _BitScanForward(&tz, x) ? tz : 0; +} +#else +#define mp_clz(x) __builtin_clz(x) +#define mp_ctz(x) __builtin_ctz(x) +#endif + #define UNSIGNED_FIT5(x) ((uint32_t)(x) < 32) #define UNSIGNED_FIT7(x) ((uint32_t)(x) < 128) #define UNSIGNED_FIT8(x) (((x) & 0xffffff00) == 0) @@ -46,7 +62,6 @@ #define SIGNED_FIT12(x) (((x) & 0xfffff800) == 0) || (((x) & 0xfffff800) == 0xfffff800) #define SIGNED_FIT23(x) (((x) & 0xffc00000) == 0) || (((x) & 0xffc00000) == 0xffc00000) -#if MICROPY_EMIT_THUMB_ARMV7M // Note: these actually take an imm12 but the high-bit is not encoded here #define OP_ADD_W_RRI_HI(reg_src) (0xf200 | (reg_src)) #define OP_ADD_W_RRI_LO(reg_dest, imm11) ((imm11 << 4 & 0x7000) | reg_dest << 8 | (imm11 & 0xff)) @@ -55,7 +70,9 @@ #define OP_LDR_W_HI(reg_base) (0xf8d0 | (reg_base)) #define OP_LDR_W_LO(reg_dest, imm12) ((reg_dest) << 12 | (imm12)) -#endif + +#define OP_LDRH_W_HI(reg_base) (0xf8b0 | (reg_base)) +#define OP_LDRH_W_LO(reg_dest, imm12) ((reg_dest) << 12 | (imm12)) static inline byte *asm_thumb_get_cur_to_write_bytes(asm_thumb_t *as, int n) { return mp_asm_base_get_cur_to_write_bytes(&as->base, n); @@ -158,21 +175,21 @@ void asm_thumb_entry(asm_thumb_t *as, int num_locals) { } asm_thumb_op16(as, OP_PUSH_RLIST_LR(reglist)); if (stack_adjust > 0) { - #if MICROPY_EMIT_THUMB_ARMV7M - if (UNSIGNED_FIT7(stack_adjust)) { - asm_thumb_op16(as, OP_SUB_SP(stack_adjust)); + if (asm_thumb_allow_armv7m(as)) { + if (UNSIGNED_FIT7(stack_adjust)) { + asm_thumb_op16(as, OP_SUB_SP(stack_adjust)); + } else { + asm_thumb_op32(as, OP_SUB_W_RRI_HI(ASM_THUMB_REG_SP), OP_SUB_W_RRI_LO(ASM_THUMB_REG_SP, stack_adjust * 4)); + } } else { - asm_thumb_op32(as, OP_SUB_W_RRI_HI(ASM_THUMB_REG_SP), OP_SUB_W_RRI_LO(ASM_THUMB_REG_SP, stack_adjust * 4)); - } - #else - int adj = stack_adjust; - // we don't expect the stack_adjust to be massive - while (!UNSIGNED_FIT7(adj)) { - asm_thumb_op16(as, OP_SUB_SP(127)); - adj -= 127; + int adj = stack_adjust; + // we don't expect the stack_adjust to be massive + while (!UNSIGNED_FIT7(adj)) { + asm_thumb_op16(as, OP_SUB_SP(127)); + adj -= 127; + } + asm_thumb_op16(as, OP_SUB_SP(adj)); } - asm_thumb_op16(as, OP_SUB_SP(adj)); - #endif } as->push_reglist = reglist; as->stack_adjust = stack_adjust; @@ -180,21 +197,21 @@ void asm_thumb_entry(asm_thumb_t *as, int num_locals) { void asm_thumb_exit(asm_thumb_t *as) { if (as->stack_adjust > 0) { - #if MICROPY_EMIT_THUMB_ARMV7M - if (UNSIGNED_FIT7(as->stack_adjust)) { - asm_thumb_op16(as, OP_ADD_SP(as->stack_adjust)); + if (asm_thumb_allow_armv7m(as)) { + if (UNSIGNED_FIT7(as->stack_adjust)) { + asm_thumb_op16(as, OP_ADD_SP(as->stack_adjust)); + } else { + asm_thumb_op32(as, OP_ADD_W_RRI_HI(ASM_THUMB_REG_SP), OP_ADD_W_RRI_LO(ASM_THUMB_REG_SP, as->stack_adjust * 4)); + } } else { - asm_thumb_op32(as, OP_ADD_W_RRI_HI(ASM_THUMB_REG_SP), OP_ADD_W_RRI_LO(ASM_THUMB_REG_SP, as->stack_adjust * 4)); - } - #else - int adj = as->stack_adjust; - // we don't expect the stack_adjust to be massive - while (!UNSIGNED_FIT7(adj)) { - asm_thumb_op16(as, OP_ADD_SP(127)); - adj -= 127; + int adj = as->stack_adjust; + // we don't expect the stack_adjust to be massive + while (!UNSIGNED_FIT7(adj)) { + asm_thumb_op16(as, OP_ADD_SP(127)); + adj -= 127; + } + asm_thumb_op16(as, OP_ADD_SP(adj)); } - asm_thumb_op16(as, OP_ADD_SP(adj)); - #endif } asm_thumb_op16(as, OP_POP_RLIST_PC(as->push_reglist)); } @@ -248,27 +265,19 @@ void asm_thumb_mov_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_src) { asm_thumb_op16(as, 0x4600 | op_lo); } -#if MICROPY_EMIT_THUMB_ARMV7M - // if loading lo half with movw, the i16 value will be zero extended into the r32 register! -size_t asm_thumb_mov_reg_i16(asm_thumb_t *as, uint mov_op, uint reg_dest, int i16_src) { +void asm_thumb_mov_reg_i16(asm_thumb_t *as, uint mov_op, uint reg_dest, int i16_src) { assert(reg_dest < ASM_THUMB_REG_R15); - size_t loc = mp_asm_base_get_code_pos(&as->base); // mov[wt] reg_dest, #i16_src asm_thumb_op32(as, mov_op | ((i16_src >> 1) & 0x0400) | ((i16_src >> 12) & 0xf), ((i16_src << 4) & 0x7000) | (reg_dest << 8) | (i16_src & 0xff)); - return loc; } -#else - -void asm_thumb_mov_rlo_i16(asm_thumb_t *as, uint rlo_dest, int i16_src) { +static void asm_thumb_mov_rlo_i16(asm_thumb_t *as, uint rlo_dest, int i16_src) { asm_thumb_mov_rlo_i8(as, rlo_dest, (i16_src >> 8) & 0xff); asm_thumb_lsl_rlo_rlo_i5(as, rlo_dest, rlo_dest, 8); asm_thumb_add_rlo_i8(as, rlo_dest, i16_src & 0xff); } -#endif - #define OP_B_N(byte_offset) (0xe000 | (((byte_offset) >> 1) & 0x07ff)) bool asm_thumb_b_n_label(asm_thumb_t *as, uint label) { @@ -292,14 +301,12 @@ bool asm_thumb_bcc_nw_label(asm_thumb_t *as, int cond, uint label, bool wide) { if (!wide) { asm_thumb_op16(as, OP_BCC_N(cond, rel)); return as->base.pass != MP_ASM_PASS_EMIT || SIGNED_FIT9(rel); - } else { - #if MICROPY_EMIT_THUMB_ARMV7M + } else if (asm_thumb_allow_armv7m(as)) { asm_thumb_op32(as, OP_BCC_W_HI(cond, rel), OP_BCC_W_LO(rel)); return true; - #else + } else { // this method should not be called for ARMV6M return false; - #endif } } @@ -320,30 +327,30 @@ size_t asm_thumb_mov_reg_i32(asm_thumb_t *as, uint reg_dest, mp_uint_t i32) { size_t loc = mp_asm_base_get_code_pos(&as->base); - #if MICROPY_EMIT_THUMB_ARMV7M - asm_thumb_mov_reg_i16(as, ASM_THUMB_OP_MOVW, reg_dest, i32); - asm_thumb_mov_reg_i16(as, ASM_THUMB_OP_MOVT, reg_dest, i32 >> 16); - #else - // should only be called with lo reg for ARMV6M - assert(reg_dest < ASM_THUMB_REG_R8); - - // sanity check that generated code is aligned - assert(!as->base.code_base || !(3u & (uintptr_t)as->base.code_base)); - - // basically: - // (nop) - // ldr reg_dest, _data - // b 1f - // _data: .word i32 - // 1: - if (as->base.code_offset & 2u) { - asm_thumb_op16(as, ASM_THUMB_OP_NOP); + if (asm_thumb_allow_armv7m(as)) { + asm_thumb_mov_reg_i16(as, ASM_THUMB_OP_MOVW, reg_dest, i32); + asm_thumb_mov_reg_i16(as, ASM_THUMB_OP_MOVT, reg_dest, i32 >> 16); + } else { + // should only be called with lo reg for ARMV6M + assert(reg_dest < ASM_THUMB_REG_R8); + + // sanity check that generated code is aligned + assert(!as->base.code_base || !(3u & (uintptr_t)as->base.code_base)); + + // basically: + // (nop) + // ldr reg_dest, _data + // b 1f + // _data: .word i32 + // 1: + if (as->base.code_offset & 2u) { + asm_thumb_op16(as, ASM_THUMB_OP_NOP); + } + asm_thumb_ldr_rlo_pcrel_i8(as, reg_dest, 0); + asm_thumb_op16(as, OP_B_N(2)); + asm_thumb_op16(as, i32 & 0xffff); + asm_thumb_op16(as, i32 >> 16); } - asm_thumb_ldr_rlo_pcrel_i8(as, reg_dest, 0); - asm_thumb_op16(as, OP_B_N(2)); - asm_thumb_op16(as, i32 & 0xffff); - asm_thumb_op16(as, i32 >> 16); - #endif return loc; } @@ -351,14 +358,13 @@ size_t asm_thumb_mov_reg_i32(asm_thumb_t *as, uint reg_dest, mp_uint_t i32) { void asm_thumb_mov_reg_i32_optimised(asm_thumb_t *as, uint reg_dest, int i32) { if (reg_dest < 8 && UNSIGNED_FIT8(i32)) { asm_thumb_mov_rlo_i8(as, reg_dest, i32); - } else { - #if MICROPY_EMIT_THUMB_ARMV7M + } else if (asm_thumb_allow_armv7m(as)) { if (UNSIGNED_FIT16(i32)) { asm_thumb_mov_reg_i16(as, ASM_THUMB_OP_MOVW, reg_dest, i32); } else { asm_thumb_mov_reg_i32(as, reg_dest, i32); } - #else + } else { uint rlo_dest = reg_dest; assert(rlo_dest < ASM_THUMB_REG_R8); // should never be called for ARMV6M @@ -367,8 +373,8 @@ void asm_thumb_mov_reg_i32_optimised(asm_thumb_t *as, uint reg_dest, int i32) { i32 = -i32; } - uint clz = __builtin_clz(i32); - uint ctz = i32 ? __builtin_ctz(i32) : 0; + uint clz = mp_clz(i32); + uint ctz = i32 ? mp_ctz(i32) : 0; assert(clz + ctz <= 32); if (clz + ctz >= 24) { asm_thumb_mov_rlo_i8(as, rlo_dest, (i32 >> ctz) & 0xff); @@ -386,7 +392,6 @@ void asm_thumb_mov_reg_i32_optimised(asm_thumb_t *as, uint reg_dest, int i32) { if (negate) { asm_thumb_neg_rlo_rlo(as, rlo_dest, rlo_dest); } - #endif } } @@ -429,62 +434,76 @@ void asm_thumb_mov_reg_pcrel(asm_thumb_t *as, uint rlo_dest, uint label) { mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - as->base.code_offset; rel |= 1; // to stay in Thumb state when jumping to this address - #if MICROPY_EMIT_THUMB_ARMV7M - rel -= 4 + 4; // adjust for mov_reg_i16 and then PC+4 prefetch of add_reg_reg - asm_thumb_mov_reg_i16(as, ASM_THUMB_OP_MOVW, rlo_dest, rel); // 4 bytes - #else - rel -= 8 + 4; // adjust for four instructions and then PC+4 prefetch of add_reg_reg - // 6 bytes - asm_thumb_mov_rlo_i16(as, rlo_dest, rel); - // 2 bytes - not always needed, but we want to keep the size the same - asm_thumb_sxth_rlo_rlo(as, rlo_dest, rlo_dest); - #endif + if (asm_thumb_allow_armv7m(as)) { + rel -= 6 + 4; // adjust for mov_reg_i16, sxth_rlo_rlo and then PC+4 prefetch of add_reg_reg + asm_thumb_mov_reg_i16(as, ASM_THUMB_OP_MOVW, rlo_dest, rel); // 4 bytes + asm_thumb_sxth_rlo_rlo(as, rlo_dest, rlo_dest); // 2 bytes + } else { + rel -= 8 + 4; // adjust for four instructions and then PC+4 prefetch of add_reg_reg + // 6 bytes + asm_thumb_mov_rlo_i16(as, rlo_dest, rel); + // 2 bytes - not always needed, but we want to keep the size the same + asm_thumb_sxth_rlo_rlo(as, rlo_dest, rlo_dest); + } asm_thumb_add_reg_reg(as, rlo_dest, ASM_THUMB_REG_R15); // 2 bytes } -#if MICROPY_EMIT_THUMB_ARMV7M +// ARMv7-M only static inline void asm_thumb_ldr_reg_reg_i12(asm_thumb_t *as, uint reg_dest, uint reg_base, uint word_offset) { asm_thumb_op32(as, OP_LDR_W_HI(reg_base), OP_LDR_W_LO(reg_dest, word_offset * 4)); } -#endif + +// emits code for: reg_dest = reg_base + offset << offset_shift +static void asm_thumb_add_reg_reg_offset(asm_thumb_t *as, uint reg_dest, uint reg_base, uint offset, uint offset_shift) { + if (reg_dest < ASM_THUMB_REG_R8 && reg_base < ASM_THUMB_REG_R8) { + if (offset << offset_shift < 256) { + if (reg_dest != reg_base) { + asm_thumb_mov_reg_reg(as, reg_dest, reg_base); + } + asm_thumb_add_rlo_i8(as, reg_dest, offset << offset_shift); + } else if (UNSIGNED_FIT8(offset) && reg_dest != reg_base) { + asm_thumb_mov_rlo_i8(as, reg_dest, offset); + asm_thumb_lsl_rlo_rlo_i5(as, reg_dest, reg_dest, offset_shift); + asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_base); + } else if (reg_dest != reg_base) { + asm_thumb_mov_rlo_i16(as, reg_dest, offset << offset_shift); + asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_dest); + } else { + uint reg_other = reg_dest ^ 7; + asm_thumb_op16(as, OP_PUSH_RLIST((1 << reg_other))); + asm_thumb_mov_rlo_i16(as, reg_other, offset << offset_shift); + asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_other); + asm_thumb_op16(as, OP_POP_RLIST((1 << reg_other))); + } + } else { + assert(0); // should never be called for ARMV6M + } +} void asm_thumb_ldr_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint word_offset) { if (reg_dest < ASM_THUMB_REG_R8 && reg_base < ASM_THUMB_REG_R8 && UNSIGNED_FIT5(word_offset)) { asm_thumb_ldr_rlo_rlo_i5(as, reg_dest, reg_base, word_offset); - } else { - #if MICROPY_EMIT_THUMB_ARMV7M + } else if (asm_thumb_allow_armv7m(as)) { asm_thumb_ldr_reg_reg_i12(as, reg_dest, reg_base, word_offset); - #else - word_offset -= 31; - if (reg_dest < ASM_THUMB_REG_R8 && reg_base < ASM_THUMB_REG_R8) { - if (UNSIGNED_FIT8(word_offset) && (word_offset < 64 || reg_dest != reg_base)) { - if (word_offset < 64) { - if (reg_dest != reg_base) { - asm_thumb_mov_reg_reg(as, reg_dest, reg_base); - } - asm_thumb_add_rlo_i8(as, reg_dest, word_offset * 4); - } else { - asm_thumb_mov_rlo_i8(as, reg_dest, word_offset); - asm_thumb_lsl_rlo_rlo_i5(as, reg_dest, reg_dest, 2); - asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_base); - } - } else { - if (reg_dest != reg_base) { - asm_thumb_mov_rlo_i16(as, reg_dest, word_offset * 4); - asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_dest); - } else { - uint reg_other = reg_dest ^ 7; - asm_thumb_op16(as, OP_PUSH_RLIST((1 << reg_other))); - asm_thumb_mov_rlo_i16(as, reg_other, word_offset * 4); - asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_other); - asm_thumb_op16(as, OP_POP_RLIST((1 << reg_other))); - } - } - } else { - assert(0); // should never be called for ARMV6M - } + } else { + asm_thumb_add_reg_reg_offset(as, reg_dest, reg_base, word_offset - 31, 2); asm_thumb_ldr_rlo_rlo_i5(as, reg_dest, reg_dest, 31); - #endif + } +} + +// ARMv7-M only +static inline void asm_thumb_ldrh_reg_reg_i12(asm_thumb_t *as, uint reg_dest, uint reg_base, uint uint16_offset) { + asm_thumb_op32(as, OP_LDRH_W_HI(reg_base), OP_LDRH_W_LO(reg_dest, uint16_offset * 2)); +} + +void asm_thumb_ldrh_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint uint16_offset) { + if (reg_dest < ASM_THUMB_REG_R8 && reg_base < ASM_THUMB_REG_R8 && UNSIGNED_FIT5(uint16_offset)) { + asm_thumb_ldrh_rlo_rlo_i5(as, reg_dest, reg_base, uint16_offset); + } else if (asm_thumb_allow_armv7m(as)) { + asm_thumb_ldrh_reg_reg_i12(as, reg_dest, reg_base, uint16_offset); + } else { + asm_thumb_add_reg_reg_offset(as, reg_dest, reg_base, uint16_offset - 31, 1); + asm_thumb_ldrh_rlo_rlo_i5(as, reg_dest, reg_dest, 31); } } @@ -496,20 +515,21 @@ void asm_thumb_b_label(asm_thumb_t *as, uint label) { mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - as->base.code_offset; rel -= 4; // account for instruction prefetch, PC is 4 bytes ahead of this instruction + if (dest != (mp_uint_t)-1 && rel <= -4) { // is a backwards jump, so we know the size of the jump on the first pass // calculate rel assuming 12 bit relative jump if (SIGNED_FIT12(rel)) { asm_thumb_op16(as, OP_B_N(rel)); - } else { - goto large_jump; + return; } - } else { - // is a forwards jump, so need to assume it's large - large_jump: - #if MICROPY_EMIT_THUMB_ARMV7M + } + + // is a large backwards jump, or a forwards jump (that must be assumed large) + + if (asm_thumb_allow_armv7m(as)) { asm_thumb_op32(as, OP_BW_HI(rel), OP_BW_LO(rel)); - #else + } else { if (SIGNED_FIT12(rel)) { // this code path has to be the same number of instructions irrespective of rel asm_thumb_op16(as, OP_B_N(rel)); @@ -520,7 +540,6 @@ void asm_thumb_b_label(asm_thumb_t *as, uint label) { mp_raise_NotImplementedError(MP_ERROR_TEXT("native method too big")); } } - #endif } } @@ -528,24 +547,24 @@ void asm_thumb_bcc_label(asm_thumb_t *as, int cond, uint label) { mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - as->base.code_offset; rel -= 4; // account for instruction prefetch, PC is 4 bytes ahead of this instruction + if (dest != (mp_uint_t)-1 && rel <= -4) { // is a backwards jump, so we know the size of the jump on the first pass // calculate rel assuming 9 bit relative jump if (SIGNED_FIT9(rel)) { asm_thumb_op16(as, OP_BCC_N(cond, rel)); - } else { - goto large_jump; + return; } - } else { - // is a forwards jump, so need to assume it's large - large_jump: - #if MICROPY_EMIT_THUMB_ARMV7M + } + + // is a large backwards jump, or a forwards jump (that must be assumed large) + + if (asm_thumb_allow_armv7m(as)) { asm_thumb_op32(as, OP_BCC_W_HI(cond, rel), OP_BCC_W_LO(rel)); - #else + } else { // reverse the sense of the branch to jump over a longer branch asm_thumb_op16(as, OP_BCC_N(cond ^ 1, 0)); asm_thumb_b_label(as, label); - #endif } } diff --git a/python/src/py/asmthumb.h b/python/src/py/asmthumb.h index 1a01d20c691..86b81665757 100644 --- a/python/src/py/asmthumb.h +++ b/python/src/py/asmthumb.h @@ -29,6 +29,7 @@ #include #include "py/misc.h" #include "py/asmbase.h" +#include "py/persistentcode.h" #define ASM_THUMB_REG_R0 (0) #define ASM_THUMB_REG_R1 (1) @@ -70,6 +71,21 @@ typedef struct _asm_thumb_t { uint32_t stack_adjust; } asm_thumb_t; +#if MICROPY_DYNAMIC_COMPILER + +static inline bool asm_thumb_allow_armv7m(asm_thumb_t *as) { + return MP_NATIVE_ARCH_ARMV7M <= mp_dynamic_compiler.native_arch + && mp_dynamic_compiler.native_arch <= MP_NATIVE_ARCH_ARMV7EMDP; +} + +#else + +static inline bool asm_thumb_allow_armv7m(asm_thumb_t *as) { + return MICROPY_EMIT_THUMB_ARMV7M; +} + +#endif + static inline void asm_thumb_end_pass(asm_thumb_t *as) { (void)as; } @@ -263,8 +279,8 @@ static inline void asm_thumb_str_rlo_rlo_i5(asm_thumb_t *as, uint rlo_src, uint static inline void asm_thumb_strb_rlo_rlo_i5(asm_thumb_t *as, uint rlo_src, uint rlo_base, uint byte_offset) { asm_thumb_format_9_10(as, ASM_THUMB_FORMAT_9_STR | ASM_THUMB_FORMAT_9_BYTE_TRANSFER, rlo_src, rlo_base, byte_offset); } -static inline void asm_thumb_strh_rlo_rlo_i5(asm_thumb_t *as, uint rlo_src, uint rlo_base, uint byte_offset) { - asm_thumb_format_9_10(as, ASM_THUMB_FORMAT_10_STRH, rlo_src, rlo_base, byte_offset); +static inline void asm_thumb_strh_rlo_rlo_i5(asm_thumb_t *as, uint rlo_src, uint rlo_base, uint uint16_offset) { + asm_thumb_format_9_10(as, ASM_THUMB_FORMAT_10_STRH, rlo_src, rlo_base, uint16_offset); } static inline void asm_thumb_ldr_rlo_rlo_i5(asm_thumb_t *as, uint rlo_dest, uint rlo_base, uint word_offset) { asm_thumb_format_9_10(as, ASM_THUMB_FORMAT_9_LDR | ASM_THUMB_FORMAT_9_WORD_TRANSFER, rlo_dest, rlo_base, word_offset); @@ -272,8 +288,8 @@ static inline void asm_thumb_ldr_rlo_rlo_i5(asm_thumb_t *as, uint rlo_dest, uint static inline void asm_thumb_ldrb_rlo_rlo_i5(asm_thumb_t *as, uint rlo_dest, uint rlo_base, uint byte_offset) { asm_thumb_format_9_10(as, ASM_THUMB_FORMAT_9_LDR | ASM_THUMB_FORMAT_9_BYTE_TRANSFER, rlo_dest, rlo_base, byte_offset); } -static inline void asm_thumb_ldrh_rlo_rlo_i5(asm_thumb_t *as, uint rlo_dest, uint rlo_base, uint byte_offset) { - asm_thumb_format_9_10(as, ASM_THUMB_FORMAT_10_LDRH, rlo_dest, rlo_base, byte_offset); +static inline void asm_thumb_ldrh_rlo_rlo_i5(asm_thumb_t *as, uint rlo_dest, uint rlo_base, uint uint16_offset) { + asm_thumb_format_9_10(as, ASM_THUMB_FORMAT_10_LDRH, rlo_dest, rlo_base, uint16_offset); } static inline void asm_thumb_lsl_rlo_rlo_i5(asm_thumb_t *as, uint rlo_dest, uint rlo_src, uint shift) { asm_thumb_format_1(as, ASM_THUMB_FORMAT_1_LSL, rlo_dest, rlo_src, shift); @@ -308,12 +324,7 @@ static inline void asm_thumb_sxth_rlo_rlo(asm_thumb_t *as, uint rlo_dest, uint r #define ASM_THUMB_OP_MOVT (0xf2c0) void asm_thumb_mov_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_src); - -#if MICROPY_EMIT_THUMB_ARMV7M -size_t asm_thumb_mov_reg_i16(asm_thumb_t *as, uint mov_op, uint reg_dest, int i16_src); -#else -void asm_thumb_mov_rlo_i16(asm_thumb_t *as, uint rlo_dest, int i16_src); -#endif +void asm_thumb_mov_reg_i16(asm_thumb_t *as, uint mov_op, uint reg_dest, int i16_src); // these return true if the destination is in range, false otherwise bool asm_thumb_b_n_label(asm_thumb_t *as, uint label); @@ -327,7 +338,8 @@ void asm_thumb_mov_reg_local(asm_thumb_t *as, uint rlo_dest, int local_num); // void asm_thumb_mov_reg_local_addr(asm_thumb_t *as, uint rlo_dest, int local_num); // convenience void asm_thumb_mov_reg_pcrel(asm_thumb_t *as, uint rlo_dest, uint label); -void asm_thumb_ldr_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint byte_offset); // convenience +void asm_thumb_ldr_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint word_offset); // convenience +void asm_thumb_ldrh_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint uint16_offset); // convenience void asm_thumb_b_label(asm_thumb_t *as, uint label); // convenience: picks narrow or wide branch void asm_thumb_bcc_label(asm_thumb_t *as, int cc, uint label); // convenience: picks narrow or wide branch @@ -389,11 +401,6 @@ void asm_thumb_b_rel12(asm_thumb_t *as, int rel); #define ASM_MOV_LOCAL_REG(as, local_num, reg) asm_thumb_mov_local_reg((as), (local_num), (reg)) #define ASM_MOV_REG_IMM(as, reg_dest, imm) asm_thumb_mov_reg_i32_optimised((as), (reg_dest), (imm)) -#if MICROPY_EMIT_THUMB_ARMV7M -#define ASM_MOV_REG_IMM_FIX_U16(as, reg_dest, imm) asm_thumb_mov_reg_i16((as), ASM_THUMB_OP_MOVW, (reg_dest), (imm)) -#else -#define ASM_MOV_REG_IMM_FIX_U16(as, reg_dest, imm) asm_thumb_mov_rlo_i16((as), (reg_dest), (imm)) -#endif #define ASM_MOV_REG_IMM_FIX_WORD(as, reg_dest, imm) asm_thumb_mov_reg_i32((as), (reg_dest), (imm)) #define ASM_MOV_REG_LOCAL(as, reg_dest, local_num) asm_thumb_mov_reg_local((as), (reg_dest), (local_num)) #define ASM_MOV_REG_REG(as, reg_dest, reg_src) asm_thumb_mov_reg_reg((as), (reg_dest), (reg_src)) @@ -414,6 +421,7 @@ void asm_thumb_b_rel12(asm_thumb_t *as, int rel); #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_thumb_ldr_reg_reg_i12_optimised((as), (reg_dest), (reg_base), (word_offset)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_thumb_ldrb_rlo_rlo_i5((as), (reg_dest), (reg_base), 0) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_thumb_ldrh_rlo_rlo_i5((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_thumb_ldrh_reg_reg_i12_optimised((as), (reg_dest), (reg_base), (uint16_offset)) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_thumb_ldr_rlo_rlo_i5((as), (reg_dest), (reg_base), 0) #define ASM_STORE_REG_REG(as, reg_src, reg_base) asm_thumb_str_rlo_rlo_i5((as), (reg_src), (reg_base), 0) diff --git a/python/src/py/asmx64.c b/python/src/py/asmx64.c index 62df5c6d4ad..5c923a523ca 100644 --- a/python/src/py/asmx64.c +++ b/python/src/py/asmx64.c @@ -319,9 +319,7 @@ void asm_x64_mov_mem64_to_r64(asm_x64_t *as, int src_r64, int src_disp, int dest STATIC void asm_x64_lea_disp_to_r64(asm_x64_t *as, int src_r64, int src_disp, int dest_r64) { // use REX prefix for 64 bit operation - assert(src_r64 < 8); - assert(dest_r64 < 8); - asm_x64_write_byte_2(as, REX_PREFIX | REX_W, OPCODE_LEA_MEM_TO_R64); + asm_x64_write_byte_2(as, REX_PREFIX | REX_W | REX_R_FROM_R64(dest_r64) | REX_B_FROM_R64(src_r64), OPCODE_LEA_MEM_TO_R64); asm_x64_write_r64_disp(as, dest_r64, src_r64, src_disp); } diff --git a/python/src/py/asmx64.h b/python/src/py/asmx64.h index 1a4987f5cbb..d132ee193cf 100644 --- a/python/src/py/asmx64.h +++ b/python/src/py/asmx64.h @@ -207,6 +207,7 @@ void asm_x64_call_ind(asm_x64_t *as, size_t fun_id, int temp_r32); #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_x64_mov_mem64_to_r64((as), (reg_base), 8 * (word_offset), (reg_dest)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_x64_mov_mem8_to_r64zx((as), (reg_base), 0, (reg_dest)) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_x64_mov_mem16_to_r64zx((as), (reg_base), 0, (reg_dest)) +#define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_x64_mov_mem16_to_r64zx((as), (reg_base), 2 * (uint16_offset), (reg_dest)) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_x64_mov_mem32_to_r64zx((as), (reg_base), 0, (reg_dest)) #define ASM_STORE_REG_REG(as, reg_src, reg_base) asm_x64_mov_r64_to_mem64((as), (reg_src), (reg_base), 0) diff --git a/python/src/py/asmx86.h b/python/src/py/asmx86.h index 8f1b06d220e..e02e6c95457 100644 --- a/python/src/py/asmx86.h +++ b/python/src/py/asmx86.h @@ -202,6 +202,7 @@ void asm_x86_call_ind(asm_x86_t *as, size_t fun_id, mp_uint_t n_args, int temp_r #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_x86_mov_mem32_to_r32((as), (reg_base), 4 * (word_offset), (reg_dest)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_x86_mov_mem8_to_r32zx((as), (reg_base), 0, (reg_dest)) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_x86_mov_mem16_to_r32zx((as), (reg_base), 0, (reg_dest)) +#define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_x86_mov_mem16_to_r32zx((as), (reg_base), 2 * (uint16_offset), (reg_dest)) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_x86_mov_mem32_to_r32((as), (reg_base), 0, (reg_dest)) #define ASM_STORE_REG_REG(as, reg_src, reg_base) asm_x86_mov_r32_to_mem32((as), (reg_src), (reg_base), 0) diff --git a/python/src/py/asmxtensa.c b/python/src/py/asmxtensa.c index 0956d50f3ef..8ac914ec412 100644 --- a/python/src/py/asmxtensa.c +++ b/python/src/py/asmxtensa.c @@ -27,7 +27,7 @@ #include #include -#include "py/mpconfig.h" +#include "py/runtime.h" // wrapper around everything in this file #if MICROPY_EMIT_XTENSA || MICROPY_EMIT_INLINE_XTENSA || MICROPY_EMIT_XTENSAWIN @@ -232,21 +232,33 @@ void asm_xtensa_mov_reg_pcrel(asm_xtensa_t *as, uint reg_dest, uint label) { asm_xtensa_op_add_n(as, reg_dest, reg_dest, ASM_XTENSA_REG_A0); } -void asm_xtensa_call_ind(asm_xtensa_t *as, uint idx) { - if (idx < 16) { - asm_xtensa_op_l32i_n(as, ASM_XTENSA_REG_A0, ASM_XTENSA_REG_FUN_TABLE, idx); +void asm_xtensa_l32i_optimised(asm_xtensa_t *as, uint reg_dest, uint reg_base, uint word_offset) { + if (word_offset < 16) { + asm_xtensa_op_l32i_n(as, reg_dest, reg_base, word_offset); + } else if (word_offset < 256) { + asm_xtensa_op_l32i(as, reg_dest, reg_base, word_offset); } else { - asm_xtensa_op_l32i(as, ASM_XTENSA_REG_A0, ASM_XTENSA_REG_FUN_TABLE, idx); + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("asm overflow")); } - asm_xtensa_op_callx0(as, ASM_XTENSA_REG_A0); } -void asm_xtensa_call_ind_win(asm_xtensa_t *as, uint idx) { - if (idx < 16) { - asm_xtensa_op_l32i_n(as, ASM_XTENSA_REG_A8, ASM_XTENSA_REG_FUN_TABLE_WIN, idx); +void asm_xtensa_s32i_optimised(asm_xtensa_t *as, uint reg_src, uint reg_base, uint word_offset) { + if (word_offset < 16) { + asm_xtensa_op_s32i_n(as, reg_src, reg_base, word_offset); + } else if (word_offset < 256) { + asm_xtensa_op_s32i(as, reg_src, reg_base, word_offset); } else { - asm_xtensa_op_l32i(as, ASM_XTENSA_REG_A8, ASM_XTENSA_REG_FUN_TABLE_WIN, idx); + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("asm overflow")); } +} + +void asm_xtensa_call_ind(asm_xtensa_t *as, uint idx) { + asm_xtensa_l32i_optimised(as, ASM_XTENSA_REG_A0, ASM_XTENSA_REG_FUN_TABLE, idx); + asm_xtensa_op_callx0(as, ASM_XTENSA_REG_A0); +} + +void asm_xtensa_call_ind_win(asm_xtensa_t *as, uint idx) { + asm_xtensa_l32i_optimised(as, ASM_XTENSA_REG_A8, ASM_XTENSA_REG_FUN_TABLE_WIN, idx); asm_xtensa_op_callx8(as, ASM_XTENSA_REG_A8); } diff --git a/python/src/py/asmxtensa.h b/python/src/py/asmxtensa.h index 43f1b608edd..5aa86d3b244 100644 --- a/python/src/py/asmxtensa.h +++ b/python/src/py/asmxtensa.h @@ -278,6 +278,8 @@ void asm_xtensa_mov_local_reg(asm_xtensa_t *as, int local_num, uint reg_src); void asm_xtensa_mov_reg_local(asm_xtensa_t *as, uint reg_dest, int local_num); void asm_xtensa_mov_reg_local_addr(asm_xtensa_t *as, uint reg_dest, int local_num); void asm_xtensa_mov_reg_pcrel(asm_xtensa_t *as, uint reg_dest, uint label); +void asm_xtensa_l32i_optimised(asm_xtensa_t *as, uint reg_dest, uint reg_base, uint word_offset); +void asm_xtensa_s32i_optimised(asm_xtensa_t *as, uint reg_src, uint reg_base, uint word_offset); void asm_xtensa_call_ind(asm_xtensa_t *as, uint idx); void asm_xtensa_call_ind_win(asm_xtensa_t *as, uint idx); @@ -393,12 +395,13 @@ void asm_xtensa_call_ind_win(asm_xtensa_t *as, uint idx); #define ASM_SUB_REG_REG(as, reg_dest, reg_src) asm_xtensa_op_sub((as), (reg_dest), (reg_dest), (reg_src)) #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_xtensa_op_mull((as), (reg_dest), (reg_dest), (reg_src)) -#define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_xtensa_op_l32i_n((as), (reg_dest), (reg_base), (word_offset)) +#define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_xtensa_l32i_optimised((as), (reg_dest), (reg_base), (word_offset)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_xtensa_op_l8ui((as), (reg_dest), (reg_base), 0) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_xtensa_op_l16ui((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_xtensa_op_l16ui((as), (reg_dest), (reg_base), (uint16_offset)) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_xtensa_op_l32i_n((as), (reg_dest), (reg_base), 0) -#define ASM_STORE_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_xtensa_op_s32i_n((as), (reg_dest), (reg_base), (word_offset)) +#define ASM_STORE_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_xtensa_s32i_optimised((as), (reg_dest), (reg_base), (word_offset)) #define ASM_STORE8_REG_REG(as, reg_src, reg_base) asm_xtensa_op_s8i((as), (reg_src), (reg_base), 0) #define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_xtensa_op_s16i((as), (reg_src), (reg_base), 0) #define ASM_STORE32_REG_REG(as, reg_src, reg_base) asm_xtensa_op_s32i_n((as), (reg_src), (reg_base), 0) diff --git a/python/src/py/bc.c b/python/src/py/bc.c index 58694b97dc2..e002bca262d 100644 --- a/python/src/py/bc.c +++ b/python/src/py/bc.c @@ -29,9 +29,9 @@ #include #include -#include "py/runtime.h" #include "py/bc0.h" #include "py/bc.h" +#include "py/objfun.h" #if MICROPY_DEBUG_VERBOSE // print debugging info #define DEBUG_PRINT (1) @@ -40,7 +40,23 @@ #define DEBUG_printf(...) (void)0 #endif -#if !MICROPY_PERSISTENT_CODE +void mp_encode_uint(void *env, mp_encode_uint_allocator_t allocator, mp_uint_t val) { + // We store each 7 bits in a separate byte, and that's how many bytes needed + byte buf[MP_ENCODE_UINT_MAX_BYTES]; + byte *p = buf + sizeof(buf); + // We encode in little-ending order, but store in big-endian, to help decoding + do { + *--p = val & 0x7f; + val >>= 7; + } while (val != 0); + byte *c = allocator(env, buf + sizeof(buf) - p); + if (c != NULL) { + while (p != buf + sizeof(buf) - 1) { + *c++ = *p++ | 0x80; + } + *c = *p; + } +} mp_uint_t mp_decode_uint(const byte **ptr) { mp_uint_t unum = 0; @@ -72,8 +88,6 @@ const byte *mp_decode_uint_skip(const byte *ptr) { return ptr; } -#endif - STATIC NORETURN void fun_pos_args_mismatch(mp_obj_fun_bc_t *f, size_t expected, size_t given) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE // generic message, used also for other argument issues @@ -107,46 +121,36 @@ STATIC void dump_args(const mp_obj_t *a, size_t sz) { // On entry code_state should be allocated somewhere (stack/heap) and // contain the following valid entries: // - code_state->fun_bc should contain a pointer to the function object -// - code_state->ip should contain the offset in bytes from the pointer -// code_state->fun_bc->bytecode to the entry n_state (0 for bytecode, non-zero for native) -void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args) { +// - code_state->ip should contain a pointer to the beginning of the prelude +// - code_state->sp should be: &code_state->state[0] - 1 +// - code_state->n_state should be the number of objects in the local state +STATIC void mp_setup_code_state_helper(mp_code_state_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args) { // This function is pretty complicated. It's main aim is to be efficient in speed and RAM // usage for the common case of positional only args. // get the function object that we want to set up (could be bytecode or native code) mp_obj_fun_bc_t *self = code_state->fun_bc; - // ip comes in as an offset into bytecode, so turn it into a true pointer - code_state->ip = self->bytecode + (size_t)code_state->ip; - - #if MICROPY_STACKLESS - code_state->prev = NULL; - #endif - - #if MICROPY_PY_SYS_SETTRACE - code_state->prev_state = NULL; - code_state->frame = NULL; - #endif - // Get cached n_state (rather than decode it again) size_t n_state = code_state->n_state; // Decode prelude size_t n_state_unused, n_exc_stack_unused, scope_flags, n_pos_args, n_kwonly_args, n_def_pos_args; MP_BC_PRELUDE_SIG_DECODE_INTO(code_state->ip, n_state_unused, n_exc_stack_unused, scope_flags, n_pos_args, n_kwonly_args, n_def_pos_args); + MP_BC_PRELUDE_SIZE_DECODE(code_state->ip); (void)n_state_unused; (void)n_exc_stack_unused; - code_state->sp = &code_state->state[0] - 1; + mp_obj_t *code_state_state = code_state->sp + 1; code_state->exc_sp_idx = 0; // zero out the local stack to begin with - memset(code_state->state, 0, n_state * sizeof(*code_state->state)); + memset(code_state_state, 0, n_state * sizeof(*code_state->state)); const mp_obj_t *kwargs = args + n_args; // var_pos_kw_args points to the stack where the var-args tuple, and var-kw dict, should go (if they are needed) - mp_obj_t *var_pos_kw_args = &code_state->state[n_state - 1 - n_pos_args - n_kwonly_args]; + mp_obj_t *var_pos_kw_args = &code_state_state[n_state - 1 - n_pos_args - n_kwonly_args]; // check positional arguments @@ -169,7 +173,7 @@ void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw if (n_args >= (size_t)(n_pos_args - n_def_pos_args)) { // given enough arguments, but may need to use some default arguments for (size_t i = n_args; i < n_pos_args; i++) { - code_state->state[n_state - 1 - i] = self->extra_args[i - (n_pos_args - n_def_pos_args)]; + code_state_state[n_state - 1 - i] = self->extra_args[i - (n_pos_args - n_def_pos_args)]; } } else { fun_pos_args_mismatch(self, n_pos_args - n_def_pos_args, n_args); @@ -179,14 +183,14 @@ void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw // copy positional args into state for (size_t i = 0; i < n_args; i++) { - code_state->state[n_state - 1 - i] = args[i]; + code_state_state[n_state - 1 - i] = args[i]; } // check keyword arguments if (n_kw != 0 || (scope_flags & MP_SCOPE_FLAG_DEFKWARGS) != 0) { DEBUG_printf("Initial args: "); - dump_args(code_state->state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); + dump_args(code_state_state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); mp_obj_t dict = MP_OBJ_NULL; if ((scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) != 0) { @@ -194,19 +198,25 @@ void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw *var_pos_kw_args = dict; } - // get pointer to arg_names array - const mp_obj_t *arg_names = (const mp_obj_t *)self->const_table; - for (size_t i = 0; i < n_kw; i++) { // the keys in kwargs are expected to be qstr objects mp_obj_t wanted_arg_name = kwargs[2 * i]; + + // get pointer to arg_names array + const uint8_t *arg_names = code_state->ip; + arg_names = mp_decode_uint_skip(arg_names); + for (size_t j = 0; j < n_pos_args + n_kwonly_args; j++) { - if (wanted_arg_name == arg_names[j]) { - if (code_state->state[n_state - 1 - j] != MP_OBJ_NULL) { + qstr arg_qstr = mp_decode_uint(&arg_names); + #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE + arg_qstr = self->context->constants.qstr_table[arg_qstr]; + #endif + if (wanted_arg_name == MP_OBJ_NEW_QSTR(arg_qstr)) { + if (code_state_state[n_state - 1 - j] != MP_OBJ_NULL) { mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("function got multiple values for argument '%q'"), MP_OBJ_QSTR_VALUE(wanted_arg_name)); } - code_state->state[n_state - 1 - j] = kwargs[2 * i + 1]; + code_state_state[n_state - 1 - j] = kwargs[2 * i + 1]; goto continue2; } } @@ -224,10 +234,10 @@ void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw } DEBUG_printf("Args with kws flattened: "); - dump_args(code_state->state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); + dump_args(code_state_state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); // fill in defaults for positional args - mp_obj_t *d = &code_state->state[n_state - n_pos_args]; + mp_obj_t *d = &code_state_state[n_state - n_pos_args]; mp_obj_t *s = &self->extra_args[n_def_pos_args - 1]; for (size_t i = n_def_pos_args; i > 0; i--, d++, s--) { if (*d == MP_OBJ_NULL) { @@ -236,29 +246,37 @@ void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw } DEBUG_printf("Args after filling default positional: "); - dump_args(code_state->state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); + dump_args(code_state_state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); // Check that all mandatory positional args are specified - while (d < &code_state->state[n_state]) { + while (d < &code_state_state[n_state]) { if (*d++ == MP_OBJ_NULL) { mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("function missing required positional argument #%d"), &code_state->state[n_state] - d); + MP_ERROR_TEXT("function missing required positional argument #%d"), &code_state_state[n_state] - d); } } // Check that all mandatory keyword args are specified // Fill in default kw args if we have them + const uint8_t *arg_names = mp_decode_uint_skip(code_state->ip); + for (size_t i = 0; i < n_pos_args; i++) { + arg_names = mp_decode_uint_skip(arg_names); + } for (size_t i = 0; i < n_kwonly_args; i++) { - if (code_state->state[n_state - 1 - n_pos_args - i] == MP_OBJ_NULL) { + qstr arg_qstr = mp_decode_uint(&arg_names); + #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE + arg_qstr = self->context->constants.qstr_table[arg_qstr]; + #endif + if (code_state_state[n_state - 1 - n_pos_args - i] == MP_OBJ_NULL) { mp_map_elem_t *elem = NULL; if ((scope_flags & MP_SCOPE_FLAG_DEFKWARGS) != 0) { - elem = mp_map_lookup(&((mp_obj_dict_t *)MP_OBJ_TO_PTR(self->extra_args[n_def_pos_args]))->map, arg_names[n_pos_args + i], MP_MAP_LOOKUP); + elem = mp_map_lookup(&((mp_obj_dict_t *)MP_OBJ_TO_PTR(self->extra_args[n_def_pos_args]))->map, MP_OBJ_NEW_QSTR(arg_qstr), MP_MAP_LOOKUP); } if (elem != NULL) { - code_state->state[n_state - 1 - n_pos_args - i] = elem->value; + code_state_state[n_state - 1 - n_pos_args - i] = elem->value; } else { mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("function missing required keyword argument '%q'"), MP_OBJ_QSTR_VALUE(arg_names[n_pos_args + i])); + MP_ERROR_TEXT("function missing required keyword argument '%q'"), arg_qstr); } } } @@ -273,71 +291,49 @@ void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw } } - // read the size part of the prelude - const byte *ip = code_state->ip; - MP_BC_PRELUDE_SIZE_DECODE(ip); - - // jump over code info (source file and line-number mapping) - ip += n_info; + // jump over code info (source file, argument names and line-number mapping) + const uint8_t *ip = code_state->ip + n_info; // bytecode prelude: initialise closed over variables for (; n_cell; --n_cell) { size_t local_num = *ip++; - code_state->state[n_state - 1 - local_num] = - mp_obj_new_cell(code_state->state[n_state - 1 - local_num]); + code_state_state[n_state - 1 - local_num] = + mp_obj_new_cell(code_state_state[n_state - 1 - local_num]); } - #if !MICROPY_PERSISTENT_CODE - // so bytecode is aligned - ip = MP_ALIGN(ip, sizeof(mp_uint_t)); - #endif - // now that we skipped over the prelude, set the ip for the VM code_state->ip = ip; DEBUG_printf("Calling: n_pos_args=%d, n_kwonly_args=%d\n", n_pos_args, n_kwonly_args); - dump_args(code_state->state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); - dump_args(code_state->state, n_state); + dump_args(code_state_state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); + dump_args(code_state_state, n_state); } -#if MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE - -// The following table encodes the number of bytes that a specific opcode -// takes up. Some opcodes have an extra byte, defined by MP_BC_MASK_EXTRA_BYTE. -// There are 4 special opcodes that have an extra byte only when -// MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE is enabled (and they take a qstr): -// MP_BC_LOAD_NAME -// MP_BC_LOAD_GLOBAL -// MP_BC_LOAD_ATTR -// MP_BC_STORE_ATTR -uint mp_opcode_format(const byte *ip, size_t *opcode_size, bool count_var_uint) { - uint f = MP_BC_FORMAT(*ip); - const byte *ip_start = ip; - if (f == MP_BC_FORMAT_QSTR) { - if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE_DYNAMIC) { - if (*ip == MP_BC_LOAD_NAME - || *ip == MP_BC_LOAD_GLOBAL - || *ip == MP_BC_LOAD_ATTR - || *ip == MP_BC_STORE_ATTR) { - ip += 1; - } - } - ip += 3; - } else { - int extra_byte = (*ip & MP_BC_MASK_EXTRA_BYTE) == 0; - ip += 1; - if (f == MP_BC_FORMAT_VAR_UINT) { - if (count_var_uint) { - while ((*ip++ & 0x80) != 0) { - } - } - } else if (f == MP_BC_FORMAT_OFFSET) { - ip += 2; - } - ip += extra_byte; - } - *opcode_size = ip - ip_start; - return f; +// On entry code_state should be allocated somewhere (stack/heap) and +// contain the following valid entries: +// - code_state->fun_bc should contain a pointer to the function object +// - code_state->n_state should be the number of objects in the local state +void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args) { + code_state->ip = code_state->fun_bc->bytecode; + code_state->sp = &code_state->state[0] - 1; + #if MICROPY_STACKLESS + code_state->prev = NULL; + #endif + #if MICROPY_PY_SYS_SETTRACE + code_state->prev_state = NULL; + code_state->frame = NULL; + #endif + mp_setup_code_state_helper(code_state, n_args, n_kw, args); } -#endif // MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE +#if MICROPY_EMIT_NATIVE +// On entry code_state should be allocated somewhere (stack/heap) and +// contain the following valid entries: +// - code_state->fun_bc should contain a pointer to the function object +// - code_state->ip should contain a pointer to the beginning of the prelude +// - code_state->n_state should be the number of objects in the local state +void mp_setup_code_state_native(mp_code_state_native_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args) { + code_state->sp = &code_state->state[0] - 1; + mp_setup_code_state_helper((mp_code_state_t *)code_state, n_args, n_kw, args); +} +#endif diff --git a/python/src/py/bc.h b/python/src/py/bc.h index ef5afeae165..6350eee52e6 100644 --- a/python/src/py/bc.h +++ b/python/src/py/bc.h @@ -28,7 +28,6 @@ #define MICROPY_INCLUDED_PY_BC_H #include "py/runtime.h" -#include "py/objfun.h" // bytecode layout: // @@ -50,7 +49,9 @@ // // source info section: // simple_name : var qstr -// source_file : var qstr +// argname0 : var qstr +// ... : var qstr +// argnameN : var qstr N = num_pos_args + num_kwonly_args - 1 // // // closure section: @@ -58,19 +59,16 @@ // ... : byte // local_numN : byte N = n_cells-1 // -// only needed if bytecode contains pointers -// // // // // constant table layout: // -// argname0 : obj (qstr) -// ... : obj (qstr) -// argnameN : obj (qstr) N = num_pos_args + num_kwonly_args // const0 : obj // constN : obj +#define MP_ENCODE_UINT_MAX_BYTES ((MP_BYTES_PER_OBJ_WORD * 8 + 6) / 7) + #define MP_BC_PRELUDE_SIG_ENCODE(S, E, scope, out_byte, out_env) \ do { \ /*// Get values to store in prelude */ \ @@ -182,9 +180,9 @@ typedef struct _mp_bytecode_prelude_t { uint n_pos_args; uint n_kwonly_args; uint n_def_pos_args; - qstr qstr_block_name; - qstr qstr_source_file; + qstr qstr_block_name_idx; const byte *line_info; + const byte *line_info_top; const byte *opcodes; } mp_bytecode_prelude_t; @@ -198,12 +196,46 @@ typedef struct _mp_exc_stack_t { mp_obj_base_t *prev_exc; } mp_exc_stack_t; +// Constants associated with a module, to interface bytecode with runtime. +typedef struct _mp_module_constants_t { + #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE + qstr_short_t *qstr_table; + #else + qstr source_file; + #endif + mp_obj_t *obj_table; +} mp_module_constants_t; + +// State associated with a module. +typedef struct _mp_module_context_t { + mp_obj_module_t module; + mp_module_constants_t constants; +} mp_module_context_t; + +// Outer level struct defining a compiled module. +typedef struct _mp_compiled_module_t { + const mp_module_context_t *context; + const struct _mp_raw_code_t *rc; + #if MICROPY_PERSISTENT_CODE_SAVE + bool has_native; + size_t n_qstr; + size_t n_obj; + #endif +} mp_compiled_module_t; + +// Outer level struct defining a frozen module. +typedef struct _mp_frozen_module_t { + const mp_module_constants_t constants; + const struct _mp_raw_code_t *rc; +} mp_frozen_module_t; + +// State for an executing function. typedef struct _mp_code_state_t { // The fun_bc entry points to the underlying function object that is being executed. // It is needed to access the start of bytecode and the const_table. // It is also needed to prevent the GC from reclaiming the bytecode during execution, // because the ip pointer below will always point to the interior of the bytecode. - mp_obj_fun_bc_t *fun_bc; + struct _mp_obj_fun_bc_t *fun_bc; const byte *ip; mp_obj_t *sp; uint16_t n_state; @@ -222,17 +254,37 @@ typedef struct _mp_code_state_t { // mp_exc_stack_t exc_state[0]; } mp_code_state_t; +// State for an executing native function (based on mp_code_state_t). +typedef struct _mp_code_state_native_t { + struct _mp_obj_fun_bc_t *fun_bc; + const byte *ip; + mp_obj_t *sp; + uint16_t n_state; + uint16_t exc_sp_idx; + mp_obj_dict_t *old_globals; + mp_obj_t state[0]; +} mp_code_state_native_t; + +// Allocator may return NULL, in which case data is not stored (can be used to compute size). +typedef uint8_t *(*mp_encode_uint_allocator_t)(void *env, size_t nbytes); + +void mp_encode_uint(void *env, mp_encode_uint_allocator_t allocator, mp_uint_t val); mp_uint_t mp_decode_uint(const byte **ptr); mp_uint_t mp_decode_uint_value(const byte *ptr); const byte *mp_decode_uint_skip(const byte *ptr); -mp_vm_return_kind_t mp_execute_bytecode(mp_code_state_t *code_state, volatile mp_obj_t inject_exc); +mp_vm_return_kind_t mp_execute_bytecode(mp_code_state_t *code_state, +#ifndef __cplusplus + volatile +#endif + mp_obj_t inject_exc); mp_code_state_t *mp_obj_fun_bc_prepare_codestate(mp_obj_t func, size_t n_args, size_t n_kw, const mp_obj_t *args); void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args); -void mp_bytecode_print(const mp_print_t *print, const void *descr, const byte *code, mp_uint_t len, const mp_uint_t *const_table); -void mp_bytecode_print2(const mp_print_t *print, const byte *code, size_t len, const mp_uint_t *const_table); -const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip); -#define mp_bytecode_print_inst(print, code, const_table) mp_bytecode_print2(print, code, 1, const_table) +void mp_setup_code_state_native(mp_code_state_native_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args); +void mp_bytecode_print(const mp_print_t *print, const struct _mp_raw_code_t *rc, const mp_module_constants_t *cm); +void mp_bytecode_print2(const mp_print_t *print, const byte *ip, size_t len, struct _mp_raw_code_t *const *child_table, const mp_module_constants_t *cm); +const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip_start, const byte *ip, struct _mp_raw_code_t *const *child_table, const mp_module_constants_t *cm); +#define mp_bytecode_print_inst(print, code, x_table) mp_bytecode_print2(print, code, 1, x_table) // Helper macros to access pointer with least significant bits holding flags #define MP_TAGPTR_PTR(x) ((void *)((uintptr_t)(x) & ~((uintptr_t)3))) @@ -240,16 +292,26 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip); #define MP_TAGPTR_TAG1(x) ((uintptr_t)(x) & 2) #define MP_TAGPTR_MAKE(ptr, tag) ((void *)((uintptr_t)(ptr) | (tag))) -#if MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE - -uint mp_opcode_format(const byte *ip, size_t *opcode_size, bool count_var_uint); - -#endif +static inline void mp_module_context_alloc_tables(mp_module_context_t *context, size_t n_qstr, size_t n_obj) { + #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE + size_t nq = (n_qstr * sizeof(qstr_short_t) + sizeof(mp_uint_t) - 1) / sizeof(mp_uint_t); + size_t no = n_obj; + mp_uint_t *mem = m_new(mp_uint_t, nq + no); + context->constants.qstr_table = (qstr_short_t *)mem; + context->constants.obj_table = (mp_obj_t *)(mem + nq); + #else + if (n_obj == 0) { + context->constants.obj_table = NULL; + } else { + context->constants.obj_table = m_new(mp_obj_t, n_obj); + } + #endif +} -static inline size_t mp_bytecode_get_source_line(const byte *line_info, size_t bc_offset) { +static inline size_t mp_bytecode_get_source_line(const byte *line_info, const byte *line_info_top, size_t bc_offset) { size_t source_line = 1; - size_t c; - while ((c = *line_info)) { + while (line_info < line_info_top) { + size_t c = *line_info; size_t b, l; if ((c & 0x80) == 0) { // 0b0LLBBBBB encoding diff --git a/python/src/py/bc0.h b/python/src/py/bc0.h index 842034ebfb6..a4a0acf9377 100644 --- a/python/src/py/bc0.h +++ b/python/src/py/bc0.h @@ -28,6 +28,18 @@ // MicroPython bytecode opcodes, grouped based on the format of the opcode +// All opcodes are encoded as a byte with an optional argument. Arguments are +// variable-length encoded so they can be as small as possible. The possible +// encodings for arguments are (ip[0] is the opcode): +// +// - unsigned relative bytecode offset: +// - if ip[1] high bit is clear then: arg = ip[1] +// - if ip[1] high bit is set then: arg = ip[1] & 0x7f | ip[2] << 7 +// +// - signed relative bytecode offset: +// - if ip[1] high bit is clear then: arg = ip[1] - 0x40 +// - if ip[1] high bit is set then: arg = (ip[1] & 0x7f | ip[2] << 7) - 0x4000 + #define MP_BC_MASK_FORMAT (0xf0) #define MP_BC_MASK_EXTRA_BYTE (0x9e) @@ -101,17 +113,17 @@ #define MP_BC_ROT_TWO (MP_BC_BASE_BYTE_O + 0x0a) #define MP_BC_ROT_THREE (MP_BC_BASE_BYTE_O + 0x0b) -#define MP_BC_JUMP (MP_BC_BASE_JUMP_E + 0x02) // rel byte code offset, 16-bit signed, in excess -#define MP_BC_POP_JUMP_IF_TRUE (MP_BC_BASE_JUMP_E + 0x03) // rel byte code offset, 16-bit signed, in excess -#define MP_BC_POP_JUMP_IF_FALSE (MP_BC_BASE_JUMP_E + 0x04) // rel byte code offset, 16-bit signed, in excess -#define MP_BC_JUMP_IF_TRUE_OR_POP (MP_BC_BASE_JUMP_E + 0x05) // rel byte code offset, 16-bit signed, in excess -#define MP_BC_JUMP_IF_FALSE_OR_POP (MP_BC_BASE_JUMP_E + 0x06) // rel byte code offset, 16-bit signed, in excess -#define MP_BC_UNWIND_JUMP (MP_BC_BASE_JUMP_E + 0x00) // rel byte code offset, 16-bit signed, in excess; then a byte -#define MP_BC_SETUP_WITH (MP_BC_BASE_JUMP_E + 0x07) // rel byte code offset, 16-bit unsigned -#define MP_BC_SETUP_EXCEPT (MP_BC_BASE_JUMP_E + 0x08) // rel byte code offset, 16-bit unsigned -#define MP_BC_SETUP_FINALLY (MP_BC_BASE_JUMP_E + 0x09) // rel byte code offset, 16-bit unsigned -#define MP_BC_POP_EXCEPT_JUMP (MP_BC_BASE_JUMP_E + 0x0a) // rel byte code offset, 16-bit unsigned -#define MP_BC_FOR_ITER (MP_BC_BASE_JUMP_E + 0x0b) // rel byte code offset, 16-bit unsigned +#define MP_BC_UNWIND_JUMP (MP_BC_BASE_JUMP_E + 0x00) // signed relative bytecode offset; then a byte +#define MP_BC_JUMP (MP_BC_BASE_JUMP_E + 0x02) // signed relative bytecode offset +#define MP_BC_POP_JUMP_IF_TRUE (MP_BC_BASE_JUMP_E + 0x03) // signed relative bytecode offset +#define MP_BC_POP_JUMP_IF_FALSE (MP_BC_BASE_JUMP_E + 0x04) // signed relative bytecode offset +#define MP_BC_JUMP_IF_TRUE_OR_POP (MP_BC_BASE_JUMP_E + 0x05) // unsigned relative bytecode offset +#define MP_BC_JUMP_IF_FALSE_OR_POP (MP_BC_BASE_JUMP_E + 0x06) // unsigned relative bytecode offset +#define MP_BC_SETUP_WITH (MP_BC_BASE_JUMP_E + 0x07) // unsigned relative bytecode offset +#define MP_BC_SETUP_EXCEPT (MP_BC_BASE_JUMP_E + 0x08) // unsigned relative bytecode offset +#define MP_BC_SETUP_FINALLY (MP_BC_BASE_JUMP_E + 0x09) // unsigned relative bytecode offset +#define MP_BC_POP_EXCEPT_JUMP (MP_BC_BASE_JUMP_E + 0x0a) // unsigned relative bytecode offset +#define MP_BC_FOR_ITER (MP_BC_BASE_JUMP_E + 0x0b) // unsigned relative bytecode offset #define MP_BC_WITH_CLEANUP (MP_BC_BASE_BYTE_O + 0x0c) #define MP_BC_END_FINALLY (MP_BC_BASE_BYTE_O + 0x0d) #define MP_BC_GET_ITER (MP_BC_BASE_BYTE_O + 0x0e) diff --git a/python/src/py/builtin.h b/python/src/py/builtin.h index 1e4769cd69a..a6f824ca20f 100644 --- a/python/src/py/builtin.h +++ b/python/src/py/builtin.h @@ -28,8 +28,43 @@ #include "py/obj.h" -mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args); +typedef enum { + MP_IMPORT_STAT_NO_EXIST, + MP_IMPORT_STAT_DIR, + MP_IMPORT_STAT_FILE, +} mp_import_stat_t; + +#if MICROPY_VFS + +// Delegate to the VFS for import stat and builtin open. + +#define mp_builtin_open_obj mp_vfs_open_obj + +mp_import_stat_t mp_vfs_import_stat(const char *path); +mp_obj_t mp_vfs_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs); + +MP_DECLARE_CONST_FUN_OBJ_KW(mp_vfs_open_obj); + +static inline mp_import_stat_t mp_import_stat(const char *path) { + return mp_vfs_import_stat(path); +} + +static inline mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + return mp_vfs_open(n_args, args, kwargs); +} + +#else + +// A port can provide implementations of these functions. +mp_import_stat_t mp_import_stat(const char *path); mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs); + +// A port can provide this object. +MP_DECLARE_CONST_FUN_OBJ_KW(mp_builtin_open_obj); + +#endif + +mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args); mp_obj_t mp_micropython_mem_info(size_t n_args, const mp_obj_t *args); MP_DECLARE_CONST_FUN_OBJ_VAR(mp_builtin___build_class___obj); @@ -76,9 +111,7 @@ MP_DECLARE_CONST_FUN_OBJ_1(mp_builtin_repr_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_round_obj); MP_DECLARE_CONST_FUN_OBJ_KW(mp_builtin_sorted_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_sum_obj); -// Defined by a port, but declared here for simplicity MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_input_obj); -MP_DECLARE_CONST_FUN_OBJ_KW(mp_builtin_open_obj); MP_DECLARE_CONST_FUN_OBJ_2(mp_namedtuple_obj); @@ -108,6 +141,7 @@ extern const mp_obj_module_t mp_module_uerrno; extern const mp_obj_module_t mp_module_uctypes; extern const mp_obj_module_t mp_module_uzlib; extern const mp_obj_module_t mp_module_ujson; +extern const mp_obj_module_t mp_module_uos; extern const mp_obj_module_t mp_module_ure; extern const mp_obj_module_t mp_module_uheapq; extern const mp_obj_module_t mp_module_uhashlib; @@ -124,6 +158,7 @@ extern const mp_obj_module_t mp_module_webrepl; extern const mp_obj_module_t mp_module_framebuf; extern const mp_obj_module_t mp_module_btree; extern const mp_obj_module_t mp_module_ubluetooth; +extern const mp_obj_module_t mp_module_uplatform; extern const char MICROPY_PY_BUILTINS_HELP_TEXT[]; diff --git a/python/src/py/builtinevex.c b/python/src/py/builtinevex.c index 800a20223aa..73b77b40b70 100644 --- a/python/src/py/builtinevex.c +++ b/python/src/py/builtinevex.c @@ -54,7 +54,7 @@ STATIC mp_obj_t code_execute(mp_obj_code_t *self, mp_obj_dict_t *globals, mp_obj // the correct one if (mp_obj_is_type(self->module_fun, &mp_type_fun_bc)) { mp_obj_fun_bc_t *fun_bc = MP_OBJ_TO_PTR(self->module_fun); - fun_bc->globals = globals; + ((mp_module_context_t *)fun_bc->context)->module.globals = globals; } // execute code @@ -103,8 +103,7 @@ STATIC mp_obj_t mp_builtin_compile(size_t n_args, const mp_obj_t *args) { mp_raise_ValueError(MP_ERROR_TEXT("bad compile mode")); } - mp_obj_code_t *code = m_new_obj(mp_obj_code_t); - code->base.type = &mp_type_code; + mp_obj_code_t *code = mp_obj_malloc(mp_obj_code_t, &mp_type_code); code->module_fun = mp_parse_compile_execute(lex, parse_input_kind, NULL, NULL); return MP_OBJ_FROM_PTR(code); } diff --git a/python/src/py/builtinhelp.c b/python/src/py/builtinhelp.c index 13735635e3c..84d69caf35b 100644 --- a/python/src/py/builtinhelp.c +++ b/python/src/py/builtinhelp.c @@ -67,10 +67,10 @@ STATIC void mp_help_add_from_map(mp_obj_t list, const mp_map_t *map) { #if MICROPY_MODULE_FROZEN STATIC void mp_help_add_from_names(mp_obj_t list, const char *name) { while (*name) { - size_t l = strlen(name); + size_t len = strlen(name); // name should end in '.py' and we strip it off - mp_obj_list_append(list, mp_obj_new_str(name, l - 3)); - name += l + 1; + mp_obj_list_append(list, mp_obj_new_str(name, len - 3)); + name += len + 1; } } #endif @@ -80,14 +80,9 @@ STATIC void mp_help_print_modules(void) { mp_help_add_from_map(list, &mp_builtin_module_map); - #if MICROPY_MODULE_FROZEN_STR - extern const char mp_frozen_str_names[]; - mp_help_add_from_names(list, mp_frozen_str_names); - #endif - - #if MICROPY_MODULE_FROZEN_MPY - extern const char mp_frozen_mpy_names[]; - mp_help_add_from_names(list, mp_frozen_mpy_names); + #if MICROPY_MODULE_FROZEN + extern const char mp_frozen_names[]; + mp_help_add_from_names(list, mp_frozen_names); #endif // sort the list so it's printed in alphabetical order diff --git a/python/src/py/builtinimport.c b/python/src/py/builtinimport.c index cdee5e4070b..cd9636ccdc2 100644 --- a/python/src/py/builtinimport.c +++ b/python/src/py/builtinimport.c @@ -5,6 +5,7 @@ * * Copyright (c) 2013-2019 Damien P. George * Copyright (c) 2014 Paul Sokolovsky + * Copyright (c) 2021 Jim Mussared * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -46,7 +47,11 @@ #if MICROPY_ENABLE_EXTERNAL_IMPORT -#define PATH_SEP_CHAR '/' +// Must be a string of one byte. +#define PATH_SEP_CHAR "/" + +// Virtual sys.path entry that maps to the frozen modules. +#define MP_FROZEN_PATH_PREFIX ".frozen/" bool mp_obj_is_package(mp_obj_t module) { mp_obj_t dest[2]; @@ -54,27 +59,33 @@ bool mp_obj_is_package(mp_obj_t module) { return dest[0] != MP_OBJ_NULL; } -// Stat either frozen or normal module by a given path -// (whatever is available, if at all). -STATIC mp_import_stat_t mp_import_stat_any(const char *path) { +// Wrapper for mp_import_stat (which is provided by the port, and typically +// uses mp_vfs_import_stat) to also search frozen modules. Given an exact +// path to a file or directory (e.g. "foo/bar", foo/bar.py" or "foo/bar.mpy"), +// will return whether the path is a file, directory, or doesn't exist. +STATIC mp_import_stat_t stat_path_or_frozen(const char *path) { #if MICROPY_MODULE_FROZEN - mp_import_stat_t st = mp_frozen_stat(path); - if (st != MP_IMPORT_STAT_NO_EXIST) { - return st; + // Only try and load as a frozen module if it starts with .frozen/. + const int frozen_path_prefix_len = strlen(MP_FROZEN_PATH_PREFIX); + if (strncmp(path, MP_FROZEN_PATH_PREFIX, frozen_path_prefix_len) == 0) { + return mp_find_frozen_module(path + frozen_path_prefix_len, NULL, NULL); } #endif return mp_import_stat(path); } +// Given a path to a .py file, try and find this path as either a .py or .mpy +// in either the filesystem or frozen modules. STATIC mp_import_stat_t stat_file_py_or_mpy(vstr_t *path) { - mp_import_stat_t stat = mp_import_stat_any(vstr_null_terminated_str(path)); + mp_import_stat_t stat = stat_path_or_frozen(vstr_null_terminated_str(path)); if (stat == MP_IMPORT_STAT_FILE) { return stat; } #if MICROPY_PERSISTENT_CODE_LOAD + // Didn't find .py -- try the .mpy instead by inserting an 'm' into the '.py'. vstr_ins_byte(path, path->len - 2, 'm'); - stat = mp_import_stat_any(vstr_null_terminated_str(path)); + stat = stat_path_or_frozen(vstr_null_terminated_str(path)); if (stat == MP_IMPORT_STAT_FILE) { return stat; } @@ -83,8 +94,10 @@ STATIC mp_import_stat_t stat_file_py_or_mpy(vstr_t *path) { return MP_IMPORT_STAT_NO_EXIST; } +// Given an import path (e.g. "foo/bar"), try and find "foo/bar" (a directory) +// or "foo/bar.(m)py" in either the filesystem or frozen modules. STATIC mp_import_stat_t stat_dir_or_file(vstr_t *path) { - mp_import_stat_t stat = mp_import_stat_any(vstr_null_terminated_str(path)); + mp_import_stat_t stat = stat_path_or_frozen(vstr_null_terminated_str(path)); DEBUG_printf("stat %s: %d\n", vstr_str(path), stat); if (stat == MP_IMPORT_STAT_DIR) { return stat; @@ -95,14 +108,16 @@ STATIC mp_import_stat_t stat_dir_or_file(vstr_t *path) { return stat_file_py_or_mpy(path); } -STATIC mp_import_stat_t find_file(const char *file_str, uint file_len, vstr_t *dest) { +// Given a top-level module, try and find it in each of the sys.path entries +// via stat_dir_or_file. +STATIC mp_import_stat_t stat_top_level_dir_or_file(qstr mod_name, vstr_t *dest) { + DEBUG_printf("stat_top_level_dir_or_file: '%s'\n", qstr_str(mod_name)); #if MICROPY_PY_SYS - // extract the list of paths size_t path_num; mp_obj_t *path_items; mp_obj_list_get(mp_sys_path, &path_num, &path_items); - if (path_num != 0) { + if (path_num > 0) { // go through each path looking for a directory or file for (size_t i = 0; i < path_num; i++) { vstr_reset(dest); @@ -110,9 +125,9 @@ STATIC mp_import_stat_t find_file(const char *file_str, uint file_len, vstr_t *d const char *p = mp_obj_str_get_data(path_items[i], &p_len); if (p_len > 0) { vstr_add_strn(dest, p, p_len); - vstr_add_char(dest, PATH_SEP_CHAR); + vstr_add_char(dest, PATH_SEP_CHAR[0]); } - vstr_add_strn(dest, file_str, file_len); + vstr_add_str(dest, qstr_str(mod_name)); mp_import_stat_t stat = stat_dir_or_file(dest); if (stat != MP_IMPORT_STAT_NO_EXIST) { return stat; @@ -124,34 +139,35 @@ STATIC mp_import_stat_t find_file(const char *file_str, uint file_len, vstr_t *d } #endif - // mp_sys_path is empty, so just use the given file name - vstr_add_strn(dest, file_str, file_len); + // mp_sys_path is empty (or not enabled), so just stat the given path + // directly. + vstr_add_str(dest, qstr_str(mod_name)); return stat_dir_or_file(dest); } #if MICROPY_MODULE_FROZEN_STR || MICROPY_ENABLE_COMPILER -STATIC void do_load_from_lexer(mp_obj_t module_obj, mp_lexer_t *lex) { +STATIC void do_load_from_lexer(mp_module_context_t *context, mp_lexer_t *lex) { #if MICROPY_PY___FILE__ qstr source_name = lex->source_name; - mp_store_attr(module_obj, MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); + mp_store_attr(MP_OBJ_FROM_PTR(&context->module), MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); #endif // parse, compile and execute the module in its context - mp_obj_dict_t *mod_globals = mp_obj_module_get_globals(module_obj); + mp_obj_dict_t *mod_globals = context->module.globals; mp_parse_compile_execute(lex, MP_PARSE_FILE_INPUT, mod_globals, mod_globals); } #endif #if (MICROPY_HAS_FILE_READER && MICROPY_PERSISTENT_CODE_LOAD) || MICROPY_MODULE_FROZEN_MPY -STATIC void do_execute_raw_code(mp_obj_t module_obj, mp_raw_code_t *raw_code, const char *source_name) { +STATIC void do_execute_raw_code(mp_module_context_t *context, const mp_raw_code_t *rc, const mp_module_context_t *mc, const char *source_name) { (void)source_name; #if MICROPY_PY___FILE__ - mp_store_attr(module_obj, MP_QSTR___file__, MP_OBJ_NEW_QSTR(qstr_from_str(source_name))); + mp_store_attr(MP_OBJ_FROM_PTR(&context->module), MP_QSTR___file__, MP_OBJ_NEW_QSTR(qstr_from_str(source_name))); #endif // execute the module in its context - mp_obj_dict_t *mod_globals = mp_obj_module_get_globals(module_obj); + mp_obj_dict_t *mod_globals = context->module.globals; // save context mp_obj_dict_t *volatile old_globals = mp_globals_get(); @@ -163,7 +179,7 @@ STATIC void do_execute_raw_code(mp_obj_t module_obj, mp_raw_code_t *raw_code, co nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { - mp_obj_t module_fun = mp_make_function_from_raw_code(raw_code, MP_OBJ_NULL, MP_OBJ_NULL); + mp_obj_t module_fun = mp_make_function_from_raw_code(rc, mc, NULL); mp_call_function_0(module_fun); // finish nlr block, restore context @@ -179,42 +195,49 @@ STATIC void do_execute_raw_code(mp_obj_t module_obj, mp_raw_code_t *raw_code, co } #endif -STATIC void do_load(mp_obj_t module_obj, vstr_t *file) { +STATIC void do_load(mp_module_context_t *module_obj, vstr_t *file) { #if MICROPY_MODULE_FROZEN || MICROPY_ENABLE_COMPILER || (MICROPY_PERSISTENT_CODE_LOAD && MICROPY_HAS_FILE_READER) - char *file_str = vstr_null_terminated_str(file); + const char *file_str = vstr_null_terminated_str(file); #endif // If we support frozen modules (either as str or mpy) then try to find the // requested filename in the list of frozen module filenames. #if MICROPY_MODULE_FROZEN void *modref; - int frozen_type = mp_find_frozen_module(file_str, file->len, &modref); - #endif + int frozen_type; + const int frozen_path_prefix_len = strlen(MP_FROZEN_PATH_PREFIX); + if (strncmp(file_str, MP_FROZEN_PATH_PREFIX, frozen_path_prefix_len) == 0) { + mp_find_frozen_module(file_str + frozen_path_prefix_len, &frozen_type, &modref); + + // If we support frozen str modules and the compiler is enabled, and we + // found the filename in the list of frozen files, then load and execute it. + #if MICROPY_MODULE_FROZEN_STR + if (frozen_type == MP_FROZEN_STR) { + do_load_from_lexer(module_obj, modref); + return; + } + #endif - // If we support frozen str modules and the compiler is enabled, and we - // found the filename in the list of frozen files, then load and execute it. - #if MICROPY_MODULE_FROZEN_STR - if (frozen_type == MP_FROZEN_STR) { - do_load_from_lexer(module_obj, modref); - return; + // If we support frozen mpy modules and we found a corresponding file (and + // its data) in the list of frozen files, execute it. + #if MICROPY_MODULE_FROZEN_MPY + if (frozen_type == MP_FROZEN_MPY) { + const mp_frozen_module_t *frozen = modref; + module_obj->constants = frozen->constants; + do_execute_raw_code(module_obj, frozen->rc, module_obj, file_str + frozen_path_prefix_len); + return; + } + #endif } - #endif - // If we support frozen mpy modules and we found a corresponding file (and - // its data) in the list of frozen files, execute it. - #if MICROPY_MODULE_FROZEN_MPY - if (frozen_type == MP_FROZEN_MPY) { - do_execute_raw_code(module_obj, modref, file_str); - return; - } - #endif + #endif // MICROPY_MODULE_FROZEN // If we support loading .mpy files then check if the file extension is of // the correct format and, if so, load and execute the file. #if MICROPY_HAS_FILE_READER && MICROPY_PERSISTENT_CODE_LOAD if (file_str[file->len - 3] == 'm') { - mp_raw_code_t *raw_code = mp_raw_code_load_file(file_str); - do_execute_raw_code(module_obj, raw_code, file_str); + mp_compiled_module_t cm = mp_raw_code_load_file(file_str, module_obj); + do_execute_raw_code(module_obj, cm.rc, cm.context, file_str); return; } #endif @@ -232,15 +255,216 @@ STATIC void do_load(mp_obj_t module_obj, vstr_t *file) { #endif } -STATIC void chop_component(const char *start, const char **end) { - const char *p = *end; - while (p > start) { +// Convert a relative (to the current module) import, going up "level" levels, +// into an absolute import. +STATIC void evaluate_relative_import(mp_int_t level, const char **module_name, size_t *module_name_len) { + // What we want to do here is to take the name of the current module, + // remove trailing components, and concatenate the passed-in + // module name. + // For example, level=3, module_name="foo.bar", __name__="a.b.c.d" --> "a.foo.bar" + // "Relative imports use a module's __name__ attribute to determine that + // module's position in the package hierarchy." + // http://legacy.python.org/dev/peps/pep-0328/#relative-imports-and-name + + mp_obj_t current_module_name_obj = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___name__)); + assert(current_module_name_obj != MP_OBJ_NULL); + + #if MICROPY_MODULE_OVERRIDE_MAIN_IMPORT && MICROPY_CPYTHON_COMPAT + if (MP_OBJ_QSTR_VALUE(current_module_name_obj) == MP_QSTR___main__) { + // This is a module loaded by -m command-line switch (e.g. unix port), + // and so its __name__ has been set to "__main__". Get its real name + // that we stored during import in the __main__ attribute. + current_module_name_obj = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); + } + #endif + + // If we have a __path__ in the globals dict, then we're a package. + bool is_pkg = mp_map_lookup(&mp_globals_get()->map, MP_OBJ_NEW_QSTR(MP_QSTR___path__), MP_MAP_LOOKUP); + + #if DEBUG_PRINT + DEBUG_printf("Current module/package: "); + mp_obj_print_helper(MICROPY_DEBUG_PRINTER, current_module_name_obj, PRINT_REPR); + DEBUG_printf(", is_package: %d", is_pkg); + DEBUG_printf("\n"); + #endif + + size_t current_module_name_len; + const char *current_module_name = mp_obj_str_get_data(current_module_name_obj, ¤t_module_name_len); + + const char *p = current_module_name + current_module_name_len; + if (is_pkg) { + // If we're evaluating relative to a package, then take off one fewer + // level (i.e. the relative search starts inside the package, rather + // than as a sibling of the package). + --level; + } + + // Walk back 'level' dots (or run out of path). + while (level && p > current_module_name) { if (*--p == '.') { - *end = p; - return; + --level; + } + } + + // We must have some component left over to import from. + if (p == current_module_name) { + mp_raise_msg(&mp_type_ImportError, MP_ERROR_TEXT("can't perform relative import")); + } + + // New length is len("."). Note: might be one byte + // more than we need if module_name is empty (for the extra . we will + // append). + uint new_module_name_len = (size_t)(p - current_module_name) + 1 + *module_name_len; + char *new_mod = mp_local_alloc(new_module_name_len); + memcpy(new_mod, current_module_name, p - current_module_name); + + // Only append "." if there was one). + if (*module_name_len != 0) { + new_mod[p - current_module_name] = '.'; + memcpy(new_mod + (p - current_module_name) + 1, *module_name, *module_name_len); + } else { + --new_module_name_len; + } + + // Copy into a QSTR. + qstr new_mod_q = qstr_from_strn(new_mod, new_module_name_len); + mp_local_free(new_mod); + + DEBUG_printf("Resolved base name for relative import: '%s'\n", qstr_str(new_mod_q)); + *module_name = qstr_str(new_mod_q); + *module_name_len = new_module_name_len; +} + +// Load a module at the specified absolute path, possibly as a submodule of the given outer module. +// full_mod_name: The full absolute path to this module (e.g. "foo.bar.baz"). +// level_mod_name: The final component of the path (e.g. "baz"). +// outer_module_obj: The parent module (we need to store this module as an +// attribute on it) (or MP_OBJ_NULL for top-level). +// path: The filesystem path where we found the parent module +// (or empty for a top level module). +// override_main: Whether to set the __name__ to "__main__" (and use __main__ +// for the actual path). +STATIC mp_obj_t process_import_at_level(qstr full_mod_name, qstr level_mod_name, mp_obj_t outer_module_obj, vstr_t *path, bool override_main) { + mp_import_stat_t stat = MP_IMPORT_STAT_NO_EXIST; + + // Exact-match of built-in (or already-loaded) takes priority. + mp_obj_t module_obj = mp_module_get_loaded_or_builtin(full_mod_name); + + // Even if we find the module, go through the motions of searching for it + // because we may actually be in the process of importing a sub-module. + // So we need to (re-)find the correct path to be finding the sub-module + // on the next iteration of process_import_at_level. + + if (outer_module_obj == MP_OBJ_NULL) { + DEBUG_printf("Searching for top-level module\n"); + + // First module in the dotted-name; search for a directory or file + // relative to all the locations in sys.path. + stat = stat_top_level_dir_or_file(full_mod_name, path); + + // If the module "foo" doesn't exist on the filesystem, and it's not a + // builtin, try and find "ufoo" as a built-in. (This feature was + // formerly known as "weak links"). + #if MICROPY_MODULE_WEAK_LINKS + if (stat == MP_IMPORT_STAT_NO_EXIST && module_obj == MP_OBJ_NULL) { + char *umodule_buf = vstr_str(path); + umodule_buf[0] = 'u'; + strcpy(umodule_buf + 1, qstr_str(level_mod_name)); + qstr umodule_name = qstr_from_str(umodule_buf); + module_obj = mp_module_get_builtin(umodule_name); + } + #elif MICROPY_PY_SYS + if (stat == MP_IMPORT_STAT_NO_EXIST && module_obj == MP_OBJ_NULL && level_mod_name == MP_QSTR_sys) { + module_obj = MP_OBJ_FROM_PTR(&mp_module_sys); + } + #endif + } else { + DEBUG_printf("Searching for sub-module\n"); + + // Add the current part of the module name to the path. + vstr_add_char(path, PATH_SEP_CHAR[0]); + vstr_add_str(path, qstr_str(level_mod_name)); + + // Because it's not top level, we already know which path the parent was found in. + stat = stat_dir_or_file(path); + } + DEBUG_printf("Current path: %.*s\n", (int)vstr_len(path), vstr_str(path)); + + if (module_obj == MP_OBJ_NULL) { + // Not a built-in and not already-loaded. + + if (stat == MP_IMPORT_STAT_NO_EXIST) { + // And the file wasn't found -- fail. + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE + mp_raise_msg(&mp_type_ImportError, MP_ERROR_TEXT("module not found")); + #else + mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("no module named '%q'"), full_mod_name); + #endif + } + + // Not a built-in but found on the filesystem, try and load it. + + DEBUG_printf("Found path: %.*s\n", (int)vstr_len(path), vstr_str(path)); + + // Prepare for loading from the filesystem. Create a new shell module. + module_obj = mp_obj_new_module(full_mod_name); + + #if MICROPY_MODULE_OVERRIDE_MAIN_IMPORT + // If this module is being loaded via -m on unix, then + // override __name__ to "__main__". Do this only for *modules* + // however - packages never have their names replaced, instead + // they're -m'ed using a special __main__ submodule in them. (This all + // apparently is done to not touch the package name itself, which is + // important for future imports). + if (override_main && stat != MP_IMPORT_STAT_DIR) { + mp_obj_module_t *o = MP_OBJ_TO_PTR(module_obj); + mp_obj_dict_store(MP_OBJ_FROM_PTR(o->globals), MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); + #if MICROPY_CPYTHON_COMPAT + // Store module as "__main__" in the dictionary of loaded modules (returned by sys.modules). + mp_obj_dict_store(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_loaded_modules_dict)), MP_OBJ_NEW_QSTR(MP_QSTR___main__), module_obj); + // Store real name in "__main__" attribute. Need this for + // resolving relative imports later. "__main__ was chosen + // semi-randonly, to reuse existing qstr's. + mp_obj_dict_store(MP_OBJ_FROM_PTR(o->globals), MP_OBJ_NEW_QSTR(MP_QSTR___main__), MP_OBJ_NEW_QSTR(full_mod_name)); + #endif + } + #endif // MICROPY_MODULE_OVERRIDE_MAIN_IMPORT + + if (stat == MP_IMPORT_STAT_DIR) { + // Directory -- execute "path/__init__.py". + DEBUG_printf("%.*s is dir\n", (int)vstr_len(path), vstr_str(path)); + // Store the __path__ attribute onto this module. + // https://docs.python.org/3/reference/import.html + // "Specifically, any module that contains a __path__ attribute is considered a package." + mp_store_attr(module_obj, MP_QSTR___path__, mp_obj_new_str(vstr_str(path), vstr_len(path))); + size_t orig_path_len = path->len; + vstr_add_str(path, PATH_SEP_CHAR "__init__.py"); + if (stat_file_py_or_mpy(path) == MP_IMPORT_STAT_FILE) { + do_load(MP_OBJ_TO_PTR(module_obj), path); + } else { + // No-op. Nothing to load. + // mp_warning("%s is imported as namespace package", vstr_str(&path)); + } + // Remove /__init__.py suffix. + path->len = orig_path_len; + } else { // MP_IMPORT_STAT_FILE + // File -- execute "path.(m)py". + do_load(MP_OBJ_TO_PTR(module_obj), path); + // Note: This should be the last component in the import path. If + // there are remaining components then it's an ImportError + // because the current path(the module that was just loaded) is + // not a package. This will be caught on the next iteration + // because the file will not exist. } } - *end = p; + + if (outer_module_obj != MP_OBJ_NULL) { + // If it's a sub-module (not a built-in one), then make it available on + // the parent module. + mp_store_attr(outer_module_obj, level_mod_name, module_obj); + } + + return module_obj; } mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { @@ -248,14 +472,28 @@ mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { DEBUG_printf("__import__:\n"); for (size_t i = 0; i < n_args; i++) { DEBUG_printf(" "); - mp_obj_print(args[i], PRINT_REPR); + mp_obj_print_helper(MICROPY_DEBUG_PRINTER, args[i], PRINT_REPR); DEBUG_printf("\n"); } #endif - mp_obj_t module_name = args[0]; + // This is the import path, with any leading dots stripped. + // "import foo.bar" --> module_name="foo.bar" + // "from foo.bar import baz" --> module_name="foo.bar" + // "from . import foo" --> module_name="" + // "from ...foo.bar import baz" --> module_name="foo.bar" + mp_obj_t module_name_obj = args[0]; + + // These are the imported names. + // i.e. "from foo.bar import baz, zap" --> fromtuple=("baz", "zap",) + // Note: There's a special case on the Unix port, where this is set to mp_const_false which means that it's __main__. mp_obj_t fromtuple = mp_const_none; + + // Level is the number of leading dots in a relative import. + // i.e. "from . import foo" --> level=1 + // i.e. "from ...foo.bar import baz" --> level=3 mp_int_t level = 0; + if (n_args >= 4) { fromtuple = args[3]; if (n_args >= 5) { @@ -266,211 +504,64 @@ mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { } } - size_t mod_len; - const char *mod_str = mp_obj_str_get_data(module_name, &mod_len); + size_t module_name_len; + const char *module_name = mp_obj_str_get_data(module_name_obj, &module_name_len); if (level != 0) { - // What we want to do here is to take name of current module, - // chop trailing components, and concatenate with passed-in - // module name, thus resolving relative import name into absolute. - // This even appears to be correct per - // http://legacy.python.org/dev/peps/pep-0328/#relative-imports-and-name - // "Relative imports use a module's __name__ attribute to determine that - // module's position in the package hierarchy." - level--; - mp_obj_t this_name_q = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___name__)); - assert(this_name_q != MP_OBJ_NULL); - #if MICROPY_CPYTHON_COMPAT - if (MP_OBJ_QSTR_VALUE(this_name_q) == MP_QSTR___main__) { - // This is a module run by -m command-line switch, get its real name from backup attribute - this_name_q = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); - } - #endif - mp_map_t *globals_map = &mp_globals_get()->map; - mp_map_elem_t *elem = mp_map_lookup(globals_map, MP_OBJ_NEW_QSTR(MP_QSTR___path__), MP_MAP_LOOKUP); - bool is_pkg = (elem != NULL); - - #if DEBUG_PRINT - DEBUG_printf("Current module/package: "); - mp_obj_print(this_name_q, PRINT_REPR); - DEBUG_printf(", is_package: %d", is_pkg); - DEBUG_printf("\n"); - #endif - - size_t this_name_l; - const char *this_name = mp_obj_str_get_data(this_name_q, &this_name_l); - - const char *p = this_name + this_name_l; - if (!is_pkg) { - // We have module, but relative imports are anchored at package, so - // go there. - chop_component(this_name, &p); - } - - while (level--) { - chop_component(this_name, &p); - } - - // We must have some component left over to import from - if (p == this_name) { - mp_raise_msg(&mp_type_ImportError, MP_ERROR_TEXT("can't perform relative import")); - } - - uint new_mod_l = (mod_len == 0 ? (size_t)(p - this_name) : (size_t)(p - this_name) + 1 + mod_len); - char *new_mod = mp_local_alloc(new_mod_l); - memcpy(new_mod, this_name, p - this_name); - if (mod_len != 0) { - new_mod[p - this_name] = '.'; - memcpy(new_mod + (p - this_name) + 1, mod_str, mod_len); - } - - qstr new_mod_q = qstr_from_strn(new_mod, new_mod_l); - mp_local_free(new_mod); - DEBUG_printf("Resolved base name for relative import: '%s'\n", qstr_str(new_mod_q)); - module_name = MP_OBJ_NEW_QSTR(new_mod_q); - mod_str = qstr_str(new_mod_q); - mod_len = new_mod_l; + // Turn "foo.bar" into ".foo.bar". + evaluate_relative_import(level, &module_name, &module_name_len); } - if (mod_len == 0) { + if (module_name_len == 0) { mp_raise_ValueError(NULL); } - // check if module already exists - qstr module_name_qstr = mp_obj_str_get_qstr(module_name); - mp_obj_t module_obj = mp_module_get(module_name_qstr); - if (module_obj != MP_OBJ_NULL) { - DEBUG_printf("Module already loaded\n"); - // If it's not a package, return module right away - char *p = strchr(mod_str, '.'); - if (p == NULL) { - return module_obj; - } - // If fromlist is not empty, return leaf module - if (fromtuple != mp_const_none) { - return module_obj; - } - // Otherwise, we need to return top-level package - qstr pkg_name = qstr_from_strn(mod_str, p - mod_str); - return mp_module_get(pkg_name); - } - DEBUG_printf("Module not yet loaded\n"); + DEBUG_printf("Starting module search for '%s'\n", module_name); - uint last = 0; VSTR_FIXED(path, MICROPY_ALLOC_PATH_MAX) - module_obj = MP_OBJ_NULL; mp_obj_t top_module_obj = MP_OBJ_NULL; mp_obj_t outer_module_obj = MP_OBJ_NULL; - uint i; - for (i = 1; i <= mod_len; i++) { - if (i == mod_len || mod_str[i] == '.') { - // create a qstr for the module name up to this depth - qstr mod_name = qstr_from_strn(mod_str, i); - DEBUG_printf("Processing module: %s\n", qstr_str(mod_name)); - DEBUG_printf("Previous path: =%.*s=\n", vstr_len(&path), vstr_str(&path)); - - // find the file corresponding to the module name - mp_import_stat_t stat; - if (vstr_len(&path) == 0) { - // first module in the dotted-name; search for a directory or file - stat = find_file(mod_str, i, &path); - } else { - // latter module in the dotted-name; append to path - vstr_add_char(&path, PATH_SEP_CHAR); - vstr_add_strn(&path, mod_str + last, i - last); - stat = stat_dir_or_file(&path); - } - DEBUG_printf("Current path: %.*s\n", vstr_len(&path), vstr_str(&path)); - - if (stat == MP_IMPORT_STAT_NO_EXIST) { - module_obj = MP_OBJ_NULL; - #if MICROPY_MODULE_WEAK_LINKS - // check if there is a weak link to this module - if (i == mod_len) { - module_obj = mp_module_search_umodule(mod_str); - if (module_obj != MP_OBJ_NULL) { - // found weak linked module - mp_module_call_init(mod_name, module_obj); - } - } - #endif - if (module_obj == MP_OBJ_NULL) { - // couldn't find the file, so fail - #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE - mp_raise_msg(&mp_type_ImportError, MP_ERROR_TEXT("module not found")); - #else - mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("no module named '%q'"), mod_name); - #endif - } - } else { - // found the file, so get the module - module_obj = mp_module_get(mod_name); - } - if (module_obj == MP_OBJ_NULL) { - // module not already loaded, so load it! - - module_obj = mp_obj_new_module(mod_name); - - // if args[3] (fromtuple) has magic value False, set up - // this module for command-line "-m" option (set module's - // name to __main__ instead of real name). Do this only - // for *modules* however - packages never have their names - // replaced, instead they're -m'ed using a special __main__ - // submodule in them. (This all apparently is done to not - // touch package name itself, which is important for future - // imports). - if (i == mod_len && fromtuple == mp_const_false && stat != MP_IMPORT_STAT_DIR) { - mp_obj_module_t *o = MP_OBJ_TO_PTR(module_obj); - mp_obj_dict_store(MP_OBJ_FROM_PTR(o->globals), MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); - #if MICROPY_CPYTHON_COMPAT - // Store module as "__main__" in the dictionary of loaded modules (returned by sys.modules). - mp_obj_dict_store(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_loaded_modules_dict)), MP_OBJ_NEW_QSTR(MP_QSTR___main__), module_obj); - // Store real name in "__main__" attribute. Chosen semi-randonly, to reuse existing qstr's. - mp_obj_dict_store(MP_OBJ_FROM_PTR(o->globals), MP_OBJ_NEW_QSTR(MP_QSTR___main__), MP_OBJ_NEW_QSTR(mod_name)); - #endif - } - - if (stat == MP_IMPORT_STAT_DIR) { - DEBUG_printf("%.*s is dir\n", vstr_len(&path), vstr_str(&path)); - // https://docs.python.org/3/reference/import.html - // "Specifically, any module that contains a __path__ attribute is considered a package." - mp_store_attr(module_obj, MP_QSTR___path__, mp_obj_new_str(vstr_str(&path), vstr_len(&path))); - size_t orig_path_len = path.len; - vstr_add_char(&path, PATH_SEP_CHAR); - vstr_add_str(&path, "__init__.py"); - if (stat_file_py_or_mpy(&path) != MP_IMPORT_STAT_FILE) { - // mp_warning("%s is imported as namespace package", vstr_str(&path)); - } else { - do_load(module_obj, &path); - } - path.len = orig_path_len; - } else { // MP_IMPORT_STAT_FILE - do_load(module_obj, &path); - // This should be the last component in the import path. If there are - // remaining components then it's an ImportError because the current path - // (the module that was just loaded) is not a package. This will be caught - // on the next iteration because the file will not exist. - } - } - if (outer_module_obj != MP_OBJ_NULL) { - qstr s = qstr_from_strn(mod_str + last, i - last); - mp_store_attr(outer_module_obj, s, module_obj); - } + // Search for the end of each component. + size_t current_component_start = 0; + for (size_t i = 1; i <= module_name_len; i++) { + if (i == module_name_len || module_name[i] == '.') { + // The module name up to this depth (e.g. foo.bar.baz). + qstr full_mod_name = qstr_from_strn(module_name, i); + // The current level name (e.g. baz). + qstr level_mod_name = qstr_from_strn(module_name + current_component_start, i - current_component_start); + + DEBUG_printf("Processing module: '%s' at level '%s'\n", qstr_str(full_mod_name), qstr_str(level_mod_name)); + DEBUG_printf("Previous path: =%.*s=\n", (int)vstr_len(&path), vstr_str(&path)); + + #if MICROPY_MODULE_OVERRIDE_MAIN_IMPORT + // On unix, if this is being loaded via -m (magic mp_const_false), + // then handle that if it's the final component. + bool override_main = (i == module_name_len && fromtuple == mp_const_false); + #else + bool override_main = false; + #endif + + // Import this module. + mp_obj_t module_obj = process_import_at_level(full_mod_name, level_mod_name, outer_module_obj, &path, override_main); + + // Set this as the parent module, and remember the top-level module if it's the first. outer_module_obj = module_obj; if (top_module_obj == MP_OBJ_NULL) { top_module_obj = module_obj; } - last = i + 1; + + current_component_start = i + 1; } } - // If fromlist is not empty, return leaf module if (fromtuple != mp_const_none) { - return module_obj; + // If fromtuple is not empty, return leaf module + return outer_module_obj; + } else { + // Otherwise, we need to return top-level package + return top_module_obj; } - // Otherwise, we need to return top-level package - return top_module_obj; } #else // MICROPY_ENABLE_EXTERNAL_IMPORT @@ -483,17 +574,19 @@ mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { // Check if module already exists, and return it if it does qstr module_name_qstr = mp_obj_str_get_qstr(args[0]); - mp_obj_t module_obj = mp_module_get(module_name_qstr); + mp_obj_t module_obj = mp_module_get_loaded_or_builtin(module_name_qstr); if (module_obj != MP_OBJ_NULL) { return module_obj; } #if MICROPY_MODULE_WEAK_LINKS // Check if there is a weak link to this module - module_obj = mp_module_search_umodule(qstr_str(module_name_qstr)); + char umodule_buf[MICROPY_ALLOC_PATH_MAX]; + umodule_buf[0] = 'u'; + strcpy(umodule_buf + 1, args[0]); + qstr umodule_name_qstr = qstr_from_str(umodule_buf); + module_obj = mp_module_get_loaded_or_builtin(umodule_name_qstr); if (module_obj != MP_OBJ_NULL) { - // Found weak-linked module - mp_module_call_init(module_name_qstr, module_obj); return module_obj; } #endif diff --git a/python/src/py/compile.c b/python/src/py/compile.c index 3d5c5f21e61..9cca5df4010 100644 --- a/python/src/py/compile.c +++ b/python/src/py/compile.c @@ -35,7 +35,9 @@ #include "py/compile.h" #include "py/runtime.h" #include "py/asmbase.h" +#include "py/nativeglue.h" #include "py/persistentcode.h" +#include "py/smallint.h" #if MICROPY_ENABLE_COMPILER @@ -59,6 +61,12 @@ typedef enum { #undef DEF_RULE_NC } pn_kind_t; +// Whether a mp_parse_node_struct_t that has pns->kind == PN_testlist_comp +// corresponds to a list comprehension or generator. +#define MP_PARSE_NODE_TESTLIST_COMP_HAS_COMP_FOR(pns) \ + (MP_PARSE_NODE_STRUCT_NUM_NODES(pns) == 2 && \ + MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[1], PN_comp_for)) + #define NEED_METHOD_TABLE MICROPY_EMIT_NATIVE #if NEED_METHOD_TABLE @@ -82,7 +90,7 @@ typedef enum { #if MICROPY_EMIT_NATIVE && MICROPY_DYNAMIC_COMPILER #define NATIVE_EMITTER(f) emit_native_table[mp_dynamic_compiler.native_arch]->emit_##f -#define NATIVE_EMITTER_TABLE emit_native_table[mp_dynamic_compiler.native_arch] +#define NATIVE_EMITTER_TABLE (emit_native_table[mp_dynamic_compiler.native_arch]) STATIC const emit_method_table_t *emit_native_table[] = { NULL, @@ -115,7 +123,7 @@ STATIC const emit_method_table_t *emit_native_table[] = { #else #error "unknown native emitter" #endif -#define NATIVE_EMITTER_TABLE &NATIVE_EMITTER(method_table) +#define NATIVE_EMITTER_TABLE (&NATIVE_EMITTER(method_table)) #endif #if MICROPY_EMIT_INLINE_ASM && MICROPY_DYNAMIC_COMPILER @@ -156,8 +164,6 @@ STATIC const emit_inline_asm_method_table_t *emit_asm_table[] = { // elements in this struct are ordered to make it compact typedef struct _compiler_t { - qstr source_file; - uint8_t is_repl; uint8_t pass; // holds enum type pass_kind_t uint8_t have_star; @@ -188,8 +194,60 @@ typedef struct _compiler_t { emit_inline_asm_t *emit_inline_asm; // current emitter for inline asm const emit_inline_asm_method_table_t *emit_inline_asm_method_table; // current emit method table for inline asm #endif + + mp_emit_common_t emit_common; } compiler_t; +/******************************************************************************/ +// mp_emit_common_t helper functions +// These are defined here so they can be inlined, to reduce code size. + +STATIC void mp_emit_common_init(mp_emit_common_t *emit, qstr source_file) { + #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE + mp_map_init(&emit->qstr_map, 1); + + // add the source file as the first entry in the qstr table + mp_map_elem_t *elem = mp_map_lookup(&emit->qstr_map, MP_OBJ_NEW_QSTR(source_file), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); + elem->value = MP_OBJ_NEW_SMALL_INT(0); + #endif + mp_obj_list_init(&emit->const_obj_list, 0); +} + +STATIC void mp_emit_common_start_pass(mp_emit_common_t *emit, pass_kind_t pass) { + emit->pass = pass; + if (pass == MP_PASS_CODE_SIZE) { + if (emit->ct_cur_child == 0) { + emit->children = NULL; + } else { + emit->children = m_new0(mp_raw_code_t *, emit->ct_cur_child); + } + } + emit->ct_cur_child = 0; +} + +STATIC void mp_emit_common_populate_module_context(mp_emit_common_t *emit, qstr source_file, mp_module_context_t *context) { + #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE + size_t qstr_map_used = emit->qstr_map.used; + mp_module_context_alloc_tables(context, qstr_map_used, emit->const_obj_list.len); + for (size_t i = 0; i < emit->qstr_map.alloc; ++i) { + if (mp_map_slot_is_filled(&emit->qstr_map, i)) { + size_t idx = MP_OBJ_SMALL_INT_VALUE(emit->qstr_map.table[i].value); + qstr qst = MP_OBJ_QSTR_VALUE(emit->qstr_map.table[i].key); + context->constants.qstr_table[idx] = qst; + } + } + #else + mp_module_context_alloc_tables(context, 0, emit->const_obj_list.len); + context->constants.source_file = source_file; + #endif + + for (size_t i = 0; i < emit->const_obj_list.len; ++i) { + context->constants.obj_table[i] = emit->const_obj_list.items[i]; + } +} + +/******************************************************************************/ + STATIC void compile_error_set_line(compiler_t *comp, mp_parse_node_t pn) { // if the line of the error is unknown then try to update it from the pn if (comp->compile_error_line == 0 && MP_PARSE_NODE_IS_STRUCT(pn)) { @@ -240,7 +298,7 @@ STATIC void compile_decrease_except_level(compiler_t *comp) { } STATIC scope_t *scope_new_and_link(compiler_t *comp, scope_kind_t kind, mp_parse_node_t pn, uint emit_options) { - scope_t *scope = scope_new(kind, pn, comp->source_file, emit_options); + scope_t *scope = scope_new(kind, pn, emit_options); scope->parent = comp->scope_cur; scope->next = NULL; if (comp->scope_head == NULL) { @@ -317,25 +375,13 @@ STATIC void compile_delete_id(compiler_t *comp, qstr qst) { } } -STATIC void c_tuple(compiler_t *comp, mp_parse_node_t pn, mp_parse_node_struct_t *pns_list) { - int total = 0; - if (!MP_PARSE_NODE_IS_NULL(pn)) { - compile_node(comp, pn); - total += 1; - } - if (pns_list != NULL) { - int n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns_list); - for (int i = 0; i < n; i++) { - compile_node(comp, pns_list->nodes[i]); - } - total += n; - } - EMIT_ARG(build, total, MP_EMIT_BUILD_TUPLE); -} - STATIC void compile_generic_tuple(compiler_t *comp, mp_parse_node_struct_t *pns) { // a simple tuple expression - c_tuple(comp, MP_PARSE_NODE_NULL, pns); + size_t num_nodes = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); + for (size_t i = 0; i < num_nodes; i++) { + compile_node(comp, pns->nodes[i]); + } + EMIT_ARG(build, num_nodes, MP_EMIT_BUILD_TUPLE); } STATIC void c_if_cond(compiler_t *comp, mp_parse_node_t pn, bool jump_if, int label) { @@ -452,21 +498,14 @@ STATIC void c_assign_atom_expr(compiler_t *comp, mp_parse_node_struct_t *pns, as compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("can't assign to expression")); } -// we need to allow for a caller passing in 1 initial node (node_head) followed by an array of nodes (nodes_tail) -STATIC void c_assign_tuple(compiler_t *comp, mp_parse_node_t node_head, uint num_tail, mp_parse_node_t *nodes_tail) { - uint num_head = (node_head == MP_PARSE_NODE_NULL) ? 0 : 1; - +STATIC void c_assign_tuple(compiler_t *comp, uint num_tail, mp_parse_node_t *nodes_tail) { // look for star expression uint have_star_index = -1; - if (num_head != 0 && MP_PARSE_NODE_IS_STRUCT_KIND(node_head, PN_star_expr)) { - EMIT_ARG(unpack_ex, 0, num_tail); - have_star_index = 0; - } for (uint i = 0; i < num_tail; i++) { if (MP_PARSE_NODE_IS_STRUCT_KIND(nodes_tail[i], PN_star_expr)) { if (have_star_index == (uint)-1) { - EMIT_ARG(unpack_ex, num_head + i, num_tail - i - 1); - have_star_index = num_head + i; + EMIT_ARG(unpack_ex, i, num_tail - i - 1); + have_star_index = i; } else { compile_syntax_error(comp, nodes_tail[i], MP_ERROR_TEXT("multiple *x in assignment")); return; @@ -474,17 +513,10 @@ STATIC void c_assign_tuple(compiler_t *comp, mp_parse_node_t node_head, uint num } } if (have_star_index == (uint)-1) { - EMIT_ARG(unpack_sequence, num_head + num_tail); - } - if (num_head != 0) { - if (0 == have_star_index) { - c_assign(comp, ((mp_parse_node_struct_t *)node_head)->nodes[0], ASSIGN_STORE); - } else { - c_assign(comp, node_head, ASSIGN_STORE); - } + EMIT_ARG(unpack_sequence, num_tail); } for (uint i = 0; i < num_tail; i++) { - if (num_head + i == have_star_index) { + if (i == have_star_index) { c_assign(comp, ((mp_parse_node_struct_t *)nodes_tail[i])->nodes[0], ASSIGN_STORE); } else { c_assign(comp, nodes_tail[i], ASSIGN_STORE); @@ -526,7 +558,7 @@ STATIC void c_assign(compiler_t *comp, mp_parse_node_t pn, assign_kind_t assign_ if (assign_kind != ASSIGN_STORE) { goto cannot_assign; } - c_assign_tuple(comp, MP_PARSE_NODE_NULL, MP_PARSE_NODE_STRUCT_NUM_NODES(pns), pns->nodes); + c_assign_tuple(comp, MP_PARSE_NODE_STRUCT_NUM_NODES(pns), pns->nodes); break; case PN_atom_paren: @@ -551,13 +583,13 @@ STATIC void c_assign(compiler_t *comp, mp_parse_node_t pn, assign_kind_t assign_ } if (MP_PARSE_NODE_IS_NULL(pns->nodes[0])) { // empty list, assignment allowed - c_assign_tuple(comp, MP_PARSE_NODE_NULL, 0, NULL); + c_assign_tuple(comp, 0, NULL); } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_testlist_comp)) { pns = (mp_parse_node_struct_t *)pns->nodes[0]; goto testlist_comp; } else { // brackets around 1 item - c_assign_tuple(comp, pns->nodes[0], 0, NULL); + c_assign_tuple(comp, 1, pns->nodes); } break; @@ -568,27 +600,10 @@ STATIC void c_assign(compiler_t *comp, mp_parse_node_t pn, assign_kind_t assign_ testlist_comp: // lhs is a sequence - if (MP_PARSE_NODE_IS_STRUCT(pns->nodes[1])) { - mp_parse_node_struct_t *pns2 = (mp_parse_node_struct_t *)pns->nodes[1]; - if (MP_PARSE_NODE_STRUCT_KIND(pns2) == PN_testlist_comp_3b) { - // sequence of one item, with trailing comma - assert(MP_PARSE_NODE_IS_NULL(pns2->nodes[0])); - c_assign_tuple(comp, pns->nodes[0], 0, NULL); - } else if (MP_PARSE_NODE_STRUCT_KIND(pns2) == PN_testlist_comp_3c) { - // sequence of many items - uint n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns2); - c_assign_tuple(comp, pns->nodes[0], n, pns2->nodes); - } else if (MP_PARSE_NODE_STRUCT_KIND(pns2) == PN_comp_for) { - goto cannot_assign; - } else { - // sequence with 2 items - goto sequence_with_2_items; - } - } else { - // sequence with 2 items - sequence_with_2_items: - c_assign_tuple(comp, MP_PARSE_NODE_NULL, 2, pns->nodes); + if (MP_PARSE_NODE_TESTLIST_COMP_HAS_COMP_FOR(pns)) { + goto cannot_assign; } + c_assign_tuple(comp, MP_PARSE_NODE_STRUCT_NUM_NODES(pns), pns->nodes); return; } return; @@ -705,7 +720,7 @@ STATIC void compile_funcdef_lambdef_param(compiler_t *comp, mp_parse_node_t pn) } else { // this parameter has a default value - // in CPython, None (and True, False?) as default parameters are loaded with LOAD_NAME; don't understand why + // in CPython, None (and True, False?) as default parameters are loaded with LOAD_NAME; don't understandy why if (comp->have_star) { comp->num_dict_params += 1; @@ -856,7 +871,7 @@ STATIC bool compile_built_in_decorator(compiler_t *comp, size_t name_len, mp_par compile_syntax_error(comp, name_nodes[1], MP_ERROR_TEXT("invalid micropython decorator")); } - #if MICROPY_DYNAMIC_COMPILER + #if MICROPY_EMIT_NATIVE && MICROPY_DYNAMIC_COMPILER if (*emit_options == MP_EMIT_OPT_NATIVE_PYTHON || *emit_options == MP_EMIT_OPT_VIPER) { if (emit_native_table[mp_dynamic_compiler.native_arch] == NULL) { compile_syntax_error(comp, name_nodes[1], MP_ERROR_TEXT("invalid arch")); @@ -983,32 +998,11 @@ STATIC void c_del_stmt(compiler_t *comp, mp_parse_node_t pn) { } else { assert(MP_PARSE_NODE_IS_STRUCT_KIND(pn, PN_testlist_comp)); mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; - // TODO perhaps factorise testlist_comp code with other uses of PN_testlist_comp - - if (MP_PARSE_NODE_IS_STRUCT(pns->nodes[1])) { - mp_parse_node_struct_t *pns1 = (mp_parse_node_struct_t *)pns->nodes[1]; - if (MP_PARSE_NODE_STRUCT_KIND(pns1) == PN_testlist_comp_3b) { - // sequence of one item, with trailing comma - assert(MP_PARSE_NODE_IS_NULL(pns1->nodes[0])); - c_del_stmt(comp, pns->nodes[0]); - } else if (MP_PARSE_NODE_STRUCT_KIND(pns1) == PN_testlist_comp_3c) { - // sequence of many items - int n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns1); - c_del_stmt(comp, pns->nodes[0]); - for (int i = 0; i < n; i++) { - c_del_stmt(comp, pns1->nodes[i]); - } - } else if (MP_PARSE_NODE_STRUCT_KIND(pns1) == PN_comp_for) { - goto cannot_delete; - } else { - // sequence with 2 items - goto sequence_with_2_items; - } - } else { - // sequence with 2 items - sequence_with_2_items: - c_del_stmt(comp, pns->nodes[0]); - c_del_stmt(comp, pns->nodes[1]); + if (MP_PARSE_NODE_TESTLIST_COMP_HAS_COMP_FOR(pns)) { + goto cannot_delete; + } + for (size_t i = 0; i < MP_PARSE_NODE_STRUCT_NUM_NODES(pns); ++i) { + c_del_stmt(comp, pns->nodes[i]); } } } else { @@ -1121,14 +1115,19 @@ STATIC void do_import_name(compiler_t *comp, mp_parse_node_t pn, qstr *q_base) { if (!is_as) { *q_base = MP_PARSE_NODE_LEAF_ARG(pns->nodes[0]); } - int n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); - int len = n - 1; - for (int i = 0; i < n; i++) { + size_t n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); + if (n == 0) { + // There must be at least one node in this PN_dotted_name. + // Let the compiler know this so it doesn't warn, and can generate better code. + MP_UNREACHABLE; + } + size_t len = n - 1; + for (size_t i = 0; i < n; i++) { len += qstr_len(MP_PARSE_NODE_LEAF_ARG(pns->nodes[i])); } char *q_ptr = mp_local_alloc(len); char *str_dest = q_ptr; - for (int i = 0; i < n; i++) { + for (size_t i = 0; i < n; i++) { if (i > 0) { *str_dest++ = '.'; } @@ -1141,7 +1140,7 @@ STATIC void do_import_name(compiler_t *comp, mp_parse_node_t pn, qstr *q_base) { mp_local_free(q_ptr); EMIT_ARG(import, q_full, MP_EMIT_IMPORT_NAME); if (is_as) { - for (int i = 1; i < n; i++) { + for (size_t i = 1; i < n; i++) { EMIT_ARG(attr, MP_PARSE_NODE_LEAF_ARG(pns->nodes[i]), MP_EMIT_ATTR_LOAD); } } @@ -2383,24 +2382,36 @@ STATIC void compile_trailer_paren_helper(compiler_t *comp, mp_parse_node_t pn_ar int n_positional = n_positional_extra; uint n_keyword = 0; uint star_flags = 0; - mp_parse_node_struct_t *star_args_node = NULL, *dblstar_args_node = NULL; + mp_uint_t star_args = 0; for (size_t i = 0; i < n_args; i++) { if (MP_PARSE_NODE_IS_STRUCT(args[i])) { mp_parse_node_struct_t *pns_arg = (mp_parse_node_struct_t *)args[i]; if (MP_PARSE_NODE_STRUCT_KIND(pns_arg) == PN_arglist_star) { - if (star_flags & MP_EMIT_STAR_FLAG_SINGLE) { - compile_syntax_error(comp, (mp_parse_node_t)pns_arg, MP_ERROR_TEXT("can't have multiple *x")); + if (star_flags & MP_EMIT_STAR_FLAG_DOUBLE) { + compile_syntax_error(comp, (mp_parse_node_t)pns_arg, MP_ERROR_TEXT("* arg after **")); return; } - star_flags |= MP_EMIT_STAR_FLAG_SINGLE; - star_args_node = pns_arg; - } else if (MP_PARSE_NODE_STRUCT_KIND(pns_arg) == PN_arglist_dbl_star) { - if (star_flags & MP_EMIT_STAR_FLAG_DOUBLE) { - compile_syntax_error(comp, (mp_parse_node_t)pns_arg, MP_ERROR_TEXT("can't have multiple **x")); + #if MICROPY_DYNAMIC_COMPILER + if (i >= (size_t)mp_dynamic_compiler.small_int_bits - 1) + #else + if (i >= MP_SMALL_INT_BITS - 1) + #endif + { + // If there are not enough bits in a small int to fit the flag, then we consider + // it a syntax error. It should be unlikely to have this many args in practice. + compile_syntax_error(comp, (mp_parse_node_t)pns_arg, MP_ERROR_TEXT("too many args")); return; } + star_flags |= MP_EMIT_STAR_FLAG_SINGLE; + star_args |= (mp_uint_t)1 << i; + compile_node(comp, pns_arg->nodes[0]); + n_positional++; + } else if (MP_PARSE_NODE_STRUCT_KIND(pns_arg) == PN_arglist_dbl_star) { star_flags |= MP_EMIT_STAR_FLAG_DOUBLE; - dblstar_args_node = pns_arg; + // double-star args are stored as kw arg with key of None + EMIT(load_null); + compile_node(comp, pns_arg->nodes[0]); + n_keyword++; } else if (MP_PARSE_NODE_STRUCT_KIND(pns_arg) == PN_argument) { #if MICROPY_PY_ASSIGN_EXPR if (MP_PARSE_NODE_IS_STRUCT_KIND(pns_arg->nodes[1], PN_argument_3)) { @@ -2415,7 +2426,7 @@ STATIC void compile_trailer_paren_helper(compiler_t *comp, mp_parse_node_t pn_ar } EMIT_ARG(load_const_str, MP_PARSE_NODE_LEAF_ARG(pns_arg->nodes[0])); compile_node(comp, pns_arg->nodes[1]); - n_keyword += 1; + n_keyword++; } else { compile_comprehension(comp, pns_arg, SCOPE_GEN_EXPR); n_positional++; @@ -2425,12 +2436,12 @@ STATIC void compile_trailer_paren_helper(compiler_t *comp, mp_parse_node_t pn_ar } } else { normal_argument: - if (star_flags) { - compile_syntax_error(comp, args[i], MP_ERROR_TEXT("non-keyword arg after */**")); + if (star_flags & MP_EMIT_STAR_FLAG_DOUBLE) { + compile_syntax_error(comp, args[i], MP_ERROR_TEXT("positional arg after **")); return; } if (n_keyword > 0) { - compile_syntax_error(comp, args[i], MP_ERROR_TEXT("non-keyword arg after keyword arg")); + compile_syntax_error(comp, args[i], MP_ERROR_TEXT("positional arg after keyword arg")); return; } compile_node(comp, args[i]); @@ -2438,19 +2449,9 @@ STATIC void compile_trailer_paren_helper(compiler_t *comp, mp_parse_node_t pn_ar } } - // compile the star/double-star arguments if we had them - // if we had one but not the other then we load "null" as a place holder if (star_flags != 0) { - if (star_args_node == NULL) { - EMIT(load_null); - } else { - compile_node(comp, star_args_node->nodes[0]); - } - if (dblstar_args_node == NULL) { - EMIT(load_null); - } else { - compile_node(comp, dblstar_args_node->nodes[0]); - } + // one extra object that contains the star_args map + EMIT_ARG(load_const_small_int, star_args); } // emit the function/method call @@ -2490,31 +2491,16 @@ STATIC void compile_comprehension(compiler_t *comp, mp_parse_node_struct_t *pns, STATIC void compile_atom_paren(compiler_t *comp, mp_parse_node_struct_t *pns) { if (MP_PARSE_NODE_IS_NULL(pns->nodes[0])) { // an empty tuple - c_tuple(comp, MP_PARSE_NODE_NULL, NULL); + EMIT_ARG(build, 0, MP_EMIT_BUILD_TUPLE); } else { assert(MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_testlist_comp)); pns = (mp_parse_node_struct_t *)pns->nodes[0]; - assert(!MP_PARSE_NODE_IS_NULL(pns->nodes[1])); - if (MP_PARSE_NODE_IS_STRUCT(pns->nodes[1])) { - mp_parse_node_struct_t *pns2 = (mp_parse_node_struct_t *)pns->nodes[1]; - if (MP_PARSE_NODE_STRUCT_KIND(pns2) == PN_testlist_comp_3b) { - // tuple of one item, with trailing comma - assert(MP_PARSE_NODE_IS_NULL(pns2->nodes[0])); - c_tuple(comp, pns->nodes[0], NULL); - } else if (MP_PARSE_NODE_STRUCT_KIND(pns2) == PN_testlist_comp_3c) { - // tuple of many items - c_tuple(comp, pns->nodes[0], pns2); - } else if (MP_PARSE_NODE_STRUCT_KIND(pns2) == PN_comp_for) { - // generator expression - compile_comprehension(comp, pns, SCOPE_GEN_EXPR); - } else { - // tuple with 2 items - goto tuple_with_2_items; - } + if (MP_PARSE_NODE_TESTLIST_COMP_HAS_COMP_FOR(pns)) { + // generator expression + compile_comprehension(comp, pns, SCOPE_GEN_EXPR); } else { - // tuple with 2 items - tuple_with_2_items: - c_tuple(comp, MP_PARSE_NODE_NULL, pns); + // tuple with N items + compile_generic_tuple(comp, pns); } } } @@ -2525,31 +2511,13 @@ STATIC void compile_atom_bracket(compiler_t *comp, mp_parse_node_struct_t *pns) EMIT_ARG(build, 0, MP_EMIT_BUILD_LIST); } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_testlist_comp)) { mp_parse_node_struct_t *pns2 = (mp_parse_node_struct_t *)pns->nodes[0]; - if (MP_PARSE_NODE_IS_STRUCT(pns2->nodes[1])) { - mp_parse_node_struct_t *pns3 = (mp_parse_node_struct_t *)pns2->nodes[1]; - if (MP_PARSE_NODE_STRUCT_KIND(pns3) == PN_testlist_comp_3b) { - // list of one item, with trailing comma - assert(MP_PARSE_NODE_IS_NULL(pns3->nodes[0])); - compile_node(comp, pns2->nodes[0]); - EMIT_ARG(build, 1, MP_EMIT_BUILD_LIST); - } else if (MP_PARSE_NODE_STRUCT_KIND(pns3) == PN_testlist_comp_3c) { - // list of many items - compile_node(comp, pns2->nodes[0]); - compile_generic_all_nodes(comp, pns3); - EMIT_ARG(build, 1 + MP_PARSE_NODE_STRUCT_NUM_NODES(pns3), MP_EMIT_BUILD_LIST); - } else if (MP_PARSE_NODE_STRUCT_KIND(pns3) == PN_comp_for) { - // list comprehension - compile_comprehension(comp, pns2, SCOPE_LIST_COMP); - } else { - // list with 2 items - goto list_with_2_items; - } + if (MP_PARSE_NODE_TESTLIST_COMP_HAS_COMP_FOR(pns2)) { + // list comprehension + compile_comprehension(comp, pns2, SCOPE_LIST_COMP); } else { - // list with 2 items - list_with_2_items: - compile_node(comp, pns2->nodes[0]); - compile_node(comp, pns2->nodes[1]); - EMIT_ARG(build, 2, MP_EMIT_BUILD_LIST); + // list with N items + compile_generic_all_nodes(comp, pns2); + EMIT_ARG(build, MP_PARSE_NODE_STRUCT_NUM_NODES(pns2), MP_EMIT_BUILD_LIST); } } else { // list with 1 item @@ -2779,12 +2747,7 @@ STATIC void compile_atom_expr_await(compiler_t *comp, mp_parse_node_struct_t *pn #endif STATIC mp_obj_t get_const_object(mp_parse_node_struct_t *pns) { - #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D - // nodes are 32-bit pointers, but need to extract 64-bit object - return (uint64_t)pns->nodes[0] | ((uint64_t)pns->nodes[1] << 32); - #else - return (mp_obj_t)pns->nodes[0]; - #endif + return mp_parse_node_extract_const_object(pns); } STATIC void compile_const_object(compiler_t *comp, mp_parse_node_struct_t *pns) { @@ -2809,23 +2772,7 @@ STATIC void compile_node(compiler_t *comp, mp_parse_node_t pn) { // pass } else if (MP_PARSE_NODE_IS_SMALL_INT(pn)) { mp_int_t arg = MP_PARSE_NODE_LEAF_SMALL_INT(pn); - #if MICROPY_DYNAMIC_COMPILER - mp_uint_t sign_mask = -((mp_uint_t)1 << (mp_dynamic_compiler.small_int_bits - 1)); - if ((arg & sign_mask) == 0 || (arg & sign_mask) == sign_mask) { - // integer fits in target runtime's small-int - EMIT_ARG(load_const_small_int, arg); - } else { - // integer doesn't fit, so create a multi-precision int object - // (but only create the actual object on the last pass) - if (comp->pass != MP_PASS_EMIT) { - EMIT_ARG(load_const_obj, mp_const_none); - } else { - EMIT_ARG(load_const_obj, mp_obj_new_int_from_ll(arg)); - } - } - #else EMIT_ARG(load_const_small_int, arg); - #endif } else if (MP_PARSE_NODE_IS_LEAF(pn)) { uintptr_t arg = MP_PARSE_NODE_LEAF_ARG(pn); switch (MP_PARSE_NODE_LEAF_KIND(pn)) { @@ -2835,16 +2782,6 @@ STATIC void compile_node(compiler_t *comp, mp_parse_node_t pn) { case MP_PARSE_NODE_STRING: EMIT_ARG(load_const_str, arg); break; - case MP_PARSE_NODE_BYTES: - // only create and load the actual bytes object on the last pass - if (comp->pass != MP_PASS_EMIT) { - EMIT_ARG(load_const_obj, mp_const_none); - } else { - size_t len; - const byte *data = qstr_data(arg, &len); - EMIT_ARG(load_const_obj, mp_obj_new_bytes(data, len)); - } - break; case MP_PARSE_NODE_TOKEN: default: if (arg == MP_TOKEN_NEWLINE) { @@ -3063,10 +3000,11 @@ STATIC void check_for_doc_string(compiler_t *comp, mp_parse_node_t pn) { #endif } -STATIC void compile_scope(compiler_t *comp, scope_t *scope, pass_kind_t pass) { +STATIC bool compile_scope(compiler_t *comp, scope_t *scope, pass_kind_t pass) { comp->pass = pass; comp->scope_cur = scope; comp->next_label = 0; + mp_emit_common_start_pass(&comp->emit_common, pass); EMIT_ARG(start_pass, pass, scope); reserve_labels_for_native(comp, 6); // used by native's start_pass @@ -3220,10 +3158,12 @@ STATIC void compile_scope(compiler_t *comp, scope_t *scope, pass_kind_t pass) { EMIT(return_value); } - EMIT(end_pass); + bool pass_complete = EMIT(end_pass); // make sure we match all the exception levels assert(comp->cur_except_level == 0); + + return pass_complete; } #if MICROPY_EMIT_INLINE_ASM @@ -3387,9 +3327,10 @@ STATIC void compile_scope_inline_asm(compiler_t *comp, scope_t *scope, pass_kind f, mp_asm_base_get_code_size((mp_asm_base_t *)comp->emit_inline_asm), NULL, #if MICROPY_PERSISTENT_CODE_SAVE - 0, 0, 0, 0, NULL, + 0, + 0, #endif - comp->scope_cur->num_pos_args, 0, type_sig); + 0, comp->scope_cur->num_pos_args, type_sig); } } @@ -3497,15 +3438,15 @@ STATIC void scope_compute_things(scope_t *scope) { #if !MICROPY_PERSISTENT_CODE_SAVE STATIC #endif -mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_file, bool is_repl) { +mp_compiled_module_t mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_file, bool is_repl, mp_module_context_t *context) { // put compiler state on the stack, it's relatively small compiler_t comp_state = {0}; compiler_t *comp = &comp_state; - comp->source_file = source_file; comp->is_repl = is_repl; comp->break_label = INVALID_LABEL; comp->continue_label = INVALID_LABEL; + mp_emit_common_init(&comp->emit_common, source_file); // create the module scope #if MICROPY_EMIT_NATIVE @@ -3516,9 +3457,9 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f scope_t *module_scope = scope_new_and_link(comp, SCOPE_MODULE, parse_tree->root, emit_opt); // create standard emitter; it's used at least for MP_PASS_SCOPE - emit_t *emit_bc = emit_bc_new(); + emit_t *emit_bc = emit_bc_new(&comp->emit_common); - // compile pass 1 + // compile MP_PASS_SCOPE comp->emit = emit_bc; #if MICROPY_EMIT_NATIVE comp->emit_method_table = &emit_bc_method_table; @@ -3556,7 +3497,7 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f // set max number of labels now that it's calculated emit_bc_set_max_num_labels(emit_bc, max_num_labels); - // compile pass 2 and 3 + // compile MP_PASS_STACK_SIZE, MP_PASS_CODE_SIZE, MP_PASS_EMIT #if MICROPY_EMIT_NATIVE emit_t *emit_native = NULL; #endif @@ -3596,7 +3537,7 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f case MP_EMIT_OPT_NATIVE_PYTHON: case MP_EMIT_OPT_VIPER: if (emit_native == NULL) { - emit_native = NATIVE_EMITTER(new)(&comp->compile_error, &comp->next_label, max_num_labels); + emit_native = NATIVE_EMITTER(new)(&comp->emit_common, &comp->compile_error, &comp->next_label, max_num_labels); } comp->emit_method_table = NATIVE_EMITTER_TABLE; comp->emit = emit_native; @@ -3620,8 +3561,10 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f } // final pass: emit code + // the emitter can request multiple of these passes if (comp->compile_error == MP_OBJ_NULL) { - compile_scope(comp, s, MP_PASS_EMIT); + while (!compile_scope(comp, s, MP_PASS_EMIT)) { + } } } } @@ -3631,10 +3574,45 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f // number for the start of this scope compile_error_set_line(comp, comp->scope_cur->pn); // add a traceback to the exception using relevant source info - mp_obj_exception_add_traceback(comp->compile_error, comp->source_file, + mp_obj_exception_add_traceback(comp->compile_error, source_file, comp->compile_error_line, comp->scope_cur->simple_name); } + // construct the global qstr/const table for this module + mp_compiled_module_t cm; + cm.rc = module_scope->raw_code; + cm.context = context; + #if MICROPY_PERSISTENT_CODE_SAVE + cm.has_native = false; + #if MICROPY_EMIT_NATIVE + if (emit_native != NULL) { + cm.has_native = true; + } + #endif + #if MICROPY_EMIT_INLINE_ASM + if (comp->emit_inline_asm != NULL) { + cm.has_native = true; + } + #endif + cm.n_qstr = comp->emit_common.qstr_map.used; + cm.n_obj = comp->emit_common.const_obj_list.len; + #endif + if (comp->compile_error == MP_OBJ_NULL) { + mp_emit_common_populate_module_context(&comp->emit_common, source_file, context); + + #if MICROPY_DEBUG_PRINTERS + // now that the module context is valid, the raw codes can be printed + if (mp_verbose_flag >= 2) { + for (scope_t *s = comp->scope_head; s != NULL; s = s->next) { + mp_raw_code_t *rc = s->raw_code; + if (rc->kind == MP_CODE_BYTECODE) { + mp_bytecode_print(&mp_plat_print, rc, &cm.context->constants); + } + } + } + #endif + } + // free the emitters emit_bc_free(emit_bc); @@ -3653,7 +3631,6 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f mp_parse_tree_clear(parse_tree); // free the scopes - mp_raw_code_t *outer_raw_code = module_scope->raw_code; for (scope_t *s = module_scope; s;) { scope_t *next = s->next; scope_free(s); @@ -3662,15 +3639,17 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f if (comp->compile_error != MP_OBJ_NULL) { nlr_raise(comp->compile_error); - } else { - return outer_raw_code; } + + return cm; } mp_obj_t mp_compile(mp_parse_tree_t *parse_tree, qstr source_file, bool is_repl) { - mp_raw_code_t *rc = mp_compile_to_raw_code(parse_tree, source_file, is_repl); + mp_module_context_t *context = m_new_obj(mp_module_context_t); + context->module.globals = mp_globals_get(); + mp_compiled_module_t cm = mp_compile_to_raw_code(parse_tree, source_file, is_repl, context); // return function that executes the outer module - return mp_make_function_from_raw_code(rc, MP_OBJ_NULL, MP_OBJ_NULL); + return mp_make_function_from_raw_code(cm.rc, cm.context, NULL); } #endif // MICROPY_ENABLE_COMPILER diff --git a/python/src/py/compile.h b/python/src/py/compile.h index 1ad1f5e9cd3..ae87bf2a04c 100644 --- a/python/src/py/compile.h +++ b/python/src/py/compile.h @@ -32,11 +32,12 @@ // the compiler will raise an exception if an error occurred // the compiler will clear the parse tree before it returns +// mp_globals_get() will be used for the context mp_obj_t mp_compile(mp_parse_tree_t *parse_tree, qstr source_file, bool is_repl); #if MICROPY_PERSISTENT_CODE_SAVE // this has the same semantics as mp_compile -mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_file, bool is_repl); +mp_compiled_module_t mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_file, bool is_repl, mp_module_context_t *globals); #endif // this is implemented in runtime.c diff --git a/python/src/py/dynruntime.h b/python/src/py/dynruntime.h index fdb91ed37e9..e3200a27195 100644 --- a/python/src/py/dynruntime.h +++ b/python/src/py/dynruntime.h @@ -30,6 +30,7 @@ // MicroPython runtime API defined in py/obj.h and py/runtime.h. #include "py/nativeglue.h" +#include "py/objfun.h" #include "py/objstr.h" #include "py/objtype.h" @@ -43,6 +44,7 @@ #undef mp_const_none #undef mp_const_false #undef mp_const_true +#undef mp_const_empty_bytes #undef mp_const_empty_tuple #undef nlr_raise @@ -80,7 +82,11 @@ static inline void *m_realloc_dyn(void *ptr, size_t new_num_bytes) { #define MP_OBJ_NEW_QSTR(x) MP_OBJ_NEW_QSTR_##x #define mp_type_type (*mp_fun_table.type_type) +#define mp_type_NoneType (*mp_obj_get_type(mp_const_none)) +#define mp_type_bool (*mp_obj_get_type(mp_const_false)) +#define mp_type_int (*(mp_obj_type_t *)(mp_load_global(MP_QSTR_int))) #define mp_type_str (*mp_fun_table.type_str) +#define mp_type_bytes (*(mp_obj_type_t *)(mp_load_global(MP_QSTR_bytes))) #define mp_type_tuple (*((mp_obj_base_t *)mp_const_empty_tuple)->type) #define mp_type_list (*mp_fun_table.type_list) #define mp_type_EOFError (*(mp_obj_type_t *)(mp_load_global(MP_QSTR_EOFError))) @@ -99,6 +105,7 @@ static inline void *m_realloc_dyn(void *ptr, size_t new_num_bytes) { #define mp_const_none ((mp_obj_t)mp_fun_table.const_none) #define mp_const_false ((mp_obj_t)mp_fun_table.const_false) #define mp_const_true ((mp_obj_t)mp_fun_table.const_true) +#define mp_const_empty_bytes (mp_type_bytes.make_new(NULL, 0, 0, NULL)) #define mp_const_empty_tuple (mp_fun_table.new_tuple(0, NULL)) #define mp_obj_new_bool(b) ((b) ? (mp_obj_t)mp_fun_table.const_true : (mp_obj_t)mp_fun_table.const_false) @@ -110,6 +117,7 @@ static inline void *m_realloc_dyn(void *ptr, size_t new_num_bytes) { #define mp_obj_new_bytearray_by_ref(n, i) (mp_fun_table.obj_new_bytearray_by_ref((n), (i))) #define mp_obj_new_tuple(n, items) (mp_fun_table.new_tuple((n), (items))) #define mp_obj_new_list(n, items) (mp_fun_table.new_list((n), (items))) +#define mp_obj_new_dict(n) (mp_fun_table.new_dict((n))) #define mp_obj_get_type(o) (mp_fun_table.obj_get_type((o))) #define mp_obj_cast_to_native_base(o, t) (mp_obj_cast_to_native_base_dyn((o), (t))) @@ -124,6 +132,9 @@ static inline void *m_realloc_dyn(void *ptr, size_t new_num_bytes) { #define mp_obj_subscr(base, index, val) (mp_fun_table.obj_subscr((base), (index), (val))) #define mp_obj_get_array(o, len, items) (mp_obj_get_array_dyn((o), (len), (items))) #define mp_obj_list_append(list, item) (mp_fun_table.list_append((list), (item))) +#define mp_obj_dict_store(dict, key, val) (mp_fun_table.dict_store((dict), (key), (val))) + +#define mp_obj_malloc_helper(n, t) (mp_obj_malloc_helper_dyn(n, t)) static inline mp_obj_t mp_obj_new_str_of_type_dyn(const mp_obj_type_t *type, const byte *data, size_t len) { if (type == &mp_type_str) { @@ -162,6 +173,12 @@ static inline mp_obj_t mp_obj_len_dyn(mp_obj_t o) { return mp_fun_table.call_function_n_kw(mp_fun_table.load_name(MP_QSTR_len), 1, &o); } +static inline void *mp_obj_malloc_helper_dyn(size_t num_bytes, const mp_obj_type_t *type) { + mp_obj_base_t *base = (mp_obj_base_t *)m_malloc(num_bytes); + base->type = type; + return base; +} + /******************************************************************************/ // General runtime functions @@ -177,8 +194,8 @@ static inline mp_obj_t mp_obj_len_dyn(mp_obj_t o) { #define mp_unary_op(op, obj) (mp_fun_table.unary_op((op), (obj))) #define mp_binary_op(op, lhs, rhs) (mp_fun_table.binary_op((op), (lhs), (rhs))) -#define mp_make_function_from_raw_code(rc, def_args, def_kw_args) \ - (mp_fun_table.make_function_from_raw_code((rc), (def_args), (def_kw_args))) +#define mp_make_function_from_raw_code(rc, context, def_args) \ + (mp_fun_table.make_function_from_raw_code((rc), (context), (def_args))) #define mp_call_function_n_kw(fun, n_args, n_kw, args) \ (mp_fun_table.call_function_n_kw((fun), (n_args) | ((n_kw) << 8), args)) @@ -187,11 +204,10 @@ static inline mp_obj_t mp_obj_len_dyn(mp_obj_t o) { (mp_fun_table.arg_check_num_sig((n_args), (n_kw), MP_OBJ_FUN_MAKE_SIG((n_args_min), (n_args_max), (takes_kw)))) #define MP_DYNRUNTIME_INIT_ENTRY \ - mp_obj_t old_globals = mp_fun_table.swap_globals(self->globals); \ + mp_obj_t old_globals = mp_fun_table.swap_globals(self->context->module.globals); \ mp_raw_code_t rc; \ rc.kind = MP_CODE_NATIVE_VIPER; \ rc.scope_flags = 0; \ - rc.const_table = (void *)self->const_table; \ (void)rc; #define MP_DYNRUNTIME_INIT_EXIT \ @@ -199,7 +215,7 @@ static inline mp_obj_t mp_obj_len_dyn(mp_obj_t o) { return mp_const_none; #define MP_DYNRUNTIME_MAKE_FUNCTION(f) \ - (mp_make_function_from_raw_code((rc.fun_data = (f), &rc), MP_OBJ_NULL, MP_OBJ_NULL)) + (mp_make_function_from_raw_code((rc.fun_data = (f), &rc), self->context, NULL)) #define mp_import_name(name, fromlist, level) \ (mp_fun_table.import_name((name), (fromlist), (level))) diff --git a/python/src/py/dynruntime.mk b/python/src/py/dynruntime.mk index cb5ab845eb1..09cbb2dd37c 100644 --- a/python/src/py/dynruntime.mk +++ b/python/src/py/dynruntime.mk @@ -46,7 +46,6 @@ ifeq ($(ARCH),x86) # x86 CROSS = CFLAGS += -m32 -fno-stack-protector -MPY_CROSS_FLAGS += -mcache-lookup-bc MICROPY_FLOAT_IMPL ?= double else ifeq ($(ARCH),x64) @@ -54,9 +53,15 @@ else ifeq ($(ARCH),x64) # x64 CROSS = CFLAGS += -fno-stack-protector -MPY_CROSS_FLAGS += -mcache-lookup-bc MICROPY_FLOAT_IMPL ?= double +else ifeq ($(ARCH),armv6m) + +# thumb +CROSS = arm-none-eabi- +CFLAGS += -mthumb -mcpu=cortex-m0 +MICROPY_FLOAT_IMPL ?= none + else ifeq ($(ARCH),armv7m) # thumb diff --git a/python/src/py/emit.h b/python/src/py/emit.h index 13bd3e9b2eb..608734552af 100644 --- a/python/src/py/emit.h +++ b/python/src/py/emit.h @@ -43,7 +43,7 @@ typedef enum { MP_PASS_SCOPE = 1, // work out id's and their kind, and number of labels MP_PASS_STACK_SIZE = 2, // work out maximum stack size MP_PASS_CODE_SIZE = 3, // work out code size and label offsets - MP_PASS_EMIT = 4, // emit code + MP_PASS_EMIT = 4, // emit code (may be run multiple times if the emitter requests it) } pass_kind_t; #define MP_EMIT_STAR_FLAG_SINGLE (0x01) @@ -92,6 +92,16 @@ typedef enum { typedef struct _emit_t emit_t; +typedef struct _mp_emit_common_t { + pass_kind_t pass; + uint16_t ct_cur_child; + mp_raw_code_t **children; + #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE + mp_map_t qstr_map; + #endif + mp_obj_list_t const_obj_list; +} mp_emit_common_t; + typedef struct _mp_emit_method_table_id_ops_t { void (*local)(emit_t *emit, qstr qst, mp_uint_t local_num, int kind); void (*global)(emit_t *emit, qstr qst, int kind); @@ -99,12 +109,12 @@ typedef struct _mp_emit_method_table_id_ops_t { typedef struct _emit_method_table_t { #if MICROPY_DYNAMIC_COMPILER - emit_t *(*emit_new)(mp_obj_t * error_slot, uint *label_slot, mp_uint_t max_num_labels); + emit_t *(*emit_new)(mp_emit_common_t * emit_common, mp_obj_t *error_slot, uint *label_slot, mp_uint_t max_num_labels); void (*emit_free)(emit_t *emit); #endif void (*start_pass)(emit_t *emit, pass_kind_t pass, scope_t *scope); - void (*end_pass)(emit_t *emit); + bool (*end_pass)(emit_t *emit); bool (*last_emit_was_return_value)(emit_t *emit); void (*adjust_stack_size)(emit_t *emit, mp_int_t delta); void (*set_source_line)(emit_t *emit, mp_uint_t line); @@ -161,6 +171,23 @@ typedef struct _emit_method_table_t { void (*end_except_handler)(emit_t *emit); } emit_method_table_t; +#if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE +qstr_short_t mp_emit_common_use_qstr(mp_emit_common_t *emit, qstr qst); +#else +static inline qstr_short_t mp_emit_common_use_qstr(mp_emit_common_t *emit, qstr qst) { + return qst; +} +#endif + +size_t mp_emit_common_use_const_obj(mp_emit_common_t *emit, mp_obj_t const_obj); + +static inline size_t mp_emit_common_alloc_const_child(mp_emit_common_t *emit, mp_raw_code_t *rc) { + if (emit->pass == MP_PASS_EMIT) { + emit->children[emit->ct_cur_child] = rc; + } + return emit->ct_cur_child++; +} + static inline void mp_emit_common_get_id_for_load(scope_t *scope, qstr qst) { scope_find_or_add_id(scope, qst, ID_INFO_KIND_GLOBAL_IMPLICIT); } @@ -180,13 +207,13 @@ extern const mp_emit_method_table_id_ops_t mp_emit_bc_method_table_load_id_ops; extern const mp_emit_method_table_id_ops_t mp_emit_bc_method_table_store_id_ops; extern const mp_emit_method_table_id_ops_t mp_emit_bc_method_table_delete_id_ops; -emit_t *emit_bc_new(void); -emit_t *emit_native_x64_new(mp_obj_t *error_slot, uint *label_slot, mp_uint_t max_num_labels); -emit_t *emit_native_x86_new(mp_obj_t *error_slot, uint *label_slot, mp_uint_t max_num_labels); -emit_t *emit_native_thumb_new(mp_obj_t *error_slot, uint *label_slot, mp_uint_t max_num_labels); -emit_t *emit_native_arm_new(mp_obj_t *error_slot, uint *label_slot, mp_uint_t max_num_labels); -emit_t *emit_native_xtensa_new(mp_obj_t *error_slot, uint *label_slot, mp_uint_t max_num_labels); -emit_t *emit_native_xtensawin_new(mp_obj_t *error_slot, uint *label_slot, mp_uint_t max_num_labels); +emit_t *emit_bc_new(mp_emit_common_t *emit_common); +emit_t *emit_native_x64_new(mp_emit_common_t *emit_common, mp_obj_t *error_slot, uint *label_slot, mp_uint_t max_num_labels); +emit_t *emit_native_x86_new(mp_emit_common_t *emit_common, mp_obj_t *error_slot, uint *label_slot, mp_uint_t max_num_labels); +emit_t *emit_native_thumb_new(mp_emit_common_t *emit_common, mp_obj_t *error_slot, uint *label_slot, mp_uint_t max_num_labels); +emit_t *emit_native_arm_new(mp_emit_common_t *emit_common, mp_obj_t *error_slot, uint *label_slot, mp_uint_t max_num_labels); +emit_t *emit_native_xtensa_new(mp_emit_common_t *emit_common, mp_obj_t *error_slot, uint *label_slot, mp_uint_t max_num_labels); +emit_t *emit_native_xtensawin_new(mp_emit_common_t *emit_common, mp_obj_t *error_slot, uint *label_slot, mp_uint_t max_num_labels); void emit_bc_set_max_num_labels(emit_t *emit, mp_uint_t max_num_labels); @@ -199,7 +226,7 @@ void emit_native_xtensa_free(emit_t *emit); void emit_native_xtensawin_free(emit_t *emit); void mp_emit_bc_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scope); -void mp_emit_bc_end_pass(emit_t *emit); +bool mp_emit_bc_end_pass(emit_t *emit); bool mp_emit_bc_last_emit_was_return_value(emit_t *emit); void mp_emit_bc_adjust_stack_size(emit_t *emit, mp_int_t delta); void mp_emit_bc_set_source_line(emit_t *emit, mp_uint_t line); diff --git a/python/src/py/emitbc.c b/python/src/py/emitbc.c index d7e8e05f0fa..2007975c5e9 100644 --- a/python/src/py/emitbc.c +++ b/python/src/py/emitbc.c @@ -28,16 +28,17 @@ #include #include #include +#include #include #include "py/mpstate.h" +#include "py/smallint.h" #include "py/emit.h" #include "py/bc0.h" #if MICROPY_ENABLE_COMPILER -#define BYTES_FOR_INT ((MP_BYTES_PER_OBJ_WORD * 8 + 6) / 7) -#define DUMMY_DATA_SIZE (BYTES_FOR_INT) +#define DUMMY_DATA_SIZE (MP_ENCODE_UINT_MAX_BYTES) struct _emit_t { // Accessed as mp_obj_t, so must be aligned as such, and we rely on the @@ -50,66 +51,45 @@ struct _emit_t { int stack_size; + mp_emit_common_t *emit_common; scope_t *scope; mp_uint_t last_source_line_offset; mp_uint_t last_source_line; - mp_uint_t max_num_labels; - mp_uint_t *label_offsets; + size_t max_num_labels; + size_t *label_offsets; size_t code_info_offset; size_t code_info_size; size_t bytecode_offset; size_t bytecode_size; byte *code_base; // stores both byte code and code info + bool overflow; size_t n_info; size_t n_cell; - - #if MICROPY_PERSISTENT_CODE - uint16_t ct_cur_obj; - uint16_t ct_num_obj; - uint16_t ct_cur_raw_code; - #endif - mp_uint_t *const_table; }; -emit_t *emit_bc_new(void) { +emit_t *emit_bc_new(mp_emit_common_t *emit_common) { emit_t *emit = m_new0(emit_t, 1); + emit->emit_common = emit_common; return emit; } void emit_bc_set_max_num_labels(emit_t *emit, mp_uint_t max_num_labels) { emit->max_num_labels = max_num_labels; - emit->label_offsets = m_new(mp_uint_t, emit->max_num_labels); + emit->label_offsets = m_new(size_t, emit->max_num_labels); } void emit_bc_free(emit_t *emit) { - m_del(mp_uint_t, emit->label_offsets, emit->max_num_labels); + m_del(size_t, emit->label_offsets, emit->max_num_labels); m_del_obj(emit_t, emit); } -typedef byte *(*emit_allocator_t)(emit_t *emit, int nbytes); - -STATIC void emit_write_uint(emit_t *emit, emit_allocator_t allocator, mp_uint_t val) { - // We store each 7 bits in a separate byte, and that's how many bytes needed - byte buf[BYTES_FOR_INT]; - byte *p = buf + sizeof(buf); - // We encode in little-ending order, but store in big-endian, to help decoding - do { - *--p = val & 0x7f; - val >>= 7; - } while (val != 0); - byte *c = allocator(emit, buf + sizeof(buf) - p); - while (p != buf + sizeof(buf) - 1) { - *c++ = *p++ | 0x80; - } - *c = *p; -} - // all functions must go through this one to emit code info -STATIC byte *emit_get_cur_to_write_code_info(emit_t *emit, int num_bytes_to_write) { +STATIC uint8_t *emit_get_cur_to_write_code_info(void *emit_in, size_t num_bytes_to_write) { + emit_t *emit = emit_in; if (emit->pass < MP_PASS_EMIT) { emit->code_info_offset += num_bytes_to_write; return emit->dummy_data; @@ -126,14 +106,7 @@ STATIC void emit_write_code_info_byte(emit_t *emit, byte val) { } STATIC void emit_write_code_info_qstr(emit_t *emit, qstr qst) { - #if MICROPY_PERSISTENT_CODE - assert((qst >> 16) == 0); - byte *c = emit_get_cur_to_write_code_info(emit, 2); - c[0] = qst; - c[1] = qst >> 8; - #else - emit_write_uint(emit, emit_get_cur_to_write_code_info, qst); - #endif + mp_encode_uint(emit, emit_get_cur_to_write_code_info, mp_emit_common_use_qstr(emit->emit_common, qst)); } #if MICROPY_ENABLE_SOURCE_LINE @@ -166,7 +139,8 @@ STATIC void emit_write_code_info_bytes_lines(emit_t *emit, mp_uint_t bytes_to_sk #endif // all functions must go through this one to emit byte code -STATIC byte *emit_get_cur_to_write_bytecode(emit_t *emit, int num_bytes_to_write) { +STATIC uint8_t *emit_get_cur_to_write_bytecode(void *emit_in, size_t num_bytes_to_write) { + emit_t *emit = emit_in; if (emit->pass < MP_PASS_EMIT) { emit->bytecode_offset += num_bytes_to_write; return emit->dummy_data; @@ -189,12 +163,12 @@ STATIC void emit_write_bytecode_byte(emit_t *emit, int stack_adj, byte b1) { c[0] = b1; } -// Similar to emit_write_bytecode_uint(), just some extra handling to encode sign +// Similar to mp_encode_uint(), just some extra handling to encode sign STATIC void emit_write_bytecode_byte_int(emit_t *emit, int stack_adj, byte b1, mp_int_t num) { emit_write_bytecode_byte(emit, stack_adj, b1); // We store each 7 bits in a separate byte, and that's how many bytes needed - byte buf[BYTES_FOR_INT]; + byte buf[MP_ENCODE_UINT_MAX_BYTES]; byte *p = buf + sizeof(buf); // We encode in little-ending order, but store in big-endian, to help decoding do { @@ -218,94 +192,81 @@ STATIC void emit_write_bytecode_byte_int(emit_t *emit, int stack_adj, byte b1, m STATIC void emit_write_bytecode_byte_uint(emit_t *emit, int stack_adj, byte b, mp_uint_t val) { emit_write_bytecode_byte(emit, stack_adj, b); - emit_write_uint(emit, emit_get_cur_to_write_bytecode, val); + mp_encode_uint(emit, emit_get_cur_to_write_bytecode, val); } -#if MICROPY_PERSISTENT_CODE -STATIC void emit_write_bytecode_byte_const(emit_t *emit, int stack_adj, byte b, mp_uint_t n, mp_uint_t c) { - if (emit->pass == MP_PASS_EMIT) { - emit->const_table[n] = c; - } +STATIC void emit_write_bytecode_byte_const(emit_t *emit, int stack_adj, byte b, mp_uint_t n) { emit_write_bytecode_byte_uint(emit, stack_adj, b, n); } -#endif STATIC void emit_write_bytecode_byte_qstr(emit_t *emit, int stack_adj, byte b, qstr qst) { - #if MICROPY_PERSISTENT_CODE - assert((qst >> 16) == 0); - mp_emit_bc_adjust_stack_size(emit, stack_adj); - byte *c = emit_get_cur_to_write_bytecode(emit, 3); - c[0] = b; - c[1] = qst; - c[2] = qst >> 8; - #else - emit_write_bytecode_byte_uint(emit, stack_adj, b, qst); - #endif + emit_write_bytecode_byte_uint(emit, stack_adj, b, mp_emit_common_use_qstr(emit->emit_common, qst)); } STATIC void emit_write_bytecode_byte_obj(emit_t *emit, int stack_adj, byte b, mp_obj_t obj) { - #if MICROPY_PERSISTENT_CODE - emit_write_bytecode_byte_const(emit, stack_adj, b, - emit->scope->num_pos_args + emit->scope->num_kwonly_args - + emit->ct_cur_obj++, (mp_uint_t)obj); - #else - // aligns the pointer so it is friendly to GC - emit_write_bytecode_byte(emit, stack_adj, b); - emit->bytecode_offset = (size_t)MP_ALIGN(emit->bytecode_offset, sizeof(mp_obj_t)); - mp_obj_t *c = (mp_obj_t *)emit_get_cur_to_write_bytecode(emit, sizeof(mp_obj_t)); - // Verify thar c is already uint-aligned - assert(c == MP_ALIGN(c, sizeof(mp_obj_t))); - *c = obj; - #endif + emit_write_bytecode_byte_const(emit, stack_adj, b, mp_emit_common_use_const_obj(emit->emit_common, obj)); } -STATIC void emit_write_bytecode_byte_raw_code(emit_t *emit, int stack_adj, byte b, mp_raw_code_t *rc) { - #if MICROPY_PERSISTENT_CODE +STATIC void emit_write_bytecode_byte_child(emit_t *emit, int stack_adj, byte b, mp_raw_code_t *rc) { emit_write_bytecode_byte_const(emit, stack_adj, b, - emit->scope->num_pos_args + emit->scope->num_kwonly_args - + emit->ct_num_obj + emit->ct_cur_raw_code++, (mp_uint_t)(uintptr_t)rc); - #else - // aligns the pointer so it is friendly to GC - emit_write_bytecode_byte(emit, stack_adj, b); - emit->bytecode_offset = (size_t)MP_ALIGN(emit->bytecode_offset, sizeof(void *)); - void **c = (void **)emit_get_cur_to_write_bytecode(emit, sizeof(void *)); - // Verify thar c is already uint-aligned - assert(c == MP_ALIGN(c, sizeof(void *))); - *c = rc; - #endif + mp_emit_common_alloc_const_child(emit->emit_common, rc)); #if MICROPY_PY_SYS_SETTRACE rc->line_of_definition = emit->last_source_line; #endif } -// unsigned labels are relative to ip following this instruction, stored as 16 bits -STATIC void emit_write_bytecode_byte_unsigned_label(emit_t *emit, int stack_adj, byte b1, mp_uint_t label) { +// Emit a jump opcode to a destination label. +// The offset to the label is relative to the ip following this instruction. +// The offset is encoded as either 1 or 2 bytes, depending on how big it is. +// The encoding of this jump opcode can change size from one pass to the next, +// but it must only ever decrease in size on successive passes. +STATIC void emit_write_bytecode_byte_label(emit_t *emit, int stack_adj, byte b1, mp_uint_t label) { mp_emit_bc_adjust_stack_size(emit, stack_adj); - mp_uint_t bytecode_offset; - if (emit->pass < MP_PASS_EMIT) { - bytecode_offset = 0; - } else { - bytecode_offset = emit->label_offsets[label] - emit->bytecode_offset - 3; + + // Determine if the jump offset is signed or unsigned, based on the opcode. + const bool is_signed = b1 <= MP_BC_POP_JUMP_IF_FALSE; + + // Default to a 2-byte encoding (the largest) with an unknown jump offset. + unsigned int jump_encoding_size = 1; + ssize_t bytecode_offset = 0; + + // Compute the jump size and offset only when code size is known. + if (emit->pass >= MP_PASS_CODE_SIZE) { + // The -2 accounts for this jump opcode taking 2 bytes (at least). + bytecode_offset = emit->label_offsets[label] - emit->bytecode_offset - 2; + + // Check if the bytecode_offset is small enough to use a 1-byte encoding. + if ((is_signed && -64 <= bytecode_offset && bytecode_offset <= 63) + || (!is_signed && (size_t)bytecode_offset <= 127)) { + // Use a 1-byte jump offset. + jump_encoding_size = 0; + } + + // Adjust the offset depending on the size of the encoding of the offset. + bytecode_offset -= jump_encoding_size; + + assert(is_signed || bytecode_offset >= 0); } - byte *c = emit_get_cur_to_write_bytecode(emit, 3); - c[0] = b1; - c[1] = bytecode_offset; - c[2] = bytecode_offset >> 8; -} -// signed labels are relative to ip following this instruction, stored as 16 bits, in excess -STATIC void emit_write_bytecode_byte_signed_label(emit_t *emit, int stack_adj, byte b1, mp_uint_t label) { - mp_emit_bc_adjust_stack_size(emit, stack_adj); - int bytecode_offset; - if (emit->pass < MP_PASS_EMIT) { - bytecode_offset = 0; + // Emit the opcode. + byte *c = emit_get_cur_to_write_bytecode(emit, 2 + jump_encoding_size); + c[0] = b1; + if (jump_encoding_size == 0) { + if (is_signed) { + bytecode_offset += 0x40; + } + assert(0 <= bytecode_offset && bytecode_offset <= 0x7f); + c[1] = bytecode_offset; } else { - bytecode_offset = emit->label_offsets[label] - emit->bytecode_offset - 3 + 0x8000; + if (is_signed) { + bytecode_offset += 0x4000; + } + if (emit->pass == MP_PASS_EMIT && !(0 <= bytecode_offset && bytecode_offset <= 0x7fff)) { + emit->overflow = true; + } + c[1] = 0x80 | (bytecode_offset & 0x7f); + c[2] = bytecode_offset >> 7; } - byte *c = emit_get_cur_to_write_bytecode(emit, 3); - c[0] = b1; - c[1] = bytecode_offset; - c[2] = bytecode_offset >> 8; } void mp_emit_bc_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scope) { @@ -315,14 +276,9 @@ void mp_emit_bc_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scope) { emit->scope = scope; emit->last_source_line_offset = 0; emit->last_source_line = 1; - #ifndef NDEBUG - // With debugging enabled labels are checked for unique assignment - if (pass < MP_PASS_EMIT && emit->label_offsets != NULL) { - memset(emit->label_offsets, -1, emit->max_num_labels * sizeof(mp_uint_t)); - } - #endif emit->bytecode_offset = 0; emit->code_info_offset = 0; + emit->overflow = false; // Write local state size, exception stack size, scope flags and number of arguments { @@ -343,27 +299,19 @@ void mp_emit_bc_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scope) { } // Write number of cells and size of the source code info - if (pass >= MP_PASS_CODE_SIZE) { - MP_BC_PRELUDE_SIZE_ENCODE(emit->n_info, emit->n_cell, emit_write_code_info_byte, emit); + if (emit->pass >= MP_PASS_CODE_SIZE) { + size_t n_info = emit->n_info; + size_t n_cell = emit->n_cell; + MP_BC_PRELUDE_SIZE_ENCODE(n_info, n_cell, emit_write_code_info_byte, emit); } emit->n_info = emit->code_info_offset; - // Write the name and source file of this function. + // Write the name of this function. emit_write_code_info_qstr(emit, scope->simple_name); - emit_write_code_info_qstr(emit, scope->source_file); - - #if MICROPY_PERSISTENT_CODE - emit->ct_cur_obj = 0; - emit->ct_cur_raw_code = 0; - #endif - - if (pass == MP_PASS_EMIT) { - // Write argument names (needed to resolve positional args passed as - // keywords). We store them as full word-sized objects for efficient access - // in mp_setup_code_state this is the start of the prelude and is guaranteed - // to be aligned on a word boundary. + // Write argument names, needed to resolve positional args passed as keywords. + { // For a given argument position (indexed by i) we need to find the // corresponding id_info which is a parameter, as it has the correct // qstr name to use as the argument name. Note that it's not a simple @@ -383,21 +331,19 @@ void mp_emit_bc_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scope) { break; } } - emit->const_table[i] = (mp_uint_t)MP_OBJ_NEW_QSTR(qst); + emit_write_code_info_qstr(emit, qst); } } } -void mp_emit_bc_end_pass(emit_t *emit) { +bool mp_emit_bc_end_pass(emit_t *emit) { if (emit->pass == MP_PASS_SCOPE) { - return; + return true; } // check stack is back to zero size assert(emit->stack_size == 0); - emit_write_code_info_byte(emit, 0); // end of line number info - // Calculate size of source code info section emit->n_info = emit->code_info_offset - emit->n_info; @@ -412,42 +358,43 @@ void mp_emit_bc_end_pass(emit_t *emit) { } } - #if MICROPY_PERSISTENT_CODE - assert(emit->pass <= MP_PASS_STACK_SIZE || (emit->ct_num_obj == emit->ct_cur_obj)); - emit->ct_num_obj = emit->ct_cur_obj; - #endif - if (emit->pass == MP_PASS_CODE_SIZE) { - #if !MICROPY_PERSISTENT_CODE - // so bytecode is aligned - emit->code_info_offset = (size_t)MP_ALIGN(emit->code_info_offset, sizeof(mp_uint_t)); - #endif - // calculate size of total code-info + bytecode, in bytes emit->code_info_size = emit->code_info_offset; emit->bytecode_size = emit->bytecode_offset; emit->code_base = m_new0(byte, emit->code_info_size + emit->bytecode_size); - #if MICROPY_PERSISTENT_CODE - emit->const_table = m_new0(mp_uint_t, - emit->scope->num_pos_args + emit->scope->num_kwonly_args - + emit->ct_cur_obj + emit->ct_cur_raw_code); - #else - emit->const_table = m_new0(mp_uint_t, - emit->scope->num_pos_args + emit->scope->num_kwonly_args); - #endif - } else if (emit->pass == MP_PASS_EMIT) { + // Code info and/or bytecode can shrink during this pass. + assert(emit->code_info_offset <= emit->code_info_size); + assert(emit->bytecode_offset <= emit->bytecode_size); + + if (emit->code_info_offset != emit->code_info_size + || emit->bytecode_offset != emit->bytecode_size) { + // Code info and/or bytecode changed size in this pass, so request the + // compiler to do another pass with these updated sizes. + emit->code_info_size = emit->code_info_offset; + emit->bytecode_size = emit->bytecode_offset; + return false; + } + + if (emit->overflow) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("bytecode overflow")); + } + + // Bytecode is finalised, assign it to the raw code object. mp_emit_glue_assign_bytecode(emit->scope->raw_code, emit->code_base, #if MICROPY_PERSISTENT_CODE_SAVE || MICROPY_DEBUG_PRINTERS emit->code_info_size + emit->bytecode_size, #endif - emit->const_table, + emit->emit_common->children, #if MICROPY_PERSISTENT_CODE_SAVE - emit->ct_cur_obj, emit->ct_cur_raw_code, + emit->emit_common->ct_cur_child, #endif emit->scope->scope_flags); } + + return true; } bool mp_emit_bc_last_emit_was_return_value(emit_t *emit) { @@ -490,15 +437,16 @@ void mp_emit_bc_label_assign(emit_t *emit, mp_uint_t l) { if (emit->pass == MP_PASS_SCOPE) { return; } + + // Label offsets can change from one pass to the next, but they must only + // decrease (ie code can only shrink). There will be multiple MP_PASS_EMIT + // stages until the labels no longer change, which is when the code size + // stays constant after a MP_PASS_EMIT. assert(l < emit->max_num_labels); - if (emit->pass < MP_PASS_EMIT) { - // assign label offset - assert(emit->label_offsets[l] == (mp_uint_t)-1); - emit->label_offsets[l] = emit->bytecode_offset; - } else { - // ensure label offset has not changed from MP_PASS_CODE_SIZE to MP_PASS_EMIT - assert(emit->label_offsets[l] == emit->bytecode_offset); - } + assert(emit->pass == MP_PASS_STACK_SIZE || emit->bytecode_offset <= emit->label_offsets[l]); + + // Assign label offset. + emit->label_offsets[l] = emit->bytecode_offset; } void mp_emit_bc_import(emit_t *emit, qstr qst, int kind) { @@ -523,6 +471,7 @@ void mp_emit_bc_load_const_tok(emit_t *emit, mp_token_kind_t tok) { } void mp_emit_bc_load_const_small_int(emit_t *emit, mp_int_t arg) { + assert(MP_SMALL_INT_FITS(arg)); if (-MP_BC_LOAD_CONST_SMALL_INT_MULTI_EXCESS <= arg && arg < MP_BC_LOAD_CONST_SMALL_INT_MULTI_NUM - MP_BC_LOAD_CONST_SMALL_INT_MULTI_EXCESS) { emit_write_bytecode_byte(emit, 1, @@ -560,9 +509,6 @@ void mp_emit_bc_load_global(emit_t *emit, qstr qst, int kind) { MP_STATIC_ASSERT(MP_BC_LOAD_NAME + MP_EMIT_IDOP_GLOBAL_GLOBAL == MP_BC_LOAD_GLOBAL); (void)qst; emit_write_bytecode_byte_qstr(emit, 1, MP_BC_LOAD_NAME + kind, qst); - if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE_DYNAMIC) { - emit_write_bytecode_raw_byte(emit, 0); - } } void mp_emit_bc_load_method(emit_t *emit, qstr qst, bool is_super) { @@ -596,9 +542,6 @@ void mp_emit_bc_attr(emit_t *emit, qstr qst, int kind) { } emit_write_bytecode_byte_qstr(emit, -2, MP_BC_STORE_ATTR, qst); } - if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE_DYNAMIC) { - emit_write_bytecode_raw_byte(emit, 0); - } } void mp_emit_bc_store_local(emit_t *emit, qstr qst, mp_uint_t local_num, int kind) { @@ -652,22 +595,22 @@ void mp_emit_bc_rot_three(emit_t *emit) { } void mp_emit_bc_jump(emit_t *emit, mp_uint_t label) { - emit_write_bytecode_byte_signed_label(emit, 0, MP_BC_JUMP, label); + emit_write_bytecode_byte_label(emit, 0, MP_BC_JUMP, label); } void mp_emit_bc_pop_jump_if(emit_t *emit, bool cond, mp_uint_t label) { if (cond) { - emit_write_bytecode_byte_signed_label(emit, -1, MP_BC_POP_JUMP_IF_TRUE, label); + emit_write_bytecode_byte_label(emit, -1, MP_BC_POP_JUMP_IF_TRUE, label); } else { - emit_write_bytecode_byte_signed_label(emit, -1, MP_BC_POP_JUMP_IF_FALSE, label); + emit_write_bytecode_byte_label(emit, -1, MP_BC_POP_JUMP_IF_FALSE, label); } } void mp_emit_bc_jump_if_or_pop(emit_t *emit, bool cond, mp_uint_t label) { if (cond) { - emit_write_bytecode_byte_signed_label(emit, -1, MP_BC_JUMP_IF_TRUE_OR_POP, label); + emit_write_bytecode_byte_label(emit, -1, MP_BC_JUMP_IF_TRUE_OR_POP, label); } else { - emit_write_bytecode_byte_signed_label(emit, -1, MP_BC_JUMP_IF_FALSE_OR_POP, label); + emit_write_bytecode_byte_label(emit, -1, MP_BC_JUMP_IF_FALSE_OR_POP, label); } } @@ -681,9 +624,9 @@ void mp_emit_bc_unwind_jump(emit_t *emit, mp_uint_t label, mp_uint_t except_dept emit_write_bytecode_raw_byte(emit, MP_BC_POP_TOP); } } - emit_write_bytecode_byte_signed_label(emit, 0, MP_BC_JUMP, label & ~MP_EMIT_BREAK_FROM_FOR); + emit_write_bytecode_byte_label(emit, 0, MP_BC_JUMP, label & ~MP_EMIT_BREAK_FROM_FOR); } else { - emit_write_bytecode_byte_signed_label(emit, 0, MP_BC_UNWIND_JUMP, label & ~MP_EMIT_BREAK_FROM_FOR); + emit_write_bytecode_byte_label(emit, 0, MP_BC_UNWIND_JUMP, label & ~MP_EMIT_BREAK_FROM_FOR); emit_write_bytecode_raw_byte(emit, ((label & MP_EMIT_BREAK_FROM_FOR) ? 0x80 : 0) | except_depth); } } @@ -695,7 +638,7 @@ void mp_emit_bc_setup_block(emit_t *emit, mp_uint_t label, int kind) { // The SETUP_WITH opcode pops ctx_mgr from the top of the stack // and then pushes 3 entries: __exit__, ctx_mgr, as_value. int stack_adj = kind == MP_EMIT_SETUP_BLOCK_WITH ? 2 : 0; - emit_write_bytecode_byte_unsigned_label(emit, stack_adj, MP_BC_SETUP_WITH + kind, label); + emit_write_bytecode_byte_label(emit, stack_adj, MP_BC_SETUP_WITH + kind, label); } void mp_emit_bc_with_cleanup(emit_t *emit, mp_uint_t label) { @@ -717,7 +660,7 @@ void mp_emit_bc_get_iter(emit_t *emit, bool use_stack) { } void mp_emit_bc_for_iter(emit_t *emit, mp_uint_t label) { - emit_write_bytecode_byte_unsigned_label(emit, 1, MP_BC_FOR_ITER, label); + emit_write_bytecode_byte_label(emit, 1, MP_BC_FOR_ITER, label); } void mp_emit_bc_for_iter_end(emit_t *emit) { @@ -726,7 +669,7 @@ void mp_emit_bc_for_iter_end(emit_t *emit) { void mp_emit_bc_pop_except_jump(emit_t *emit, mp_uint_t label, bool within_exc_handler) { (void)within_exc_handler; - emit_write_bytecode_byte_unsigned_label(emit, 0, MP_BC_POP_EXCEPT_JUMP, label); + emit_write_bytecode_byte_label(emit, 0, MP_BC_POP_EXCEPT_JUMP, label); } void mp_emit_bc_unary_op(emit_t *emit, mp_unary_op_t op) { @@ -789,28 +732,30 @@ void mp_emit_bc_unpack_ex(emit_t *emit, mp_uint_t n_left, mp_uint_t n_right) { void mp_emit_bc_make_function(emit_t *emit, scope_t *scope, mp_uint_t n_pos_defaults, mp_uint_t n_kw_defaults) { if (n_pos_defaults == 0 && n_kw_defaults == 0) { - emit_write_bytecode_byte_raw_code(emit, 1, MP_BC_MAKE_FUNCTION, scope->raw_code); + emit_write_bytecode_byte_child(emit, 1, MP_BC_MAKE_FUNCTION, scope->raw_code); } else { - emit_write_bytecode_byte_raw_code(emit, -1, MP_BC_MAKE_FUNCTION_DEFARGS, scope->raw_code); + emit_write_bytecode_byte_child(emit, -1, MP_BC_MAKE_FUNCTION_DEFARGS, scope->raw_code); } } void mp_emit_bc_make_closure(emit_t *emit, scope_t *scope, mp_uint_t n_closed_over, mp_uint_t n_pos_defaults, mp_uint_t n_kw_defaults) { if (n_pos_defaults == 0 && n_kw_defaults == 0) { int stack_adj = -n_closed_over + 1; - emit_write_bytecode_byte_raw_code(emit, stack_adj, MP_BC_MAKE_CLOSURE, scope->raw_code); + emit_write_bytecode_byte_child(emit, stack_adj, MP_BC_MAKE_CLOSURE, scope->raw_code); emit_write_bytecode_raw_byte(emit, n_closed_over); } else { assert(n_closed_over <= 255); int stack_adj = -2 - (mp_int_t)n_closed_over + 1; - emit_write_bytecode_byte_raw_code(emit, stack_adj, MP_BC_MAKE_CLOSURE_DEFARGS, scope->raw_code); + emit_write_bytecode_byte_child(emit, stack_adj, MP_BC_MAKE_CLOSURE_DEFARGS, scope->raw_code); emit_write_bytecode_raw_byte(emit, n_closed_over); } } STATIC void emit_bc_call_function_method_helper(emit_t *emit, int stack_adj, mp_uint_t bytecode_base, mp_uint_t n_positional, mp_uint_t n_keyword, mp_uint_t star_flags) { if (star_flags) { - stack_adj -= (int)n_positional + 2 * (int)n_keyword + 2; + // each positional arg is one object, each kwarg is two objects, the key + // and the value and one extra object for the star args bitmap. + stack_adj -= (int)n_positional + 2 * (int)n_keyword + 1; emit_write_bytecode_byte_uint(emit, stack_adj, bytecode_base + 1, (n_keyword << 8) | n_positional); // TODO make it 2 separate uints? } else { stack_adj -= (int)n_positional + 2 * (int)n_keyword; diff --git a/python/src/py/emitcommon.c b/python/src/py/emitcommon.c index 791bf398ab9..679ef1d973b 100644 --- a/python/src/py/emitcommon.c +++ b/python/src/py/emitcommon.c @@ -27,15 +27,76 @@ #include #include "py/emit.h" +#include "py/nativeglue.h" #if MICROPY_ENABLE_COMPILER +#if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE +qstr_short_t mp_emit_common_use_qstr(mp_emit_common_t *emit, qstr qst) { + mp_map_elem_t *elem = mp_map_lookup(&emit->qstr_map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); + if (elem->value == MP_OBJ_NULL) { + elem->value = MP_OBJ_NEW_SMALL_INT(emit->qstr_map.used - 1); + } + return MP_OBJ_SMALL_INT_VALUE(elem->value); +} +#endif + +// Compare two objects for strict equality, including equality of type. This is +// different to the semantics of mp_obj_equal which, eg, has (True,) == (1.0,). +static bool strictly_equal(mp_obj_t a, mp_obj_t b) { + if (a == b) { + return true; + } + + #if MICROPY_EMIT_NATIVE + if (a == MP_OBJ_FROM_PTR(&mp_fun_table) || b == MP_OBJ_FROM_PTR(&mp_fun_table)) { + return false; + } + #endif + + const mp_obj_type_t *a_type = mp_obj_get_type(a); + const mp_obj_type_t *b_type = mp_obj_get_type(b); + if (a_type != b_type) { + return false; + } + if (a_type == &mp_type_tuple) { + mp_obj_tuple_t *a_tuple = MP_OBJ_TO_PTR(a); + mp_obj_tuple_t *b_tuple = MP_OBJ_TO_PTR(b); + if (a_tuple->len != b_tuple->len) { + return false; + } + for (size_t i = 0; i < a_tuple->len; ++i) { + if (!strictly_equal(a_tuple->items[i], b_tuple->items[i])) { + return false; + } + } + return true; + } else { + return mp_obj_equal(a, b); + } +} + +size_t mp_emit_common_use_const_obj(mp_emit_common_t *emit, mp_obj_t const_obj) { + for (size_t i = 0; i < emit->const_obj_list.len; ++i) { + if (strictly_equal(emit->const_obj_list.items[i], const_obj)) { + return i; + } + } + mp_obj_list_append(MP_OBJ_FROM_PTR(&emit->const_obj_list), const_obj); + return emit->const_obj_list.len - 1; +} + void mp_emit_common_get_id_for_modification(scope_t *scope, qstr qst) { // name adding/lookup id_info_t *id = scope_find_or_add_id(scope, qst, ID_INFO_KIND_GLOBAL_IMPLICIT); - if (SCOPE_IS_FUNC_LIKE(scope->kind) && id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT) { - // rebind as a local variable - id->kind = ID_INFO_KIND_LOCAL; + if (id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT) { + if (SCOPE_IS_FUNC_LIKE(scope->kind)) { + // rebind as a local variable + id->kind = ID_INFO_KIND_LOCAL; + } else { + // mark this as assigned, to prevent it from being closed over + id->kind = ID_INFO_KIND_GLOBAL_IMPLICIT_ASSIGNED; + } } } @@ -46,7 +107,7 @@ void mp_emit_common_id_op(emit_t *emit, const mp_emit_method_table_id_ops_t *emi assert(id != NULL); // call the emit backend with the correct code - if (id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT) { + if (id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT || id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT_ASSIGNED) { emit_method_table->global(emit, qst, MP_EMIT_IDOP_GLOBAL_NAME); } else if (id->kind == ID_INFO_KIND_GLOBAL_EXPLICIT) { emit_method_table->global(emit, qst, MP_EMIT_IDOP_GLOBAL_GLOBAL); diff --git a/python/src/py/emitglue.c b/python/src/py/emitglue.c index 09b48682ffb..95be7f661ab 100644 --- a/python/src/py/emitglue.c +++ b/python/src/py/emitglue.c @@ -34,6 +34,7 @@ #include "py/emitglue.h" #include "py/runtime0.h" #include "py/bc.h" +#include "py/objfun.h" #include "py/profile.h" #if MICROPY_DEBUG_VERBOSE // print debugging info @@ -63,20 +64,22 @@ void mp_emit_glue_assign_bytecode(mp_raw_code_t *rc, const byte *code, #if MICROPY_PERSISTENT_CODE_SAVE || MICROPY_DEBUG_PRINTERS size_t len, #endif - const mp_uint_t *const_table, + mp_raw_code_t **children, #if MICROPY_PERSISTENT_CODE_SAVE - uint16_t n_obj, uint16_t n_raw_code, + size_t n_children, #endif mp_uint_t scope_flags) { rc->kind = MP_CODE_BYTECODE; rc->scope_flags = scope_flags; rc->fun_data = code; - rc->const_table = const_table; - #if MICROPY_PERSISTENT_CODE_SAVE + #if MICROPY_PERSISTENT_CODE_SAVE || MICROPY_DEBUG_PRINTERS rc->fun_data_len = len; - rc->n_obj = n_obj; - rc->n_raw_code = n_raw_code; + #endif + rc->children = children; + + #if MICROPY_PERSISTENT_CODE_SAVE + rc->n_children = n_children; #endif #if MICROPY_PY_SYS_SETTRACE @@ -85,26 +88,21 @@ void mp_emit_glue_assign_bytecode(mp_raw_code_t *rc, const byte *code, #endif #ifdef DEBUG_PRINT - #if !MICROPY_DEBUG_PRINTERS + #if !(MICROPY_PERSISTENT_CODE_SAVE || MICROPY_DEBUG_PRINTERS) const size_t len = 0; #endif DEBUG_printf("assign byte code: code=%p len=" UINT_FMT " flags=%x\n", code, len, (uint)scope_flags); #endif - #if MICROPY_DEBUG_PRINTERS - if (mp_verbose_flag >= 2) { - mp_bytecode_print(&mp_plat_print, rc, code, len, const_table); - } - #endif } #if MICROPY_EMIT_MACHINE_CODE -void mp_emit_glue_assign_native(mp_raw_code_t *rc, mp_raw_code_kind_t kind, void *fun_data, mp_uint_t fun_len, const mp_uint_t *const_table, +void mp_emit_glue_assign_native(mp_raw_code_t *rc, mp_raw_code_kind_t kind, void *fun_data, mp_uint_t fun_len, + mp_raw_code_t **children, #if MICROPY_PERSISTENT_CODE_SAVE + size_t n_children, uint16_t prelude_offset, - uint16_t n_obj, uint16_t n_raw_code, - uint16_t n_qstr, mp_qstr_link_entry_t *qstr_link, #endif - mp_uint_t n_pos_args, mp_uint_t scope_flags, mp_uint_t type_sig) { + mp_uint_t scope_flags, mp_uint_t n_pos_args, mp_uint_t type_sig) { assert(kind == MP_CODE_NATIVE_PY || kind == MP_CODE_NATIVE_VIPER || kind == MP_CODE_NATIVE_ASM); @@ -135,20 +133,22 @@ void mp_emit_glue_assign_native(mp_raw_code_t *rc, mp_raw_code_kind_t kind, void rc->kind = kind; rc->scope_flags = scope_flags; - rc->n_pos_args = n_pos_args; rc->fun_data = fun_data; - rc->const_table = const_table; - rc->type_sig = type_sig; - #if MICROPY_PERSISTENT_CODE_SAVE + #if MICROPY_PERSISTENT_CODE_SAVE || MICROPY_DEBUG_PRINTERS rc->fun_data_len = fun_len; + #endif + rc->children = children; + + #if MICROPY_PERSISTENT_CODE_SAVE + rc->n_children = n_children; rc->prelude_offset = prelude_offset; - rc->n_obj = n_obj; - rc->n_raw_code = n_raw_code; - rc->n_qstr = n_qstr; - rc->qstr_link = qstr_link; #endif + // These two entries are only needed for MP_CODE_NATIVE_ASM. + rc->n_pos_args = n_pos_args; + rc->type_sig = type_sig; + #ifdef DEBUG_PRINT DEBUG_printf("assign native: kind=%d fun=%p len=" UINT_FMT " n_pos_args=" UINT_FMT " flags=%x\n", kind, fun_data, fun_len, n_pos_args, (uint)scope_flags); for (mp_uint_t i = 0; i < fun_len; i++) { @@ -170,15 +170,15 @@ void mp_emit_glue_assign_native(mp_raw_code_t *rc, mp_raw_code_kind_t kind, void } #endif -mp_obj_t mp_make_function_from_raw_code(const mp_raw_code_t *rc, mp_obj_t def_args, mp_obj_t def_kw_args) { +mp_obj_t mp_make_function_from_raw_code(const mp_raw_code_t *rc, const mp_module_context_t *context, const mp_obj_t *def_args) { DEBUG_OP_printf("make_function_from_raw_code %p\n", rc); assert(rc != NULL); // def_args must be MP_OBJ_NULL or a tuple - assert(def_args == MP_OBJ_NULL || mp_obj_is_type(def_args, &mp_type_tuple)); + assert(def_args == NULL || def_args[0] == MP_OBJ_NULL || mp_obj_is_type(def_args[0], &mp_type_tuple)); // def_kw_args must be MP_OBJ_NULL or a dict - assert(def_kw_args == MP_OBJ_NULL || mp_obj_is_type(def_kw_args, &mp_type_dict)); + assert(def_args == NULL || def_args[1] == MP_OBJ_NULL || mp_obj_is_type(def_args[1], &mp_type_dict)); // make the function, depending on the raw code kind mp_obj_t fun; @@ -186,7 +186,7 @@ mp_obj_t mp_make_function_from_raw_code(const mp_raw_code_t *rc, mp_obj_t def_ar #if MICROPY_EMIT_NATIVE case MP_CODE_NATIVE_PY: case MP_CODE_NATIVE_VIPER: - fun = mp_obj_new_fun_native(def_args, def_kw_args, rc->fun_data, rc->const_table); + fun = mp_obj_new_fun_native(def_args, rc->fun_data, context, rc->children); // Check for a generator function, and if so change the type of the object if ((rc->scope_flags & MP_SCOPE_FLAG_GENERATOR) != 0) { ((mp_obj_base_t *)MP_OBJ_TO_PTR(fun))->type = &mp_type_native_gen_wrap; @@ -201,7 +201,7 @@ mp_obj_t mp_make_function_from_raw_code(const mp_raw_code_t *rc, mp_obj_t def_ar default: // rc->kind should always be set and BYTECODE is the only remaining case assert(rc->kind == MP_CODE_BYTECODE); - fun = mp_obj_new_fun_bc(def_args, def_kw_args, rc->fun_data, rc->const_table); + fun = mp_obj_new_fun_bc(def_args, rc->fun_data, context, rc->children); // check for generator functions and if so change the type of the object if ((rc->scope_flags & MP_SCOPE_FLAG_GENERATOR) != 0) { ((mp_obj_base_t *)MP_OBJ_TO_PTR(fun))->type = &mp_type_gen_wrap; @@ -218,16 +218,16 @@ mp_obj_t mp_make_function_from_raw_code(const mp_raw_code_t *rc, mp_obj_t def_ar return fun; } -mp_obj_t mp_make_closure_from_raw_code(const mp_raw_code_t *rc, mp_uint_t n_closed_over, const mp_obj_t *args) { +mp_obj_t mp_make_closure_from_raw_code(const mp_raw_code_t *rc, const mp_module_context_t *context, mp_uint_t n_closed_over, const mp_obj_t *args) { DEBUG_OP_printf("make_closure_from_raw_code %p " UINT_FMT " %p\n", rc, n_closed_over, args); // make function object mp_obj_t ffun; if (n_closed_over & 0x100) { // default positional and keyword args given - ffun = mp_make_function_from_raw_code(rc, args[0], args[1]); + ffun = mp_make_function_from_raw_code(rc, context, args); } else { // default positional and keyword args not given - ffun = mp_make_function_from_raw_code(rc, MP_OBJ_NULL, MP_OBJ_NULL); + ffun = mp_make_function_from_raw_code(rc, context, NULL); } // wrap function in closure object return mp_obj_new_closure(ffun, n_closed_over & 0xff, args + ((n_closed_over >> 7) & 2)); diff --git a/python/src/py/emitglue.h b/python/src/py/emitglue.h index a5411dc2e20..4ddf74011fa 100644 --- a/python/src/py/emitglue.h +++ b/python/src/py/emitglue.h @@ -49,21 +49,20 @@ typedef enum { MP_CODE_NATIVE_ASM, } mp_raw_code_kind_t; -typedef struct _mp_qstr_link_entry_t { - uint16_t off; - uint16_t qst; -} mp_qstr_link_entry_t; - +// compiled bytecode: instance in RAM, referenced by outer scope, usually freed after first (and only) use +// mpy file: instance in RAM, created when .mpy file is loaded (same comments as above) +// frozen: instance in ROM typedef struct _mp_raw_code_t { mp_uint_t kind : 3; // of type mp_raw_code_kind_t mp_uint_t scope_flags : 7; mp_uint_t n_pos_args : 11; const void *fun_data; - const mp_uint_t *const_table; + #if MICROPY_PERSISTENT_CODE_SAVE || MICROPY_DEBUG_PRINTERS + size_t fun_data_len; // so mp_raw_code_save and mp_bytecode_print work + #endif + struct _mp_raw_code_t **children; #if MICROPY_PERSISTENT_CODE_SAVE - size_t fun_data_len; - uint16_t n_obj; - uint16_t n_raw_code; + size_t n_children; #if MICROPY_PY_SYS_SETTRACE mp_bytecode_prelude_t prelude; // line_of_definition is a Python source line where the raw_code was @@ -74,8 +73,6 @@ typedef struct _mp_raw_code_t { #endif #if MICROPY_EMIT_MACHINE_CODE uint16_t prelude_offset; - uint16_t n_qstr; - mp_qstr_link_entry_t *qstr_link; #endif #endif #if MICROPY_EMIT_MACHINE_CODE @@ -89,22 +86,21 @@ void mp_emit_glue_assign_bytecode(mp_raw_code_t *rc, const byte *code, #if MICROPY_PERSISTENT_CODE_SAVE || MICROPY_DEBUG_PRINTERS size_t len, #endif - const mp_uint_t *const_table, + mp_raw_code_t **children, #if MICROPY_PERSISTENT_CODE_SAVE - uint16_t n_obj, uint16_t n_raw_code, + size_t n_children, #endif mp_uint_t scope_flags); void mp_emit_glue_assign_native(mp_raw_code_t *rc, mp_raw_code_kind_t kind, void *fun_data, mp_uint_t fun_len, - const mp_uint_t *const_table, + mp_raw_code_t **children, #if MICROPY_PERSISTENT_CODE_SAVE + size_t n_children, uint16_t prelude_offset, - uint16_t n_obj, uint16_t n_raw_code, - uint16_t n_qstr, mp_qstr_link_entry_t *qstr_link, #endif - mp_uint_t n_pos_args, mp_uint_t scope_flags, mp_uint_t type_sig); + mp_uint_t scope_flags, mp_uint_t n_pos_args, mp_uint_t type_sig); -mp_obj_t mp_make_function_from_raw_code(const mp_raw_code_t *rc, mp_obj_t def_args, mp_obj_t def_kw_args); -mp_obj_t mp_make_closure_from_raw_code(const mp_raw_code_t *rc, mp_uint_t n_closed_over, const mp_obj_t *args); +mp_obj_t mp_make_function_from_raw_code(const mp_raw_code_t *rc, const mp_module_context_t *context, const mp_obj_t *def_args); +mp_obj_t mp_make_closure_from_raw_code(const mp_raw_code_t *rc, const mp_module_context_t *context, mp_uint_t n_closed_over, const mp_obj_t *args); #endif // MICROPY_INCLUDED_PY_EMITGLUE_H diff --git a/python/src/py/emitinlinethumb.c b/python/src/py/emitinlinethumb.c index 1a35e25ad35..29487f10483 100644 --- a/python/src/py/emitinlinethumb.c +++ b/python/src/py/emitinlinethumb.c @@ -59,6 +59,21 @@ struct _emit_inline_asm_t { qstr *label_lookup; }; +#if MICROPY_DYNAMIC_COMPILER + +static inline bool emit_inline_thumb_allow_float(emit_inline_asm_t *emit) { + return MP_NATIVE_ARCH_ARMV7EMSP <= mp_dynamic_compiler.native_arch + && mp_dynamic_compiler.native_arch <= MP_NATIVE_ARCH_ARMV7EMDP; +} + +#else + +static inline bool emit_inline_thumb_allow_float(emit_inline_asm_t *emit) { + return MICROPY_EMIT_INLINE_THUMB_FLOAT; +} + +#endif + STATIC void emit_inline_thumb_error_msg(emit_inline_asm_t *emit, mp_rom_error_text_t msg) { *emit->error_slot = mp_obj_new_exception_msg(&mp_type_SyntaxError, msg); } @@ -216,7 +231,6 @@ STATIC mp_uint_t get_arg_special_reg(emit_inline_asm_t *emit, const char *op, mp return 0; } -#if MICROPY_EMIT_INLINE_THUMB_FLOAT STATIC mp_uint_t get_arg_vfpreg(emit_inline_asm_t *emit, const char *op, mp_parse_node_t pn) { const char *reg_str = get_arg_str(pn); if (reg_str[0] == 's' && reg_str[1] != '\0') { @@ -243,7 +257,6 @@ STATIC mp_uint_t get_arg_vfpreg(emit_inline_asm_t *emit, const char *op, mp_pars MP_ERROR_TEXT("'%s' expects an FPU register"), op)); return 0; } -#endif STATIC mp_uint_t get_arg_reglist(emit_inline_asm_t *emit, const char *op, mp_parse_node_t pn) { // a register list looks like {r0, r1, r2} and is parsed as a Python set @@ -409,10 +422,10 @@ STATIC const format_9_10_op_t format_9_10_op_table[] = { }; #undef X -#if MICROPY_EMIT_INLINE_THUMB_FLOAT // actual opcodes are: 0xee00 | op.hi_nibble, 0x0a00 | op.lo_nibble -typedef struct _format_vfp_op_t { byte op; - char name[3]; +typedef struct _format_vfp_op_t { + byte op; + char name[3]; } format_vfp_op_t; STATIC const format_vfp_op_t format_vfp_op_table[] = { { 0x30, "add" }, @@ -420,10 +433,9 @@ STATIC const format_vfp_op_t format_vfp_op_table[] = { { 0x20, "mul" }, { 0x80, "div" }, }; -#endif // shorthand alias for whether we allow ARMv7-M instructions -#define ARMV7M MICROPY_EMIT_INLINE_THUMB_ARMV7M +#define ARMV7M asm_thumb_allow_armv7m(&emit->as) STATIC void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_args, mp_parse_node_t *pn_args) { // TODO perhaps make two tables: @@ -439,8 +451,7 @@ STATIC void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_a size_t op_len; const char *op_str = (const char *)qstr_data(op, &op_len); - #if MICROPY_EMIT_INLINE_THUMB_FLOAT - if (op_str[0] == 'v') { + if (emit_inline_thumb_allow_float(emit) && op_str[0] == 'v') { // floating point operations if (n_args == 2) { mp_uint_t op_code = 0x0ac0, op_code_hi; @@ -535,7 +546,6 @@ STATIC void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_a } return; } - #endif if (n_args == 0) { if (op == MP_QSTR_nop) { @@ -621,8 +631,13 @@ STATIC void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_a asm_thumb_op16(&emit->as, ASM_THUMB_OP_CPSIE_I); } else if (op == MP_QSTR_push) { mp_uint_t reglist = get_arg_reglist(emit, op_str, pn_args[0]); - if ((reglist & 0xff00) == 0) { - asm_thumb_op16(&emit->as, 0xb400 | reglist); + if ((reglist & 0xbf00) == 0) { + if ((reglist & (1 << 14)) == 0) { + asm_thumb_op16(&emit->as, 0xb400 | reglist); + } else { + // 16-bit encoding for pushing low registers and LR + asm_thumb_op16(&emit->as, 0xb500 | (reglist & 0xff)); + } } else { if (!ARMV7M) { goto unknown_op; @@ -631,8 +646,13 @@ STATIC void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_a } } else if (op == MP_QSTR_pop) { mp_uint_t reglist = get_arg_reglist(emit, op_str, pn_args[0]); - if ((reglist & 0xff00) == 0) { - asm_thumb_op16(&emit->as, 0xbc00 | reglist); + if ((reglist & 0x7f00) == 0) { + if ((reglist & (1 << 15)) == 0) { + asm_thumb_op16(&emit->as, 0xbc00 | reglist); + } else { + // 16-bit encoding for popping low registers and PC, i.e., returning + asm_thumb_op16(&emit->as, 0xbd00 | (reglist & 0xff)); + } } else { if (!ARMV7M) { goto unknown_op; @@ -705,24 +725,23 @@ STATIC void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_a } else if (op == MP_QSTR_sub) { op_code = ASM_THUMB_FORMAT_3_SUB; goto op_format_3; - #if ARMV7M - } else if (op == MP_QSTR_movw) { + } else if (ARMV7M && op == MP_QSTR_movw) { op_code = ASM_THUMB_OP_MOVW; mp_uint_t reg_dest; op_movw_movt: reg_dest = get_arg_reg(emit, op_str, pn_args[0], 15); int i_src = get_arg_i(emit, op_str, pn_args[1], 0xffff); asm_thumb_mov_reg_i16(&emit->as, op_code, reg_dest, i_src); - } else if (op == MP_QSTR_movt) { + } else if (ARMV7M && op == MP_QSTR_movt) { op_code = ASM_THUMB_OP_MOVT; goto op_movw_movt; - } else if (op == MP_QSTR_movwt) { + } else if (ARMV7M && op == MP_QSTR_movwt) { // this is a convenience instruction mp_uint_t reg_dest = get_arg_reg(emit, op_str, pn_args[0], 15); uint32_t i_src = get_arg_i(emit, op_str, pn_args[1], 0xffffffff); asm_thumb_mov_reg_i16(&emit->as, ASM_THUMB_OP_MOVW, reg_dest, i_src & 0xffff); asm_thumb_mov_reg_i16(&emit->as, ASM_THUMB_OP_MOVT, reg_dest, (i_src >> 16) & 0xffff); - } else if (op == MP_QSTR_ldrex) { + } else if (ARMV7M && op == MP_QSTR_ldrex) { mp_uint_t r_dest = get_arg_reg(emit, op_str, pn_args[0], 15); mp_parse_node_t pn_base, pn_offset; if (get_arg_addr(emit, op_str, pn_args[1], &pn_base, &pn_offset)) { @@ -730,7 +749,6 @@ STATIC void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_a mp_uint_t i8 = get_arg_i(emit, op_str, pn_offset, 0xff) >> 2; asm_thumb_op32(&emit->as, 0xe850 | r_base, 0x0f00 | (r_dest << 12) | i8); } - #endif } else { // search table for ldr/str instructions for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(format_9_10_op_table); i++) { diff --git a/python/src/py/emitnarm.c b/python/src/py/emitnarm.c index 8297ad61921..59075b6074a 100644 --- a/python/src/py/emitnarm.c +++ b/python/src/py/emitnarm.c @@ -10,8 +10,6 @@ // Word indices of REG_LOCAL_x in nlr_buf_t #define NLR_BUF_IDX_LOCAL_1 (3) // r4 -#define NLR_BUF_IDX_LOCAL_2 (4) // r5 -#define NLR_BUF_IDX_LOCAL_3 (5) // r6 #define N_ARM (1) #define EXPORT_FUN(name) emit_native_arm_##name diff --git a/python/src/py/emitnative.c b/python/src/py/emitnative.c index f63b6d289d3..6683ea42026 100644 --- a/python/src/py/emitnative.c +++ b/python/src/py/emitnative.c @@ -48,6 +48,7 @@ #include "py/emit.h" #include "py/nativeglue.h" +#include "py/objfun.h" #include "py/objstr.h" #if MICROPY_DEBUG_VERBOSE // print debugging info @@ -62,20 +63,26 @@ // C stack layout for native functions: // 0: nlr_buf_t [optional] -// emit->code_state_start: mp_code_state_t +// return_value [optional word] +// exc_handler_unwind [optional word] +// emit->code_state_start: mp_code_state_native_t // emit->stack_start: Python object stack | emit->n_state // locals (reversed, L0 at end) | // // C stack layout for native generator functions: // 0=emit->stack_start: nlr_buf_t +// return_value +// exc_handler_unwind [optional word] // // Then REG_GENERATOR_STATE points to: -// 0=emit->code_state_start: mp_code_state_t +// 0=emit->code_state_start: mp_code_state_native_t // emit->stack_start: Python object stack | emit->n_state // locals (reversed, L0 at end) | // // C stack layout for viper functions: // 0: nlr_buf_t [optional] +// return_value [optional word] +// exc_handler_unwind [optional word] // emit->code_state_start: fun_obj, old_globals [optional] // emit->stack_start: Python object stack | emit->n_state // locals (reversed, L0 at end) | @@ -87,14 +94,18 @@ #else #define SIZEOF_NLR_BUF (sizeof(nlr_buf_t) / sizeof(uintptr_t)) #endif -#define SIZEOF_CODE_STATE (sizeof(mp_code_state_t) / sizeof(uintptr_t)) -#define OFFSETOF_CODE_STATE_STATE (offsetof(mp_code_state_t, state) / sizeof(uintptr_t)) -#define OFFSETOF_CODE_STATE_FUN_BC (offsetof(mp_code_state_t, fun_bc) / sizeof(uintptr_t)) -#define OFFSETOF_CODE_STATE_IP (offsetof(mp_code_state_t, ip) / sizeof(uintptr_t)) -#define OFFSETOF_CODE_STATE_SP (offsetof(mp_code_state_t, sp) / sizeof(uintptr_t)) -#define OFFSETOF_OBJ_FUN_BC_GLOBALS (offsetof(mp_obj_fun_bc_t, globals) / sizeof(uintptr_t)) +#define SIZEOF_CODE_STATE (sizeof(mp_code_state_native_t) / sizeof(uintptr_t)) +#define OFFSETOF_CODE_STATE_STATE (offsetof(mp_code_state_native_t, state) / sizeof(uintptr_t)) +#define OFFSETOF_CODE_STATE_FUN_BC (offsetof(mp_code_state_native_t, fun_bc) / sizeof(uintptr_t)) +#define OFFSETOF_CODE_STATE_IP (offsetof(mp_code_state_native_t, ip) / sizeof(uintptr_t)) +#define OFFSETOF_CODE_STATE_SP (offsetof(mp_code_state_native_t, sp) / sizeof(uintptr_t)) +#define OFFSETOF_CODE_STATE_N_STATE (offsetof(mp_code_state_native_t, n_state) / sizeof(uintptr_t)) +#define OFFSETOF_OBJ_FUN_BC_CONTEXT (offsetof(mp_obj_fun_bc_t, context) / sizeof(uintptr_t)) +#define OFFSETOF_OBJ_FUN_BC_CHILD_TABLE (offsetof(mp_obj_fun_bc_t, child_table) / sizeof(uintptr_t)) #define OFFSETOF_OBJ_FUN_BC_BYTECODE (offsetof(mp_obj_fun_bc_t, bytecode) / sizeof(uintptr_t)) -#define OFFSETOF_OBJ_FUN_BC_CONST_TABLE (offsetof(mp_obj_fun_bc_t, const_table) / sizeof(uintptr_t)) +#define OFFSETOF_MODULE_CONTEXT_QSTR_TABLE (offsetof(mp_module_context_t, constants.qstr_table) / sizeof(uintptr_t)) +#define OFFSETOF_MODULE_CONTEXT_OBJ_TABLE (offsetof(mp_module_context_t, constants.obj_table) / sizeof(uintptr_t)) +#define OFFSETOF_MODULE_CONTEXT_GLOBALS (offsetof(mp_module_context_t, module.globals) / sizeof(uintptr_t)) // If not already defined, set parent args to same as child call registers #ifndef REG_PARENT_RET @@ -116,6 +127,9 @@ #define NEED_GLOBAL_EXC_HANDLER(emit) ((emit)->scope->exc_stack_size > 0 \ || ((emit)->scope->scope_flags & (MP_SCOPE_FLAG_GENERATOR | MP_SCOPE_FLAG_REFGLOBALS))) +// Whether a slot is needed to store LOCAL_IDX_EXC_HANDLER_UNWIND +#define NEED_EXC_HANDLER_UNWIND(emit) ((emit)->scope->exc_stack_size > 0) + // Whether registers can be used to store locals (only true if there are no // exception handlers, because otherwise an nlr_jump will restore registers to // their state at the start of the function and updates to locals will be lost) @@ -124,14 +138,41 @@ // Indices within the local C stack for various variables #define LOCAL_IDX_EXC_VAL(emit) (NLR_BUF_IDX_RET_VAL) #define LOCAL_IDX_EXC_HANDLER_PC(emit) (NLR_BUF_IDX_LOCAL_1) -#define LOCAL_IDX_EXC_HANDLER_UNWIND(emit) (NLR_BUF_IDX_LOCAL_2) -#define LOCAL_IDX_RET_VAL(emit) (NLR_BUF_IDX_LOCAL_3) +#define LOCAL_IDX_EXC_HANDLER_UNWIND(emit) (SIZEOF_NLR_BUF + 1) // this needs a dedicated variable outside nlr_buf_t +#define LOCAL_IDX_RET_VAL(emit) (SIZEOF_NLR_BUF) // needed when NEED_GLOBAL_EXC_HANDLER is true #define LOCAL_IDX_FUN_OBJ(emit) ((emit)->code_state_start + OFFSETOF_CODE_STATE_FUN_BC) #define LOCAL_IDX_OLD_GLOBALS(emit) ((emit)->code_state_start + OFFSETOF_CODE_STATE_IP) #define LOCAL_IDX_GEN_PC(emit) ((emit)->code_state_start + OFFSETOF_CODE_STATE_IP) #define LOCAL_IDX_LOCAL_VAR(emit, local_num) ((emit)->stack_start + (emit)->n_state - 1 - (local_num)) +#if MICROPY_PERSISTENT_CODE_SAVE + +// When building with the ability to save native code to .mpy files: +// - Qstrs are indirect via qstr_table, and REG_LOCAL_3 always points to qstr_table. +// - In a generator no registers are used to store locals, and REG_LOCAL_2 points to the generator state. +// - At most 2 registers hold local variables (see CAN_USE_REGS_FOR_LOCALS for when this is possible). + +#define REG_GENERATOR_STATE (REG_LOCAL_2) +#define REG_QSTR_TABLE (REG_LOCAL_3) +#define MAX_REGS_FOR_LOCAL_VARS (2) + +STATIC const uint8_t reg_local_table[MAX_REGS_FOR_LOCAL_VARS] = {REG_LOCAL_1, REG_LOCAL_2}; + +#else + +// When building without the ability to save native code to .mpy files: +// - Qstrs values are written directly into the machine code. +// - In a generator no registers are used to store locals, and REG_LOCAL_3 points to the generator state. +// - At most 3 registers hold local variables (see CAN_USE_REGS_FOR_LOCALS for when this is possible). + #define REG_GENERATOR_STATE (REG_LOCAL_3) +#define MAX_REGS_FOR_LOCAL_VARS (3) + +STATIC const uint8_t reg_local_table[MAX_REGS_FOR_LOCAL_VARS] = {REG_LOCAL_1, REG_LOCAL_2, REG_LOCAL_3}; + +#endif + +#define REG_LOCAL_LAST (reg_local_table[MAX_REGS_FOR_LOCAL_VARS - 1]) #define EMIT_NATIVE_VIPER_TYPE_ERROR(emit, ...) do { \ *emit->error_slot = mp_obj_new_exception_msg_varg(&mp_type_ViperTypeError, __VA_ARGS__); \ @@ -205,6 +246,7 @@ typedef struct _exc_stack_entry_t { } exc_stack_entry_t; struct _emit_t { + mp_emit_common_t *emit_common; mp_obj_t *error_slot; uint *label_slot; uint exit_label; @@ -225,23 +267,15 @@ struct _emit_t { exc_stack_entry_t *exc_stack; int prelude_offset; + int prelude_ptr_index; int start_offset; int n_state; uint16_t code_state_start; uint16_t stack_start; int stack_size; + uint16_t n_info; uint16_t n_cell; - uint16_t const_table_cur_obj; - uint16_t const_table_num_obj; - uint16_t const_table_cur_raw_code; - mp_uint_t *const_table; - - #if MICROPY_PERSISTENT_CODE_SAVE - uint16_t qstr_link_cur; - mp_qstr_link_entry_t *qstr_link; - #endif - bool last_emit_was_return_value; scope_t *scope; @@ -249,14 +283,14 @@ struct _emit_t { ASM_T *as; }; -STATIC const uint8_t reg_local_table[REG_LOCAL_NUM] = {REG_LOCAL_1, REG_LOCAL_2, REG_LOCAL_3}; - +STATIC void emit_load_reg_with_object(emit_t *emit, int reg, mp_obj_t obj); STATIC void emit_native_global_exc_entry(emit_t *emit); STATIC void emit_native_global_exc_exit(emit_t *emit); STATIC void emit_native_load_const_obj(emit_t *emit, mp_obj_t obj); -emit_t *EXPORT_FUN(new)(mp_obj_t * error_slot, uint *label_slot, mp_uint_t max_num_labels) { +emit_t *EXPORT_FUN(new)(mp_emit_common_t * emit_common, mp_obj_t *error_slot, uint *label_slot, mp_uint_t max_num_labels) { emit_t *emit = m_new0(emit_t, 1); + emit->emit_common = emit_common; emit->error_slot = error_slot; emit->label_slot = label_slot; emit->stack_info_alloc = 8; @@ -310,12 +344,7 @@ STATIC void emit_native_mov_reg_state_addr(emit_t *emit, int reg_dest, int local STATIC void emit_native_mov_reg_qstr(emit_t *emit, int arg_reg, qstr qst) { #if MICROPY_PERSISTENT_CODE_SAVE - size_t loc = ASM_MOV_REG_IMM_FIX_U16(emit->as, arg_reg, qst); - size_t link_idx = emit->qstr_link_cur++; - if (emit->pass == MP_PASS_EMIT) { - emit->qstr_link[link_idx].off = loc << 2 | 1; - emit->qstr_link[link_idx].qst = qst; - } + ASM_LOAD16_REG_REG_OFFSET(emit->as, arg_reg, REG_QSTR_TABLE, mp_emit_common_use_qstr(emit->emit_common, qst)); #else ASM_MOV_REG_IMM(emit->as, arg_reg, qst); #endif @@ -323,12 +352,7 @@ STATIC void emit_native_mov_reg_qstr(emit_t *emit, int arg_reg, qstr qst) { STATIC void emit_native_mov_reg_qstr_obj(emit_t *emit, int reg_dest, qstr qst) { #if MICROPY_PERSISTENT_CODE_SAVE - size_t loc = ASM_MOV_REG_IMM_FIX_WORD(emit->as, reg_dest, (mp_uint_t)MP_OBJ_NEW_QSTR(qst)); - size_t link_idx = emit->qstr_link_cur++; - if (emit->pass == MP_PASS_EMIT) { - emit->qstr_link[link_idx].off = loc << 2 | 2; - emit->qstr_link[link_idx].qst = qst; - } + emit_load_reg_with_object(emit, reg_dest, MP_OBJ_NEW_QSTR(qst)); #else ASM_MOV_REG_IMM(emit->as, reg_dest, (mp_uint_t)MP_OBJ_NEW_QSTR(qst)); #endif @@ -340,33 +364,12 @@ STATIC void emit_native_mov_reg_qstr_obj(emit_t *emit, int reg_dest, qstr qst) { emit_native_mov_state_reg((emit), (local_num), (reg_temp)); \ } while (false) -#define emit_native_mov_state_imm_fix_u16_via(emit, local_num, imm, reg_temp) \ - do { \ - ASM_MOV_REG_IMM_FIX_U16((emit)->as, (reg_temp), (imm)); \ - emit_native_mov_state_reg((emit), (local_num), (reg_temp)); \ - } while (false) - -#define emit_native_mov_state_imm_fix_word_via(emit, local_num, imm, reg_temp) \ - do { \ - ASM_MOV_REG_IMM_FIX_WORD((emit)->as, (reg_temp), (imm)); \ - emit_native_mov_state_reg((emit), (local_num), (reg_temp)); \ - } while (false) - STATIC void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scope) { DEBUG_printf("start_pass(pass=%u, scope=%p)\n", pass, scope); emit->pass = pass; emit->do_viper_types = scope->emit_options == MP_EMIT_OPT_VIPER; emit->stack_size = 0; - #if N_PRELUDE_AS_BYTES_OBJ - emit->const_table_cur_obj = emit->do_viper_types ? 0 : 1; // reserve first obj for prelude bytes obj - #else - emit->const_table_cur_obj = 0; - #endif - emit->const_table_cur_raw_code = 0; - #if MICROPY_PERSISTENT_CODE_SAVE - emit->qstr_link_cur = 0; - #endif emit->last_emit_was_return_value = false; emit->scope = scope; @@ -414,12 +417,18 @@ STATIC void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scop // generate code for entry to function - // Work out start of code state (mp_code_state_t or reduced version for viper) + // Work out start of code state (mp_code_state_native_t or reduced version for viper) emit->code_state_start = 0; if (NEED_GLOBAL_EXC_HANDLER(emit)) { - emit->code_state_start = SIZEOF_NLR_BUF; + emit->code_state_start = SIZEOF_NLR_BUF; // for nlr_buf_t + emit->code_state_start += 1; // for return_value + if (NEED_EXC_HANDLER_UNWIND(emit)) { + emit->code_state_start += 1; + } } + size_t fun_table_off = mp_emit_common_use_const_obj(emit->emit_common, MP_OBJ_FROM_PTR(&mp_fun_table)); + if (emit->do_viper_types) { // Work out size of state (locals plus stack) // n_state counts all stack and locals, even those in registers @@ -427,11 +436,11 @@ STATIC void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scop int num_locals_in_regs = 0; if (CAN_USE_REGS_FOR_LOCALS(emit)) { num_locals_in_regs = scope->num_locals; - if (num_locals_in_regs > REG_LOCAL_NUM) { - num_locals_in_regs = REG_LOCAL_NUM; + if (num_locals_in_regs > MAX_REGS_FOR_LOCAL_VARS) { + num_locals_in_regs = MAX_REGS_FOR_LOCAL_VARS; } - // Need a spot for REG_LOCAL_3 if 4 or more args (see below) - if (scope->num_pos_args >= 4) { + // Need a spot for REG_LOCAL_LAST (see below) + if (scope->num_pos_args >= MAX_REGS_FOR_LOCAL_VARS + 1) { --num_locals_in_regs; } } @@ -455,23 +464,27 @@ STATIC void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scop #endif // Load REG_FUN_TABLE with a pointer to mp_fun_table, found in the const_table - ASM_LOAD_REG_REG_OFFSET(emit->as, REG_LOCAL_3, REG_PARENT_ARG_1, OFFSETOF_OBJ_FUN_BC_CONST_TABLE); - ASM_LOAD_REG_REG_OFFSET(emit->as, REG_FUN_TABLE, REG_LOCAL_3, 0); + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_FUN_TABLE, REG_PARENT_ARG_1, OFFSETOF_OBJ_FUN_BC_CONTEXT); + #if MICROPY_PERSISTENT_CODE_SAVE + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_QSTR_TABLE, REG_FUN_TABLE, OFFSETOF_MODULE_CONTEXT_QSTR_TABLE); + #endif + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_FUN_TABLE, REG_FUN_TABLE, OFFSETOF_MODULE_CONTEXT_OBJ_TABLE); + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_FUN_TABLE, REG_FUN_TABLE, fun_table_off); // Store function object (passed as first arg) to stack if needed if (NEED_FUN_OBJ(emit)) { ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_FUN_OBJ(emit), REG_PARENT_ARG_1); } - // Put n_args in REG_ARG_1, n_kw in REG_ARG_2, args array in REG_LOCAL_3 + // Put n_args in REG_ARG_1, n_kw in REG_ARG_2, args array in REG_LOCAL_LAST #if N_X86 asm_x86_mov_arg_to_r32(emit->as, 1, REG_ARG_1); asm_x86_mov_arg_to_r32(emit->as, 2, REG_ARG_2); - asm_x86_mov_arg_to_r32(emit->as, 3, REG_LOCAL_3); + asm_x86_mov_arg_to_r32(emit->as, 3, REG_LOCAL_LAST); #else ASM_MOV_REG_REG(emit->as, REG_ARG_1, REG_PARENT_ARG_2); ASM_MOV_REG_REG(emit->as, REG_ARG_2, REG_PARENT_ARG_3); - ASM_MOV_REG_REG(emit->as, REG_LOCAL_3, REG_PARENT_ARG_4); + ASM_MOV_REG_REG(emit->as, REG_LOCAL_LAST, REG_PARENT_ARG_4); #endif // Check number of args matches this function, and call mp_arg_check_num_sig if not @@ -486,21 +499,21 @@ STATIC void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scop // Store arguments into locals (reg or stack), converting to native if needed for (int i = 0; i < emit->scope->num_pos_args; i++) { int r = REG_ARG_1; - ASM_LOAD_REG_REG_OFFSET(emit->as, REG_ARG_1, REG_LOCAL_3, i); + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_ARG_1, REG_LOCAL_LAST, i); if (emit->local_vtype[i] != VTYPE_PYOBJ) { emit_call_with_imm_arg(emit, MP_F_CONVERT_OBJ_TO_NATIVE, emit->local_vtype[i], REG_ARG_2); r = REG_RET; } - // REG_LOCAL_3 points to the args array so be sure not to overwrite it if it's still needed - if (i < REG_LOCAL_NUM && CAN_USE_REGS_FOR_LOCALS(emit) && (i != 2 || emit->scope->num_pos_args == 3)) { + // REG_LOCAL_LAST points to the args array so be sure not to overwrite it if it's still needed + if (i < MAX_REGS_FOR_LOCAL_VARS && CAN_USE_REGS_FOR_LOCALS(emit) && (i != MAX_REGS_FOR_LOCAL_VARS - 1 || emit->scope->num_pos_args == MAX_REGS_FOR_LOCAL_VARS)) { ASM_MOV_REG_REG(emit->as, reg_local_table[i], r); } else { emit_native_mov_state_reg(emit, LOCAL_IDX_LOCAL_VAR(emit, i), r); } } - // Get 3rd local from the stack back into REG_LOCAL_3 if this reg couldn't be written to above - if (emit->scope->num_pos_args >= 4 && CAN_USE_REGS_FOR_LOCALS(emit)) { - ASM_MOV_REG_LOCAL(emit->as, REG_LOCAL_3, LOCAL_IDX_LOCAL_VAR(emit, 2)); + // Get local from the stack back into REG_LOCAL_LAST if this reg couldn't be written to above + if (emit->scope->num_pos_args >= MAX_REGS_FOR_LOCAL_VARS + 1 && CAN_USE_REGS_FOR_LOCALS(emit)) { + ASM_MOV_REG_LOCAL(emit->as, REG_LOCAL_LAST, LOCAL_IDX_LOCAL_VAR(emit, MAX_REGS_FOR_LOCAL_VARS - 1)); } emit_native_global_exc_entry(emit); @@ -510,16 +523,13 @@ STATIC void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scop emit->n_state = scope->num_locals + scope->stack_size; if (emit->scope->scope_flags & MP_SCOPE_FLAG_GENERATOR) { + mp_asm_base_data(&emit->as->base, ASM_WORD_SIZE, (uintptr_t)emit->prelude_ptr_index); + mp_asm_base_data(&emit->as->base, ASM_WORD_SIZE, (uintptr_t)emit->start_offset); + ASM_ENTRY(emit->as, emit->code_state_start); + + // Reset the state size for the state pointed to by REG_GENERATOR_STATE emit->code_state_start = 0; emit->stack_start = SIZEOF_CODE_STATE; - #if N_PRELUDE_AS_BYTES_OBJ - // Load index of prelude bytes object in const_table - mp_asm_base_data(&emit->as->base, ASM_WORD_SIZE, (uintptr_t)(emit->scope->num_pos_args + emit->scope->num_kwonly_args + 1)); - #else - mp_asm_base_data(&emit->as->base, ASM_WORD_SIZE, (uintptr_t)emit->prelude_offset); - #endif - mp_asm_base_data(&emit->as->base, ASM_WORD_SIZE, (uintptr_t)emit->start_offset); - ASM_ENTRY(emit->as, SIZEOF_NLR_BUF); // Put address of code_state into REG_GENERATOR_STATE #if N_X86 @@ -536,8 +546,12 @@ STATIC void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scop // Load REG_FUN_TABLE with a pointer to mp_fun_table, found in the const_table ASM_LOAD_REG_REG_OFFSET(emit->as, REG_TEMP0, REG_GENERATOR_STATE, LOCAL_IDX_FUN_OBJ(emit)); - ASM_LOAD_REG_REG_OFFSET(emit->as, REG_TEMP0, REG_TEMP0, OFFSETOF_OBJ_FUN_BC_CONST_TABLE); - ASM_LOAD_REG_REG_OFFSET(emit->as, REG_FUN_TABLE, REG_TEMP0, emit->scope->num_pos_args + emit->scope->num_kwonly_args); + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_TEMP0, REG_TEMP0, OFFSETOF_OBJ_FUN_BC_CONTEXT); + #if MICROPY_PERSISTENT_CODE_SAVE + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_QSTR_TABLE, REG_TEMP0, OFFSETOF_MODULE_CONTEXT_QSTR_TABLE); + #endif + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_TEMP0, REG_TEMP0, OFFSETOF_MODULE_CONTEXT_OBJ_TABLE); + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_FUN_TABLE, REG_TEMP0, fun_table_off); } else { // The locals and stack start after the code_state structure emit->stack_start = emit->code_state_start + SIZEOF_CODE_STATE; @@ -555,38 +569,27 @@ STATIC void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scop #endif // Load REG_FUN_TABLE with a pointer to mp_fun_table, found in the const_table - ASM_LOAD_REG_REG_OFFSET(emit->as, REG_LOCAL_3, REG_PARENT_ARG_1, OFFSETOF_OBJ_FUN_BC_CONST_TABLE); - ASM_LOAD_REG_REG_OFFSET(emit->as, REG_FUN_TABLE, REG_LOCAL_3, emit->scope->num_pos_args + emit->scope->num_kwonly_args); + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_FUN_TABLE, REG_PARENT_ARG_1, OFFSETOF_OBJ_FUN_BC_CONTEXT); + #if MICROPY_PERSISTENT_CODE_SAVE + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_QSTR_TABLE, REG_FUN_TABLE, OFFSETOF_MODULE_CONTEXT_QSTR_TABLE); + #endif + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_FUN_TABLE, REG_FUN_TABLE, OFFSETOF_MODULE_CONTEXT_OBJ_TABLE); + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_FUN_TABLE, REG_FUN_TABLE, fun_table_off); // Set code_state.fun_bc ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_FUN_OBJ(emit), REG_PARENT_ARG_1); - // Set code_state.ip (offset from start of this function to prelude info) - int code_state_ip_local = emit->code_state_start + OFFSETOF_CODE_STATE_IP; - #if N_PRELUDE_AS_BYTES_OBJ - // Prelude is a bytes object in const_table; store ip = prelude->data - fun_bc->bytecode - ASM_LOAD_REG_REG_OFFSET(emit->as, REG_LOCAL_3, REG_LOCAL_3, emit->scope->num_pos_args + emit->scope->num_kwonly_args + 1); - ASM_LOAD_REG_REG_OFFSET(emit->as, REG_LOCAL_3, REG_LOCAL_3, offsetof(mp_obj_str_t, data) / sizeof(uintptr_t)); - ASM_LOAD_REG_REG_OFFSET(emit->as, REG_PARENT_ARG_1, REG_PARENT_ARG_1, OFFSETOF_OBJ_FUN_BC_BYTECODE); - ASM_SUB_REG_REG(emit->as, REG_LOCAL_3, REG_PARENT_ARG_1); - emit_native_mov_state_reg(emit, code_state_ip_local, REG_LOCAL_3); - #else - if (emit->pass == MP_PASS_CODE_SIZE) { - // Commit to the encoding size based on the value of prelude_offset in this pass. - // By using 32768 as the cut-off it is highly unlikely that prelude_offset will - // grow beyond 65535 by the end of this pass, and so require the larger encoding. - emit->prelude_offset_uses_u16_encoding = emit->prelude_offset < 32768; + // Set code_state.ip, a pointer to the beginning of the prelude. This pointer is found + // either directly in mp_obj_fun_bc_t.child_table (if there are no children), or in + // mp_obj_fun_bc_t.child_table[num_children] (if num_children > 0). + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_PARENT_ARG_1, REG_PARENT_ARG_1, OFFSETOF_OBJ_FUN_BC_CHILD_TABLE); + if (emit->prelude_ptr_index != 0) { + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_PARENT_ARG_1, REG_PARENT_ARG_1, emit->prelude_ptr_index); } - if (emit->prelude_offset_uses_u16_encoding) { - assert(emit->prelude_offset <= 65535); - emit_native_mov_state_imm_fix_u16_via(emit, code_state_ip_local, emit->prelude_offset, REG_PARENT_ARG_1); - } else { - emit_native_mov_state_imm_fix_word_via(emit, code_state_ip_local, emit->prelude_offset, REG_PARENT_ARG_1); - } - #endif + emit_native_mov_state_reg(emit, emit->code_state_start + OFFSETOF_CODE_STATE_IP, REG_PARENT_ARG_1); // Set code_state.n_state (only works on little endian targets due to n_state being uint16_t) - emit_native_mov_state_imm_via(emit, emit->code_state_start + offsetof(mp_code_state_t, n_state) / sizeof(uintptr_t), emit->n_state, REG_ARG_1); + emit_native_mov_state_imm_via(emit, emit->code_state_start + OFFSETOF_CODE_STATE_N_STATE, emit->n_state, REG_ARG_1); // Put address of code_state into first arg ASM_MOV_REG_LOCAL_ADDR(emit->as, REG_ARG_1, emit->code_state_start); @@ -616,7 +619,7 @@ STATIC void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scop // cache some locals in registers, but only if no exception handlers if (CAN_USE_REGS_FOR_LOCALS(emit)) { - for (int i = 0; i < REG_LOCAL_NUM && i < scope->num_locals; ++i) { + for (int i = 0; i < MAX_REGS_FOR_LOCAL_VARS && i < scope->num_locals; ++i) { ASM_MOV_REG_LOCAL(emit->as, reg_local_table[i], LOCAL_IDX_LOCAL_VAR(emit, i)); } } @@ -628,55 +631,47 @@ STATIC void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scop emit->local_vtype[id->local_num] = VTYPE_PYOBJ; } } - - if (pass == MP_PASS_EMIT) { - // write argument names as qstr objects - // see comment in corresponding part of emitbc.c about the logic here - for (int i = 0; i < scope->num_pos_args + scope->num_kwonly_args; i++) { - qstr qst = MP_QSTR__star_; - for (int j = 0; j < scope->id_info_len; ++j) { - id_info_t *id = &scope->id_info[j]; - if ((id->flags & ID_FLAG_IS_PARAM) && id->local_num == i) { - qst = id->qst; - break; - } - } - emit->const_table[i] = (mp_uint_t)MP_OBJ_NEW_QSTR(qst); - } - } } - } static inline void emit_native_write_code_info_byte(emit_t *emit, byte val) { mp_asm_base_data(&emit->as->base, 1, val); } -STATIC void emit_native_end_pass(emit_t *emit) { +static inline void emit_native_write_code_info_qstr(emit_t *emit, qstr qst) { + mp_encode_uint(&emit->as->base, mp_asm_base_get_cur_to_write_bytes, mp_emit_common_use_qstr(emit->emit_common, qst)); +} + +STATIC bool emit_native_end_pass(emit_t *emit) { emit_native_global_exc_exit(emit); if (!emit->do_viper_types) { emit->prelude_offset = mp_asm_base_get_code_pos(&emit->as->base); + emit->prelude_ptr_index = emit->emit_common->ct_cur_child; size_t n_state = emit->n_state; size_t n_exc_stack = 0; // exc-stack not needed for native code MP_BC_PRELUDE_SIG_ENCODE(n_state, n_exc_stack, emit->scope, emit_native_write_code_info_byte, emit); - #if MICROPY_PERSISTENT_CODE - size_t n_info = 4; - #else - size_t n_info = 1; - #endif - MP_BC_PRELUDE_SIZE_ENCODE(n_info, emit->n_cell, emit_native_write_code_info_byte, emit); - - #if MICROPY_PERSISTENT_CODE - mp_asm_base_data(&emit->as->base, 1, emit->scope->simple_name); - mp_asm_base_data(&emit->as->base, 1, emit->scope->simple_name >> 8); - mp_asm_base_data(&emit->as->base, 1, emit->scope->source_file); - mp_asm_base_data(&emit->as->base, 1, emit->scope->source_file >> 8); - #else - mp_asm_base_data(&emit->as->base, 1, 1); - #endif + size_t n_info = emit->n_info; + size_t n_cell = emit->n_cell; + MP_BC_PRELUDE_SIZE_ENCODE(n_info, n_cell, emit_native_write_code_info_byte, emit); + + // bytecode prelude: source info (function and argument qstrs) + size_t info_start = mp_asm_base_get_code_pos(&emit->as->base); + emit_native_write_code_info_qstr(emit, emit->scope->simple_name); + for (int i = 0; i < emit->scope->num_pos_args + emit->scope->num_kwonly_args; i++) { + qstr qst = MP_QSTR__star_; + for (int j = 0; j < emit->scope->id_info_len; ++j) { + id_info_t *id = &emit->scope->id_info[j]; + if ((id->flags & ID_FLAG_IS_PARAM) && id->local_num == i) { + qst = id->qst; + break; + } + } + emit_native_write_code_info_qstr(emit, qst); + } + emit->n_info = mp_asm_base_get_code_pos(&emit->as->base) - info_start; // bytecode prelude: initialise closed over variables size_t cell_start = mp_asm_base_get_code_pos(&emit->as->base); @@ -689,15 +684,6 @@ STATIC void emit_native_end_pass(emit_t *emit) { } emit->n_cell = mp_asm_base_get_code_pos(&emit->as->base) - cell_start; - #if N_PRELUDE_AS_BYTES_OBJ - // Prelude bytes object is after qstr arg names and mp_fun_table - size_t table_off = emit->scope->num_pos_args + emit->scope->num_kwonly_args + 1; - if (emit->pass == MP_PASS_EMIT) { - void *buf = emit->as->base.code_base + emit->prelude_offset; - size_t n = emit->as->base.code_offset - emit->prelude_offset; - emit->const_table[table_off] = (uintptr_t)mp_obj_new_bytes(buf, n); - } - #endif } ASM_END_PASS(emit->as); @@ -706,46 +692,45 @@ STATIC void emit_native_end_pass(emit_t *emit) { assert(emit->stack_size == 0); assert(emit->exc_stack_size == 0); - // Deal with const table accounting - assert(emit->pass <= MP_PASS_STACK_SIZE || (emit->const_table_num_obj == emit->const_table_cur_obj)); - emit->const_table_num_obj = emit->const_table_cur_obj; - if (emit->pass == MP_PASS_CODE_SIZE) { - size_t const_table_alloc = 1 + emit->const_table_num_obj + emit->const_table_cur_raw_code; - size_t nqstr = 0; - if (!emit->do_viper_types) { - // Add room for qstr names of arguments - nqstr = emit->scope->num_pos_args + emit->scope->num_kwonly_args; - const_table_alloc += nqstr; - } - emit->const_table = m_new(mp_uint_t, const_table_alloc); - #if !MICROPY_DYNAMIC_COMPILER - // Store mp_fun_table pointer just after qstrs - // (but in dynamic-compiler mode eliminate dependency on mp_fun_table) - emit->const_table[nqstr] = (mp_uint_t)(uintptr_t)&mp_fun_table; - #endif - - #if MICROPY_PERSISTENT_CODE_SAVE - size_t qstr_link_alloc = emit->qstr_link_cur; - if (qstr_link_alloc > 0) { - emit->qstr_link = m_new(mp_qstr_link_entry_t, qstr_link_alloc); - } - #endif - } - if (emit->pass == MP_PASS_EMIT) { void *f = mp_asm_base_get_code(&emit->as->base); mp_uint_t f_len = mp_asm_base_get_code_size(&emit->as->base); + mp_raw_code_t **children = emit->emit_common->children; + if (!emit->do_viper_types) { + #if MICROPY_EMIT_NATIVE_PRELUDE_SEPARATE_FROM_MACHINE_CODE + // Executable code cannot be accessed byte-wise on this architecture, so copy + // the prelude to a separate memory region that is byte-wise readable. + void *buf = emit->as->base.code_base + emit->prelude_offset; + size_t n = emit->as->base.code_offset - emit->prelude_offset; + const uint8_t *prelude_ptr = memcpy(m_new(uint8_t, n), buf, n); + #else + // Point to the prelude directly, at the end of the machine code data. + const uint8_t *prelude_ptr = (const uint8_t *)f + emit->prelude_offset; + #endif + + // Store the pointer to the prelude using the child_table. + assert(emit->prelude_ptr_index == emit->emit_common->ct_cur_child); + if (emit->prelude_ptr_index == 0) { + children = (void *)prelude_ptr; + } else { + children = m_renew(mp_raw_code_t *, children, emit->prelude_ptr_index, emit->prelude_ptr_index + 1); + children[emit->prelude_ptr_index] = (void *)prelude_ptr; + } + } + mp_emit_glue_assign_native(emit->scope->raw_code, emit->do_viper_types ? MP_CODE_NATIVE_VIPER : MP_CODE_NATIVE_PY, - f, f_len, emit->const_table, + f, f_len, + children, #if MICROPY_PERSISTENT_CODE_SAVE + emit->emit_common->ct_cur_child, emit->prelude_offset, - emit->const_table_cur_obj, emit->const_table_cur_raw_code, - emit->qstr_link_cur, emit->qstr_link, #endif - emit->scope->num_pos_args, emit->scope->scope_flags, 0); + emit->scope->scope_flags, 0, 0); } + + return true; } STATIC bool emit_native_last_emit_was_return_value(emit_t *emit) { @@ -874,7 +859,7 @@ STATIC vtype_kind_t load_reg_stack_imm(emit_t *emit, int reg_dest, const stack_i } } -// Copies all unsettled registers and immediate that are Python values into the +// Copies all unsettled registers and immediates that are Python values into the // concrete Python stack. This ensures the concrete Python stack holds valid // values for the current stack_size. // This function may clobber REG_TEMP1. @@ -1070,7 +1055,7 @@ STATIC void emit_get_stack_pointer_to_reg_for_pop(emit_t *emit, mp_uint_t reg_de } } - // Adjust the stack for a pop of n_pop items, and load the stack pointer into reg_dest. + // Adujust the stack for a pop of n_pop items, and load the stack pointer into reg_dest. adjust_stack(emit, -n_pop); emit_native_mov_reg_state_addr(emit, reg_dest, emit->stack_start + emit->stack_size); } @@ -1137,29 +1122,20 @@ STATIC exc_stack_entry_t *emit_native_pop_exc_stack(emit_t *emit) { return e; } -STATIC void emit_load_reg_with_ptr(emit_t *emit, int reg, mp_uint_t ptr, size_t table_off) { - if (!emit->do_viper_types) { - // Skip qstr names of arguments - table_off += emit->scope->num_pos_args + emit->scope->num_kwonly_args; - } - if (emit->pass == MP_PASS_EMIT) { - emit->const_table[table_off] = ptr; - } +STATIC void emit_load_reg_with_object(emit_t *emit, int reg, mp_obj_t obj) { + emit->scope->scope_flags |= MP_SCOPE_FLAG_HASCONSTS; + size_t table_off = mp_emit_common_use_const_obj(emit->emit_common, obj); emit_native_mov_reg_state(emit, REG_TEMP0, LOCAL_IDX_FUN_OBJ(emit)); - ASM_LOAD_REG_REG_OFFSET(emit->as, REG_TEMP0, REG_TEMP0, OFFSETOF_OBJ_FUN_BC_CONST_TABLE); + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_TEMP0, REG_TEMP0, OFFSETOF_OBJ_FUN_BC_CONTEXT); + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_TEMP0, REG_TEMP0, OFFSETOF_MODULE_CONTEXT_OBJ_TABLE); ASM_LOAD_REG_REG_OFFSET(emit->as, reg, REG_TEMP0, table_off); } -STATIC void emit_load_reg_with_object(emit_t *emit, int reg, mp_obj_t obj) { - // First entry is for mp_fun_table - size_t table_off = 1 + emit->const_table_cur_obj++; - emit_load_reg_with_ptr(emit, reg, (mp_uint_t)obj, table_off); -} - -STATIC void emit_load_reg_with_raw_code(emit_t *emit, int reg, mp_raw_code_t *rc) { - // First entry is for mp_fun_table, then constant objects - size_t table_off = 1 + emit->const_table_num_obj + emit->const_table_cur_raw_code++; - emit_load_reg_with_ptr(emit, reg, (mp_uint_t)rc, table_off); +STATIC void emit_load_reg_with_child(emit_t *emit, int reg, mp_raw_code_t *rc) { + size_t table_off = mp_emit_common_alloc_const_child(emit->emit_common, rc); + emit_native_mov_reg_state(emit, REG_TEMP0, LOCAL_IDX_FUN_OBJ(emit)); + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_TEMP0, REG_TEMP0, OFFSETOF_OBJ_FUN_BC_CHILD_TABLE); + ASM_LOAD_REG_REG_OFFSET(emit->as, reg, REG_TEMP0, table_off); } STATIC void emit_native_label_assign(emit_t *emit, mp_uint_t l) { @@ -1203,7 +1179,8 @@ STATIC void emit_native_global_exc_entry(emit_t *emit) { if (!(emit->scope->scope_flags & MP_SCOPE_FLAG_GENERATOR)) { // Set new globals emit_native_mov_reg_state(emit, REG_ARG_1, LOCAL_IDX_FUN_OBJ(emit)); - ASM_LOAD_REG_REG_OFFSET(emit->as, REG_ARG_1, REG_ARG_1, OFFSETOF_OBJ_FUN_BC_GLOBALS); + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_ARG_1, REG_ARG_1, OFFSETOF_OBJ_FUN_BC_CONTEXT); + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_ARG_1, REG_ARG_1, OFFSETOF_MODULE_CONTEXT_GLOBALS); emit_call(emit, MP_F_NATIVE_SWAP_GLOBALS); // Save old globals (or NULL if globals didn't change) @@ -1234,14 +1211,12 @@ STATIC void emit_native_global_exc_entry(emit_t *emit) { // Wrap everything in an nlr context emit_native_label_assign(emit, nlr_label); - ASM_MOV_REG_LOCAL(emit->as, REG_LOCAL_2, LOCAL_IDX_EXC_HANDLER_UNWIND(emit)); ASM_MOV_REG_LOCAL_ADDR(emit->as, REG_ARG_1, 0); emit_call(emit, MP_F_NLR_PUSH); #if N_NLR_SETJMP ASM_MOV_REG_LOCAL_ADDR(emit->as, REG_ARG_1, 2); emit_call(emit, MP_F_SETJMP); #endif - ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_HANDLER_UNWIND(emit), REG_LOCAL_2); ASM_JUMP_IF_REG_NONZERO(emit->as, REG_RET, global_except_label, true); // Clear PC of current code block, and jump there to resume execution @@ -1251,12 +1226,6 @@ STATIC void emit_native_global_exc_entry(emit_t *emit) { // Global exception handler: check for valid exception handler emit_native_label_assign(emit, global_except_label); - #if N_NLR_SETJMP - // Reload REG_FUN_TABLE, since it may be clobbered by longjmp - emit_native_mov_reg_state(emit, REG_LOCAL_1, LOCAL_IDX_FUN_OBJ(emit)); - ASM_LOAD_REG_REG_OFFSET(emit->as, REG_LOCAL_1, REG_LOCAL_1, offsetof(mp_obj_fun_bc_t, const_table) / sizeof(uintptr_t)); - ASM_LOAD_REG_REG_OFFSET(emit->as, REG_FUN_TABLE, REG_LOCAL_1, emit->scope->num_pos_args + emit->scope->num_kwonly_args); - #endif ASM_MOV_REG_LOCAL(emit->as, REG_LOCAL_1, LOCAL_IDX_EXC_HANDLER_PC(emit)); ASM_JUMP_IF_REG_NONZERO(emit->as, REG_LOCAL_1, nlr_label, false); } @@ -1385,11 +1354,7 @@ STATIC void emit_native_import(emit_t *emit, qstr qst, int kind) { STATIC void emit_native_load_const_tok(emit_t *emit, mp_token_kind_t tok) { DEBUG_printf("load_const_tok(tok=%u)\n", tok); if (tok == MP_TOKEN_ELLIPSIS) { - #if MICROPY_PERSISTENT_CODE_SAVE emit_native_load_const_obj(emit, MP_OBJ_FROM_PTR(&mp_const_ellipsis_obj)); - #else - emit_post_push_imm(emit, VTYPE_PYOBJ, (mp_uint_t)MP_OBJ_FROM_PTR(&mp_const_ellipsis_obj)); - #endif } else { emit_native_pre(emit); if (tok == MP_TOKEN_KW_NONE) { @@ -1424,7 +1389,6 @@ STATIC void emit_native_load_const_str(emit_t *emit, qstr qst) { } STATIC void emit_native_load_const_obj(emit_t *emit, mp_obj_t obj) { - emit->scope->scope_flags |= MP_SCOPE_FLAG_HASCONSTS; emit_native_pre(emit); need_reg_single(emit, REG_RET, 0); emit_load_reg_with_object(emit, REG_RET, obj); @@ -1443,7 +1407,7 @@ STATIC void emit_native_load_fast(emit_t *emit, qstr qst, mp_uint_t local_num) { EMIT_NATIVE_VIPER_TYPE_ERROR(emit, MP_ERROR_TEXT("local '%q' used before type known"), qst); } emit_native_pre(emit); - if (local_num < REG_LOCAL_NUM && CAN_USE_REGS_FOR_LOCALS(emit)) { + if (local_num < MAX_REGS_FOR_LOCAL_VARS && CAN_USE_REGS_FOR_LOCALS(emit)) { emit_post_push_reg(emit, vtype, reg_local_table[local_num]); } else { need_reg_single(emit, REG_TEMP0, 0); @@ -1560,6 +1524,7 @@ STATIC void emit_native_load_subscr(emit_t *emit) { int reg_base = REG_ARG_1; int reg_index = REG_ARG_2; emit_pre_pop_reg_flexible(emit, &vtype_base, ®_base, reg_index, reg_index); + need_reg_single(emit, REG_RET, 0); switch (vtype_base) { case VTYPE_PTR8: { // pointer to 8-bit memory @@ -1623,6 +1588,7 @@ STATIC void emit_native_load_subscr(emit_t *emit) { int reg_index = REG_ARG_2; emit_pre_pop_reg_flexible(emit, &vtype_index, ®_index, REG_ARG_1, REG_ARG_1); emit_pre_pop_reg(emit, &vtype_base, REG_ARG_1); + need_reg_single(emit, REG_RET, 0); if (vtype_index != VTYPE_INT && vtype_index != VTYPE_UINT) { EMIT_NATIVE_VIPER_TYPE_ERROR(emit, MP_ERROR_TEXT("can't load with '%q' index"), vtype_to_qstr(vtype_index)); @@ -1662,7 +1628,7 @@ STATIC void emit_native_load_subscr(emit_t *emit) { STATIC void emit_native_store_fast(emit_t *emit, qstr qst, mp_uint_t local_num) { vtype_kind_t vtype; - if (local_num < REG_LOCAL_NUM && CAN_USE_REGS_FOR_LOCALS(emit)) { + if (local_num < MAX_REGS_FOR_LOCAL_VARS && CAN_USE_REGS_FOR_LOCALS(emit)) { emit_pre_pop_reg(emit, &vtype, reg_local_table[local_num]); } else { emit_pre_pop_reg(emit, &vtype, REG_TEMP0); @@ -2455,48 +2421,48 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { asm_x86_setcc_r8(emit->as, ops[op_idx], REG_RET); #elif N_THUMB asm_thumb_cmp_rlo_rlo(emit->as, REG_ARG_2, reg_rhs); - #if MICROPY_EMIT_THUMB_ARMV7M - static uint16_t ops[6 + 6] = { - // unsigned - ASM_THUMB_OP_ITE_CC, - ASM_THUMB_OP_ITE_HI, - ASM_THUMB_OP_ITE_EQ, - ASM_THUMB_OP_ITE_LS, - ASM_THUMB_OP_ITE_CS, - ASM_THUMB_OP_ITE_NE, - // signed - ASM_THUMB_OP_ITE_LT, - ASM_THUMB_OP_ITE_GT, - ASM_THUMB_OP_ITE_EQ, - ASM_THUMB_OP_ITE_LE, - ASM_THUMB_OP_ITE_GE, - ASM_THUMB_OP_ITE_NE, - }; - asm_thumb_op16(emit->as, ops[op_idx]); - asm_thumb_mov_rlo_i8(emit->as, REG_RET, 1); - asm_thumb_mov_rlo_i8(emit->as, REG_RET, 0); - #else - static uint16_t ops[6 + 6] = { - // unsigned - ASM_THUMB_CC_CC, - ASM_THUMB_CC_HI, - ASM_THUMB_CC_EQ, - ASM_THUMB_CC_LS, - ASM_THUMB_CC_CS, - ASM_THUMB_CC_NE, - // signed - ASM_THUMB_CC_LT, - ASM_THUMB_CC_GT, - ASM_THUMB_CC_EQ, - ASM_THUMB_CC_LE, - ASM_THUMB_CC_GE, - ASM_THUMB_CC_NE, - }; - asm_thumb_bcc_rel9(emit->as, ops[op_idx], 6); - asm_thumb_mov_rlo_i8(emit->as, REG_RET, 0); - asm_thumb_b_rel12(emit->as, 4); - asm_thumb_mov_rlo_i8(emit->as, REG_RET, 1); - #endif + if (asm_thumb_allow_armv7m(emit->as)) { + static uint16_t ops[6 + 6] = { + // unsigned + ASM_THUMB_OP_ITE_CC, + ASM_THUMB_OP_ITE_HI, + ASM_THUMB_OP_ITE_EQ, + ASM_THUMB_OP_ITE_LS, + ASM_THUMB_OP_ITE_CS, + ASM_THUMB_OP_ITE_NE, + // signed + ASM_THUMB_OP_ITE_LT, + ASM_THUMB_OP_ITE_GT, + ASM_THUMB_OP_ITE_EQ, + ASM_THUMB_OP_ITE_LE, + ASM_THUMB_OP_ITE_GE, + ASM_THUMB_OP_ITE_NE, + }; + asm_thumb_op16(emit->as, ops[op_idx]); + asm_thumb_mov_rlo_i8(emit->as, REG_RET, 1); + asm_thumb_mov_rlo_i8(emit->as, REG_RET, 0); + } else { + static uint16_t ops[6 + 6] = { + // unsigned + ASM_THUMB_CC_CC, + ASM_THUMB_CC_HI, + ASM_THUMB_CC_EQ, + ASM_THUMB_CC_LS, + ASM_THUMB_CC_CS, + ASM_THUMB_CC_NE, + // signed + ASM_THUMB_CC_LT, + ASM_THUMB_CC_GT, + ASM_THUMB_CC_EQ, + ASM_THUMB_CC_LE, + ASM_THUMB_CC_GE, + ASM_THUMB_CC_NE, + }; + asm_thumb_bcc_rel9(emit->as, ops[op_idx], 6); + asm_thumb_mov_rlo_i8(emit->as, REG_RET, 0); + asm_thumb_b_rel12(emit->as, 4); + asm_thumb_mov_rlo_i8(emit->as, REG_RET, 1); + } #elif N_ARM asm_arm_cmp_reg_reg(emit->as, REG_ARG_2, reg_rhs); static uint ccs[6 + 6] = { @@ -2680,33 +2646,46 @@ STATIC void emit_native_unpack_ex(emit_t *emit, mp_uint_t n_left, mp_uint_t n_ri STATIC void emit_native_make_function(emit_t *emit, scope_t *scope, mp_uint_t n_pos_defaults, mp_uint_t n_kw_defaults) { // call runtime, with type info for args, or don't support dict/default params, or only support Python objects for them emit_native_pre(emit); + emit_native_mov_reg_state(emit, REG_ARG_2, LOCAL_IDX_FUN_OBJ(emit)); + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_ARG_2, REG_ARG_2, OFFSETOF_OBJ_FUN_BC_CONTEXT); if (n_pos_defaults == 0 && n_kw_defaults == 0) { need_reg_all(emit); - ASM_MOV_REG_IMM(emit->as, REG_ARG_2, (mp_uint_t)MP_OBJ_NULL); - ASM_MOV_REG_IMM(emit->as, REG_ARG_3, (mp_uint_t)MP_OBJ_NULL); + ASM_MOV_REG_IMM(emit->as, REG_ARG_3, 0); } else { - vtype_kind_t vtype_def_tuple, vtype_def_dict; - emit_pre_pop_reg_reg(emit, &vtype_def_dict, REG_ARG_3, &vtype_def_tuple, REG_ARG_2); - assert(vtype_def_tuple == VTYPE_PYOBJ); - assert(vtype_def_dict == VTYPE_PYOBJ); + emit_get_stack_pointer_to_reg_for_pop(emit, REG_ARG_3, 2); need_reg_all(emit); } - emit_load_reg_with_raw_code(emit, REG_ARG_1, scope->raw_code); + emit_load_reg_with_child(emit, REG_ARG_1, scope->raw_code); ASM_CALL_IND(emit->as, MP_F_MAKE_FUNCTION_FROM_RAW_CODE); emit_post_push_reg(emit, VTYPE_PYOBJ, REG_RET); } STATIC void emit_native_make_closure(emit_t *emit, scope_t *scope, mp_uint_t n_closed_over, mp_uint_t n_pos_defaults, mp_uint_t n_kw_defaults) { + // make function emit_native_pre(emit); + emit_native_mov_reg_state(emit, REG_ARG_2, LOCAL_IDX_FUN_OBJ(emit)); + ASM_LOAD_REG_REG_OFFSET(emit->as, REG_ARG_2, REG_ARG_2, OFFSETOF_OBJ_FUN_BC_CONTEXT); if (n_pos_defaults == 0 && n_kw_defaults == 0) { - emit_get_stack_pointer_to_reg_for_pop(emit, REG_ARG_3, n_closed_over); - ASM_MOV_REG_IMM(emit->as, REG_ARG_2, n_closed_over); + need_reg_all(emit); + ASM_MOV_REG_IMM(emit->as, REG_ARG_3, 0); } else { - emit_get_stack_pointer_to_reg_for_pop(emit, REG_ARG_3, n_closed_over + 2); - ASM_MOV_REG_IMM(emit->as, REG_ARG_2, 0x100 | n_closed_over); + emit_get_stack_pointer_to_reg_for_pop(emit, REG_ARG_3, 2 + n_closed_over); + adjust_stack(emit, 2 + n_closed_over); + need_reg_all(emit); + } + emit_load_reg_with_child(emit, REG_ARG_1, scope->raw_code); + ASM_CALL_IND(emit->as, MP_F_MAKE_FUNCTION_FROM_RAW_CODE); + + // make closure + #if REG_ARG_1 != REG_RET + ASM_MOV_REG_REG(emit->as, REG_ARG_1, REG_RET); + #endif + ASM_MOV_REG_IMM(emit->as, REG_ARG_2, n_closed_over); + emit_get_stack_pointer_to_reg_for_pop(emit, REG_ARG_3, n_closed_over); + if (n_pos_defaults != 0 || n_kw_defaults != 0) { + adjust_stack(emit, -2); } - emit_load_reg_with_raw_code(emit, REG_ARG_1, scope->raw_code); - ASM_CALL_IND(emit->as, MP_F_MAKE_CLOSURE_FROM_RAW_CODE); + ASM_CALL_IND(emit->as, MP_F_NEW_CLOSURE); emit_post_push_reg(emit, VTYPE_PYOBJ, REG_RET); } @@ -2751,7 +2730,7 @@ STATIC void emit_native_call_function(emit_t *emit, mp_uint_t n_positional, mp_u } else { assert(vtype_fun == VTYPE_PYOBJ); if (star_flags) { - emit_get_stack_pointer_to_reg_for_pop(emit, REG_ARG_3, n_positional + 2 * n_keyword + 3); // pointer to args + emit_get_stack_pointer_to_reg_for_pop(emit, REG_ARG_3, n_positional + 2 * n_keyword + 2); // pointer to args emit_call_with_2_imm_args(emit, MP_F_CALL_METHOD_N_KW_VAR, 0, REG_ARG_1, n_positional | (n_keyword << 8), REG_ARG_2); emit_post_push_reg(emit, VTYPE_PYOBJ, REG_RET); } else { @@ -2767,7 +2746,7 @@ STATIC void emit_native_call_function(emit_t *emit, mp_uint_t n_positional, mp_u STATIC void emit_native_call_method(emit_t *emit, mp_uint_t n_positional, mp_uint_t n_keyword, mp_uint_t star_flags) { if (star_flags) { - emit_get_stack_pointer_to_reg_for_pop(emit, REG_ARG_3, n_positional + 2 * n_keyword + 4); // pointer to args + emit_get_stack_pointer_to_reg_for_pop(emit, REG_ARG_3, n_positional + 2 * n_keyword + 3); // pointer to args emit_call_with_2_imm_args(emit, MP_F_CALL_METHOD_N_KW_VAR, 1, REG_ARG_1, n_positional | (n_keyword << 8), REG_ARG_2); emit_post_push_reg(emit, VTYPE_PYOBJ, REG_RET); } else { diff --git a/python/src/py/emitnthumb.c b/python/src/py/emitnthumb.c index 1c33e7a68b5..844a73ffa8c 100644 --- a/python/src/py/emitnthumb.c +++ b/python/src/py/emitnthumb.c @@ -10,8 +10,6 @@ // Word indices of REG_LOCAL_x in nlr_buf_t #define NLR_BUF_IDX_LOCAL_1 (3) // r4 -#define NLR_BUF_IDX_LOCAL_2 (4) // r5 -#define NLR_BUF_IDX_LOCAL_3 (5) // r6 #define N_THUMB (1) #define EXPORT_FUN(name) emit_native_thumb_##name diff --git a/python/src/py/emitnx64.c b/python/src/py/emitnx64.c index 4abb3ecad3a..1b32286d27b 100644 --- a/python/src/py/emitnx64.c +++ b/python/src/py/emitnx64.c @@ -10,8 +10,6 @@ // Word indices of REG_LOCAL_x in nlr_buf_t #define NLR_BUF_IDX_LOCAL_1 (5) // rbx -#define NLR_BUF_IDX_LOCAL_2 (6) // r12 -#define NLR_BUF_IDX_LOCAL_3 (7) // r13 #define N_X64 (1) #define EXPORT_FUN(name) emit_native_x64_##name diff --git a/python/src/py/emitnx86.c b/python/src/py/emitnx86.c index f0553f0682b..a9050c65d40 100644 --- a/python/src/py/emitnx86.c +++ b/python/src/py/emitnx86.c @@ -11,8 +11,6 @@ // Word indices of REG_LOCAL_x in nlr_buf_t #define NLR_BUF_IDX_LOCAL_1 (5) // ebx -#define NLR_BUF_IDX_LOCAL_2 (7) // esi -#define NLR_BUF_IDX_LOCAL_3 (6) // edi // x86 needs a table to know how many args a given function has STATIC byte mp_f_n_args[MP_F_NUMBER_OF] = { @@ -56,7 +54,7 @@ STATIC byte mp_f_n_args[MP_F_NUMBER_OF] = { [MP_F_UNPACK_EX] = 3, [MP_F_DELETE_NAME] = 1, [MP_F_DELETE_GLOBAL] = 1, - [MP_F_MAKE_CLOSURE_FROM_RAW_CODE] = 3, + [MP_F_NEW_CLOSURE] = 3, [MP_F_ARG_CHECK_NUM_SIG] = 3, [MP_F_SETUP_CODE_STATE] = 4, [MP_F_SMALL_INT_FLOOR_DIVIDE] = 2, diff --git a/python/src/py/emitnxtensa.c b/python/src/py/emitnxtensa.c index 34089e90dc2..c89b0290230 100644 --- a/python/src/py/emitnxtensa.c +++ b/python/src/py/emitnxtensa.c @@ -10,8 +10,6 @@ // Word indices of REG_LOCAL_x in nlr_buf_t #define NLR_BUF_IDX_LOCAL_1 (8) // a12 -#define NLR_BUF_IDX_LOCAL_2 (9) // a13 -#define NLR_BUF_IDX_LOCAL_3 (10) // a14 #define N_XTENSA (1) #define EXPORT_FUN(name) emit_native_xtensa_##name diff --git a/python/src/py/emitnxtensawin.c b/python/src/py/emitnxtensawin.c index 38d5db13ea4..f6eeff8455b 100644 --- a/python/src/py/emitnxtensawin.c +++ b/python/src/py/emitnxtensawin.c @@ -11,11 +11,8 @@ // Word indices of REG_LOCAL_x in nlr_buf_t #define NLR_BUF_IDX_LOCAL_1 (2 + 4) // a4 -#define NLR_BUF_IDX_LOCAL_2 (2 + 5) // a5 -#define NLR_BUF_IDX_LOCAL_3 (2 + 6) // a6 #define N_NLR_SETJMP (1) -#define N_PRELUDE_AS_BYTES_OBJ (1) #define N_XTENSAWIN (1) #define EXPORT_FUN(name) emit_native_xtensawin_##name #include "py/emitnative.c" diff --git a/python/src/py/formatfloat.c b/python/src/py/formatfloat.c index 6f4eee8220e..9d28b2317dc 100644 --- a/python/src/py/formatfloat.c +++ b/python/src/py/formatfloat.c @@ -318,7 +318,7 @@ int mp_format_float(FPTYPE f, char *buf, size_t buf_size, char fmt, int prec, ch // We now have num.f as a floating point number between >= 1 and < 10 // (or equal to zero), and e contains the absolute value of the power of - // 10 exponent. and (dec + 1) == the number of digits before the decimal. + // 10 exponent. and (dec + 1) == the number of dgits before the decimal. // For e, prec is # digits after the decimal // For f, prec is # digits after the decimal diff --git a/python/src/py/frozenmod.c b/python/src/py/frozenmod.c index a250c02151e..61c2f20aa1d 100644 --- a/python/src/py/frozenmod.c +++ b/python/src/py/frozenmod.c @@ -5,6 +5,7 @@ * * Copyright (c) 2015 Paul Sokolovsky * Copyright (c) 2016 Damien P. George + * Copyright (c) 2021 Jim Mussared * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,6 +32,13 @@ #include "py/lexer.h" #include "py/frozenmod.h" +#if MICROPY_MODULE_FROZEN + +// Null-separated frozen file names. All string-type entries are listed first, +// followed by mpy-type entries. Use mp_frozen_str_sizes to determine how +// many string entries. +extern const char mp_frozen_names[]; + #if MICROPY_MODULE_FROZEN_STR #ifndef MICROPY_MODULE_FROZEN_LEXER @@ -39,118 +47,89 @@ mp_lexer_t *MICROPY_MODULE_FROZEN_LEXER(qstr src_name, const char *str, mp_uint_t len, mp_uint_t free_len); #endif -extern const char mp_frozen_str_names[]; +// Size in bytes of each string entry, followed by a zero (terminator). extern const uint32_t mp_frozen_str_sizes[]; +// Null-separated string content. extern const char mp_frozen_str_content[]; - -// On input, *len contains size of name, on output - size of content -const char *mp_find_frozen_str(const char *str, size_t *len) { - const char *name = mp_frozen_str_names; - - size_t offset = 0; - for (int i = 0; *name != 0; i++) { - size_t l = strlen(name); - if (l == *len && !memcmp(str, name, l)) { - *len = mp_frozen_str_sizes[i]; - return mp_frozen_str_content + offset; - } - name += l + 1; - offset += mp_frozen_str_sizes[i] + 1; - } - return NULL; -} - -STATIC mp_lexer_t *mp_lexer_frozen_str(const char *str, size_t len) { - size_t name_len = len; - const char *content = mp_find_frozen_str(str, &len); - - if (content == NULL) { - return NULL; - } - - qstr source = qstr_from_strn(str, name_len); - mp_lexer_t *lex = MICROPY_MODULE_FROZEN_LEXER(source, content, len, 0); - return lex; -} - -#endif +#endif // MICROPY_MODULE_FROZEN_STR #if MICROPY_MODULE_FROZEN_MPY #include "py/emitglue.h" -extern const char mp_frozen_mpy_names[]; -extern const mp_raw_code_t *const mp_frozen_mpy_content[]; +extern const mp_frozen_module_t *const mp_frozen_mpy_content[]; -STATIC const mp_raw_code_t *mp_find_frozen_mpy(const char *str, size_t len) { - const char *name = mp_frozen_mpy_names; - for (size_t i = 0; *name != 0; i++) { - size_t l = strlen(name); - if (l == len && !memcmp(str, name, l)) { - return mp_frozen_mpy_content[i]; - } - name += l + 1; - } - return NULL; -} +#endif // MICROPY_MODULE_FROZEN_MPY -#endif +// Search for "str" as a frozen entry, returning the stat result +// (no-exist/file/dir), as well as the type (none/str/mpy) and data. +// frozen_type can be NULL if its value isn't needed (and then data is assumed to be NULL). +mp_import_stat_t mp_find_frozen_module(const char *str, int *frozen_type, void **data) { + size_t len = strlen(str); + const char *name = mp_frozen_names; -#if MICROPY_MODULE_FROZEN + if (frozen_type != NULL) { + *frozen_type = MP_FROZEN_NONE; + } -STATIC mp_import_stat_t mp_frozen_stat_helper(const char *name, const char *str) { - size_t len = strlen(str); + // Count the number of str lengths we have to find how many str entries. + size_t num_str = 0; + #if MICROPY_MODULE_FROZEN_STR && MICROPY_MODULE_FROZEN_MPY + for (const uint32_t *s = mp_frozen_str_sizes; *s != 0; ++s) { + ++num_str; + } + #endif + + for (size_t i = 0; *name != 0; i++) { + size_t entry_len = strlen(name); + if (entry_len >= len && memcmp(str, name, len) == 0) { + // Query is a prefix of the current entry. + if (entry_len == len) { + // Exact match --> file. + + if (frozen_type != NULL) { + #if MICROPY_MODULE_FROZEN_STR + if (i < num_str) { + *frozen_type = MP_FROZEN_STR; + // Use the size table to figure out where this index starts. + size_t offset = 0; + for (size_t j = 0; j < i; ++j) { + offset += mp_frozen_str_sizes[j] + 1; + } + size_t content_len = mp_frozen_str_sizes[i]; + const char *content = &mp_frozen_str_content[offset]; + + // Note: str & len have been updated by find_frozen_entry to strip + // the ".frozen/" prefix (to avoid this being a distinct qstr to + // the original path QSTR in frozen_content.c). + qstr source = qstr_from_strn(str, len); + mp_lexer_t *lex = MICROPY_MODULE_FROZEN_LEXER(source, content, content_len, 0); + *data = lex; + } + #endif + + #if MICROPY_MODULE_FROZEN_MPY + if (i >= num_str) { + *frozen_type = MP_FROZEN_MPY; + // Load the corresponding index as a raw_code, taking + // into account any string entries to offset by. + *data = (void *)mp_frozen_mpy_content[i - num_str]; + } + #endif + } - for (int i = 0; *name != 0; i++) { - size_t l = strlen(name); - if (l >= len && !memcmp(str, name, len)) { - if (name[len] == 0) { return MP_IMPORT_STAT_FILE; } else if (name[len] == '/') { + // Matches up to directory separator, this is a valid + // directory path. return MP_IMPORT_STAT_DIR; } } - name += l + 1; - } - return MP_IMPORT_STAT_NO_EXIST; -} - -mp_import_stat_t mp_frozen_stat(const char *str) { - mp_import_stat_t stat; - - #if MICROPY_MODULE_FROZEN_STR - stat = mp_frozen_stat_helper(mp_frozen_str_names, str); - if (stat != MP_IMPORT_STAT_NO_EXIST) { - return stat; + // Skip null separator. + name += entry_len + 1; } - #endif - - #if MICROPY_MODULE_FROZEN_MPY - stat = mp_frozen_stat_helper(mp_frozen_mpy_names, str); - if (stat != MP_IMPORT_STAT_NO_EXIST) { - return stat; - } - #endif return MP_IMPORT_STAT_NO_EXIST; } -int mp_find_frozen_module(const char *str, size_t len, void **data) { - #if MICROPY_MODULE_FROZEN_STR - mp_lexer_t *lex = mp_lexer_frozen_str(str, len); - if (lex != NULL) { - *data = lex; - return MP_FROZEN_STR; - } - #endif - #if MICROPY_MODULE_FROZEN_MPY - const mp_raw_code_t *rc = mp_find_frozen_mpy(str, len); - if (rc != NULL) { - *data = (void *)rc; - return MP_FROZEN_MPY; - } - #endif - return MP_FROZEN_NONE; -} - -#endif +#endif // MICROPY_MODULE_FROZEN diff --git a/python/src/py/frozenmod.h b/python/src/py/frozenmod.h index 8a477d028e0..cff6c8616c7 100644 --- a/python/src/py/frozenmod.h +++ b/python/src/py/frozenmod.h @@ -27,7 +27,7 @@ #ifndef MICROPY_INCLUDED_PY_FROZENMOD_H #define MICROPY_INCLUDED_PY_FROZENMOD_H -#include "py/lexer.h" +#include "py/builtin.h" enum { MP_FROZEN_NONE, @@ -35,8 +35,6 @@ enum { MP_FROZEN_MPY, }; -int mp_find_frozen_module(const char *str, size_t len, void **data); -const char *mp_find_frozen_str(const char *str, size_t *len); -mp_import_stat_t mp_frozen_stat(const char *str); +mp_import_stat_t mp_find_frozen_module(const char *str, int *frozen_type, void **data); #endif // MICROPY_INCLUDED_PY_FROZENMOD_H diff --git a/python/src/py/gc.c b/python/src/py/gc.c index 1256d35249c..0c1f3961df3 100644 --- a/python/src/py/gc.c +++ b/python/src/py/gc.c @@ -213,6 +213,7 @@ STATIC void gc_mark_subtree(size_t block) { // Start with the block passed in the argument. size_t sp = 0; for (;;) { + MICROPY_GC_HOOK_LOOP // work out number of consecutive blocks in the chain starting with this one size_t n_blocks = 0; do { @@ -222,6 +223,7 @@ STATIC void gc_mark_subtree(size_t block) { // check this block's children void **ptrs = (void **)PTR_FROM_BLOCK(block); for (size_t i = n_blocks * BYTES_PER_BLOCK / sizeof(void *); i > 0; i--, ptrs++) { + MICROPY_GC_HOOK_LOOP void *ptr = *ptrs; if (VERIFY_PTR(ptr)) { // Mark and push this pointer @@ -255,6 +257,7 @@ STATIC void gc_deal_with_stack_overflow(void) { // scan entire memory looking for blocks which have been marked but not their children for (size_t block = 0; block < MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB; block++) { + MICROPY_GC_HOOK_LOOP // trace (again) if mark bit set if (ATB_GET_KIND(block) == AT_MARK) { gc_mark_subtree(block); @@ -270,6 +273,7 @@ STATIC void gc_sweep(void) { // free unmarked heads and their tails int free_tail = 0; for (size_t block = 0; block < MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB; block++) { + MICROPY_GC_HOOK_LOOP switch (ATB_GET_KIND(block)) { case AT_HEAD: #if MICROPY_ENABLE_FINALISER @@ -354,6 +358,7 @@ static void *gc_get_ptr(void **ptrs, int i) { void gc_collect_root(void **ptrs, size_t len) { for (size_t i = 0; i < len; i++) { + MICROPY_GC_HOOK_LOOP void *ptr = gc_get_ptr(ptrs, i); if (VERIFY_PTR(ptr)) { size_t block = BLOCK_FROM_PTR(ptr); @@ -512,7 +517,7 @@ void *gc_alloc(size_t n_bytes, unsigned int alloc_flags) { // Set last free ATB index to block after last block we found, for start of // next scan. To reduce fragmentation, we only do this if we were looking // for a single free block, which guarantees that there are no free blocks - // before this one. Also, whenever we free or shrink a block we must check + // before this one. Also, whenever we free or shink a block we must check // if this index needs adjusting (see gc_realloc and gc_free). if (n_free == 1) { MP_STATE_MEM(gc_last_free_atb_index) = (i + 1) / BLOCKS_PER_ATB; @@ -915,13 +920,13 @@ void gc_dump_alloc_table(void) { // This code prints "Q" for qstr-pool data, and "q" for qstr-str // data. It can be useful to see how qstrs are being allocated, // but is disabled by default because it is very slow. - for (qstr_pool_t *pool = MP_STATE_VM(last_pool); c == 'h' && pool != NULL; pool = pool->prev) { - if ((qstr_pool_t *)ptr == pool) { + for (const qstr_pool_t *pool = MP_STATE_VM(last_pool); c == 'h' && pool != NULL; pool = pool->prev) { + if ((const qstr_pool_t *)ptr == pool) { c = 'Q'; break; } - for (const byte **q = pool->qstrs, **q_top = pool->qstrs + pool->len; q < q_top; q++) { - if ((const byte *)ptr == *q) { + for (const char *const *q = pool->qstrs, *const *q_top = pool->qstrs + pool->len; q < q_top; q++) { + if ((const char *)ptr == *q) { c = 'q'; break; } diff --git a/python/src/py/lexer.c b/python/src/py/lexer.c index e1858d8eed0..39e9662f63e 100644 --- a/python/src/py/lexer.c +++ b/python/src/py/lexer.c @@ -363,9 +363,16 @@ STATIC void parse_string_literal(mp_lexer_t *lex, bool is_raw, bool is_fstring) // (MicroPython limitation) note: this is completely unaware of // Python syntax and will not handle any expression containing '}' or ':'. // e.g. f'{"}"}' or f'{foo({})}'. - while (!is_end(lex) && !is_char_or(lex, ':', '}')) { + unsigned int nested_bracket_level = 0; + while (!is_end(lex) && (nested_bracket_level != 0 || !is_char_or(lex, ':', '}'))) { + unichar c = CUR_CHAR(lex); + if (c == '[' || c == '{') { + nested_bracket_level += 1; + } else if (c == ']' || c == '}') { + nested_bracket_level -= 1; + } // like the default case at the end of this function, stay 8-bit clean - vstr_add_byte(&lex->fstring_args, CUR_CHAR(lex)); + vstr_add_byte(&lex->fstring_args, c); next_char(lex); } if (lex->fstring_args.buf[lex->fstring_args.len - 1] == '=') { @@ -466,25 +473,23 @@ STATIC void parse_string_literal(mp_lexer_t *lex, bool is_raw, bool is_fstring) } } if (c != MP_LEXER_EOF) { - if (MICROPY_PY_BUILTINS_STR_UNICODE_DYNAMIC) { - if (c < 0x110000 && lex->tok_kind == MP_TOKEN_STRING) { - vstr_add_char(&lex->vstr, c); - } else if (c < 0x100 && lex->tok_kind == MP_TOKEN_BYTES) { - vstr_add_byte(&lex->vstr, c); - } else { - // unicode character out of range - // this raises a generic SyntaxError; could provide more info - lex->tok_kind = MP_TOKEN_INVALID; - } - } else { - // without unicode everything is just added as an 8-bit byte - if (c < 0x100) { - vstr_add_byte(&lex->vstr, c); - } else { - // 8-bit character out of range - // this raises a generic SyntaxError; could provide more info - lex->tok_kind = MP_TOKEN_INVALID; - } + #if MICROPY_PY_BUILTINS_STR_UNICODE + if (c < 0x110000 && lex->tok_kind == MP_TOKEN_STRING) { + // Valid unicode character in a str object. + vstr_add_char(&lex->vstr, c); + } else if (c < 0x100 && lex->tok_kind == MP_TOKEN_BYTES) { + // Valid byte in a bytes object. + vstr_add_byte(&lex->vstr, c); + } + #else + if (c < 0x100) { + // Without unicode everything is just added as an 8-bit byte. + vstr_add_byte(&lex->vstr, c); + } + #endif + else { + // Character out of range; this raises a generic SyntaxError. + lex->tok_kind = MP_TOKEN_INVALID; } } } else { @@ -594,7 +599,7 @@ void mp_lexer_to_next(mp_lexer_t *lex) { // a string or bytes literal // Python requires adjacent string/bytes literals to be automatically - // concatenated. We do it here in the tokenizer to make efficient use of RAM, + // concatenated. We do it here in the tokeniser to make efficient use of RAM, // because then the lexer's vstr can be used to accumulate the string literal, // in contrast to creating a parse tree of strings and then joining them later // in the compiler. It's also more compact in code size to do it here. diff --git a/python/src/py/lexer.h b/python/src/py/lexer.h index 4b0c097af5d..8295dec0f71 100644 --- a/python/src/py/lexer.h +++ b/python/src/py/lexer.h @@ -32,7 +32,7 @@ #include "py/qstr.h" #include "py/reader.h" -/* lexer.h -- simple tokenizer for MicroPython +/* lexer.h -- simple tokeniser for MicroPython * * Uses (byte) length instead of null termination. * Tokens are the same - UTF-8 with (byte) length. @@ -189,24 +189,15 @@ typedef struct _mp_lexer_t { mp_lexer_t *mp_lexer_new(qstr src_name, mp_reader_t reader); mp_lexer_t *mp_lexer_new_from_str_len(qstr src_name, const char *str, size_t len, size_t free_len); -void mp_lexer_free(mp_lexer_t *lex); -void mp_lexer_to_next(mp_lexer_t *lex); - -/******************************************************************/ -// platform specific import function; must be implemented for a specific port -// TODO tidy up, rename, or put elsewhere - -typedef enum { - MP_IMPORT_STAT_NO_EXIST, - MP_IMPORT_STAT_DIR, - MP_IMPORT_STAT_FILE, -} mp_import_stat_t; - -mp_import_stat_t mp_import_stat(const char *path); +// If MICROPY_READER_POSIX or MICROPY_READER_VFS aren't enabled then +// this function must be implemented by the port. mp_lexer_t *mp_lexer_new_from_file(const char *filename); #if MICROPY_HELPER_LEXER_UNIX mp_lexer_t *mp_lexer_new_from_fd(qstr filename, int fd, bool close_fd); #endif +void mp_lexer_free(mp_lexer_t *lex); +void mp_lexer_to_next(mp_lexer_t *lex); + #endif // MICROPY_INCLUDED_PY_LEXER_H diff --git a/python/src/py/makecompresseddata.py b/python/src/py/makecompresseddata.py index 1bce3e8e837..9603de87131 100644 --- a/python/src/py/makecompresseddata.py +++ b/python/src/py/makecompresseddata.py @@ -24,7 +24,7 @@ def check_non_ascii(msg): # Replace with . -# Trivial scheme to demo/test. +# Trival scheme to demo/test. def space_compression(error_strings): for line in error_strings: check_non_ascii(line) diff --git a/python/src/py/makemoduledefs.py b/python/src/py/makemoduledefs.py index 612f3d29a36..9061cd890be 100644 --- a/python/src/py/makemoduledefs.py +++ b/python/src/py/makemoduledefs.py @@ -1,88 +1,69 @@ -#!/usr/bin/env python - -# This pre-processor parses provided objects' c files for -# MP_REGISTER_MODULE(module_name, obj_module, enabled_define) -# These are used to generate a header with the required entries for -# "mp_rom_map_elem_t mp_builtin_module_table[]" in py/objmodule.c +""" +This pre-processor parses a single file containing a list of +MP_REGISTER_MODULE(module_name, obj_module) +These are used to generate a header with the required entries for +"mp_rom_map_elem_t mp_builtin_module_table[]" in py/objmodule.c +""" from __future__ import print_function +import sys import re import io -import os import argparse -pattern = re.compile(r"[\n;]\s*MP_REGISTER_MODULE\((.*?),\s*(.*?),\s*(.*?)\);", flags=re.DOTALL) - - -def find_c_file(obj_file, vpath): - """Search vpaths for the c file that matches the provided object_file. - - :param str obj_file: object file to find the matching c file for - :param List[str] vpath: List of base paths, similar to gcc vpath - :return: str path to c file or None - """ - c_file = None - relative_c_file = os.path.splitext(obj_file)[0] + ".c" - relative_c_file = relative_c_file.lstrip("/\\") - for p in vpath: - possible_c_file = os.path.join(p, relative_c_file) - if os.path.exists(possible_c_file): - c_file = possible_c_file - break - - return c_file +pattern = re.compile(r"\s*MP_REGISTER_MODULE\((.*?),\s*(.*?)\);", flags=re.DOTALL) -def find_module_registrations(c_file): - """Find any MP_REGISTER_MODULE definitions in the provided c file. +def find_module_registrations(filename): + """Find any MP_REGISTER_MODULE definitions in the provided file. - :param str c_file: path to c file to check - :return: List[(module_name, obj_module, enabled_define)] + :param str filename: path to file to check + :return: List[(module_name, obj_module)] """ global pattern - if c_file is None: - # No c file to match the object file, skip - return set() - - with io.open(c_file, encoding="utf-8") as c_file_obj: + with io.open(filename, encoding="utf-8") as c_file_obj: return set(re.findall(pattern, c_file_obj.read())) def generate_module_table_header(modules): """Generate header with module table entries for builtin modules. - :param List[(module_name, obj_module, enabled_define)] modules: module defs + :param List[(module_name, obj_module)] modules: module defs :return: None """ # Print header file for all external modules. - mod_defs = [] + mod_defs = set() print("// Automatically generated by makemoduledefs.py.\n") - for module_name, obj_module, enabled_define in modules: + for module_name, obj_module in modules: mod_def = "MODULE_DEF_{}".format(module_name.upper()) - mod_defs.append(mod_def) + mod_defs.add(mod_def) + if "," in obj_module: + print( + "ERROR: Call to MP_REGISTER_MODULE({}, {}) should be MP_REGISTER_MODULE({}, {})\n".format( + module_name, obj_module, module_name, obj_module.split(",")[0] + ), + file=sys.stderr, + ) + sys.exit(1) print( ( - "#if ({enabled_define})\n" - " extern const struct _mp_obj_module_t {obj_module};\n" - " #define {mod_def} {{ MP_ROM_QSTR({module_name}), MP_ROM_PTR(&{obj_module}) }},\n" - "#else\n" - " #define {mod_def}\n" - "#endif\n" + "extern const struct _mp_obj_module_t {obj_module};\n" + "#undef {mod_def}\n" + "#define {mod_def} {{ MP_ROM_QSTR({module_name}), MP_ROM_PTR(&{obj_module}) }},\n" ).format( module_name=module_name, obj_module=obj_module, - enabled_define=enabled_define, mod_def=mod_def, ) ) print("\n#define MICROPY_REGISTERED_MODULES \\") - for mod_def in mod_defs: + for mod_def in sorted(mod_defs): print(" {mod_def} \\".format(mod_def=mod_def)) print("// MICROPY_REGISTERED_MODULES") @@ -90,19 +71,10 @@ def generate_module_table_header(modules): def main(): parser = argparse.ArgumentParser() - parser.add_argument( - "--vpath", default=".", help="comma separated list of folders to search for c files in" - ) - parser.add_argument("files", nargs="*", help="list of c files to search") + parser.add_argument("file", nargs=1, help="file with MP_REGISTER_MODULE definitions") args = parser.parse_args() - vpath = [p.strip() for p in args.vpath.split(",")] - - modules = set() - for obj_file in args.files: - c_file = find_c_file(obj_file, vpath) - modules |= find_module_registrations(c_file) - + modules = find_module_registrations(args.file[0]) generate_module_table_header(sorted(modules)) diff --git a/python/src/py/makeqstrdata.py b/python/src/py/makeqstrdata.py index 403c4068889..e332ab94ed5 100644 --- a/python/src/py/makeqstrdata.py +++ b/python/src/py/makeqstrdata.py @@ -317,26 +317,24 @@ def parse_input_headers(infiles): return qcfgs, qstrs -def make_bytes(cfg_bytes_len, cfg_bytes_hash, qstr): - qbytes = bytes_cons(qstr, "utf8") - qlen = len(qbytes) - qhash = compute_hash(qbytes, cfg_bytes_hash) +def escape_bytes(qstr, qbytes): if all(32 <= ord(c) <= 126 and c != "\\" and c != '"' for c in qstr): # qstr is all printable ASCII so render it as-is (for easier debugging) - qdata = qstr + return qstr else: # qstr contains non-printable codes so render entire thing as hex pairs - qdata = "".join(("\\x%02x" % b) for b in qbytes) + return "".join(("\\x%02x" % b) for b in qbytes) + + +def make_bytes(cfg_bytes_len, cfg_bytes_hash, qstr): + qbytes = bytes_cons(qstr, "utf8") + qlen = len(qbytes) + qhash = compute_hash(qbytes, cfg_bytes_hash) if qlen >= (1 << (8 * cfg_bytes_len)): print("qstr is too long:", qstr) assert False - qlen_str = ("\\x%02x" * cfg_bytes_len) % tuple( - ((qlen >> (8 * i)) & 0xFF) for i in range(cfg_bytes_len) - ) - qhash_str = ("\\x%02x" * cfg_bytes_hash) % tuple( - ((qhash >> (8 * i)) & 0xFF) for i in range(cfg_bytes_hash) - ) - return '(const byte*)"%s%s" "%s"' % (qhash_str, qlen_str, qdata) + qdata = escape_bytes(qstr, qbytes) + return '%d, %d, "%s"' % (qhash, qlen, qdata) def print_qstr_data(qcfgs, qstrs): @@ -349,10 +347,7 @@ def print_qstr_data(qcfgs, qstrs): print("") # add NULL qstr with no hash or data - print( - 'QDEF(MP_QSTRnull, (const byte*)"%s%s" "")' - % ("\\x00" * cfg_bytes_hash, "\\x00" * cfg_bytes_len) - ) + print('QDEF(MP_QSTRnull, 0, 0, "")') # go through each qstr and print it out for order, ident, qstr in sorted(qstrs.values(), key=lambda x: x[0]): diff --git a/python/src/py/makeqstrdefs.py b/python/src/py/makeqstrdefs.py index 187a9aeeaa4..4c416a874a6 100644 --- a/python/src/py/makeqstrdefs.py +++ b/python/src/py/makeqstrdefs.py @@ -21,6 +21,17 @@ # Extract MP_COMPRESSED_ROM_TEXT("") macros. (Which come from MP_ERROR_TEXT) _MODE_COMPRESS = "compress" +# Extract MP_REGISTER_MODULE(...) macros. +_MODE_MODULE = "module" + + +def is_c_source(fname): + return os.path.splitext(fname)[1] in [".c"] + + +def is_cxx_source(fname): + return os.path.splitext(fname)[1] in [".cc", ".cp", ".cxx", ".cpp", ".CPP", ".c++", ".C"] + def preprocess(): if any(src in args.dependencies for src in args.changed_sources): @@ -32,9 +43,9 @@ def preprocess(): csources = [] cxxsources = [] for source in sources: - if source.endswith(".cpp"): + if is_cxx_source(source): cxxsources.append(source) - elif source.endswith(".c"): + elif is_c_source(source): csources.append(source) try: os.makedirs(os.path.dirname(args.output[0])) @@ -77,6 +88,8 @@ def process_file(f): re_match = re.compile(r"MP_QSTR_[_a-zA-Z0-9]+") elif args.mode == _MODE_COMPRESS: re_match = re.compile(r'MP_COMPRESSED_ROM_TEXT\("([^"]*)"\)') + elif args.mode == _MODE_MODULE: + re_match = re.compile(r"MP_REGISTER_MODULE\(.*?,\s*.*?\);") output = [] last_fname = None for line in f: @@ -87,7 +100,7 @@ def process_file(f): m = re_line.match(line) assert m is not None fname = m.group(1) - if os.path.splitext(fname)[1] not in [".c", ".cpp"]: + if not is_c_source(fname) and not is_cxx_source(fname): continue if fname != last_fname: write_out(last_fname, output) @@ -98,7 +111,7 @@ def process_file(f): if args.mode == _MODE_QSTR: name = match.replace("MP_QSTR_", "") output.append("Q(" + name + ")") - elif args.mode == _MODE_COMPRESS: + elif args.mode in (_MODE_COMPRESS, _MODE_MODULE): output.append(match) if last_fname: @@ -133,6 +146,8 @@ def cat_together(): mode_full = "QSTR" if args.mode == _MODE_COMPRESS: mode_full = "Compressed data" + elif args.mode == _MODE_MODULE: + mode_full = "Module registrations" if old_hash != new_hash: print(mode_full, "updated") try: @@ -193,7 +208,7 @@ class Args: args.output_dir = sys.argv[4] args.output_file = None if len(sys.argv) == 5 else sys.argv[5] # Unused for command=split - if args.mode not in (_MODE_QSTR, _MODE_COMPRESS): + if args.mode not in (_MODE_QSTR, _MODE_COMPRESS, _MODE_MODULE): print("error: mode %s unrecognised" % sys.argv[2]) sys.exit(2) diff --git a/python/src/py/malloc.c b/python/src/py/malloc.c index c775d5b1573..efdff753969 100644 --- a/python/src/py/malloc.c +++ b/python/src/py/malloc.c @@ -207,6 +207,99 @@ void m_free(void *ptr) #endif } +#if MICROPY_TRACKED_ALLOC + +#define MICROPY_TRACKED_ALLOC_STORE_SIZE (!MICROPY_ENABLE_GC) + +typedef struct _m_tracked_node_t { + struct _m_tracked_node_t *prev; + struct _m_tracked_node_t *next; + #if MICROPY_TRACKED_ALLOC_STORE_SIZE + uintptr_t size; + #endif + uint8_t data[]; +} m_tracked_node_t; + +#if MICROPY_DEBUG_VERBOSE +STATIC size_t m_tracked_count_links(size_t *nb) { + m_tracked_node_t *node = MP_STATE_VM(m_tracked_head); + size_t n = 0; + *nb = 0; + while (node != NULL) { + ++n; + #if MICROPY_TRACKED_ALLOC_STORE_SIZE + *nb += node->size; + #else + *nb += gc_nbytes(node); + #endif + node = node->next; + } + return n; +} +#endif + +void *m_tracked_calloc(size_t nmemb, size_t size) { + m_tracked_node_t *node = m_malloc_maybe(sizeof(m_tracked_node_t) + nmemb * size); + if (node == NULL) { + return NULL; + } + #if MICROPY_DEBUG_VERBOSE + size_t nb; + size_t n = m_tracked_count_links(&nb); + DEBUG_printf("m_tracked_calloc(%u, %u) -> (%u;%u) %p\n", (int)nmemb, (int)size, (int)n, (int)nb, node); + #endif + if (MP_STATE_VM(m_tracked_head) != NULL) { + MP_STATE_VM(m_tracked_head)->prev = node; + } + node->prev = NULL; + node->next = MP_STATE_VM(m_tracked_head); + MP_STATE_VM(m_tracked_head) = node; + #if MICROPY_TRACKED_ALLOC_STORE_SIZE + node->size = nmemb * size; + #endif + #if !MICROPY_GC_CONSERVATIVE_CLEAR + memset(&node->data[0], 0, nmemb * size); + #endif + return &node->data[0]; +} + +void m_tracked_free(void *ptr_in) { + if (ptr_in == NULL) { + return; + } + m_tracked_node_t *node = (m_tracked_node_t *)((uint8_t *)ptr_in - sizeof(m_tracked_node_t)); + #if MICROPY_DEBUG_VERBOSE + size_t data_bytes; + #if MICROPY_TRACKED_ALLOC_STORE_SIZE + data_bytes = node->size; + #else + data_bytes = gc_nbytes(node); + #endif + size_t nb; + size_t n = m_tracked_count_links(&nb); + DEBUG_printf("m_tracked_free(%p, [%p, %p], nbytes=%u, links=%u;%u)\n", node, node->prev, node->next, (int)data_bytes, (int)n, (int)nb); + #endif + if (node->next != NULL) { + node->next->prev = node->prev; + } + if (node->prev != NULL) { + node->prev->next = node->next; + } else { + MP_STATE_VM(m_tracked_head) = node->next; + } + m_free(node + #if MICROPY_MALLOC_USES_ALLOCATED_SIZE + #if MICROPY_TRACKED_ALLOC_STORE_SIZE + , node->size + #else + , gc_nbytes(node) + #endif + #endif + ); +} + +#endif // MICROPY_TRACKED_ALLOC + #if MICROPY_MEM_STATS size_t m_get_total_bytes_allocated(void) { return MP_STATE_MEM(total_bytes_allocated); diff --git a/python/src/py/map.c b/python/src/py/map.c index 54f4b0204b8..b194250cb4f 100644 --- a/python/src/py/map.c +++ b/python/src/py/map.c @@ -40,6 +40,27 @@ #define DEBUG_printf(...) (void)0 #endif +#if MICROPY_OPT_MAP_LOOKUP_CACHE +// MP_STATE_VM(map_lookup_cache) provides a cache of index to the last known +// position of that index in any map. On a cache hit, this allows +// short-circuiting the full linear search in the case of an ordered map +// (i.e. all builtin modules and objects' locals dicts), and computation of +// the hash (and potentially some linear probing) in the case of a regular +// map. Note the same cache is shared across all maps. + +// Gets the index into the cache for this index. Shift down by two to remove +// mp_obj_t tag bits. +#define MAP_CACHE_OFFSET(index) ((((uintptr_t)(index)) >> 2) % MICROPY_OPT_MAP_LOOKUP_CACHE_SIZE) +// Gets the map cache entry for the corresponding index. +#define MAP_CACHE_ENTRY(index) (MP_STATE_VM(map_lookup_cache)[MAP_CACHE_OFFSET(index)]) +// Retrieve the mp_obj_t at the location suggested by the cache. +#define MAP_CACHE_GET(map, index) (&(map)->table[MAP_CACHE_ENTRY(index) % (map)->alloc]) +// Update the cache for this index. +#define MAP_CACHE_SET(index, pos) MAP_CACHE_ENTRY(index) = (pos) & 0xff; +#else +#define MAP_CACHE_SET(index, pos) +#endif + // This table of sizes is used to control the growth of hash tables. // The first set of sizes are chosen so the allocation fits exactly in a // 4-word GC block, and it's not so important for these small values to be @@ -132,10 +153,22 @@ STATIC void mp_map_rehash(mp_map_t *map) { // - returns slot, with key non-null and value=MP_OBJ_NULL if it was added // MP_MAP_LOOKUP_REMOVE_IF_FOUND behaviour: // - returns NULL if not found, else the slot if was found in with key null and value non-null -mp_map_elem_t *mp_map_lookup(mp_map_t *map, mp_obj_t index, mp_map_lookup_kind_t lookup_kind) { +mp_map_elem_t *MICROPY_WRAP_MP_MAP_LOOKUP(mp_map_lookup)(mp_map_t * map, mp_obj_t index, mp_map_lookup_kind_t lookup_kind) { // If the map is a fixed array then we must only be called for a lookup assert(!map->is_fixed || lookup_kind == MP_MAP_LOOKUP); + #if MICROPY_OPT_MAP_LOOKUP_CACHE + // Try the cache for lookup or add-if-not-found. + if (lookup_kind != MP_MAP_LOOKUP_REMOVE_IF_FOUND && map->alloc) { + mp_map_elem_t *slot = MAP_CACHE_GET(map, index); + // Note: Just comparing key for value equality will have false negatives, but + // these will be handled by the regular path below. + if (slot->key == index) { + return slot; + } + } + #endif + // Work out if we can compare just pointers bool compare_only_ptrs = map->all_keys_are_qstrs; if (compare_only_ptrs) { @@ -172,6 +205,7 @@ mp_map_elem_t *mp_map_lookup(mp_map_t *map, mp_obj_t index, mp_map_lookup_kind_t elem->value = value; } #endif + MAP_CACHE_SET(index, elem - map->table); return elem; } } @@ -254,6 +288,7 @@ mp_map_elem_t *mp_map_lookup(mp_map_t *map, mp_obj_t index, mp_map_lookup_kind_t } // keep slot->value so that caller can access it if needed } + MAP_CACHE_SET(index, pos); return slot; } diff --git a/python/src/py/misc.h b/python/src/py/misc.h index e1d27dc7b89..d94afd0b0db 100644 --- a/python/src/py/misc.h +++ b/python/src/py/misc.h @@ -103,6 +103,13 @@ void m_free(void *ptr); #endif NORETURN void m_malloc_fail(size_t num_bytes); +#if MICROPY_TRACKED_ALLOC +// These alloc/free functions track the pointers in a linked list so the GC does not reclaim +// them. They can be used by code that requires traditional C malloc/free semantics. +void *m_tracked_calloc(size_t nmemb, size_t size); +void m_tracked_free(void *ptr_in); +#endif + #if MICROPY_MEM_STATS size_t m_get_total_bytes_allocated(void); size_t m_get_current_bytes_allocated(void); diff --git a/python/src/py/mkrules.cmake b/python/src/py/mkrules.cmake index 9d080179319..d0dc0196253 100644 --- a/python/src/py/mkrules.cmake +++ b/python/src/py/mkrules.cmake @@ -2,13 +2,24 @@ set(MICROPY_GENHDR_DIR "${CMAKE_BINARY_DIR}/genhdr") set(MICROPY_MPVERSION "${MICROPY_GENHDR_DIR}/mpversion.h") -set(MICROPY_MODULEDEFS "${MICROPY_GENHDR_DIR}/moduledefs.h") set(MICROPY_QSTRDEFS_PY "${MICROPY_PY_DIR}/qstrdefs.h") set(MICROPY_QSTRDEFS_LAST "${MICROPY_GENHDR_DIR}/qstr.i.last") set(MICROPY_QSTRDEFS_SPLIT "${MICROPY_GENHDR_DIR}/qstr.split") set(MICROPY_QSTRDEFS_COLLECTED "${MICROPY_GENHDR_DIR}/qstrdefs.collected.h") set(MICROPY_QSTRDEFS_PREPROCESSED "${MICROPY_GENHDR_DIR}/qstrdefs.preprocessed.h") set(MICROPY_QSTRDEFS_GENERATED "${MICROPY_GENHDR_DIR}/qstrdefs.generated.h") +set(MICROPY_MODULEDEFS_SPLIT "${MICROPY_GENHDR_DIR}/moduledefs.split") +set(MICROPY_MODULEDEFS_COLLECTED "${MICROPY_GENHDR_DIR}/moduledefs.collected") +set(MICROPY_MODULEDEFS "${MICROPY_GENHDR_DIR}/moduledefs.h") + +# Need to do this before extracting MICROPY_CPP_DEF below. Rest of frozen +# manifest handling is at the end of this file. +if(MICROPY_FROZEN_MANIFEST) + target_compile_definitions(${MICROPY_TARGET} PUBLIC + MICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool + MICROPY_MODULE_FROZEN_MPY=\(1\) + ) +endif() # Provide defaults for preprocessor flags if not already defined if(NOT MICROPY_CPP_FLAGS) @@ -34,6 +45,7 @@ find_package(Python3 REQUIRED COMPONENTS Interpreter) target_sources(${MICROPY_TARGET} PRIVATE ${MICROPY_MPVERSION} ${MICROPY_QSTRDEFS_GENERATED} + ${MICROPY_MODULEDEFS} ) # Command to force the build of another command @@ -53,15 +65,6 @@ add_custom_command( DEPENDS MICROPY_FORCE_BUILD ) -# Generate moduledefs.h - -add_custom_command( - OUTPUT ${MICROPY_MODULEDEFS} - COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makemoduledefs.py --vpath="/" ${MICROPY_SOURCE_QSTR} > ${MICROPY_MODULEDEFS} - DEPENDS ${MICROPY_MPVERSION} - ${MICROPY_SOURCE_QSTR} -) - # Generate qstrs # If any of the dependencies in this rule change then the C-preprocessor step must be run. @@ -70,7 +73,7 @@ add_custom_command( add_custom_command( OUTPUT ${MICROPY_QSTRDEFS_LAST} COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makeqstrdefs.py pp ${CMAKE_C_COMPILER} -E output ${MICROPY_GENHDR_DIR}/qstr.i.last cflags ${MICROPY_CPP_FLAGS} -DNO_QSTR cxxflags ${MICROPY_CPP_FLAGS} -DNO_QSTR sources ${MICROPY_SOURCE_QSTR} - DEPENDS ${MICROPY_MODULEDEFS} + DEPENDS ${MICROPY_MPVERSION} ${MICROPY_SOURCE_QSTR} VERBATIM COMMAND_EXPAND_LISTS @@ -111,6 +114,31 @@ add_custom_command( COMMAND_EXPAND_LISTS ) +# Generate moduledefs.h + +add_custom_command( + OUTPUT ${MICROPY_MODULEDEFS_SPLIT} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makeqstrdefs.py split module ${MICROPY_GENHDR_DIR}/qstr.i.last ${MICROPY_GENHDR_DIR}/module _ + COMMAND touch ${MICROPY_MODULEDEFS_SPLIT} + DEPENDS ${MICROPY_QSTRDEFS_LAST} + VERBATIM + COMMAND_EXPAND_LISTS +) + +add_custom_command( + OUTPUT ${MICROPY_MODULEDEFS_COLLECTED} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makeqstrdefs.py cat module _ ${MICROPY_GENHDR_DIR}/module ${MICROPY_MODULEDEFS_COLLECTED} + DEPENDS ${MICROPY_MODULEDEFS_SPLIT} + VERBATIM + COMMAND_EXPAND_LISTS +) + +add_custom_command( + OUTPUT ${MICROPY_MODULEDEFS} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makemoduledefs.py ${MICROPY_MODULEDEFS_COLLECTED} > ${MICROPY_MODULEDEFS} + DEPENDS ${MICROPY_MODULEDEFS_COLLECTED} +) + # Build frozen code if enabled if(MICROPY_FROZEN_MANIFEST) @@ -120,10 +148,7 @@ if(MICROPY_FROZEN_MANIFEST) ${MICROPY_FROZEN_CONTENT} ) - target_compile_definitions(${MICROPY_TARGET} PUBLIC - MICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool - MICROPY_MODULE_FROZEN_MPY=\(1\) - ) + # Note: target_compile_definitions already added earlier. if(NOT MICROPY_LIB_DIR) set(MICROPY_LIB_DIR ${MICROPY_DIR}/../micropython-lib) @@ -153,3 +178,10 @@ if(MICROPY_FROZEN_MANIFEST) VERBATIM ) endif() + +# Update submodules +if(ECHO_SUBMODULES) + # If cmake is run with GIT_SUBMODULES defined on command line, process the port / board + # settings then print the final GIT_SUBMODULES variable as a fatal error and exit. + message(FATAL_ERROR "GIT_SUBMODULES=${GIT_SUBMODULES}") +endif() diff --git a/python/src/py/mkrules.mk b/python/src/py/mkrules.mk index bde96c7b4b7..fa1aad881ba 100644 --- a/python/src/py/mkrules.mk +++ b/python/src/py/mkrules.mk @@ -7,6 +7,9 @@ endif # Extra deps that need to happen before object compilation. OBJ_EXTRA_ORDER_DEPS = +# Generate moduledefs.h. +OBJ_EXTRA_ORDER_DEPS += $(HEADER_BUILD)/moduledefs.h + ifeq ($(MICROPY_ROM_TEXT_COMPRESSION),1) # If compression is enabled, trigger the build of compressed.data.h... OBJ_EXTRA_ORDER_DEPS += $(HEADER_BUILD)/compressed.data.h @@ -16,7 +19,7 @@ endif # QSTR generation uses the same CFLAGS, with these modifications. QSTR_GEN_FLAGS = -DNO_QSTR -# Note: := to force evaluation immediately. +# Note: := to force evalulation immediately. QSTR_GEN_CFLAGS := $(CFLAGS) QSTR_GEN_CFLAGS += $(QSTR_GEN_FLAGS) QSTR_GEN_CXXFLAGS := $(CXXFLAGS) @@ -28,7 +31,7 @@ QSTR_GEN_CXXFLAGS += $(QSTR_GEN_FLAGS) # tree. # # So for example, py/map.c would have an object file name py/map.o -# The object files will go into the build directory and maintain the same +# The object files will go into the build directory and mantain the same # directory structure as the source tree. So the final dependency will look # like this: # @@ -100,7 +103,7 @@ $(OBJ): | $(HEADER_BUILD)/qstrdefs.generated.h $(HEADER_BUILD)/mpversion.h $(OBJ # - else, if list of newer prerequisites ($?) is not empty, then process just these ($?) # - else, process all source files ($^) [this covers "make -B" which can set $? to empty] # See more information about this process in docs/develop/qstr.rst. -$(HEADER_BUILD)/qstr.i.last: $(SRC_QSTR) $(QSTR_GLOBAL_DEPENDENCIES) $(HEADER_BUILD)/moduledefs.h | $(QSTR_GLOBAL_REQUIREMENTS) +$(HEADER_BUILD)/qstr.i.last: $(SRC_QSTR) $(QSTR_GLOBAL_DEPENDENCIES) | $(QSTR_GLOBAL_REQUIREMENTS) $(ECHO) "GEN $@" $(Q)$(PYTHON) $(PY_SRC)/makeqstrdefs.py pp $(CPP) output $(HEADER_BUILD)/qstr.i.last cflags $(QSTR_GEN_CFLAGS) cxxflags $(QSTR_GEN_CXXFLAGS) sources $^ dependencies $(QSTR_GLOBAL_DEPENDENCIES) changed_sources $? @@ -113,6 +116,16 @@ $(QSTR_DEFS_COLLECTED): $(HEADER_BUILD)/qstr.split $(ECHO) "GEN $@" $(Q)$(PYTHON) $(PY_SRC)/makeqstrdefs.py cat qstr _ $(HEADER_BUILD)/qstr $@ +# Module definitions via MP_REGISTER_MODULE. +$(HEADER_BUILD)/moduledefs.split: $(HEADER_BUILD)/qstr.i.last + $(ECHO) "GEN $@" + $(Q)$(PYTHON) $(PY_SRC)/makeqstrdefs.py split module $< $(HEADER_BUILD)/module _ + $(Q)$(TOUCH) $@ + +$(HEADER_BUILD)/moduledefs.collected: $(HEADER_BUILD)/moduledefs.split + $(ECHO) "GEN $@" + $(Q)$(PYTHON) $(PY_SRC)/makeqstrdefs.py cat module _ $(HEADER_BUILD)/module $@ + # Compressed error strings. $(HEADER_BUILD)/compressed.split: $(HEADER_BUILD)/qstr.i.last $(ECHO) "GEN $@" @@ -142,49 +155,24 @@ $(MICROPY_MPYCROSS_DEPENDENCY): $(MAKE) -C $(dir $@) endif -ifneq ($(FROZEN_MANIFEST),) -# to build frozen_content.c from a manifest -$(BUILD)/frozen_content.c: FORCE $(BUILD)/genhdr/qstrdefs.generated.h | $(MICROPY_MPYCROSS_DEPENDENCY) - $(Q)$(MAKE_MANIFEST) -o $@ -v "MPY_DIR=$(TOP)" -v "MPY_LIB_DIR=$(MPY_LIB_DIR)" -v "PORT_DIR=$(shell pwd)" -v "BOARD_DIR=$(BOARD_DIR)" -b "$(BUILD)" $(if $(MPY_CROSS_FLAGS),-f"$(MPY_CROSS_FLAGS)",) --mpy-tool-flags="$(MPY_TOOL_FLAGS)" $(FROZEN_MANIFEST) - ifneq ($(FROZEN_DIR),) -$(error FROZEN_DIR cannot be used in conjunction with FROZEN_MANIFEST) +$(error Support for FROZEN_DIR was removed. Please use manifest.py instead, see https://docs.micropython.org/en/latest/reference/manifest.html) endif ifneq ($(FROZEN_MPY_DIR),) -$(error FROZEN_MPY_DIR cannot be used in conjunction with FROZEN_MANIFEST) -endif -endif - -ifneq ($(FROZEN_DIR),) -$(info Warning: FROZEN_DIR is deprecated in favour of FROZEN_MANIFEST) -$(BUILD)/frozen.c: $(wildcard $(FROZEN_DIR)/*) $(HEADER_BUILD) $(FROZEN_EXTRA_DEPS) - $(ECHO) "GEN $@" - $(Q)$(MAKE_FROZEN) $(FROZEN_DIR) > $@ +$(error Support for FROZEN_MPY_DIR was removed. Please use manifest.py instead, see https://docs.micropython.org/en/latest/reference/manifest.html) endif -ifneq ($(FROZEN_MPY_DIR),) -$(info Warning: FROZEN_MPY_DIR is deprecated in favour of FROZEN_MANIFEST) -# make a list of all the .py files that need compiling and freezing -FROZEN_MPY_PY_FILES := $(shell find -L $(FROZEN_MPY_DIR) -type f -name '*.py' | $(SED) -e 's=^$(FROZEN_MPY_DIR)/==') -FROZEN_MPY_MPY_FILES := $(addprefix $(BUILD)/frozen_mpy/,$(FROZEN_MPY_PY_FILES:.py=.mpy)) - -# to build .mpy files from .py files -$(BUILD)/frozen_mpy/%.mpy: $(FROZEN_MPY_DIR)/%.py | $(MICROPY_MPYCROSS_DEPENDENCY) - @$(ECHO) "MPY $<" - $(Q)$(MKDIR) -p $(dir $@) - $(Q)$(MICROPY_MPYCROSS) -o $@ -s $(<:$(FROZEN_MPY_DIR)/%=%) $(MPY_CROSS_FLAGS) $< - -# to build frozen_mpy.c from all .mpy files -$(BUILD)/frozen_mpy.c: $(FROZEN_MPY_MPY_FILES) $(BUILD)/genhdr/qstrdefs.generated.h - @$(ECHO) "GEN $@" - $(Q)$(MPY_TOOL) -f -q $(BUILD)/genhdr/qstrdefs.preprocessed.h $(FROZEN_MPY_MPY_FILES) > $@ +ifneq ($(FROZEN_MANIFEST),) +# to build frozen_content.c from a manifest +$(BUILD)/frozen_content.c: FORCE $(BUILD)/genhdr/qstrdefs.generated.h | $(MICROPY_MPYCROSS_DEPENDENCY) + $(Q)$(MAKE_MANIFEST) -o $@ -v "MPY_DIR=$(TOP)" -v "MPY_LIB_DIR=$(MPY_LIB_DIR)" -v "PORT_DIR=$(shell pwd)" -v "BOARD_DIR=$(BOARD_DIR)" -b "$(BUILD)" $(if $(MPY_CROSS_FLAGS),-f"$(MPY_CROSS_FLAGS)",) --mpy-tool-flags="$(MPY_TOOL_FLAGS)" $(FROZEN_MANIFEST) endif ifneq ($(PROG),) # Build a standalone executable (unix does this) -# The executable should have an .exe extension for builds targeting 'pure' +# The executable should have an .exe extension for builds targetting 'pure' # Windows, i.e. msvc or mingw builds, but not when using msys or cygwin's gcc. COMPILER_TARGET := $(shell $(CC) -dumpmachine) ifneq (,$(findstring mingw,$(COMPILER_TARGET))) @@ -234,27 +222,6 @@ clean: $(RM) -rf $(BUILD) $(CLEAN_EXTRA) .PHONY: clean -# Clean every non-git file from FROZEN_DIR/FROZEN_MPY_DIR, but making a backup. -# We run rmdir below to avoid empty backup dir (it will silently fail if backup -# is non-empty). -clean-frozen: - if [ -n "$(FROZEN_MPY_DIR)" ]; then \ - backup_dir=$(FROZEN_MPY_DIR).$$(date +%Y%m%dT%H%M%S); mkdir $$backup_dir; \ - cd $(FROZEN_MPY_DIR); git status --ignored -u all -s . | awk ' {print $$2}' \ - | xargs --no-run-if-empty cp --parents -t ../$$backup_dir; \ - rmdir ../$$backup_dir 2>/dev/null || true; \ - git clean -d -f .; \ - fi - - if [ -n "$(FROZEN_DIR)" ]; then \ - backup_dir=$(FROZEN_DIR).$$(date +%Y%m%dT%H%M%S); mkdir $$backup_dir; \ - cd $(FROZEN_DIR); git status --ignored -u all -s . | awk ' {print $$2}' \ - | xargs --no-run-if-empty cp --parents -t ../$$backup_dir; \ - rmdir ../$$backup_dir 2>/dev/null || true; \ - git clean -d -f .; \ - fi -.PHONY: clean-frozen - print-cfg: $(ECHO) "PY_SRC = $(PY_SRC)" $(ECHO) "BUILD = $(BUILD)" diff --git a/python/src/py/modarray.c b/python/src/py/modarray.c index 9ab1795f8b8..d9f7a045288 100644 --- a/python/src/py/modarray.c +++ b/python/src/py/modarray.c @@ -40,6 +40,6 @@ const mp_obj_module_t mp_module_uarray = { .globals = (mp_obj_dict_t *)&mp_module_array_globals, }; -MP_REGISTER_MODULE(MP_QSTR_uarray, mp_module_uarray, MICROPY_PY_ARRAY); +MP_REGISTER_MODULE(MP_QSTR_uarray, mp_module_uarray); #endif diff --git a/python/src/py/modbuiltins.c b/python/src/py/modbuiltins.c index 2a142a6bb87..f3caccbc83e 100644 --- a/python/src/py/modbuiltins.c +++ b/python/src/py/modbuiltins.c @@ -79,7 +79,7 @@ STATIC mp_obj_t mp_builtin___build_class__(size_t n_args, const mp_obj_t *args) meta_args[2] = class_locals; // dict of members mp_obj_t new_class = mp_call_function_n_kw(meta, 3, 0, meta_args); - // store into cell if needed + // store into cell if neede if (cell != mp_const_none) { mp_obj_cell_set(cell, new_class); } @@ -729,6 +729,9 @@ STATIC const mp_rom_map_elem_t mp_module_builtins_globals_table[] = { #endif { MP_ROM_QSTR(MP_QSTR_next), MP_ROM_PTR(&mp_builtin_next_obj) }, { MP_ROM_QSTR(MP_QSTR_oct), MP_ROM_PTR(&mp_builtin_oct_obj) }, + #if MICROPY_PY_IO + { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_ord), MP_ROM_PTR(&mp_builtin_ord_obj) }, { MP_ROM_QSTR(MP_QSTR_pow), MP_ROM_PTR(&mp_builtin_pow_obj) }, { MP_ROM_QSTR(MP_QSTR_print), MP_ROM_PTR(&mp_builtin_print_obj) }, @@ -775,6 +778,7 @@ STATIC const mp_rom_map_elem_t mp_module_builtins_globals_table[] = { // Extra builtins as defined by a port MICROPY_PORT_BUILTINS + MICROPY_PORT_EXTRA_BUILTINS }; MP_DEFINE_CONST_DICT(mp_module_builtins_globals, mp_module_builtins_globals_table); @@ -783,3 +787,5 @@ const mp_obj_module_t mp_module_builtins = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&mp_module_builtins_globals, }; + +MP_REGISTER_MODULE(MP_QSTR_builtins, mp_module_builtins); diff --git a/python/src/py/modcmath.c b/python/src/py/modcmath.c index fb1f2a8fc26..1418362ad9b 100644 --- a/python/src/py/modcmath.c +++ b/python/src/py/modcmath.c @@ -149,4 +149,6 @@ const mp_obj_module_t mp_module_cmath = { .globals = (mp_obj_dict_t *)&mp_module_cmath_globals, }; -#endif // MICROPY_PY_BUILTINS_FLOAT && MICROPY_PY_CMATH +MP_REGISTER_MODULE(MP_QSTR_cmath, mp_module_cmath); + +#endif // MICROPY_PY_BUILTINS_FLOAT && MICROPY_PY_BUILTINS_COMPLEX && MICROPY_PY_CMATH diff --git a/python/src/py/modcollections.c b/python/src/py/modcollections.c index c145f12cc6e..8c62f34db7e 100644 --- a/python/src/py/modcollections.c +++ b/python/src/py/modcollections.c @@ -46,4 +46,6 @@ const mp_obj_module_t mp_module_collections = { .globals = (mp_obj_dict_t *)&mp_module_collections_globals, }; +MP_REGISTER_MODULE(MP_QSTR_ucollections, mp_module_collections); + #endif // MICROPY_PY_COLLECTIONS diff --git a/python/src/py/modgc.c b/python/src/py/modgc.c index 534a711c16e..c11bcaecd7d 100644 --- a/python/src/py/modgc.c +++ b/python/src/py/modgc.c @@ -115,4 +115,6 @@ const mp_obj_module_t mp_module_gc = { .globals = (mp_obj_dict_t *)&mp_module_gc_globals, }; +MP_REGISTER_MODULE(MP_QSTR_gc, mp_module_gc); + #endif diff --git a/python/src/py/modio.c b/python/src/py/modio.c index 7f0d13cdfaa..50af0b6a479 100644 --- a/python/src/py/modio.c +++ b/python/src/py/modio.c @@ -121,8 +121,7 @@ typedef struct _mp_obj_bufwriter_t { STATIC mp_obj_t bufwriter_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 2, 2, false); size_t alloc = mp_obj_get_int(args[1]); - mp_obj_bufwriter_t *o = m_new_obj_var(mp_obj_bufwriter_t, byte, alloc); - o->base.type = type; + mp_obj_bufwriter_t *o = mp_obj_malloc_var(mp_obj_bufwriter_t, byte, alloc, type); o->stream = args[0]; o->alloc = alloc; o->len = 0; @@ -204,50 +203,6 @@ STATIC const mp_obj_type_t mp_type_bufwriter = { }; #endif // MICROPY_PY_IO_BUFFEREDWRITER -#if MICROPY_PY_IO_RESOURCE_STREAM -STATIC mp_obj_t resource_stream(mp_obj_t package_in, mp_obj_t path_in) { - VSTR_FIXED(path_buf, MICROPY_ALLOC_PATH_MAX); - size_t len; - - // As an extension to pkg_resources.resource_stream(), we support - // package parameter being None, the path_in is interpreted as a - // raw path. - if (package_in != mp_const_none) { - // Pass "True" as sentinel value in fromlist to force returning of leaf module - mp_obj_t pkg = mp_import_name(mp_obj_str_get_qstr(package_in), mp_const_true, MP_OBJ_NEW_SMALL_INT(0)); - - mp_obj_t dest[2]; - mp_load_method_maybe(pkg, MP_QSTR___path__, dest); - if (dest[0] == MP_OBJ_NULL) { - mp_raise_TypeError(NULL); - } - - const char *path = mp_obj_str_get_data(dest[0], &len); - vstr_add_strn(&path_buf, path, len); - vstr_add_byte(&path_buf, '/'); - } - - const char *path = mp_obj_str_get_data(path_in, &len); - vstr_add_strn(&path_buf, path, len); - - len = path_buf.len; - const char *data = mp_find_frozen_str(path_buf.buf, &len); - if (data != NULL) { - mp_obj_stringio_t *o = m_new_obj(mp_obj_stringio_t); - o->base.type = &mp_type_bytesio; - o->vstr = m_new_obj(vstr_t); - vstr_init_fixed_buf(o->vstr, len + 1, (char *)data); - o->vstr->len = len; - o->pos = 0; - return MP_OBJ_FROM_PTR(o); - } - - mp_obj_t path_out = mp_obj_new_str(path_buf.buf, path_buf.len); - return mp_builtin_open(1, &path_out, (mp_map_t *)&mp_const_empty_map); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(resource_stream_obj, resource_stream); -#endif - STATIC const mp_rom_map_elem_t mp_module_io_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uio) }, // Note: mp_builtin_open_obj should be defined by port, it's not @@ -256,9 +211,6 @@ STATIC const mp_rom_map_elem_t mp_module_io_globals_table[] = { #if MICROPY_PY_IO_IOBASE { MP_ROM_QSTR(MP_QSTR_IOBase), MP_ROM_PTR(&mp_type_iobase) }, #endif - #if MICROPY_PY_IO_RESOURCE_STREAM - { MP_ROM_QSTR(MP_QSTR_resource_stream), MP_ROM_PTR(&resource_stream_obj) }, - #endif #if MICROPY_PY_IO_FILEIO { MP_ROM_QSTR(MP_QSTR_FileIO), MP_ROM_PTR(&mp_type_fileio) }, #if MICROPY_CPYTHON_COMPAT @@ -281,4 +233,6 @@ const mp_obj_module_t mp_module_io = { .globals = (mp_obj_dict_t *)&mp_module_io_globals, }; +MP_REGISTER_MODULE(MP_QSTR_uio, mp_module_io); + #endif diff --git a/python/src/py/modmath.c b/python/src/py/modmath.c index ac9e0bbc449..72b5dde5175 100644 --- a/python/src/py/modmath.c +++ b/python/src/py/modmath.c @@ -371,6 +371,11 @@ STATIC const mp_rom_map_elem_t mp_module_math_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_math) }, { MP_ROM_QSTR(MP_QSTR_e), mp_const_float_e }, { MP_ROM_QSTR(MP_QSTR_pi), mp_const_float_pi }, + #if MICROPY_PY_MATH_CONSTANTS + { MP_ROM_QSTR(MP_QSTR_tau), mp_const_float_tau }, + { MP_ROM_QSTR(MP_QSTR_inf), mp_const_float_inf }, + { MP_ROM_QSTR(MP_QSTR_nan), mp_const_float_nan }, + #endif { MP_ROM_QSTR(MP_QSTR_sqrt), MP_ROM_PTR(&mp_math_sqrt_obj) }, { MP_ROM_QSTR(MP_QSTR_pow), MP_ROM_PTR(&mp_math_pow_obj) }, { MP_ROM_QSTR(MP_QSTR_exp), MP_ROM_PTR(&mp_math_exp_obj) }, @@ -430,4 +435,6 @@ const mp_obj_module_t mp_module_math = { .globals = (mp_obj_dict_t *)&mp_module_math_globals, }; +MP_REGISTER_MODULE(MP_QSTR_math, mp_module_math); + #endif // MICROPY_PY_BUILTINS_FLOAT && MICROPY_PY_MATH diff --git a/python/src/py/modmicropython.c b/python/src/py/modmicropython.c index 180f7f186c6..eafff90c60e 100644 --- a/python/src/py/modmicropython.c +++ b/python/src/py/modmicropython.c @@ -209,3 +209,5 @@ const mp_obj_module_t mp_module_micropython = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&mp_module_micropython_globals, }; + +MP_REGISTER_MODULE(MP_QSTR_micropython, mp_module_micropython); diff --git a/python/src/py/modstruct.c b/python/src/py/modstruct.c index 4cbcad6d496..69c7279e370 100644 --- a/python/src/py/modstruct.c +++ b/python/src/py/modstruct.c @@ -266,4 +266,6 @@ const mp_obj_module_t mp_module_ustruct = { .globals = (mp_obj_dict_t *)&mp_module_struct_globals, }; +MP_REGISTER_MODULE(MP_QSTR_ustruct, mp_module_ustruct); + #endif diff --git a/python/src/py/modsys.c b/python/src/py/modsys.c index 64349f3c306..a090f1212d3 100644 --- a/python/src/py/modsys.c +++ b/python/src/py/modsys.c @@ -27,6 +27,7 @@ #include "py/builtin.h" #include "py/objlist.h" +#include "py/objmodule.h" #include "py/objtuple.h" #include "py/objstr.h" #include "py/objint.h" @@ -35,6 +36,8 @@ #include "py/smallint.h" #include "py/runtime.h" #include "py/persistentcode.h" +#include "extmod/moduplatform.h" +#include "genhdr/mpversion.h" #if MICROPY_PY_SYS_SETTRACE #include "py/objmodule.h" @@ -53,7 +56,7 @@ const mp_print_t mp_sys_stdout_print = {&mp_sys_stdout_obj, mp_stream_write_adap #endif // version - Python language version that this implementation conforms to, as a string -STATIC const MP_DEFINE_STR_OBJ(mp_sys_version_obj, "3.4.0"); +STATIC const MP_DEFINE_STR_OBJ(mp_sys_version_obj, "3.4.0; " MICROPY_BANNER_NAME_AND_VERSION); // version_info - Python language version that this implementation conforms to, as a tuple of ints #define I(n) MP_OBJ_NEW_SMALL_INT(n) @@ -67,34 +70,38 @@ STATIC const mp_obj_tuple_t mp_sys_implementation_version_info_obj = { 3, { I(MICROPY_VERSION_MAJOR), I(MICROPY_VERSION_MINOR), I(MICROPY_VERSION_MICRO) } }; +STATIC const MP_DEFINE_STR_OBJ(mp_sys_implementation_machine_obj, MICROPY_BANNER_MACHINE); #if MICROPY_PERSISTENT_CODE_LOAD #define SYS_IMPLEMENTATION_ELEMS \ MP_ROM_QSTR(MP_QSTR_micropython), \ MP_ROM_PTR(&mp_sys_implementation_version_info_obj), \ + MP_ROM_PTR(&mp_sys_implementation_machine_obj), \ MP_ROM_INT(MPY_FILE_HEADER_INT) #else #define SYS_IMPLEMENTATION_ELEMS \ MP_ROM_QSTR(MP_QSTR_micropython), \ - MP_ROM_PTR(&mp_sys_implementation_version_info_obj) + MP_ROM_PTR(&mp_sys_implementation_version_info_obj), \ + MP_ROM_PTR(&mp_sys_implementation_machine_obj) #endif #if MICROPY_PY_ATTRTUPLE STATIC const qstr impl_fields[] = { MP_QSTR_name, MP_QSTR_version, + MP_QSTR__machine, #if MICROPY_PERSISTENT_CODE_LOAD - MP_QSTR_mpy, + MP_QSTR__mpy, #endif }; STATIC MP_DEFINE_ATTRTUPLE( mp_sys_implementation_obj, impl_fields, - 2 + MICROPY_PERSISTENT_CODE_LOAD, + 3 + MICROPY_PERSISTENT_CODE_LOAD, SYS_IMPLEMENTATION_ELEMS ); #else STATIC const mp_rom_obj_tuple_t mp_sys_implementation_obj = { {&mp_type_tuple}, - 2 + MICROPY_PERSISTENT_CODE_LOAD, + 3 + MICROPY_PERSISTENT_CODE_LOAD, { SYS_IMPLEMENTATION_ELEMS } @@ -175,13 +182,32 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_sys_atexit_obj, mp_sys_atexit); #endif #if MICROPY_PY_SYS_SETTRACE -// settrace(tracefunc): Set the system’s trace function. +// settrace(tracefunc): Set the system's trace function. STATIC mp_obj_t mp_sys_settrace(mp_obj_t obj) { return mp_prof_settrace(obj); } MP_DEFINE_CONST_FUN_OBJ_1(mp_sys_settrace_obj, mp_sys_settrace); #endif // MICROPY_PY_SYS_SETTRACE +#if MICROPY_PY_SYS_ATTR_DELEGATION +STATIC const uint16_t sys_mutable_keys[] = { + #if MICROPY_PY_SYS_PS1_PS2 + MP_QSTR_ps1, + MP_QSTR_ps2, + #endif + #if MICROPY_PY_SYS_TRACEBACKLIMIT + MP_QSTR_tracebacklimit, + #endif + MP_QSTRnull, +}; + +STATIC void mp_module_sys_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + MP_STATIC_ASSERT(MP_ARRAY_SIZE(sys_mutable_keys) == MP_SYS_MUTABLE_NUM + 1); + MP_STATIC_ASSERT(MP_ARRAY_SIZE(MP_STATE_VM(sys_mutable)) == MP_SYS_MUTABLE_NUM); + mp_module_generic_attr(attr, dest, sys_mutable_keys, MP_STATE_VM(sys_mutable)); +} +#endif + STATIC const mp_rom_map_elem_t mp_module_sys_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_sys) }, @@ -244,6 +270,11 @@ STATIC const mp_rom_map_elem_t mp_module_sys_globals_table[] = { #if MICROPY_PY_SYS_ATEXIT { MP_ROM_QSTR(MP_QSTR_atexit), MP_ROM_PTR(&mp_sys_atexit_obj) }, #endif + + #if MICROPY_PY_SYS_ATTR_DELEGATION + // Delegation of attr lookup. + MP_MODULE_ATTR_DELEGATION_ENTRY(&mp_module_sys_attr), + #endif }; STATIC MP_DEFINE_CONST_DICT(mp_module_sys_globals, mp_module_sys_globals_table); @@ -253,4 +284,6 @@ const mp_obj_module_t mp_module_sys = { .globals = (mp_obj_dict_t *)&mp_module_sys_globals, }; +MP_REGISTER_MODULE(MP_QSTR_usys, mp_module_sys); + #endif diff --git a/python/src/py/modthread.c b/python/src/py/modthread.c index 29b765493ac..bad94fbf2f0 100644 --- a/python/src/py/modthread.c +++ b/python/src/py/modthread.c @@ -54,8 +54,7 @@ typedef struct _mp_obj_thread_lock_t { } mp_obj_thread_lock_t; STATIC mp_obj_thread_lock_t *mp_obj_new_thread_lock(void) { - mp_obj_thread_lock_t *self = m_new_obj(mp_obj_thread_lock_t); - self->base.type = &mp_type_thread_lock; + mp_obj_thread_lock_t *self = mp_obj_malloc(mp_obj_thread_lock_t, &mp_type_thread_lock); mp_thread_mutex_init(&self->mutex); self->locked = false; return self; @@ -301,4 +300,6 @@ const mp_obj_module_t mp_module_thread = { .globals = (mp_obj_dict_t *)&mp_module_thread_globals, }; +MP_REGISTER_MODULE(MP_QSTR__thread, mp_module_thread); + #endif // MICROPY_PY_THREAD diff --git a/python/src/py/moduerrno.c b/python/src/py/moduerrno.c index d9affd9b20c..1b16fd9d9ed 100644 --- a/python/src/py/moduerrno.c +++ b/python/src/py/moduerrno.c @@ -99,6 +99,8 @@ const mp_obj_module_t mp_module_uerrno = { .globals = (mp_obj_dict_t *)&mp_module_uerrno_globals, }; +MP_REGISTER_MODULE(MP_QSTR_uerrno, mp_module_uerrno); + qstr mp_errno_to_str(mp_obj_t errno_val) { #if MICROPY_PY_UERRNO_ERRORCODE // We have the errorcode dict so can do a lookup using the hash map diff --git a/python/src/py/mpconfig.h b/python/src/py/mpconfig.h index 680b43d62cd..d70d39ae97e 100644 --- a/python/src/py/mpconfig.h +++ b/python/src/py/mpconfig.h @@ -28,8 +28,8 @@ // Current version of MicroPython #define MICROPY_VERSION_MAJOR 1 -#define MICROPY_VERSION_MINOR 17 -#define MICROPY_VERSION_MICRO 0 +#define MICROPY_VERSION_MINOR 19 +#define MICROPY_VERSION_MICRO 1 // Combined version as a 32-bit number for convenience #define MICROPY_VERSION ( \ @@ -62,6 +62,31 @@ #include #endif +// Disable all optional features (i.e. minimal port). +#define MICROPY_CONFIG_ROM_LEVEL_MINIMUM (0) +// Only enable core features (constrained flash, e.g. STM32L072) +#define MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES (10) +// Enable most common features (small on-device flash, e.g. STM32F411) +#define MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES (20) +// Enable convenience features (medium on-device flash, e.g. STM32F405) +#define MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES (30) +// Enable all common features (large/external flash, rp2, unix) +#define MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES (40) +// Enable everything (e.g. coverage) +#define MICROPY_CONFIG_ROM_LEVEL_EVERYTHING (50) + +// Ports/boards should set this, but default to level=core. +#ifndef MICROPY_CONFIG_ROM_LEVEL +#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES) +#endif + +// Helper macros for "have at least this level". +#define MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES (MICROPY_CONFIG_ROM_LEVEL >= MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES) +#define MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES (MICROPY_CONFIG_ROM_LEVEL >= MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES) +#define MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES (MICROPY_CONFIG_ROM_LEVEL >= MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) +#define MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_FULL_FEATURES (MICROPY_CONFIG_ROM_LEVEL >= MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES) +#define MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING (MICROPY_CONFIG_ROM_LEVEL >= MICROPY_CONFIG_ROM_LEVEL_EVERYTHING) + // Any options not explicitly set in mpconfigport.h will get default // values below. @@ -113,6 +138,13 @@ #define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_A) #endif +// Whether to encode None/False/True as immediate objects instead of pointers to +// real objects. Reduces code size by a decent amount without hurting +// performance, for all representations except D on some architectures. +#ifndef MICROPY_OBJ_IMMEDIATE_OBJS +#define MICROPY_OBJ_IMMEDIATE_OBJS (MICROPY_OBJ_REPR != MICROPY_OBJ_REPR_D) +#endif + /*****************************************************************************/ /* Memory allocation policy */ @@ -149,7 +181,7 @@ // Support automatic GC when reaching allocation threshold, // configurable by gc.threshold(). #ifndef MICROPY_GC_ALLOC_THRESHOLD -#define MICROPY_GC_ALLOC_THRESHOLD (1) +#define MICROPY_GC_ALLOC_THRESHOLD (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Number of bytes to allocate initially when creating new chunks to store @@ -242,7 +274,11 @@ // Number of bytes used to store qstr hash #ifndef MICROPY_QSTR_BYTES_IN_HASH +#if MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES #define MICROPY_QSTR_BYTES_IN_HASH (2) +#else +#define MICROPY_QSTR_BYTES_IN_HASH (1) +#endif #endif // Avoid using C stack when making Python function calls. C stack still @@ -292,6 +328,14 @@ #define MICROPY_PERSISTENT_CODE (MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE || MICROPY_MODULE_FROZEN_MPY) #endif +// Whether bytecode uses a qstr_table to map internal qstr indices in the bytecode +// to global qstr values in the runtime (behaviour when feature is enabled), or +// just stores global qstr values directly in the bytecode. This must be enabled +// if MICROPY_PERSISTENT_CODE is enabled. +#ifndef MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE +#define MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE (MICROPY_PERSISTENT_CODE) +#endif + // Whether to emit x64 native code #ifndef MICROPY_EMIT_X64 #define MICROPY_EMIT_X64 (0) @@ -317,11 +361,6 @@ #define MICROPY_EMIT_INLINE_THUMB (0) #endif -// Whether to enable ARMv7-M instruction support in the Thumb2 inline assembler -#ifndef MICROPY_EMIT_INLINE_THUMB_ARMV7M -#define MICROPY_EMIT_INLINE_THUMB_ARMV7M (1) -#endif - // Whether to enable float support in the Thumb2 inline assembler #ifndef MICROPY_EMIT_INLINE_THUMB_FLOAT #define MICROPY_EMIT_INLINE_THUMB_FLOAT (1) @@ -350,8 +389,10 @@ // Convenience definition for whether any native emitter is enabled #define MICROPY_EMIT_NATIVE (MICROPY_EMIT_X64 || MICROPY_EMIT_X86 || MICROPY_EMIT_THUMB || MICROPY_EMIT_ARM || MICROPY_EMIT_XTENSA || MICROPY_EMIT_XTENSAWIN) -// Select prelude-as-bytes-object for certain emitters -#define MICROPY_EMIT_NATIVE_PRELUDE_AS_BYTES_OBJ (MICROPY_EMIT_XTENSAWIN) +// Some architectures cannot read byte-wise from executable memory. In this case +// the prelude for a native function (which usually sits after the machine code) +// must be separated and placed somewhere where it can be read byte-wise. +#define MICROPY_EMIT_NATIVE_PRELUDE_SEPARATE_FROM_MACHINE_CODE (MICROPY_EMIT_XTENSAWIN) // Convenience definition for whether any inline assembler emitter is enabled #define MICROPY_EMIT_INLINE_ASM (MICROPY_EMIT_INLINE_THUMB || MICROPY_EMIT_INLINE_XTENSA) @@ -376,7 +417,7 @@ // Whether to include the compiler #ifndef MICROPY_ENABLE_COMPILER -#define MICROPY_ENABLE_COMPILER (1) +#define MICROPY_ENABLE_COMPILER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether the compiler is dynamically configurable (ie at runtime) @@ -385,51 +426,48 @@ #define MICROPY_DYNAMIC_COMPILER (0) #endif -// Configure dynamic compiler macros -#if MICROPY_DYNAMIC_COMPILER -#define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE_DYNAMIC (mp_dynamic_compiler.opt_cache_map_lookup_in_bytecode) -#define MICROPY_PY_BUILTINS_STR_UNICODE_DYNAMIC (mp_dynamic_compiler.py_builtins_str_unicode) -#else -#define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE_DYNAMIC MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE -#define MICROPY_PY_BUILTINS_STR_UNICODE_DYNAMIC MICROPY_PY_BUILTINS_STR_UNICODE -#endif - // Whether to enable constant folding; eg 1+2 rewritten as 3 #ifndef MICROPY_COMP_CONST_FOLDING -#define MICROPY_COMP_CONST_FOLDING (1) +#define MICROPY_COMP_CONST_FOLDING (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) +#endif + +// Whether to compile constant tuples immediately to their respective objects; eg (1, True) +// Otherwise the tuple will be built at runtime +#ifndef MICROPY_COMP_CONST_TUPLE +#define MICROPY_COMP_CONST_TUPLE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to enable optimisations for constant literals, eg OrderedDict #ifndef MICROPY_COMP_CONST_LITERAL -#define MICROPY_COMP_CONST_LITERAL (1) +#define MICROPY_COMP_CONST_LITERAL (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to enable lookup of constants in modules; eg module.CONST #ifndef MICROPY_COMP_MODULE_CONST -#define MICROPY_COMP_MODULE_CONST (0) +#define MICROPY_COMP_MODULE_CONST (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to enable constant optimisation; id = const(value) #ifndef MICROPY_COMP_CONST -#define MICROPY_COMP_CONST (1) +#define MICROPY_COMP_CONST (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to enable optimisation of: a, b = c, d // Costs 124 bytes (Thumb2) #ifndef MICROPY_COMP_DOUBLE_TUPLE_ASSIGN -#define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (1) +#define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to enable optimisation of: a, b, c = d, e, f // Requires MICROPY_COMP_DOUBLE_TUPLE_ASSIGN and costs 68 bytes (Thumb2) #ifndef MICROPY_COMP_TRIPLE_TUPLE_ASSIGN -#define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN (0) +#define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to enable optimisation of: return a if b else c // Costs about 80 bytes (Thumb2) and saves 2 bytes of bytecode for each use #ifndef MICROPY_COMP_RETURN_IF_EXPR -#define MICROPY_COMP_RETURN_IF_EXPR (0) +#define MICROPY_COMP_RETURN_IF_EXPR (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif /*****************************************************************************/ @@ -484,23 +522,36 @@ #define MICROPY_OPT_COMPUTED_GOTO (0) #endif -// Whether to cache result of map lookups in LOAD_NAME, LOAD_GLOBAL, LOAD_ATTR, -// STORE_ATTR bytecodes. Uses 1 byte extra RAM for each of these opcodes and -// uses a bit of extra code ROM, but greatly improves lookup speed. -#ifndef MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE -#define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (0) +// Optimise the fast path for loading attributes from instance types. Increases +// Thumb2 code size by about 48 bytes. +#ifndef MICROPY_OPT_LOAD_ATTR_FAST_PATH +#define MICROPY_OPT_LOAD_ATTR_FAST_PATH (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#endif + +// Use extra RAM to cache map lookups by remembering the likely location of +// the index. Avoids the hash computation on unordered maps, and avoids the +// linear search on ordered (especially in-ROM) maps. Can provide a +10-15% +// performance improvement on benchmarks involving lots of attribute access +// or dictionary lookup. +#ifndef MICROPY_OPT_MAP_LOOKUP_CACHE +#define MICROPY_OPT_MAP_LOOKUP_CACHE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#endif + +// How much RAM (in bytes) to use for the map lookup cache. +#ifndef MICROPY_OPT_MAP_LOOKUP_CACHE_SIZE +#define MICROPY_OPT_MAP_LOOKUP_CACHE_SIZE (128) #endif // Whether to use fast versions of bitwise operations (and, or, xor) when the // arguments are both positive. Increases Thumb2 code size by about 250 bytes. #ifndef MICROPY_OPT_MPZ_BITWISE -#define MICROPY_OPT_MPZ_BITWISE (0) +#define MICROPY_OPT_MPZ_BITWISE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether math.factorial is large, fast and recursive (1) or small and slow (0). #ifndef MICROPY_OPT_MATH_FACTORIAL -#define MICROPY_OPT_MATH_FACTORIAL (0) +#define MICROPY_OPT_MATH_FACTORIAL (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif /*****************************************************************************/ @@ -510,7 +561,7 @@ // When disabled, only importing of built-in modules is supported // When enabled, a port must implement mp_import_stat (among other things) #ifndef MICROPY_ENABLE_EXTERNAL_IMPORT -#define MICROPY_ENABLE_EXTERNAL_IMPORT (1) +#define MICROPY_ENABLE_EXTERNAL_IMPORT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to use the POSIX reader for importing files @@ -555,9 +606,19 @@ #define MICROPY_ENABLE_GC (0) #endif +// Hook to run code during time consuming garbage collector operations +#ifndef MICROPY_GC_HOOK_LOOP +#define MICROPY_GC_HOOK_LOOP +#endif + +// Whether to provide m_tracked_calloc, m_tracked_free functions +#ifndef MICROPY_TRACKED_ALLOC +#define MICROPY_TRACKED_ALLOC (0) +#endif + // Whether to enable finalisers in the garbage collector (ie call __del__) #ifndef MICROPY_ENABLE_FINALISER -#define MICROPY_ENABLE_FINALISER (0) +#define MICROPY_ENABLE_FINALISER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to enable a separate allocator for the Python stack. @@ -574,7 +635,7 @@ // Whether to check C stack usage. C stack used for calling Python functions, // etc. Not checking means segfault on overflow. #ifndef MICROPY_STACK_CHECK -#define MICROPY_STACK_CHECK (0) +#define MICROPY_STACK_CHECK (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to have an emergency exception buffer @@ -589,7 +650,7 @@ // Whether to provide the mp_kbd_exception object, and micropython.kbd_intr function #ifndef MICROPY_KBD_EXCEPTION -#define MICROPY_KBD_EXCEPTION (0) +#define MICROPY_KBD_EXCEPTION (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Prefer to raise KeyboardInterrupt asynchronously (from signal or interrupt @@ -600,7 +661,7 @@ // Whether to include REPL helper function #ifndef MICROPY_HELPER_REPL -#define MICROPY_HELPER_REPL (0) +#define MICROPY_HELPER_REPL (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Allow enabling debug prints after each REPL line @@ -610,7 +671,7 @@ // Whether to include emacs-style readline behavior in REPL #ifndef MICROPY_REPL_EMACS_KEYS -#define MICROPY_REPL_EMACS_KEYS (0) +#define MICROPY_REPL_EMACS_KEYS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to include emacs-style word movement/kill readline behavior in REPL. @@ -630,7 +691,7 @@ // Whether to implement auto-indent in REPL #ifndef MICROPY_REPL_AUTO_INDENT -#define MICROPY_REPL_AUTO_INDENT (0) +#define MICROPY_REPL_AUTO_INDENT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether port requires event-driven REPL functions @@ -659,7 +720,7 @@ typedef long long mp_longint_impl_t; // Whether to include information in the byte code to determine source // line number (increases RAM usage, but doesn't slow byte code execution) #ifndef MICROPY_ENABLE_SOURCE_LINE -#define MICROPY_ENABLE_SOURCE_LINE (0) +#define MICROPY_ENABLE_SOURCE_LINE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to include doc strings (increases RAM usage) @@ -677,7 +738,13 @@ typedef long long mp_longint_impl_t; #define MICROPY_ERROR_REPORTING_DETAILED (3) #ifndef MICROPY_ERROR_REPORTING +#if MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_FULL_FEATURES +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_DETAILED) +#elif MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NORMAL) +#else +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_TERSE) +#endif #endif // Whether issue warnings during compiling/execution @@ -733,7 +800,7 @@ typedef double mp_float_t; // TODO: Originally intended as generic category to not // add bunch of once-off options. May need refactoring later #ifndef MICROPY_CPYTHON_COMPAT -#define MICROPY_CPYTHON_COMPAT (1) +#define MICROPY_CPYTHON_COMPAT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Perform full checks as done by CPython. Disabling this @@ -742,12 +809,12 @@ typedef double mp_float_t; // grave issues (in other words, only user app should be, // affected, not system). #ifndef MICROPY_FULL_CHECKS -#define MICROPY_FULL_CHECKS (1) +#define MICROPY_FULL_CHECKS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether POSIX-semantics non-blocking streams are supported #ifndef MICROPY_STREAMS_NON_BLOCK -#define MICROPY_STREAMS_NON_BLOCK (0) +#define MICROPY_STREAMS_NON_BLOCK (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide stream functions with POSIX-like signatures @@ -756,19 +823,31 @@ typedef double mp_float_t; #define MICROPY_STREAMS_POSIX_API (0) #endif +// Whether modules can use MP_MODULE_ATTR_DELEGATION_ENTRY() to delegate failed +// attribute lookups. +#ifndef MICROPY_MODULE_ATTR_DELEGATION +#define MICROPY_MODULE_ATTR_DELEGATION (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#endif + // Whether to call __init__ when importing builtin modules for the first time #ifndef MICROPY_MODULE_BUILTIN_INIT -#define MICROPY_MODULE_BUILTIN_INIT (0) +#define MICROPY_MODULE_BUILTIN_INIT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to support module-level __getattr__ (see PEP 562) #ifndef MICROPY_MODULE_GETATTR -#define MICROPY_MODULE_GETATTR (1) +#define MICROPY_MODULE_GETATTR (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether module weak links are supported #ifndef MICROPY_MODULE_WEAK_LINKS -#define MICROPY_MODULE_WEAK_LINKS (0) +#define MICROPY_MODULE_WEAK_LINKS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#endif + +// Whether to enable importing foo.py with __name__ set to '__main__' +// Used by the unix port for the -m flag. +#ifndef MICROPY_MODULE_OVERRIDE_MAIN_IMPORT +#define MICROPY_MODULE_OVERRIDE_MAIN_IMPORT (0) #endif // Whether frozen modules are supported in the form of strings @@ -788,7 +867,7 @@ typedef double mp_float_t; // Whether you can override builtins in the builtins module #ifndef MICROPY_CAN_OVERRIDE_BUILTINS -#define MICROPY_CAN_OVERRIDE_BUILTINS (0) +#define MICROPY_CAN_OVERRIDE_BUILTINS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to check that the "self" argument of a builtin method has the @@ -797,7 +876,7 @@ typedef double mp_float_t; // list.append([], 1). Without this check such calls will have undefined // behaviour (usually segfault) if the first argument is the wrong type. #ifndef MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG -#define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (1) +#define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to use internally defined errno's (otherwise system provided ones) @@ -812,7 +891,12 @@ typedef double mp_float_t; // Support for internal scheduler #ifndef MICROPY_ENABLE_SCHEDULER -#define MICROPY_ENABLE_SCHEDULER (0) +#define MICROPY_ENABLE_SCHEDULER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#endif + +// Whether the scheduler supports scheduling static nodes with C callbacks +#ifndef MICROPY_SCHEDULER_STATIC_NODES +#define MICROPY_SCHEDULER_STATIC_NODES (0) #endif // Maximum number of entries in the scheduler @@ -842,41 +926,41 @@ typedef double mp_float_t; // inheritance makes some C functions inherently recursive, and adds a bit of // code overhead. #ifndef MICROPY_MULTIPLE_INHERITANCE -#define MICROPY_MULTIPLE_INHERITANCE (1) +#define MICROPY_MULTIPLE_INHERITANCE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to implement attributes on functions #ifndef MICROPY_PY_FUNCTION_ATTRS -#define MICROPY_PY_FUNCTION_ATTRS (0) +#define MICROPY_PY_FUNCTION_ATTRS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to support the descriptors __get__, __set__, __delete__ // This costs some code size and makes load/store/delete of instance // attributes slower for the classes that use this feature #ifndef MICROPY_PY_DESCRIPTORS -#define MICROPY_PY_DESCRIPTORS (0) +#define MICROPY_PY_DESCRIPTORS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to support class __delattr__ and __setattr__ methods // This costs some code size and makes store/delete of instance // attributes slower for the classes that use this feature #ifndef MICROPY_PY_DELATTR_SETATTR -#define MICROPY_PY_DELATTR_SETATTR (0) +#define MICROPY_PY_DELATTR_SETATTR (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Support for async/await/async for/async with #ifndef MICROPY_PY_ASYNC_AWAIT -#define MICROPY_PY_ASYNC_AWAIT (1) +#define MICROPY_PY_ASYNC_AWAIT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Support for literal string interpolation, f-strings (see PEP 498, Python 3.6+) #ifndef MICROPY_PY_FSTRINGS -#define MICROPY_PY_FSTRINGS (0) +#define MICROPY_PY_FSTRINGS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Support for assignment expressions with := (see PEP 572, Python 3.8+) #ifndef MICROPY_PY_ASSIGN_EXPR -#define MICROPY_PY_ASSIGN_EXPR (1) +#define MICROPY_PY_ASSIGN_EXPR (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Non-standard .pend_throw() method for generators, allowing for @@ -885,7 +969,7 @@ typedef double mp_float_t; // to generator's .send() or .__next__(). (This is useful to implement // async schedulers.) #ifndef MICROPY_PY_GENERATOR_PEND_THROW -#define MICROPY_PY_GENERATOR_PEND_THROW (1) +#define MICROPY_PY_GENERATOR_PEND_THROW (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Issue a warning when comparing str and bytes objects @@ -895,7 +979,7 @@ typedef double mp_float_t; // Whether str object is proper unicode #ifndef MICROPY_PY_BUILTINS_STR_UNICODE -#define MICROPY_PY_BUILTINS_STR_UNICODE (0) +#define MICROPY_PY_BUILTINS_STR_UNICODE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to check for valid UTF-8 when converting bytes to str @@ -905,42 +989,42 @@ typedef double mp_float_t; // Whether str.center() method provided #ifndef MICROPY_PY_BUILTINS_STR_CENTER -#define MICROPY_PY_BUILTINS_STR_CENTER (0) +#define MICROPY_PY_BUILTINS_STR_CENTER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether str.count() method provided #ifndef MICROPY_PY_BUILTINS_STR_COUNT -#define MICROPY_PY_BUILTINS_STR_COUNT (1) +#define MICROPY_PY_BUILTINS_STR_COUNT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether str % (...) formatting operator provided #ifndef MICROPY_PY_BUILTINS_STR_OP_MODULO -#define MICROPY_PY_BUILTINS_STR_OP_MODULO (1) +#define MICROPY_PY_BUILTINS_STR_OP_MODULO (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether str.partition()/str.rpartition() method provided #ifndef MICROPY_PY_BUILTINS_STR_PARTITION -#define MICROPY_PY_BUILTINS_STR_PARTITION (0) +#define MICROPY_PY_BUILTINS_STR_PARTITION (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether str.splitlines() method provided #ifndef MICROPY_PY_BUILTINS_STR_SPLITLINES -#define MICROPY_PY_BUILTINS_STR_SPLITLINES (0) +#define MICROPY_PY_BUILTINS_STR_SPLITLINES (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to support bytearray object #ifndef MICROPY_PY_BUILTINS_BYTEARRAY -#define MICROPY_PY_BUILTINS_BYTEARRAY (1) +#define MICROPY_PY_BUILTINS_BYTEARRAY (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to support dict.fromkeys() class method #ifndef MICROPY_PY_BUILTINS_DICT_FROMKEYS -#define MICROPY_PY_BUILTINS_DICT_FROMKEYS (1) +#define MICROPY_PY_BUILTINS_DICT_FROMKEYS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to support memoryview object #ifndef MICROPY_PY_BUILTINS_MEMORYVIEW -#define MICROPY_PY_BUILTINS_MEMORYVIEW (0) +#define MICROPY_PY_BUILTINS_MEMORYVIEW (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to support memoryview.itemsize attribute @@ -950,39 +1034,39 @@ typedef double mp_float_t; // Whether to support set object #ifndef MICROPY_PY_BUILTINS_SET -#define MICROPY_PY_BUILTINS_SET (1) +#define MICROPY_PY_BUILTINS_SET (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to support slice subscript operators and slice object #ifndef MICROPY_PY_BUILTINS_SLICE -#define MICROPY_PY_BUILTINS_SLICE (1) +#define MICROPY_PY_BUILTINS_SLICE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to support slice attribute read access, // i.e. slice.start, slice.stop, slice.step #ifndef MICROPY_PY_BUILTINS_SLICE_ATTRS -#define MICROPY_PY_BUILTINS_SLICE_ATTRS (0) +#define MICROPY_PY_BUILTINS_SLICE_ATTRS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to support the .indices(len) method on slice objects #ifndef MICROPY_PY_BUILTINS_SLICE_INDICES -#define MICROPY_PY_BUILTINS_SLICE_INDICES (0) +#define MICROPY_PY_BUILTINS_SLICE_INDICES (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to support frozenset object #ifndef MICROPY_PY_BUILTINS_FROZENSET -#define MICROPY_PY_BUILTINS_FROZENSET (0) +#define MICROPY_PY_BUILTINS_FROZENSET (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to support property object #ifndef MICROPY_PY_BUILTINS_PROPERTY -#define MICROPY_PY_BUILTINS_PROPERTY (1) +#define MICROPY_PY_BUILTINS_PROPERTY (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to implement the start/stop/step attributes (readback) on // the "range" builtin type. Rarely used, and costs ~60 bytes (x86). #ifndef MICROPY_PY_BUILTINS_RANGE_ATTRS -#define MICROPY_PY_BUILTINS_RANGE_ATTRS (1) +#define MICROPY_PY_BUILTINS_RANGE_ATTRS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to support binary ops [only (in)equality is defined] between range @@ -1000,7 +1084,7 @@ typedef double mp_float_t; // Whether to support rounding of integers (incl bignum); eg round(123,-1)=120 #ifndef MICROPY_PY_BUILTINS_ROUND_INT -#define MICROPY_PY_BUILTINS_ROUND_INT (0) +#define MICROPY_PY_BUILTINS_ROUND_INT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to support complete set of special methods for user @@ -1009,7 +1093,7 @@ typedef double mp_float_t; // "Reverse" methods are controlled by // MICROPY_PY_REVERSE_SPECIAL_METHODS below. #ifndef MICROPY_PY_ALL_SPECIAL_METHODS -#define MICROPY_PY_ALL_SPECIAL_METHODS (0) +#define MICROPY_PY_ALL_SPECIAL_METHODS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to support all inplace arithmetic operarion methods @@ -1022,17 +1106,17 @@ typedef double mp_float_t; // (__radd__, etc.). Additionally gated by // MICROPY_PY_ALL_SPECIAL_METHODS. #ifndef MICROPY_PY_REVERSE_SPECIAL_METHODS -#define MICROPY_PY_REVERSE_SPECIAL_METHODS (0) +#define MICROPY_PY_REVERSE_SPECIAL_METHODS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to support compile function #ifndef MICROPY_PY_BUILTINS_COMPILE -#define MICROPY_PY_BUILTINS_COMPILE (0) +#define MICROPY_PY_BUILTINS_COMPILE (MICROPY_ENABLE_COMPILER && MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to support enumerate function(type) #ifndef MICROPY_PY_BUILTINS_ENUMERATE -#define MICROPY_PY_BUILTINS_ENUMERATE (1) +#define MICROPY_PY_BUILTINS_ENUMERATE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to support eval and exec functions @@ -1043,43 +1127,43 @@ typedef double mp_float_t; // Whether to support the Python 2 execfile function #ifndef MICROPY_PY_BUILTINS_EXECFILE -#define MICROPY_PY_BUILTINS_EXECFILE (0) +#define MICROPY_PY_BUILTINS_EXECFILE (MICROPY_ENABLE_COMPILER && MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to support filter function(type) #ifndef MICROPY_PY_BUILTINS_FILTER -#define MICROPY_PY_BUILTINS_FILTER (1) +#define MICROPY_PY_BUILTINS_FILTER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to support reversed function(type) #ifndef MICROPY_PY_BUILTINS_REVERSED -#define MICROPY_PY_BUILTINS_REVERSED (1) +#define MICROPY_PY_BUILTINS_REVERSED (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to define "NotImplemented" special constant #ifndef MICROPY_PY_BUILTINS_NOTIMPLEMENTED -#define MICROPY_PY_BUILTINS_NOTIMPLEMENTED (0) +#define MICROPY_PY_BUILTINS_NOTIMPLEMENTED (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide the built-in input() function. The implementation of this // uses shared/readline, so can only be enabled if the port uses this readline. #ifndef MICROPY_PY_BUILTINS_INPUT -#define MICROPY_PY_BUILTINS_INPUT (0) +#define MICROPY_PY_BUILTINS_INPUT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to support min/max functions #ifndef MICROPY_PY_BUILTINS_MIN_MAX -#define MICROPY_PY_BUILTINS_MIN_MAX (1) +#define MICROPY_PY_BUILTINS_MIN_MAX (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Support for calls to pow() with 3 integer arguments #ifndef MICROPY_PY_BUILTINS_POW3 -#define MICROPY_PY_BUILTINS_POW3 (0) +#define MICROPY_PY_BUILTINS_POW3 (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide the help function #ifndef MICROPY_PY_BUILTINS_HELP -#define MICROPY_PY_BUILTINS_HELP (0) +#define MICROPY_PY_BUILTINS_HELP (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Use this to configure the help text shown for help(). It should be a @@ -1090,17 +1174,17 @@ typedef double mp_float_t; // Add the ability to list the available modules when executing help('modules') #ifndef MICROPY_PY_BUILTINS_HELP_MODULES -#define MICROPY_PY_BUILTINS_HELP_MODULES (0) +#define MICROPY_PY_BUILTINS_HELP_MODULES (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to set __file__ for imported modules #ifndef MICROPY_PY___FILE__ -#define MICROPY_PY___FILE__ (1) +#define MICROPY_PY___FILE__ (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to provide mem-info related functions in micropython module #ifndef MICROPY_PY_MICROPYTHON_MEM_INFO -#define MICROPY_PY_MICROPYTHON_MEM_INFO (0) +#define MICROPY_PY_MICROPYTHON_MEM_INFO (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide "micropython.stack_use" function @@ -1117,34 +1201,34 @@ typedef double mp_float_t; // underlying code is shared with "bytearray" builtin type, so to // get real savings, it should be disabled too. #ifndef MICROPY_PY_ARRAY -#define MICROPY_PY_ARRAY (1) +#define MICROPY_PY_ARRAY (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to support slice assignments for array (and bytearray). // This is rarely used, but adds ~0.5K of code. #ifndef MICROPY_PY_ARRAY_SLICE_ASSIGN -#define MICROPY_PY_ARRAY_SLICE_ASSIGN (0) +#define MICROPY_PY_ARRAY_SLICE_ASSIGN (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to support attrtuple type (MicroPython extension) // It provides space-efficient tuples with attribute access #ifndef MICROPY_PY_ATTRTUPLE -#define MICROPY_PY_ATTRTUPLE (1) +#define MICROPY_PY_ATTRTUPLE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to provide "collections" module #ifndef MICROPY_PY_COLLECTIONS -#define MICROPY_PY_COLLECTIONS (1) +#define MICROPY_PY_COLLECTIONS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to provide "ucollections.deque" type #ifndef MICROPY_PY_COLLECTIONS_DEQUE -#define MICROPY_PY_COLLECTIONS_DEQUE (0) +#define MICROPY_PY_COLLECTIONS_DEQUE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide "collections.OrderedDict" type #ifndef MICROPY_PY_COLLECTIONS_ORDEREDDICT -#define MICROPY_PY_COLLECTIONS_ORDEREDDICT (0) +#define MICROPY_PY_COLLECTIONS_ORDEREDDICT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide the _asdict function for namedtuple @@ -1154,22 +1238,27 @@ typedef double mp_float_t; // Whether to provide "math" module #ifndef MICROPY_PY_MATH -#define MICROPY_PY_MATH (1) +#define MICROPY_PY_MATH (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) +#endif + +// Whether to provide all math module constants (Python 3.5+), or just pi and e. +#ifndef MICROPY_PY_MATH_CONSTANTS +#define MICROPY_PY_MATH_CONSTANTS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide special math functions: math.{erf,erfc,gamma,lgamma} #ifndef MICROPY_PY_MATH_SPECIAL_FUNCTIONS -#define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (0) +#define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide math.factorial function #ifndef MICROPY_PY_MATH_FACTORIAL -#define MICROPY_PY_MATH_FACTORIAL (0) +#define MICROPY_PY_MATH_FACTORIAL (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide math.isclose function #ifndef MICROPY_PY_MATH_ISCLOSE -#define MICROPY_PY_MATH_ISCLOSE (0) +#define MICROPY_PY_MATH_ISCLOSE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide fix for atan2 Inf handling. @@ -1194,12 +1283,12 @@ typedef double mp_float_t; // Whether to provide "cmath" module #ifndef MICROPY_PY_CMATH -#define MICROPY_PY_CMATH (0) +#define MICROPY_PY_CMATH (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide "gc" module #ifndef MICROPY_PY_GC -#define MICROPY_PY_GC (1) +#define MICROPY_PY_GC (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to return number of collected objects from gc.collect() @@ -1209,28 +1298,17 @@ typedef double mp_float_t; // Whether to provide "io" module #ifndef MICROPY_PY_IO -#define MICROPY_PY_IO (1) +#define MICROPY_PY_IO (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to provide "io.IOBase" class to support user streams #ifndef MICROPY_PY_IO_IOBASE -#define MICROPY_PY_IO_IOBASE (0) -#endif - -// Whether to provide "uio.resource_stream()" function with -// the semantics of CPython's pkg_resources.resource_stream() -// (allows to access binary resources in frozen source packages). -// Note that the same functionality can be achieved in "pure -// Python" by preprocessing binary resources into Python source -// and bytecode-freezing it (with a simple helper module available -// e.g. in micropython-lib). -#ifndef MICROPY_PY_IO_RESOURCE_STREAM -#define MICROPY_PY_IO_RESOURCE_STREAM (0) +#define MICROPY_PY_IO_IOBASE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide "io.FileIO" class #ifndef MICROPY_PY_IO_FILEIO -#define MICROPY_PY_IO_FILEIO (0) +#define MICROPY_PY_IO_FILEIO (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide "io.BytesIO" class @@ -1245,17 +1323,22 @@ typedef double mp_float_t; // Whether to provide "struct" module #ifndef MICROPY_PY_STRUCT -#define MICROPY_PY_STRUCT (1) +#define MICROPY_PY_STRUCT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to provide "sys" module #ifndef MICROPY_PY_SYS -#define MICROPY_PY_SYS (1) +#define MICROPY_PY_SYS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) +#endif + +// Whether to initialise "sys.path" and "sys.argv" to their defaults in mp_init() +#ifndef MICROPY_PY_SYS_PATH_ARGV_DEFAULTS +#define MICROPY_PY_SYS_PATH_ARGV_DEFAULTS (MICROPY_PY_SYS) #endif // Whether to provide "sys.maxsize" constant #ifndef MICROPY_PY_SYS_MAXSIZE -#define MICROPY_PY_SYS_MAXSIZE (0) +#define MICROPY_PY_SYS_MAXSIZE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide "sys.modules" dictionary @@ -1279,6 +1362,11 @@ typedef double mp_float_t; #define MICROPY_PY_SYS_ATEXIT (0) #endif +// Whether to provide sys.{ps1,ps2} mutable attributes, to control REPL prompts +#ifndef MICROPY_PY_SYS_PS1_PS2 +#define MICROPY_PY_SYS_PS1_PS2 (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#endif + // Whether to provide "sys.settrace" function #ifndef MICROPY_PY_SYS_SETTRACE #define MICROPY_PY_SYS_SETTRACE (0) @@ -1291,18 +1379,29 @@ typedef double mp_float_t; // Whether to provide sys.{stdin,stdout,stderr} objects #ifndef MICROPY_PY_SYS_STDFILES -#define MICROPY_PY_SYS_STDFILES (0) +#define MICROPY_PY_SYS_STDFILES (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide sys.{stdin,stdout,stderr}.buffer object // This is implemented per-port #ifndef MICROPY_PY_SYS_STDIO_BUFFER -#define MICROPY_PY_SYS_STDIO_BUFFER (0) +#define MICROPY_PY_SYS_STDIO_BUFFER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#endif + +// Whether to provide sys.tracebacklimit mutable attribute +#ifndef MICROPY_PY_SYS_TRACEBACKLIMIT +#define MICROPY_PY_SYS_TRACEBACKLIMIT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING) +#endif + +// Whether the sys module supports attribute delegation +// This is enabled automatically when needed by other features +#ifndef MICROPY_PY_SYS_ATTR_DELEGATION +#define MICROPY_PY_SYS_ATTR_DELEGATION (MICROPY_PY_SYS_PS1_PS2 || MICROPY_PY_SYS_TRACEBACKLIMIT) #endif // Whether to provide "uerrno" module #ifndef MICROPY_PY_UERRNO -#define MICROPY_PY_UERRNO (0) +#define MICROPY_PY_UERRNO (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide the uerrno.errorcode dict @@ -1312,7 +1411,7 @@ typedef double mp_float_t; // Whether to provide "uselect" module (baremetal implementation) #ifndef MICROPY_PY_USELECT -#define MICROPY_PY_USELECT (0) +#define MICROPY_PY_USELECT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to enable the select() function in the "uselect" module (baremetal @@ -1358,11 +1457,11 @@ typedef double mp_float_t; // Extended modules #ifndef MICROPY_PY_UASYNCIO -#define MICROPY_PY_UASYNCIO (0) +#define MICROPY_PY_UASYNCIO (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif #ifndef MICROPY_PY_UCTYPES -#define MICROPY_PY_UCTYPES (0) +#define MICROPY_PY_UCTYPES (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide SHORT, INT, LONG, etc. types in addition to @@ -1372,11 +1471,11 @@ typedef double mp_float_t; #endif #ifndef MICROPY_PY_UZLIB -#define MICROPY_PY_UZLIB (0) +#define MICROPY_PY_UZLIB (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif #ifndef MICROPY_PY_UJSON -#define MICROPY_PY_UJSON (0) +#define MICROPY_PY_UJSON (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to support the "separators" argument to dump, dumps @@ -1384,8 +1483,16 @@ typedef double mp_float_t; #define MICROPY_PY_UJSON_SEPARATORS (1) #endif +#ifndef MICROPY_PY_UOS +#define MICROPY_PY_UOS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#endif + +#ifndef MICROPY_PY_UOS_STATVFS +#define MICROPY_PY_UOS_STATVFS (MICROPY_PY_UOS) +#endif + #ifndef MICROPY_PY_URE -#define MICROPY_PY_URE (0) +#define MICROPY_PY_URE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif #ifndef MICROPY_PY_URE_DEBUG @@ -1401,20 +1508,20 @@ typedef double mp_float_t; #endif #ifndef MICROPY_PY_URE_SUB -#define MICROPY_PY_URE_SUB (0) +#define MICROPY_PY_URE_SUB (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif #ifndef MICROPY_PY_UHEAPQ -#define MICROPY_PY_UHEAPQ (0) +#define MICROPY_PY_UHEAPQ (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif -// Optimized heap queue for relative timestamps +// Optimized heap queue for relative timestamps (only used by uasyncio v2) #ifndef MICROPY_PY_UTIMEQ #define MICROPY_PY_UTIMEQ (0) #endif #ifndef MICROPY_PY_UHASHLIB -#define MICROPY_PY_UHASHLIB (0) +#define MICROPY_PY_UHASHLIB (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif #ifndef MICROPY_PY_UHASHLIB_MD5 @@ -1443,21 +1550,21 @@ typedef double mp_float_t; #endif #ifndef MICROPY_PY_UBINASCII -#define MICROPY_PY_UBINASCII (0) +#define MICROPY_PY_UBINASCII (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Depends on MICROPY_PY_UZLIB #ifndef MICROPY_PY_UBINASCII_CRC32 -#define MICROPY_PY_UBINASCII_CRC32 (0) +#define MICROPY_PY_UBINASCII_CRC32 (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif #ifndef MICROPY_PY_URANDOM -#define MICROPY_PY_URANDOM (0) +#define MICROPY_PY_URANDOM (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to include: randrange, randint, choice, random, uniform #ifndef MICROPY_PY_URANDOM_EXTRA_FUNCS -#define MICROPY_PY_URANDOM_EXTRA_FUNCS (0) +#define MICROPY_PY_URANDOM_EXTRA_FUNCS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif #ifndef MICROPY_PY_MACHINE @@ -1478,13 +1585,36 @@ typedef double mp_float_t; #define MICROPY_PY_MACHINE_I2C (0) #endif +// Whether the low-level I2C transfer function supports a separate write as the first transfer +#ifndef MICROPY_PY_MACHINE_I2C_TRANSFER_WRITE1 +#define MICROPY_PY_MACHINE_I2C_TRANSFER_WRITE1 (0) +#endif + +// Whether to provide the "machine.SoftI2C" class +#ifndef MICROPY_PY_MACHINE_SOFTI2C +#define MICROPY_PY_MACHINE_SOFTI2C (0) +#endif + #ifndef MICROPY_PY_MACHINE_SPI #define MICROPY_PY_MACHINE_SPI (0) #endif +// Whether to provide the "machine.SoftSPI" class +#ifndef MICROPY_PY_MACHINE_SOFTSPI +#define MICROPY_PY_MACHINE_SOFTSPI (0) +#endif + +// The default backlog value for socket.listen(backlog) +#ifndef MICROPY_PY_USOCKET_LISTEN_BACKLOG_DEFAULT +#define MICROPY_PY_USOCKET_LISTEN_BACKLOG_DEFAULT (2) +#endif + #ifndef MICROPY_PY_USSL #define MICROPY_PY_USSL (0) +#endif + // Whether to add finaliser code to ussl objects +#ifndef MICROPY_PY_USSL_FINALISER #define MICROPY_PY_USSL_FINALISER (0) #endif @@ -1493,13 +1623,18 @@ typedef double mp_float_t; #endif #ifndef MICROPY_PY_FRAMEBUF -#define MICROPY_PY_FRAMEBUF (0) +#define MICROPY_PY_FRAMEBUF (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif #ifndef MICROPY_PY_BTREE #define MICROPY_PY_BTREE (0) #endif +// Whether to provide the low-level "_onewire" module +#ifndef MICROPY_PY_ONEWIRE +#define MICROPY_PY_ONEWIRE (0) +#endif + /*****************************************************************************/ /* Hooks for a port to add builtins */ @@ -1508,9 +1643,10 @@ typedef double mp_float_t; #define MICROPY_PORT_BUILTINS #endif -// Additional builtin module definitions - see objmodule.c:mp_builtin_module_table for format. -#ifndef MICROPY_PORT_BUILTIN_MODULES -#define MICROPY_PORT_BUILTIN_MODULES +// Additional builtin function definitions for extension by command-line, boards or variants. +// See modbuiltins.c:mp_module_builtins_globals_table for format. +#ifndef MICROPY_PORT_EXTRA_BUILTINS +#define MICROPY_PORT_EXTRA_BUILTINS #endif // Additional constant definitions for the compiler - see compile.c:mp_constants_table. @@ -1526,6 +1662,30 @@ typedef double mp_float_t; /*****************************************************************************/ /* Hooks for a port to wrap functions with attributes */ +#ifndef MICROPY_WRAP_MP_BINARY_OP +#define MICROPY_WRAP_MP_BINARY_OP(f) f +#endif + +#ifndef MICROPY_WRAP_MP_EXECUTE_BYTECODE +#define MICROPY_WRAP_MP_EXECUTE_BYTECODE(f) f +#endif + +#ifndef MICROPY_WRAP_MP_LOAD_GLOBAL +#define MICROPY_WRAP_MP_LOAD_GLOBAL(f) f +#endif + +#ifndef MICROPY_WRAP_MP_LOAD_NAME +#define MICROPY_WRAP_MP_LOAD_NAME(f) f +#endif + +#ifndef MICROPY_WRAP_MP_MAP_LOOKUP +#define MICROPY_WRAP_MP_MAP_LOOKUP(f) f +#endif + +#ifndef MICROPY_WRAP_MP_OBJ_GET_TYPE +#define MICROPY_WRAP_MP_OBJ_GET_TYPE(f) f +#endif + #ifndef MICROPY_WRAP_MP_SCHED_EXCEPTION #define MICROPY_WRAP_MP_SCHED_EXCEPTION(f) f #endif @@ -1549,6 +1709,20 @@ typedef double mp_float_t; #define MICROPY_OBJ_BASE_ALIGNMENT #endif +// String used for the banner, and sys.version additional information +#ifndef MICROPY_BANNER_NAME_AND_VERSION +#define MICROPY_BANNER_NAME_AND_VERSION "MicroPython " MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE +#endif + +// String used for the second part of the banner, and sys.implementation._machine +#ifndef MICROPY_BANNER_MACHINE +#ifdef MICROPY_HW_BOARD_NAME +#define MICROPY_BANNER_MACHINE MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME +#else +#define MICROPY_BANNER_MACHINE MICROPY_PY_SYS_PLATFORM " [" MICROPY_PLATFORM_COMPILER "] version" +#endif +#endif + // On embedded platforms, these will typically enable/disable irqs. #ifndef MICROPY_BEGIN_ATOMIC_SECTION #define MICROPY_BEGIN_ATOMIC_SECTION() (0) diff --git a/python/src/py/mpstate.h b/python/src/py/mpstate.h index 07335bae4c5..98aa9a84989 100644 --- a/python/src/py/mpstate.h +++ b/python/src/py/mpstate.h @@ -40,12 +40,21 @@ // memory system, runtime and virtual machine. The state is a global // variable, but in the future it is hoped that the state can become local. +enum { + #if MICROPY_PY_SYS_PS1_PS2 + MP_SYS_MUTABLE_PS1, + MP_SYS_MUTABLE_PS2, + #endif + #if MICROPY_PY_SYS_TRACEBACKLIMIT + MP_SYS_MUTABLE_TRACEBACKLIMIT, + #endif + MP_SYS_MUTABLE_NUM, +}; + // This structure contains dynamic configuration for the compiler. #if MICROPY_DYNAMIC_COMPILER typedef struct mp_dynamic_compiler_t { uint8_t small_int_bits; // must be <= host small_int_bits - bool opt_cache_map_lookup_in_bytecode; - bool py_builtins_str_unicode; uint8_t native_arch; uint8_t nlr_buf_num_regs; } mp_dynamic_compiler_t; @@ -115,6 +124,10 @@ typedef struct _mp_state_vm_t { qstr_pool_t *last_pool; + #if MICROPY_TRACKED_ALLOC + struct _m_tracked_node_t *m_tracked_head; + #endif + // non-heap memory for creating an exception if we can't allocate RAM mp_obj_exception_t mp_emergency_exception_obj; @@ -154,10 +167,18 @@ typedef struct _mp_state_vm_t { // dictionary for the __main__ module mp_obj_dict_t dict_main; - // these two lists must be initialised per port, after the call to mp_init + #if MICROPY_PY_SYS + // If MICROPY_PY_SYS_PATH_ARGV_DEFAULTS is not enabled then these two lists + // must be initialised after the call to mp_init. mp_obj_list_t mp_sys_path_obj; mp_obj_list_t mp_sys_argv_obj; + #if MICROPY_PY_SYS_ATTR_DELEGATION + // Contains mutable sys attributes. + mp_obj_t sys_mutable[MP_SYS_MUTABLE_NUM]; + #endif + #endif + // dictionary for overridden builtins #if MICROPY_CAN_OVERRIDE_BUILTINS mp_obj_dict_t *mp_module_builtins_override_dict; @@ -200,7 +221,7 @@ typedef struct _mp_state_vm_t { // pointer and sizes to store interned string data // (qstr_last_chunk can be root pointer but is also stored in qstr pool) - byte *qstr_last_chunk; + char *qstr_last_chunk; size_t qstr_last_alloc; size_t qstr_last_used; @@ -223,6 +244,16 @@ typedef struct _mp_state_vm_t { #if MICROPY_ENABLE_SCHEDULER volatile int16_t sched_state; + + #if MICROPY_SCHEDULER_STATIC_NODES + // These will usually point to statically allocated memory. They are not + // traced by the GC. They are assumed to be zero'd out before mp_init() is + // called (usually because this struct lives in the BSS). + struct _mp_sched_node_t *sched_head; + struct _mp_sched_node_t *sched_tail; + #endif + + // These index sched_queue. uint8_t sched_len; uint8_t sched_idx; #endif @@ -231,6 +262,11 @@ typedef struct _mp_state_vm_t { // This is a global mutex used to make the VM/runtime thread-safe. mp_thread_mutex_t gil_mutex; #endif + + #if MICROPY_OPT_MAP_LOOKUP_CACHE + // See mp_map_lookup. + uint8_t map_lookup_cache[MICROPY_OPT_MAP_LOOKUP_CACHE_SIZE]; + #endif } mp_state_vm_t; // This structure holds state that is specific to a given thread. diff --git a/python/src/py/mpz.c b/python/src/py/mpz.c index 75e1fb1fdb6..b61997e2fd4 100644 --- a/python/src/py/mpz.c +++ b/python/src/py/mpz.c @@ -713,6 +713,7 @@ void mpz_set(mpz_t *dest, const mpz_t *src) { void mpz_set_from_int(mpz_t *z, mp_int_t val) { if (val == 0) { + z->neg = 0; z->len = 0; return; } @@ -899,10 +900,6 @@ bool mpz_is_even(const mpz_t *z) { #endif int mpz_cmp(const mpz_t *z1, const mpz_t *z2) { - // to catch comparison of -0 with +0 - if (z1->len == 0 && z2->len == 0) { - return 0; - } int cmp = (int)z2->neg - (int)z1->neg; if (cmp != 0) { return cmp; @@ -1052,7 +1049,9 @@ void mpz_neg_inpl(mpz_t *dest, const mpz_t *z) { if (dest != z) { mpz_set(dest, z); } - dest->neg = 1 - dest->neg; + if (dest->len) { + dest->neg = 1 - dest->neg; + } } /* computes dest = ~z (= -z - 1) @@ -1148,7 +1147,7 @@ void mpz_add_inpl(mpz_t *dest, const mpz_t *lhs, const mpz_t *rhs) { dest->len = mpn_sub(dest->dig, lhs->dig, lhs->len, rhs->dig, rhs->len); } - dest->neg = lhs->neg; + dest->neg = lhs->neg & !!dest->len; } /* computes dest = lhs - rhs @@ -1172,7 +1171,9 @@ void mpz_sub_inpl(mpz_t *dest, const mpz_t *lhs, const mpz_t *rhs) { dest->len = mpn_sub(dest->dig, lhs->dig, lhs->len, rhs->dig, rhs->len); } - if (neg) { + if (dest->len == 0) { + dest->neg = 0; + } else if (neg) { dest->neg = 1 - lhs->neg; } else { dest->neg = lhs->neg; @@ -1484,14 +1485,16 @@ void mpz_divmod_inpl(mpz_t *dest_quo, mpz_t *dest_rem, const mpz_t *lhs, const m mpz_need_dig(dest_quo, lhs->len + 1); // +1 necessary? memset(dest_quo->dig, 0, (lhs->len + 1) * sizeof(mpz_dig_t)); + dest_quo->neg = 0; dest_quo->len = 0; mpz_need_dig(dest_rem, lhs->len + 1); // +1 necessary? mpz_set(dest_rem, lhs); mpn_div(dest_rem->dig, &dest_rem->len, rhs->dig, rhs->len, dest_quo->dig, &dest_quo->len); + dest_rem->neg &= !!dest_rem->len; // check signs and do Python style modulo if (lhs->neg != rhs->neg) { - dest_quo->neg = 1; + dest_quo->neg = !!dest_quo->len; if (!mpz_is_zero(dest_rem)) { mpz_t mpzone; mpz_init_from_int(&mpzone, -1); diff --git a/python/src/py/mpz.h b/python/src/py/mpz.h index 425587ee9b3..d27f5724047 100644 --- a/python/src/py/mpz.h +++ b/python/src/py/mpz.h @@ -91,6 +91,7 @@ typedef int8_t mpz_dbl_dig_signed_t; #define MPZ_NUM_DIG_FOR_LL ((sizeof(long long) * 8 + MPZ_DIG_SIZE - 1) / MPZ_DIG_SIZE) typedef struct _mpz_t { + // Zero has neg=0, len=0. Negative zero is not allowed. size_t neg : 1; size_t fixed_dig : 1; size_t alloc : (8 * sizeof(size_t) - 2); @@ -119,7 +120,7 @@ static inline bool mpz_is_zero(const mpz_t *z) { return z->len == 0; } static inline bool mpz_is_neg(const mpz_t *z) { - return z->len != 0 && z->neg != 0; + return z->neg != 0; } int mpz_cmp(const mpz_t *lhs, const mpz_t *rhs); diff --git a/python/src/py/nativeglue.c b/python/src/py/nativeglue.c index 30e5b40061b..743ff38ccbb 100644 --- a/python/src/py/nativeglue.c +++ b/python/src/py/nativeglue.c @@ -300,9 +300,9 @@ const mp_fun_table_t mp_fun_table = { mp_unpack_ex, mp_delete_name, mp_delete_global, - mp_make_closure_from_raw_code, + mp_obj_new_closure, mp_arg_check_num_sig, - mp_setup_code_state, + mp_setup_code_state_native, mp_small_int_floor_divide, mp_small_int_modulo, mp_native_yield_from, @@ -344,4 +344,8 @@ const mp_fun_table_t mp_fun_table = { &mp_stream_write_obj, }; +#elif MICROPY_EMIT_NATIVE && MICROPY_DYNAMIC_COMPILER + +const int mp_fun_table; + #endif // MICROPY_EMIT_NATIVE diff --git a/python/src/py/nativeglue.h b/python/src/py/nativeglue.h index 9d9a97b9e79..7b1ccd8d464 100644 --- a/python/src/py/nativeglue.h +++ b/python/src/py/nativeglue.h @@ -75,7 +75,7 @@ typedef enum { MP_F_UNPACK_EX, MP_F_DELETE_NAME, MP_F_DELETE_GLOBAL, - MP_F_MAKE_CLOSURE_FROM_RAW_CODE, + MP_F_NEW_CLOSURE, MP_F_ARG_CHECK_NUM_SIG, MP_F_SETUP_CODE_STATE, MP_F_SMALL_INT_FLOOR_DIVIDE, @@ -112,7 +112,7 @@ typedef struct _mp_fun_table_t { void (*set_store)(mp_obj_t self_in, mp_obj_t item); mp_obj_t (*list_append)(mp_obj_t self_in, mp_obj_t arg); mp_obj_t (*dict_store)(mp_obj_t self_in, mp_obj_t key, mp_obj_t value); - mp_obj_t (*make_function_from_raw_code)(const mp_raw_code_t *rc, mp_obj_t def_args, mp_obj_t def_kw_args); + mp_obj_t (*make_function_from_raw_code)(const mp_raw_code_t *rc, const mp_module_context_t *cm, const mp_obj_t *def_args); mp_obj_t (*call_function_n_kw)(mp_obj_t fun_in, size_t n_args_kw, const mp_obj_t *args); mp_obj_t (*call_method_n_kw)(size_t n_args, size_t n_kw, const mp_obj_t *args); mp_obj_t (*call_method_n_kw_var)(bool have_self, size_t n_args_n_kw, const mp_obj_t *args); @@ -129,9 +129,9 @@ typedef struct _mp_fun_table_t { void (*unpack_ex)(mp_obj_t seq, size_t num, mp_obj_t *items); void (*delete_name)(qstr qst); void (*delete_global)(qstr qst); - mp_obj_t (*make_closure_from_raw_code)(const mp_raw_code_t *rc, mp_uint_t n_closed_over, const mp_obj_t *args); + mp_obj_t (*new_closure)(mp_obj_t fun, size_t n_closed_over, const mp_obj_t *closed); void (*arg_check_num_sig)(size_t n_args, size_t n_kw, uint32_t sig); - void (*setup_code_state)(mp_code_state_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args); + void (*setup_code_state_native)(mp_code_state_native_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args); mp_int_t (*small_int_floor_divide)(mp_int_t num, mp_int_t denom); mp_int_t (*small_int_modulo)(mp_int_t dividend, mp_int_t divisor); bool (*yield_from)(mp_obj_t gen, mp_obj_t send_value, mp_obj_t *ret_value); @@ -172,6 +172,12 @@ typedef struct _mp_fun_table_t { const mp_obj_fun_builtin_var_t *stream_write_obj; } mp_fun_table_t; +#if (MICROPY_EMIT_NATIVE && !MICROPY_DYNAMIC_COMPILER) || MICROPY_ENABLE_DYNRUNTIME extern const mp_fun_table_t mp_fun_table; +#elif MICROPY_EMIT_NATIVE && MICROPY_DYNAMIC_COMPILER +// In dynamic-compiler mode eliminate dependency on entries in mp_fun_table. +// This only needs to be an independent pointer, content doesn't matter. +extern const int mp_fun_table; +#endif #endif // MICROPY_INCLUDED_PY_NATIVEGLUE_H diff --git a/python/src/py/nlr.h b/python/src/py/nlr.h index 9f12ede9cd3..dade10771ee 100644 --- a/python/src/py/nlr.h +++ b/python/src/py/nlr.h @@ -167,7 +167,7 @@ NORETURN void nlr_jump_fail(void *val); #if !MICROPY_NLR_SETJMP #define nlr_push(val) \ - assert(MP_STATE_THREAD(nlr_top) != val),nlr_push(val) + assert(MP_STATE_THREAD(nlr_top) != val), nlr_push(val) /* #define nlr_push(val) \ diff --git a/python/src/py/obj.c b/python/src/py/obj.c index e7a23be5321..024de3c96cd 100644 --- a/python/src/py/obj.c +++ b/python/src/py/obj.c @@ -37,7 +37,14 @@ #include "py/stackctrl.h" #include "py/stream.h" // for mp_obj_print -const mp_obj_type_t *mp_obj_get_type(mp_const_obj_t o_in) { +// Allocates an object and also sets type, for mp_obj_malloc{,_var} macros. +void *mp_obj_malloc_helper(size_t num_bytes, const mp_obj_type_t *type) { + mp_obj_base_t *base = (mp_obj_base_t *)m_malloc(num_bytes); + base->type = type; + return base; +} + +const mp_obj_type_t *MICROPY_WRAP_MP_OBJ_GET_TYPE(mp_obj_get_type)(mp_const_obj_t o_in) { #if MICROPY_OBJ_IMMEDIATE_OBJS && MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_A if (mp_obj_is_obj(o_in)) { @@ -279,7 +286,7 @@ mp_obj_t mp_obj_equal_not_equal(mp_binary_op_t op, mp_obj_t o1, mp_obj_t o2) { o2 = temp; } - // equality not implemented, so fall back to pointer comparison + // equality not implemented, so fall back to pointer conparison return (o1 == o2) ? local_true : local_false; } diff --git a/python/src/py/obj.h b/python/src/py/obj.h index 11918ba1765..29cd1855c17 100644 --- a/python/src/py/obj.h +++ b/python/src/py/obj.h @@ -104,8 +104,18 @@ static inline bool mp_obj_is_immediate_obj(mp_const_obj_t o) { #if MICROPY_PY_BUILTINS_FLOAT #define mp_const_float_e MP_ROM_PTR(&mp_const_float_e_obj) #define mp_const_float_pi MP_ROM_PTR(&mp_const_float_pi_obj) +#if MICROPY_PY_MATH_CONSTANTS +#define mp_const_float_tau MP_ROM_PTR(&mp_const_float_tau_obj) +#define mp_const_float_inf MP_ROM_PTR(&mp_const_float_inf_obj) +#define mp_const_float_nan MP_ROM_PTR(&mp_const_float_nan_obj) +#endif extern const struct _mp_obj_float_t mp_const_float_e_obj; extern const struct _mp_obj_float_t mp_const_float_pi_obj; +#if MICROPY_PY_MATH_CONSTANTS +extern const struct _mp_obj_float_t mp_const_float_tau_obj; +extern const struct _mp_obj_float_t mp_const_float_inf_obj; +extern const struct _mp_obj_float_t mp_const_float_nan_obj; +#endif #define mp_obj_is_float(o) mp_obj_is_type((o), &mp_type_float) mp_float_t mp_obj_float_get(mp_obj_t self_in); @@ -139,8 +149,18 @@ static inline bool mp_obj_is_immediate_obj(mp_const_obj_t o) { #if MICROPY_PY_BUILTINS_FLOAT #define mp_const_float_e MP_ROM_PTR(&mp_const_float_e_obj) #define mp_const_float_pi MP_ROM_PTR(&mp_const_float_pi_obj) +#if MICROPY_PY_MATH_CONSTANTS +#define mp_const_float_tau MP_ROM_PTR(&mp_const_float_tau_obj) +#define mp_const_float_inf MP_ROM_PTR(&mp_const_float_inf_obj) +#define mp_const_float_nan MP_ROM_PTR(&mp_const_float_nan_obj) +#endif extern const struct _mp_obj_float_t mp_const_float_e_obj; extern const struct _mp_obj_float_t mp_const_float_pi_obj; +#if MICROPY_PY_MATH_CONSTANTS +extern const struct _mp_obj_float_t mp_const_float_tau_obj; +extern const struct _mp_obj_float_t mp_const_float_inf_obj; +extern const struct _mp_obj_float_t mp_const_float_nan_obj; +#endif #define mp_obj_is_float(o) mp_obj_is_type((o), &mp_type_float) mp_float_t mp_obj_float_get(mp_obj_t self_in); @@ -162,6 +182,11 @@ static inline bool mp_obj_is_small_int(mp_const_obj_t o) { #if MICROPY_PY_BUILTINS_FLOAT #define mp_const_float_e MP_ROM_PTR((mp_obj_t)(((0x402df854 & ~3) | 2) + 0x80800000)) #define mp_const_float_pi MP_ROM_PTR((mp_obj_t)(((0x40490fdb & ~3) | 2) + 0x80800000)) +#if MICROPY_PY_MATH_CONSTANTS +#define mp_const_float_tau MP_ROM_PTR((mp_obj_t)(((0x40c90fdb & ~3) | 2) + 0x80800000)) +#define mp_const_float_inf MP_ROM_PTR((mp_obj_t)(((0x7f800000 & ~3) | 2) + 0x80800000)) +#define mp_const_float_nan MP_ROM_PTR((mp_obj_t)(((0xffc00000 & ~3) | 2) + 0x80800000)) +#endif static inline bool mp_obj_is_float(mp_const_obj_t o) { return (((mp_uint_t)(o)) & 3) == 2 && (((mp_uint_t)(o)) & 0xff800007) != 0x00000006; @@ -226,6 +251,11 @@ static inline bool mp_obj_is_immediate_obj(mp_const_obj_t o) { #define mp_const_float_e {((mp_obj_t)((uint64_t)0x4005bf0a8b145769 + 0x8004000000000000))} #define mp_const_float_pi {((mp_obj_t)((uint64_t)0x400921fb54442d18 + 0x8004000000000000))} +#if MICROPY_PY_MATH_CONSTANTS +#define mp_const_float_tau {((mp_obj_t)((uint64_t)0x401921fb54442d18 + 0x8004000000000000))} +#define mp_const_float_inf {((mp_obj_t)((uint64_t)0x7ff0000000000000 + 0x8004000000000000))} +#define mp_const_float_nan {((mp_obj_t)((uint64_t)0xfff8000000000000 + 0x8004000000000000))} +#endif static inline bool mp_obj_is_float(mp_const_obj_t o) { return ((uint64_t)(o) & 0xfffc000000000000) != 0; @@ -389,9 +419,10 @@ typedef struct _mp_rom_obj_t { mp_const_obj_t o; } mp_rom_obj_t; // Declare a module as a builtin, processed by makemoduledefs.py // param module_name: MP_QSTR_ // param obj_module: mp_obj_module_t instance -// prarm enabled_define: used as `#if (enabled_define) around entry` -#define MP_REGISTER_MODULE(module_name, obj_module, enabled_define) +#ifndef NO_QSTR +#define MP_REGISTER_MODULE(module_name, obj_module) +#endif // Underlying map/hash table implementation (not dict object or map function) @@ -551,6 +582,7 @@ struct _mp_obj_type_t { // // dest[0] = MP_OBJ_NULL means load // return: for fail, do nothing + // for fail but continue lookup in locals_dict, dest[1] = MP_OBJ_SENTINEL // for attr, dest[0] = value // for method, dest[0] = method, dest[1] = self // @@ -701,6 +733,12 @@ extern const struct _mp_obj_exception_t mp_const_GeneratorExit_obj; // General API for objects +// Helper versions of m_new_obj when you need to immediately set base.type. +// Implementing this as a call rather than inline saves 8 bytes per usage. +#define mp_obj_malloc(struct_type, obj_type) ((struct_type *)mp_obj_malloc_helper(sizeof(struct_type), obj_type)) +#define mp_obj_malloc_var(struct_type, var_type, var_num, obj_type) ((struct_type *)mp_obj_malloc_helper(sizeof(struct_type) + sizeof(var_type) * (var_num), obj_type)) +void *mp_obj_malloc_helper(size_t num_bytes, const mp_obj_type_t *type); + // These macros are derived from more primitive ones and are used to // check for more specific object types. // Note: these are kept as macros because inline functions sometimes use much @@ -750,9 +788,6 @@ mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, mp_rom_err #ifdef va_start mp_obj_t mp_obj_new_exception_msg_vlist(const mp_obj_type_t *exc_type, mp_rom_error_text_t fmt, va_list arg); // same fmt restrictions as above #endif -mp_obj_t mp_obj_new_fun_bc(mp_obj_t def_args, mp_obj_t def_kw_args, const byte *code, const mp_uint_t *const_table); -mp_obj_t mp_obj_new_fun_native(mp_obj_t def_args_in, mp_obj_t def_kw_args, const void *fun_data, const mp_uint_t *const_table); -mp_obj_t mp_obj_new_fun_asm(size_t n_args, const void *fun_data, mp_uint_t type_sig); mp_obj_t mp_obj_new_gen_wrap(mp_obj_t fun); mp_obj_t mp_obj_new_closure(mp_obj_t fun, size_t n_closed, const mp_obj_t *closed); mp_obj_t mp_obj_new_tuple(size_t n, const mp_obj_t *items); @@ -961,7 +996,6 @@ typedef struct _mp_obj_fun_builtin_var_t { } mp_obj_fun_builtin_var_t; qstr mp_obj_fun_get_name(mp_const_obj_t fun); -qstr mp_obj_code_get_name(const byte *code_info); mp_obj_t mp_identity(mp_obj_t self); MP_DECLARE_CONST_FUN_OBJ_1(mp_identity_obj); diff --git a/python/src/py/objarray.c b/python/src/py/objarray.c index 16a4d4aac7e..bff3126a2bc 100644 --- a/python/src/py/objarray.c +++ b/python/src/py/objarray.c @@ -639,8 +639,7 @@ mp_obj_t mp_obj_new_bytearray(size_t n, void *items) { // Create bytearray which references specified memory area mp_obj_t mp_obj_new_bytearray_by_ref(size_t n, void *items) { - mp_obj_array_t *o = m_new_obj(mp_obj_array_t); - o->base.type = &mp_type_bytearray; + mp_obj_array_t *o = mp_obj_malloc(mp_obj_array_t, &mp_type_bytearray); o->typecode = BYTEARRAY_TYPECODE; o->free = 0; o->len = n; diff --git a/python/src/py/objattrtuple.c b/python/src/py/objattrtuple.c index 3422d0146ab..13c281aa1c6 100644 --- a/python/src/py/objattrtuple.c +++ b/python/src/py/objattrtuple.c @@ -71,8 +71,7 @@ STATIC void mp_obj_attrtuple_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { } mp_obj_t mp_obj_new_attrtuple(const qstr *fields, size_t n, const mp_obj_t *items) { - mp_obj_tuple_t *o = m_new_obj_var(mp_obj_tuple_t, mp_obj_t, n + 1); - o->base.type = &mp_type_attrtuple; + mp_obj_tuple_t *o = mp_obj_malloc_var(mp_obj_tuple_t, mp_obj_t, n + 1, &mp_type_attrtuple); o->len = n; for (size_t i = 0; i < n; i++) { o->items[i] = items[i]; diff --git a/python/src/py/objboundmeth.c b/python/src/py/objboundmeth.c index a3e1d302d96..9936c06e494 100644 --- a/python/src/py/objboundmeth.c +++ b/python/src/py/objboundmeth.c @@ -108,8 +108,7 @@ STATIC const mp_obj_type_t mp_type_bound_meth = { }; mp_obj_t mp_obj_new_bound_meth(mp_obj_t meth, mp_obj_t self) { - mp_obj_bound_meth_t *o = m_new_obj(mp_obj_bound_meth_t); - o->base.type = &mp_type_bound_meth; + mp_obj_bound_meth_t *o = mp_obj_malloc(mp_obj_bound_meth_t, &mp_type_bound_meth); o->meth = meth; o->self = self; return MP_OBJ_FROM_PTR(o); diff --git a/python/src/py/objcell.c b/python/src/py/objcell.c index be2ae8cd9c7..2702ca53505 100644 --- a/python/src/py/objcell.c +++ b/python/src/py/objcell.c @@ -64,8 +64,7 @@ STATIC const mp_obj_type_t mp_type_cell = { }; mp_obj_t mp_obj_new_cell(mp_obj_t obj) { - mp_obj_cell_t *o = m_new_obj(mp_obj_cell_t); - o->base.type = &mp_type_cell; + mp_obj_cell_t *o = mp_obj_malloc(mp_obj_cell_t, &mp_type_cell); o->obj = obj; return MP_OBJ_FROM_PTR(o); } diff --git a/python/src/py/objclosure.c b/python/src/py/objclosure.c index 054b6578965..9dc3e545321 100644 --- a/python/src/py/objclosure.c +++ b/python/src/py/objclosure.c @@ -89,8 +89,7 @@ const mp_obj_type_t mp_type_closure = { }; mp_obj_t mp_obj_new_closure(mp_obj_t fun, size_t n_closed_over, const mp_obj_t *closed) { - mp_obj_closure_t *o = m_new_obj_var(mp_obj_closure_t, mp_obj_t, n_closed_over); - o->base.type = &mp_type_closure; + mp_obj_closure_t *o = mp_obj_malloc_var(mp_obj_closure_t, mp_obj_t, n_closed_over, &mp_type_closure); o->fun = fun; o->n_closed = n_closed_over; memcpy(o->closed, closed, n_closed_over * sizeof(mp_obj_t)); diff --git a/python/src/py/objcomplex.c b/python/src/py/objcomplex.c index f4c4aeffcb9..56c8353e905 100644 --- a/python/src/py/objcomplex.c +++ b/python/src/py/objcomplex.c @@ -162,8 +162,7 @@ const mp_obj_type_t mp_type_complex = { }; mp_obj_t mp_obj_new_complex(mp_float_t real, mp_float_t imag) { - mp_obj_complex_t *o = m_new_obj(mp_obj_complex_t); - o->base.type = &mp_type_complex; + mp_obj_complex_t *o = mp_obj_malloc(mp_obj_complex_t, &mp_type_complex); o->real = real; o->imag = imag; return MP_OBJ_FROM_PTR(o); diff --git a/python/src/py/objdeque.c b/python/src/py/objdeque.c index c95bdeee9e3..b1c59a81e9f 100644 --- a/python/src/py/objdeque.c +++ b/python/src/py/objdeque.c @@ -57,8 +57,7 @@ STATIC mp_obj_t deque_make_new(const mp_obj_type_t *type, size_t n_args, size_t mp_raise_ValueError(NULL); } - mp_obj_deque_t *o = m_new_obj(mp_obj_deque_t); - o->base.type = type; + mp_obj_deque_t *o = mp_obj_malloc(mp_obj_deque_t, type); o->alloc = maxlen + 1; o->i_get = o->i_put = 0; o->items = m_new0(mp_obj_t, o->alloc); diff --git a/python/src/py/objdict.c b/python/src/py/objdict.c index ed4376aa4f0..1d8e9059a18 100644 --- a/python/src/py/objdict.c +++ b/python/src/py/objdict.c @@ -521,8 +521,7 @@ STATIC const mp_obj_type_t mp_type_dict_view = { }; STATIC mp_obj_t mp_obj_new_dict_view(mp_obj_t dict, mp_dict_view_kind_t kind) { - mp_obj_dict_view_t *o = m_new_obj(mp_obj_dict_view_t); - o->base.type = &mp_type_dict_view; + mp_obj_dict_view_t *o = mp_obj_malloc(mp_obj_dict_view_t, &mp_type_dict_view); o->dict = dict; o->kind = kind; return MP_OBJ_FROM_PTR(o); diff --git a/python/src/py/objenumerate.c b/python/src/py/objenumerate.c index d1de4add47b..241aef30236 100644 --- a/python/src/py/objenumerate.c +++ b/python/src/py/objenumerate.c @@ -54,14 +54,12 @@ STATIC mp_obj_t enumerate_make_new(const mp_obj_type_t *type, size_t n_args, siz MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&arg_vals); // create enumerate object - mp_obj_enumerate_t *o = m_new_obj(mp_obj_enumerate_t); - o->base.type = type; + mp_obj_enumerate_t *o = mp_obj_malloc(mp_obj_enumerate_t, type); o->iter = mp_getiter(arg_vals.iterable.u_obj, NULL); o->cur = arg_vals.start.u_int; #else mp_arg_check_num(n_args, n_kw, 1, 2, false); - mp_obj_enumerate_t *o = m_new_obj(mp_obj_enumerate_t); - o->base.type = type; + mp_obj_enumerate_t *o = mp_obj_malloc(mp_obj_enumerate_t, type); o->iter = mp_getiter(args[0], NULL); o->cur = n_args > 1 ? mp_obj_get_int(args[1]) : 0; #endif diff --git a/python/src/py/objexcept.c b/python/src/py/objexcept.c index 7a86c36471b..dca287bb6ed 100644 --- a/python/src/py/objexcept.c +++ b/python/src/py/objexcept.c @@ -575,6 +575,16 @@ void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, size_t line, qs // append this traceback info to traceback data // if memory allocation fails (eg because gc is locked), just return + #if MICROPY_PY_SYS_TRACEBACKLIMIT + mp_int_t max_traceback = MP_OBJ_SMALL_INT_VALUE(MP_STATE_VM(sys_mutable[MP_SYS_MUTABLE_TRACEBACKLIMIT])); + if (max_traceback <= 0) { + return; + } else if (self->traceback_data != NULL && self->traceback_len >= max_traceback * TRACEBACK_ENTRY_LEN) { + self->traceback_len -= TRACEBACK_ENTRY_LEN; + memmove(self->traceback_data, self->traceback_data + TRACEBACK_ENTRY_LEN, self->traceback_len * sizeof(self->traceback_data[0])); + } + #endif + if (self->traceback_data == NULL) { self->traceback_data = m_new_maybe(size_t, TRACEBACK_ENTRY_LEN); if (self->traceback_data == NULL) { diff --git a/python/src/py/objfilter.c b/python/src/py/objfilter.c index 41b2a3bc5f2..a402d8c6488 100644 --- a/python/src/py/objfilter.c +++ b/python/src/py/objfilter.c @@ -36,8 +36,7 @@ typedef struct _mp_obj_filter_t { STATIC mp_obj_t filter_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 2, 2, false); - mp_obj_filter_t *o = m_new_obj(mp_obj_filter_t); - o->base.type = type; + mp_obj_filter_t *o = mp_obj_malloc(mp_obj_filter_t, type); o->fun = args[0]; o->iter = mp_getiter(args[1], NULL); return MP_OBJ_FROM_PTR(o); diff --git a/python/src/py/objfloat.c b/python/src/py/objfloat.c index 5194dba51ee..0855baac838 100644 --- a/python/src/py/objfloat.c +++ b/python/src/py/objfloat.c @@ -54,6 +54,14 @@ typedef struct _mp_obj_float_t { const mp_obj_float_t mp_const_float_e_obj = {{&mp_type_float}, (mp_float_t)M_E}; const mp_obj_float_t mp_const_float_pi_obj = {{&mp_type_float}, (mp_float_t)M_PI}; +#if MICROPY_PY_MATH_CONSTANTS +#ifndef NAN +#error NAN macro is not defined +#endif +const mp_obj_float_t mp_const_float_tau_obj = {{&mp_type_float}, (mp_float_t)(2.0 * M_PI)}; +const mp_obj_float_t mp_const_float_inf_obj = {{&mp_type_float}, (mp_float_t)INFINITY}; +const mp_obj_float_t mp_const_float_nan_obj = {{&mp_type_float}, (mp_float_t)NAN}; +#endif #endif @@ -187,7 +195,8 @@ const mp_obj_type_t mp_type_float = { #if MICROPY_OBJ_REPR != MICROPY_OBJ_REPR_C && MICROPY_OBJ_REPR != MICROPY_OBJ_REPR_D mp_obj_t mp_obj_new_float(mp_float_t value) { - mp_obj_float_t *o = m_new(mp_obj_float_t, 1); + // Don't use mp_obj_malloc here to avoid extra function call overhead. + mp_obj_float_t *o = m_new_obj(mp_obj_float_t); o->base.type = &mp_type_float; o->value = value; return MP_OBJ_FROM_PTR(o); diff --git a/python/src/py/objfun.c b/python/src/py/objfun.c index d86a4d235a2..8f0c3eb6d4d 100644 --- a/python/src/py/objfun.c +++ b/python/src/py/objfun.c @@ -143,13 +143,13 @@ const mp_obj_type_t mp_type_fun_builtin_var = { /******************************************************************************/ /* byte code functions */ -qstr mp_obj_code_get_name(const byte *code_info) { +STATIC qstr mp_obj_code_get_name(const mp_obj_fun_bc_t *fun, const byte *code_info) { MP_BC_PRELUDE_SIZE_DECODE(code_info); - #if MICROPY_PERSISTENT_CODE - return code_info[0] | (code_info[1] << 8); - #else - return mp_decode_uint_value(code_info); + mp_uint_t name = mp_decode_uint_value(code_info); + #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE + name = fun->context->constants.qstr_table[name]; #endif + return name; } #if MICROPY_EMIT_NATIVE @@ -167,7 +167,7 @@ qstr mp_obj_fun_get_name(mp_const_obj_t fun_in) { const byte *bc = fun->bytecode; MP_BC_PRELUDE_SIG_DECODE(bc); - return mp_obj_code_get_name(bc); + return mp_obj_code_get_name(fun, bc); } #if MICROPY_CPYTHON_COMPAT @@ -209,7 +209,6 @@ STATIC void dump_args(const mp_obj_t *a, size_t sz) { #define INIT_CODESTATE(code_state, _fun_bc, _n_state, n_args, n_kw, args) \ code_state->fun_bc = _fun_bc; \ - code_state->ip = 0; \ code_state->n_state = _n_state; \ mp_setup_code_state(code_state, n_args, n_kw, args); \ code_state->old_globals = mp_globals_get(); @@ -240,7 +239,7 @@ mp_code_state_t *mp_obj_fun_bc_prepare_codestate(mp_obj_t self_in, size_t n_args INIT_CODESTATE(code_state, self, n_state, n_args, n_kw, args); // execute the byte code with the correct globals context - mp_globals_set(self->globals); + mp_globals_set(self->context->module.globals); return code_state; } @@ -285,7 +284,7 @@ STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const INIT_CODESTATE(code_state, self, n_state, n_args, n_kw, args); // execute the byte code with the correct globals context - mp_globals_set(self->globals); + mp_globals_set(self->context->module.globals); mp_vm_return_kind_t vm_return_kind = mp_execute_bytecode(code_state, MP_OBJ_NULL); mp_globals_set(code_state->old_globals); @@ -358,7 +357,7 @@ void mp_obj_fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { } if (attr == MP_QSTR___globals__) { mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in); - dest[0] = MP_OBJ_FROM_PTR(self->globals); + dest[0] = MP_OBJ_FROM_PTR(self->context->module.globals); } } #endif @@ -377,25 +376,28 @@ const mp_obj_type_t mp_type_fun_bc = { #endif }; -mp_obj_t mp_obj_new_fun_bc(mp_obj_t def_args_in, mp_obj_t def_kw_args, const byte *code, const mp_uint_t *const_table) { +mp_obj_t mp_obj_new_fun_bc(const mp_obj_t *def_args, const byte *code, const mp_module_context_t *context, struct _mp_raw_code_t *const *child_table) { size_t n_def_args = 0; size_t n_extra_args = 0; - mp_obj_tuple_t *def_args = MP_OBJ_TO_PTR(def_args_in); - if (def_args_in != MP_OBJ_NULL) { - assert(mp_obj_is_type(def_args_in, &mp_type_tuple)); - n_def_args = def_args->len; - n_extra_args = def_args->len; + mp_obj_tuple_t *def_pos_args = NULL; + mp_obj_t def_kw_args = MP_OBJ_NULL; + if (def_args != NULL && def_args[0] != MP_OBJ_NULL) { + assert(mp_obj_is_type(def_args[0], &mp_type_tuple)); + def_pos_args = MP_OBJ_TO_PTR(def_args[0]); + n_def_args = def_pos_args->len; + n_extra_args = def_pos_args->len; } - if (def_kw_args != MP_OBJ_NULL) { + if (def_args != NULL && def_args[1] != MP_OBJ_NULL) { + assert(mp_obj_is_type(def_args[1], &mp_type_dict)); + def_kw_args = def_args[1]; n_extra_args += 1; } - mp_obj_fun_bc_t *o = m_new_obj_var(mp_obj_fun_bc_t, mp_obj_t, n_extra_args); - o->base.type = &mp_type_fun_bc; - o->globals = mp_globals_get(); + mp_obj_fun_bc_t *o = mp_obj_malloc_var(mp_obj_fun_bc_t, mp_obj_t, n_extra_args, &mp_type_fun_bc); o->bytecode = code; - o->const_table = const_table; - if (def_args != NULL) { - memcpy(o->extra_args, def_args->items, n_def_args * sizeof(mp_obj_t)); + o->context = context; + o->child_table = child_table; + if (def_pos_args != NULL) { + memcpy(o->extra_args, def_pos_args->items, n_def_args * sizeof(mp_obj_t)); } if (def_kw_args != MP_OBJ_NULL) { o->extra_args[n_def_args] = def_kw_args; @@ -410,7 +412,7 @@ mp_obj_t mp_obj_new_fun_bc(mp_obj_t def_args_in, mp_obj_t def_kw_args, const byt STATIC mp_obj_t fun_native_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { MP_STACK_CHECK(); - mp_obj_fun_bc_t *self = self_in; + mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in); mp_call_fun_t fun = MICROPY_MAKE_POINTER_CALLABLE((void *)self->bytecode); return fun(self_in, n_args, n_kw, args); } @@ -423,10 +425,10 @@ STATIC const mp_obj_type_t mp_type_fun_native = { .unary_op = mp_generic_unary_op, }; -mp_obj_t mp_obj_new_fun_native(mp_obj_t def_args_in, mp_obj_t def_kw_args, const void *fun_data, const mp_uint_t *const_table) { - mp_obj_fun_bc_t *o = mp_obj_new_fun_bc(def_args_in, def_kw_args, (const byte *)fun_data, const_table); +mp_obj_t mp_obj_new_fun_native(const mp_obj_t *def_args, const void *fun_data, const mp_module_context_t *mc, struct _mp_raw_code_t *const *child_table) { + mp_obj_fun_bc_t *o = MP_OBJ_TO_PTR(mp_obj_new_fun_bc(def_args, (const byte *)fun_data, mc, child_table)); o->base.type = &mp_type_fun_native; - return o; + return MP_OBJ_FROM_PTR(o); } #endif // MICROPY_EMIT_NATIVE @@ -494,7 +496,7 @@ STATIC mp_uint_t convert_obj_for_inline_asm(mp_obj_t obj) { } STATIC mp_obj_t fun_asm_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { - mp_obj_fun_asm_t *self = self_in; + mp_obj_fun_asm_t *self = MP_OBJ_TO_PTR(self_in); mp_arg_check_num(n_args, n_kw, self->n_args, self->n_args, false); @@ -532,12 +534,11 @@ STATIC const mp_obj_type_t mp_type_fun_asm = { }; mp_obj_t mp_obj_new_fun_asm(size_t n_args, const void *fun_data, mp_uint_t type_sig) { - mp_obj_fun_asm_t *o = m_new_obj(mp_obj_fun_asm_t); - o->base.type = &mp_type_fun_asm; + mp_obj_fun_asm_t *o = mp_obj_malloc(mp_obj_fun_asm_t, &mp_type_fun_asm); o->n_args = n_args; o->fun_data = fun_data; o->type_sig = type_sig; - return o; + return MP_OBJ_FROM_PTR(o); } #endif // MICROPY_EMIT_INLINE_ASM diff --git a/python/src/py/objfun.h b/python/src/py/objfun.h index 905b5dbca6d..9de15b88415 100644 --- a/python/src/py/objfun.h +++ b/python/src/py/objfun.h @@ -26,24 +26,26 @@ #ifndef MICROPY_INCLUDED_PY_OBJFUN_H #define MICROPY_INCLUDED_PY_OBJFUN_H +#include "py/bc.h" #include "py/obj.h" typedef struct _mp_obj_fun_bc_t { mp_obj_base_t base; - mp_obj_dict_t *globals; // the context within which this function was defined - const byte *bytecode; // bytecode for the function - const mp_uint_t *const_table; // constant table + const mp_module_context_t *context; // context within which this function was defined + struct _mp_raw_code_t *const *child_table; // table of children + const byte *bytecode; // bytecode for the function #if MICROPY_PY_SYS_SETTRACE const struct _mp_raw_code_t *rc; #endif // the following extra_args array is allocated space to take (in order): // - values of positional default args (if any) // - a single slot for default kw args dict (if it has them) - // - a single slot for var args tuple (if it takes them) - // - a single slot for kw args dict (if it takes them) mp_obj_t extra_args[]; } mp_obj_fun_bc_t; +mp_obj_t mp_obj_new_fun_bc(const mp_obj_t *def_args, const byte *code, const mp_module_context_t *cm, struct _mp_raw_code_t *const *raw_code_table); +mp_obj_t mp_obj_new_fun_native(const mp_obj_t *def_args, const void *fun_data, const mp_module_context_t *cm, struct _mp_raw_code_t *const *raw_code_table); +mp_obj_t mp_obj_new_fun_asm(size_t n_args, const void *fun_data, mp_uint_t type_sig); void mp_obj_fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest); #endif // MICROPY_INCLUDED_PY_OBJFUN_H diff --git a/python/src/py/objgenerator.c b/python/src/py/objgenerator.c index 784310092ea..802fd45bbdf 100644 --- a/python/src/py/objgenerator.c +++ b/python/src/py/objgenerator.c @@ -59,13 +59,12 @@ STATIC mp_obj_t gen_wrap_call(mp_obj_t self_in, size_t n_args, size_t n_kw, cons MP_BC_PRELUDE_SIG_DECODE(ip); // allocate the generator object, with room for local stack and exception stack - mp_obj_gen_instance_t *o = m_new_obj_var(mp_obj_gen_instance_t, byte, - n_state * sizeof(mp_obj_t) + n_exc_stack * sizeof(mp_exc_stack_t)); - o->base.type = &mp_type_gen_instance; + mp_obj_gen_instance_t *o = mp_obj_malloc_var(mp_obj_gen_instance_t, byte, + n_state * sizeof(mp_obj_t) + n_exc_stack * sizeof(mp_exc_stack_t), + &mp_type_gen_instance); o->pend_exc = mp_const_none; o->code_state.fun_bc = self_fun; - o->code_state.ip = 0; o->code_state.n_state = n_state; mp_setup_code_state(&o->code_state, n_args, n_kw, args); return MP_OBJ_FROM_PTR(o); @@ -87,33 +86,40 @@ const mp_obj_type_t mp_type_gen_wrap = { #if MICROPY_EMIT_NATIVE +// Based on mp_obj_gen_instance_t. +typedef struct _mp_obj_gen_instance_native_t { + mp_obj_base_t base; + mp_obj_t pend_exc; + mp_code_state_native_t code_state; +} mp_obj_gen_instance_native_t; + STATIC mp_obj_t native_gen_wrap_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { // The state for a native generating function is held in the same struct as a bytecode function mp_obj_fun_bc_t *self_fun = MP_OBJ_TO_PTR(self_in); - // Determine start of prelude, and extract n_state from it - uintptr_t prelude_offset = ((uintptr_t *)self_fun->bytecode)[0]; - #if MICROPY_EMIT_NATIVE_PRELUDE_AS_BYTES_OBJ - // Prelude is in bytes object in const_table, at index prelude_offset - mp_obj_str_t *prelude_bytes = MP_OBJ_TO_PTR(self_fun->const_table[prelude_offset]); - prelude_offset = (const byte *)prelude_bytes->data - self_fun->bytecode; - #endif - const uint8_t *ip = self_fun->bytecode + prelude_offset; - size_t n_state, n_exc_stack_unused, scope_flags, n_pos_args, n_kwonly_args, n_def_args; - MP_BC_PRELUDE_SIG_DECODE_INTO(ip, n_state, n_exc_stack_unused, scope_flags, n_pos_args, n_kwonly_args, n_def_args); - size_t n_exc_stack = 0; + // Determine start of prelude. + uintptr_t prelude_ptr_index = ((uintptr_t *)self_fun->bytecode)[0]; + const uint8_t *prelude_ptr; + if (prelude_ptr_index == 0) { + prelude_ptr = (void *)self_fun->child_table; + } else { + prelude_ptr = (void *)self_fun->child_table[prelude_ptr_index]; + } + + // Extract n_state from the prelude. + const uint8_t *ip = prelude_ptr; + MP_BC_PRELUDE_SIG_DECODE(ip); - // Allocate the generator object, with room for local stack and exception stack - mp_obj_gen_instance_t *o = m_new_obj_var(mp_obj_gen_instance_t, byte, - n_state * sizeof(mp_obj_t) + n_exc_stack * sizeof(mp_exc_stack_t)); - o->base.type = &mp_type_gen_instance; + // Allocate the generator object, with room for local stack (exception stack not needed). + mp_obj_gen_instance_native_t *o = mp_obj_malloc_var(mp_obj_gen_instance_native_t, byte, n_state * sizeof(mp_obj_t), &mp_type_gen_instance); // Parse the input arguments and set up the code state o->pend_exc = mp_const_none; o->code_state.fun_bc = self_fun; - o->code_state.ip = (const byte *)prelude_offset; + o->code_state.ip = prelude_ptr; o->code_state.n_state = n_state; - mp_setup_code_state(&o->code_state, n_args, n_kw, args); + o->code_state.sp = &o->code_state.state[0] - 1; + mp_setup_code_state_native(&o->code_state, n_args, n_kw, args); // Indicate we are a native function, which doesn't use this variable o->code_state.exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_SENTINEL; @@ -171,7 +177,13 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_ #endif // If the generator is started, allow sending a value. - if (self->code_state.sp == self->code_state.state - 1) { + void *state_start = self->code_state.state - 1; + #if MICROPY_EMIT_NATIVE + if (self->code_state.exc_sp_idx == MP_CODE_STATE_EXC_SP_IDX_SENTINEL) { + state_start = ((mp_obj_gen_instance_native_t *)self)->code_state.state - 1; + } + #endif + if (self->code_state.sp == state_start) { if (send_value != mp_const_none) { mp_raise_TypeError(MP_ERROR_TEXT("can't send non-None value to a just-started generator")); } @@ -184,7 +196,7 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_ // Set up the correct globals context for the generator and execute it self->code_state.old_globals = mp_globals_get(); - mp_globals_set(self->code_state.fun_bc->globals); + mp_globals_set(self->code_state.fun_bc->context->module.globals); mp_vm_return_kind_t ret_kind; @@ -226,7 +238,14 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_ case MP_VM_RETURN_EXCEPTION: { self->code_state.ip = 0; - *ret_val = self->code_state.state[0]; + #if MICROPY_EMIT_NATIVE + if (self->code_state.exc_sp_idx == MP_CODE_STATE_EXC_SP_IDX_SENTINEL) { + *ret_val = ((mp_obj_gen_instance_native_t *)self)->code_state.state[0]; + } else + #endif + { + *ret_val = self->code_state.state[0]; + } // PEP479: if StopIteration is raised inside a generator it is replaced with RuntimeError if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(*ret_val)), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { *ret_val = mp_obj_new_exception_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("generator raised StopIteration")); diff --git a/python/src/py/objint_longlong.c b/python/src/py/objint_longlong.c index f2e88c3ea56..7fcb5462f80 100644 --- a/python/src/py/objint_longlong.c +++ b/python/src/py/objint_longlong.c @@ -243,8 +243,7 @@ mp_obj_t mp_obj_new_int_from_uint(mp_uint_t value) { } mp_obj_t mp_obj_new_int_from_ll(long long val) { - mp_obj_int_t *o = m_new_obj(mp_obj_int_t); - o->base.type = &mp_type_int; + mp_obj_int_t *o = mp_obj_malloc(mp_obj_int_t, &mp_type_int); o->val = val; return o; } @@ -254,8 +253,7 @@ mp_obj_t mp_obj_new_int_from_ull(unsigned long long val) { if (val >> (sizeof(unsigned long long) * 8 - 1) != 0) { mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("ulonglong too large")); } - mp_obj_int_t *o = m_new_obj(mp_obj_int_t); - o->base.type = &mp_type_int; + mp_obj_int_t *o = mp_obj_malloc(mp_obj_int_t, &mp_type_int); o->val = val; return o; } @@ -263,8 +261,7 @@ mp_obj_t mp_obj_new_int_from_ull(unsigned long long val) { mp_obj_t mp_obj_new_int_from_str_len(const char **str, size_t len, bool neg, unsigned int base) { // TODO this does not honor the given length of the string, but it all cases it should anyway be null terminated // TODO check overflow - mp_obj_int_t *o = m_new_obj(mp_obj_int_t); - o->base.type = &mp_type_int; + mp_obj_int_t *o = mp_obj_malloc(mp_obj_int_t, &mp_type_int); char *endptr; o->val = strtoll(*str, &endptr, base); *str = endptr; diff --git a/python/src/py/objint_mpz.c b/python/src/py/objint_mpz.c index ef3e0179678..cbc4cb75a78 100644 --- a/python/src/py/objint_mpz.c +++ b/python/src/py/objint_mpz.c @@ -75,8 +75,7 @@ const mp_obj_int_t mp_sys_maxsize_obj = { #endif mp_obj_int_t *mp_obj_int_new_mpz(void) { - mp_obj_int_t *o = m_new_obj(mp_obj_int_t); - o->base.type = &mp_type_int; + mp_obj_int_t *o = mp_obj_malloc(mp_obj_int_t, &mp_type_int); mpz_init_zero(&o->mpz); return o; } diff --git a/python/src/py/objmap.c b/python/src/py/objmap.c index 78c52c89257..1f9275854f8 100644 --- a/python/src/py/objmap.c +++ b/python/src/py/objmap.c @@ -38,8 +38,7 @@ typedef struct _mp_obj_map_t { STATIC mp_obj_t map_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 2, MP_OBJ_FUN_ARGS_MAX, false); - mp_obj_map_t *o = m_new_obj_var(mp_obj_map_t, mp_obj_t, n_args - 1); - o->base.type = type; + mp_obj_map_t *o = mp_obj_malloc_var(mp_obj_map_t, mp_obj_t, n_args - 1, type); o->n_iters = n_args - 1; o->fun = args[0]; for (size_t i = 0; i < n_args - 1; i++) { diff --git a/python/src/py/objmodule.c b/python/src/py/objmodule.c index a1f9d9d7f14..783d6b05086 100644 --- a/python/src/py/objmodule.c +++ b/python/src/py/objmodule.c @@ -29,11 +29,20 @@ #include #include +#include "py/bc.h" #include "py/objmodule.h" #include "py/runtime.h" #include "py/builtin.h" +#ifndef NO_QSTR +// Only include module definitions when not doing qstr extraction, because the +// qstr extraction stage also generates this module definition header file. #include "genhdr/moduledefs.h" +#endif + +#if MICROPY_MODULE_BUILTIN_INIT +STATIC void mp_module_call_init(mp_obj_t module_name, mp_obj_t module_obj); +#endif STATIC void module_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; @@ -58,6 +67,21 @@ STATIC void module_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kin mp_printf(print, "", module_name); } +STATIC void module_attr_try_delegation(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + #if MICROPY_MODULE_ATTR_DELEGATION + // Delegate lookup to a module's custom attr method (found in last lot of globals dict). + mp_obj_module_t *self = MP_OBJ_TO_PTR(self_in); + mp_map_t *map = &self->globals->map; + if (map->table[map->alloc - 1].key == MP_OBJ_NEW_QSTR(MP_QSTRnull)) { + ((mp_attr_fun_t)MP_OBJ_TO_PTR(map->table[map->alloc - 1].value))(self_in, attr, dest); + } + #else + (void)self_in; + (void)attr; + (void)dest; + #endif +} + STATIC void module_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { mp_obj_module_t *self = MP_OBJ_TO_PTR(self_in); if (dest[0] == MP_OBJ_NULL) { @@ -70,8 +94,12 @@ STATIC void module_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { elem = mp_map_lookup(&self->globals->map, MP_OBJ_NEW_QSTR(MP_QSTR___getattr__), MP_MAP_LOOKUP); if (elem != NULL) { dest[0] = mp_call_function_1(elem->value, MP_OBJ_NEW_QSTR(attr)); + } else { + module_attr_try_delegation(self_in, attr, dest); } #endif + } else { + module_attr_try_delegation(self_in, attr, dest); } } else { // delete/store attribute @@ -87,6 +115,7 @@ STATIC void module_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { #endif { // can't delete or store to fixed map + module_attr_try_delegation(self_in, attr, dest); return; } } @@ -118,12 +147,12 @@ mp_obj_t mp_obj_new_module(qstr module_name) { } // create new module object - mp_obj_module_t *o = m_new_obj(mp_obj_module_t); - o->base.type = &mp_type_module; - o->globals = MP_OBJ_TO_PTR(mp_obj_new_dict(MICROPY_MODULE_DICT_SIZE)); + mp_module_context_t *o = m_new_obj(mp_module_context_t); + o->module.base.type = &mp_type_module; + o->module.globals = MP_OBJ_TO_PTR(mp_obj_new_dict(MICROPY_MODULE_DICT_SIZE)); // store __name__ entry in the module - mp_obj_dict_store(MP_OBJ_FROM_PTR(o->globals), MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(module_name)); + mp_obj_dict_store(MP_OBJ_FROM_PTR(o->module.globals), MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(module_name)); // store the new module into the slot in the global dict holding all modules el->value = MP_OBJ_FROM_PTR(o); @@ -136,153 +165,62 @@ mp_obj_t mp_obj_new_module(qstr module_name) { // Global module table and related functions STATIC const mp_rom_map_elem_t mp_builtin_module_table[] = { - { MP_ROM_QSTR(MP_QSTR___main__), MP_ROM_PTR(&mp_module___main__) }, - { MP_ROM_QSTR(MP_QSTR_builtins), MP_ROM_PTR(&mp_module_builtins) }, - { MP_ROM_QSTR(MP_QSTR_micropython), MP_ROM_PTR(&mp_module_micropython) }, - - #if MICROPY_PY_IO - { MP_ROM_QSTR(MP_QSTR_uio), MP_ROM_PTR(&mp_module_io) }, - #endif - #if MICROPY_PY_COLLECTIONS - { MP_ROM_QSTR(MP_QSTR_ucollections), MP_ROM_PTR(&mp_module_collections) }, - #endif - #if MICROPY_PY_STRUCT - { MP_ROM_QSTR(MP_QSTR_ustruct), MP_ROM_PTR(&mp_module_ustruct) }, - #endif - - #if MICROPY_PY_BUILTINS_FLOAT - #if MICROPY_PY_MATH - { MP_ROM_QSTR(MP_QSTR_math), MP_ROM_PTR(&mp_module_math) }, - #endif - #if MICROPY_PY_BUILTINS_COMPLEX && MICROPY_PY_CMATH - { MP_ROM_QSTR(MP_QSTR_cmath), MP_ROM_PTR(&mp_module_cmath) }, - #endif - #endif - #if MICROPY_PY_SYS - { MP_ROM_QSTR(MP_QSTR_usys), MP_ROM_PTR(&mp_module_sys) }, - #endif - #if MICROPY_PY_GC && MICROPY_ENABLE_GC - { MP_ROM_QSTR(MP_QSTR_gc), MP_ROM_PTR(&mp_module_gc) }, - #endif - #if MICROPY_PY_THREAD - { MP_ROM_QSTR(MP_QSTR__thread), MP_ROM_PTR(&mp_module_thread) }, - #endif - - // extmod modules - - #if MICROPY_PY_UASYNCIO - { MP_ROM_QSTR(MP_QSTR__uasyncio), MP_ROM_PTR(&mp_module_uasyncio) }, - #endif - #if MICROPY_PY_UERRNO - { MP_ROM_QSTR(MP_QSTR_uerrno), MP_ROM_PTR(&mp_module_uerrno) }, - #endif - #if MICROPY_PY_UCTYPES - { MP_ROM_QSTR(MP_QSTR_uctypes), MP_ROM_PTR(&mp_module_uctypes) }, - #endif - #if MICROPY_PY_UZLIB - { MP_ROM_QSTR(MP_QSTR_uzlib), MP_ROM_PTR(&mp_module_uzlib) }, - #endif - #if MICROPY_PY_UJSON - { MP_ROM_QSTR(MP_QSTR_ujson), MP_ROM_PTR(&mp_module_ujson) }, - #endif - #if MICROPY_PY_URE - { MP_ROM_QSTR(MP_QSTR_ure), MP_ROM_PTR(&mp_module_ure) }, - #endif - #if MICROPY_PY_UHEAPQ - { MP_ROM_QSTR(MP_QSTR_uheapq), MP_ROM_PTR(&mp_module_uheapq) }, - #endif - #if MICROPY_PY_UTIMEQ - { MP_ROM_QSTR(MP_QSTR_utimeq), MP_ROM_PTR(&mp_module_utimeq) }, - #endif - #if MICROPY_PY_UHASHLIB - { MP_ROM_QSTR(MP_QSTR_uhashlib), MP_ROM_PTR(&mp_module_uhashlib) }, - #endif - #if MICROPY_PY_UCRYPTOLIB - { MP_ROM_QSTR(MP_QSTR_ucryptolib), MP_ROM_PTR(&mp_module_ucryptolib) }, - #endif - #if MICROPY_PY_UBINASCII - { MP_ROM_QSTR(MP_QSTR_ubinascii), MP_ROM_PTR(&mp_module_ubinascii) }, - #endif - #if MICROPY_PY_URANDOM - { MP_ROM_QSTR(MP_QSTR_urandom), MP_ROM_PTR(&mp_module_urandom) }, - #endif - #if MICROPY_PY_USELECT - { MP_ROM_QSTR(MP_QSTR_uselect), MP_ROM_PTR(&mp_module_uselect) }, - #endif - #if MICROPY_PY_USSL - { MP_ROM_QSTR(MP_QSTR_ussl), MP_ROM_PTR(&mp_module_ussl) }, - #endif - #if MICROPY_PY_LWIP - { MP_ROM_QSTR(MP_QSTR_lwip), MP_ROM_PTR(&mp_module_lwip) }, - #endif - #if MICROPY_PY_UWEBSOCKET - { MP_ROM_QSTR(MP_QSTR_uwebsocket), MP_ROM_PTR(&mp_module_uwebsocket) }, - #endif - #if MICROPY_PY_WEBREPL - { MP_ROM_QSTR(MP_QSTR__webrepl), MP_ROM_PTR(&mp_module_webrepl) }, - #endif - #if MICROPY_PY_FRAMEBUF - { MP_ROM_QSTR(MP_QSTR_framebuf), MP_ROM_PTR(&mp_module_framebuf) }, - #endif - #if MICROPY_PY_BTREE - { MP_ROM_QSTR(MP_QSTR_btree), MP_ROM_PTR(&mp_module_btree) }, - #endif - #if MICROPY_PY_BLUETOOTH - { MP_ROM_QSTR(MP_QSTR_ubluetooth), MP_ROM_PTR(&mp_module_ubluetooth) }, - #endif - - // extra builtin modules as defined by a port - MICROPY_PORT_BUILTIN_MODULES - - #ifdef MICROPY_REGISTERED_MODULES // builtin modules declared with MP_REGISTER_MODULE() MICROPY_REGISTERED_MODULES - #endif }; MP_DEFINE_CONST_MAP(mp_builtin_module_map, mp_builtin_module_table); -// returns MP_OBJ_NULL if not found -mp_obj_t mp_module_get(qstr module_name) { - mp_map_t *mp_loaded_modules_map = &MP_STATE_VM(mp_loaded_modules_dict).map; - // lookup module - mp_map_elem_t *el = mp_map_lookup(mp_loaded_modules_map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP); - - if (el == NULL) { - // module not found, look for builtin module names - el = mp_map_lookup((mp_map_t *)&mp_builtin_module_map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP); - if (el == NULL) { +// Tries to find a loaded module, otherwise attempts to load a builtin, otherwise MP_OBJ_NULL. +mp_obj_t mp_module_get_loaded_or_builtin(qstr module_name) { + // First try loaded modules. + mp_map_elem_t *elem = mp_map_lookup(&MP_STATE_VM(mp_loaded_modules_dict).map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP); + + if (!elem) { + #if MICROPY_MODULE_WEAK_LINKS + return mp_module_get_builtin(module_name); + #else + // Otherwise try builtin. + elem = mp_map_lookup((mp_map_t *)&mp_builtin_module_map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP); + if (!elem) { return MP_OBJ_NULL; } - mp_module_call_init(module_name, el->value); - } - // module found, return it - return el->value; -} + #if MICROPY_MODULE_BUILTIN_INIT + // If found, it's a newly loaded built-in, so init it. + mp_module_call_init(MP_OBJ_NEW_QSTR(module_name), elem->value); + #endif + #endif + } -void mp_module_register(qstr qst, mp_obj_t module) { - mp_map_t *mp_loaded_modules_map = &MP_STATE_VM(mp_loaded_modules_dict).map; - mp_map_lookup(mp_loaded_modules_map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = module; + return elem->value; } #if MICROPY_MODULE_WEAK_LINKS -// Search for u"foo" in built-in modules, return MP_OBJ_NULL if not found -mp_obj_t mp_module_search_umodule(const char *module_str) { - for (size_t i = 0; i < MP_ARRAY_SIZE(mp_builtin_module_table); ++i) { - const mp_map_elem_t *entry = (const mp_map_elem_t *)&mp_builtin_module_table[i]; - const char *key = qstr_str(MP_OBJ_QSTR_VALUE(entry->key)); - if (key[0] == 'u' && strcmp(&key[1], module_str) == 0) { - return (mp_obj_t)entry->value; - } - +// Tries to find a loaded module, otherwise attempts to load a builtin, otherwise MP_OBJ_NULL. +mp_obj_t mp_module_get_builtin(qstr module_name) { + // Try builtin. + mp_map_elem_t *elem = mp_map_lookup((mp_map_t *)&mp_builtin_module_map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP); + if (!elem) { + return MP_OBJ_NULL; } - return MP_OBJ_NULL; + + #if MICROPY_MODULE_BUILTIN_INIT + // If found, it's a newly loaded built-in, so init it. + mp_module_call_init(MP_OBJ_NEW_QSTR(module_name), elem->value); + #endif + + return elem->value; } #endif #if MICROPY_MODULE_BUILTIN_INIT -void mp_module_call_init(qstr module_name, mp_obj_t module_obj) { +STATIC void mp_module_register(mp_obj_t module_name, mp_obj_t module) { + mp_map_t *mp_loaded_modules_map = &MP_STATE_VM(mp_loaded_modules_dict).map; + mp_map_lookup(mp_loaded_modules_map, module_name, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = module; +} + +STATIC void mp_module_call_init(mp_obj_t module_name, mp_obj_t module_obj) { // Look for __init__ and call it if it exists mp_obj_t dest[2]; mp_load_method_maybe(module_obj, MP_QSTR___init__, dest); @@ -296,3 +234,19 @@ void mp_module_call_init(qstr module_name, mp_obj_t module_obj) { } } #endif + +void mp_module_generic_attr(qstr attr, mp_obj_t *dest, const uint16_t *keys, mp_obj_t *values) { + for (size_t i = 0; keys[i] != MP_QSTRnull; ++i) { + if (attr == keys[i]) { + if (dest[0] == MP_OBJ_NULL) { + // load attribute (MP_OBJ_NULL returned for deleted items) + dest[0] = values[i]; + } else { + // delete or store (delete stores MP_OBJ_NULL) + values[i] = dest[1]; + dest[0] = MP_OBJ_NULL; // indicate success + } + return; + } + } +} diff --git a/python/src/py/objmodule.h b/python/src/py/objmodule.h index fde4fff34e0..d11d5bcd746 100644 --- a/python/src/py/objmodule.h +++ b/python/src/py/objmodule.h @@ -28,20 +28,16 @@ #include "py/obj.h" -extern const mp_map_t mp_builtin_module_map; - -mp_obj_t mp_module_get(qstr module_name); -void mp_module_register(qstr qstr, mp_obj_t module); +// Place at the very end of a module's globals_table. +#define MP_MODULE_ATTR_DELEGATION_ENTRY(ptr) { MP_ROM_QSTR(MP_QSTRnull), MP_ROM_PTR(ptr) } -mp_obj_t mp_module_search_umodule(const char *module_str); +extern const mp_map_t mp_builtin_module_map; -#if MICROPY_MODULE_BUILTIN_INIT -void mp_module_call_init(qstr module_name, mp_obj_t module_obj); -#else -static inline void mp_module_call_init(qstr module_name, mp_obj_t module_obj) { - (void)module_name; - (void)module_obj; -} +mp_obj_t mp_module_get_loaded_or_builtin(qstr module_name); +#if MICROPY_MODULE_WEAK_LINKS +mp_obj_t mp_module_get_builtin(qstr module_name); #endif +void mp_module_generic_attr(qstr attr, mp_obj_t *dest, const uint16_t *keys, mp_obj_t *values); + #endif // MICROPY_INCLUDED_PY_OBJMODULE_H diff --git a/python/src/py/objobject.c b/python/src/py/objobject.c index 00082dfe087..1652802805b 100644 --- a/python/src/py/objobject.c +++ b/python/src/py/objobject.c @@ -36,8 +36,7 @@ typedef struct _mp_obj_object_t { STATIC mp_obj_t object_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)args; mp_arg_check_num(n_args, n_kw, 0, 0, false); - mp_obj_object_t *o = m_new_obj(mp_obj_object_t); - o->base.type = type; + mp_obj_object_t *o = mp_obj_malloc(mp_obj_object_t, type); return MP_OBJ_FROM_PTR(o); } diff --git a/python/src/py/objproperty.c b/python/src/py/objproperty.c index 8d2c292c52d..49327c981e0 100644 --- a/python/src/py/objproperty.c +++ b/python/src/py/objproperty.c @@ -47,8 +47,7 @@ STATIC mp_obj_t property_make_new(const mp_obj_type_t *type, size_t n_args, size mp_arg_val_t vals[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, args, MP_ARRAY_SIZE(allowed_args), allowed_args, vals); - mp_obj_property_t *o = m_new_obj(mp_obj_property_t); - o->base.type = type; + mp_obj_property_t *o = mp_obj_malloc(mp_obj_property_t, type); o->proxy[0] = vals[ARG_fget].u_obj; o->proxy[1] = vals[ARG_fset].u_obj; o->proxy[2] = vals[ARG_fdel].u_obj; diff --git a/python/src/py/objrange.c b/python/src/py/objrange.c index 1f028eb867a..5496021892c 100644 --- a/python/src/py/objrange.c +++ b/python/src/py/objrange.c @@ -92,8 +92,7 @@ STATIC void range_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind STATIC mp_obj_t range_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 1, 3, false); - mp_obj_range_t *o = m_new_obj(mp_obj_range_t); - o->base.type = type; + mp_obj_range_t *o = mp_obj_malloc(mp_obj_range_t, type); o->start = 0; o->step = 1; @@ -168,8 +167,7 @@ STATIC mp_obj_t range_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { if (mp_obj_is_type(index, &mp_type_slice)) { mp_bound_slice_t slice; mp_seq_get_fast_slice_indexes(len, index, &slice); - mp_obj_range_t *o = m_new_obj(mp_obj_range_t); - o->base.type = &mp_type_range; + mp_obj_range_t *o = mp_obj_malloc(mp_obj_range_t, &mp_type_range); o->start = self->start + slice.start * self->step; o->stop = self->start + slice.stop * self->step; o->step = slice.step * self->step; diff --git a/python/src/py/objreversed.c b/python/src/py/objreversed.c index 4254668e751..08961c0d2d8 100644 --- a/python/src/py/objreversed.c +++ b/python/src/py/objreversed.c @@ -47,8 +47,7 @@ STATIC mp_obj_t reversed_make_new(const mp_obj_type_t *type, size_t n_args, size return mp_call_method_n_kw(0, 0, dest); } - mp_obj_reversed_t *o = m_new_obj(mp_obj_reversed_t); - o->base.type = type; + mp_obj_reversed_t *o = mp_obj_malloc(mp_obj_reversed_t, type); o->seq = args[0]; o->cur_index = mp_obj_get_int(mp_obj_len(args[0])); // start at the end of the sequence diff --git a/python/src/py/objset.c b/python/src/py/objset.c index d2508bfbf9e..26fd74398b8 100644 --- a/python/src/py/objset.c +++ b/python/src/py/objset.c @@ -174,8 +174,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(set_clear_obj, set_clear); STATIC mp_obj_t set_copy(mp_obj_t self_in) { check_set_or_frozenset(self_in); mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); - mp_obj_set_t *other = m_new_obj(mp_obj_set_t); - other->base.type = self->base.type; + mp_obj_set_t *other = mp_obj_malloc(mp_obj_set_t, self->base.type); mp_set_init(&other->set, self->set.alloc); other->set.used = self->set.used; memcpy(other->set.table, self->set.table, self->set.alloc * sizeof(mp_obj_t)); @@ -579,8 +578,7 @@ const mp_obj_type_t mp_type_frozenset = { #endif mp_obj_t mp_obj_new_set(size_t n_args, mp_obj_t *items) { - mp_obj_set_t *o = m_new_obj(mp_obj_set_t); - o->base.type = &mp_type_set; + mp_obj_set_t *o = mp_obj_malloc(mp_obj_set_t, &mp_type_set); mp_set_init(&o->set, n_args); for (size_t i = 0; i < n_args; i++) { mp_set_lookup(&o->set, items[i], MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); diff --git a/python/src/py/objslice.c b/python/src/py/objslice.c index c65c30601be..0b34516c185 100644 --- a/python/src/py/objslice.c +++ b/python/src/py/objslice.c @@ -104,8 +104,7 @@ const mp_obj_type_t mp_type_slice = { }; mp_obj_t mp_obj_new_slice(mp_obj_t ostart, mp_obj_t ostop, mp_obj_t ostep) { - mp_obj_slice_t *o = m_new_obj(mp_obj_slice_t); - o->base.type = &mp_type_slice; + mp_obj_slice_t *o = mp_obj_malloc(mp_obj_slice_t, &mp_type_slice); o->start = ostart; o->stop = ostop; o->step = ostep; diff --git a/python/src/py/objstr.c b/python/src/py/objstr.c index 7d7f0e1dfa4..6e5a316d784 100644 --- a/python/src/py/objstr.c +++ b/python/src/py/objstr.c @@ -1163,7 +1163,7 @@ STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar s++; } if (*s == '0') { - if (!align) { + if (!align && arg_looks_numeric(arg)) { align = '='; } if (!fill) { @@ -2026,8 +2026,7 @@ const mp_obj_str_t mp_const_empty_bytes_obj = {{&mp_type_bytes}, 0, 0, (const by // the data is copied across. This function should only be used if the type is bytes, // or if the type is str and the string data is known to be not interned. mp_obj_t mp_obj_new_str_copy(const mp_obj_type_t *type, const byte *data, size_t len) { - mp_obj_str_t *o = m_new_obj(mp_obj_str_t); - o->base.type = type; + mp_obj_str_t *o = mp_obj_malloc(mp_obj_str_t, type); o->len = len; if (data) { o->hash = qstr_compute_hash(data, len); @@ -2070,8 +2069,7 @@ mp_obj_t mp_obj_new_str_from_vstr(const mp_obj_type_t *type, vstr_t *vstr) { } // make a new str/bytes object - mp_obj_str_t *o = m_new_obj(mp_obj_str_t); - o->base.type = type; + mp_obj_str_t *o = mp_obj_malloc(mp_obj_str_t, type); o->len = vstr->len; o->hash = qstr_compute_hash((byte *)vstr->buf, vstr->len); if (vstr->len + 1 == vstr->alloc) { diff --git a/python/src/py/objstringio.c b/python/src/py/objstringio.c index ef942e74e8f..8b6c7531d79 100644 --- a/python/src/py/objstringio.c +++ b/python/src/py/objstringio.c @@ -177,8 +177,7 @@ STATIC mp_obj_t stringio___exit__(size_t n_args, const mp_obj_t *args) { STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(stringio___exit___obj, 4, 4, stringio___exit__); STATIC mp_obj_stringio_t *stringio_new(const mp_obj_type_t *type) { - mp_obj_stringio_t *o = m_new_obj(mp_obj_stringio_t); - o->base.type = type; + mp_obj_stringio_t *o = mp_obj_malloc(mp_obj_stringio_t, type); o->pos = 0; o->ref_obj = MP_OBJ_NULL; return o; diff --git a/python/src/py/objtuple.c b/python/src/py/objtuple.c index 67d7bc356f4..e0cec844736 100644 --- a/python/src/py/objtuple.c +++ b/python/src/py/objtuple.c @@ -243,8 +243,7 @@ mp_obj_t mp_obj_new_tuple(size_t n, const mp_obj_t *items) { if (n == 0) { return mp_const_empty_tuple; } - mp_obj_tuple_t *o = m_new_obj_var(mp_obj_tuple_t, mp_obj_t, n); - o->base.type = &mp_type_tuple; + mp_obj_tuple_t *o = mp_obj_malloc_var(mp_obj_tuple_t, mp_obj_t, n, &mp_type_tuple); o->len = n; if (items) { for (size_t i = 0; i < n; i++) { diff --git a/python/src/py/objtype.c b/python/src/py/objtype.c index 508bab99d39..37c1e3bd22d 100644 --- a/python/src/py/objtype.c +++ b/python/src/py/objtype.c @@ -99,8 +99,7 @@ STATIC mp_obj_instance_t *mp_obj_new_instance(const mp_obj_type_t *class, const mp_obj_type_t **native_base) { size_t num_native_bases = instance_count_native_bases(class, native_base); assert(num_native_bases < 2); - mp_obj_instance_t *o = m_new_obj_var(mp_obj_instance_t, mp_obj_t, num_native_bases); - o->base.type = class; + mp_obj_instance_t *o = mp_obj_malloc_var(mp_obj_instance_t, mp_obj_t, num_native_bases, class); mp_map_init(&o->members, 0); // Initialise the native base-class slot (should be 1 at most) with a valid // object. It doesn't matter which object, so long as it can be uniquely @@ -549,6 +548,7 @@ retry:; } else if (dest[0] != MP_OBJ_NULL) { dest[2] = rhs_in; res = mp_call_method_n_kw(1, 0, dest); + res = op == MP_BINARY_OP_CONTAINS ? mp_obj_new_bool(mp_obj_is_true(res)) : res; } else { // If this was an inplace method, fallback to normal method // https://docs.python.org/3/reference/datamodel.html#object.__iadd__ : @@ -579,6 +579,7 @@ STATIC void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *des assert(mp_obj_is_instance_type(mp_obj_get_type(self_in))); mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + // Note: This is fast-path'ed in the VM for the MP_BC_LOAD_ATTR operation. mp_map_elem_t *elem = mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP); if (elem != NULL) { // object member, always treated as a value diff --git a/python/src/py/objzip.c b/python/src/py/objzip.c index 4abc917c3f5..81fa1d587e3 100644 --- a/python/src/py/objzip.c +++ b/python/src/py/objzip.c @@ -39,8 +39,7 @@ typedef struct _mp_obj_zip_t { STATIC mp_obj_t zip_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 0, MP_OBJ_FUN_ARGS_MAX, false); - mp_obj_zip_t *o = m_new_obj_var(mp_obj_zip_t, mp_obj_t, n_args); - o->base.type = type; + mp_obj_zip_t *o = mp_obj_malloc_var(mp_obj_zip_t, mp_obj_t, n_args, type); o->n_iters = n_args; for (size_t i = 0; i < n_args; i++) { o->iters[i] = mp_getiter(args[i], NULL); diff --git a/python/src/py/parse.c b/python/src/py/parse.c index ae3fa8ea6d0..14f5f6c1050 100644 --- a/python/src/py/parse.c +++ b/python/src/py/parse.c @@ -291,6 +291,16 @@ STATIC void *parser_alloc(parser_t *parser, size_t num_bytes) { return ret; } +#if MICROPY_COMP_CONST_TUPLE +STATIC void parser_free_parse_node_struct(parser_t *parser, mp_parse_node_struct_t *pns) { + mp_parse_chunk_t *chunk = parser->cur_chunk; + if (chunk->data <= (byte *)pns && (byte *)pns < chunk->data + chunk->union_.used) { + size_t num_bytes = sizeof(mp_parse_node_struct_t) + sizeof(mp_parse_node_t) * MP_PARSE_NODE_STRUCT_NUM_NODES(pns); + chunk->union_.used -= num_bytes; + } +} +#endif + STATIC void push_rule(parser_t *parser, size_t src_line, uint8_t rule_id, size_t arg_i) { if (parser->rule_stack_top >= parser->rule_stack_alloc) { rule_stack_t *rs = m_renew(rule_stack_t, parser->rule_stack, parser->rule_stack_alloc, parser->rule_stack_alloc + MICROPY_ALLOC_PARSE_RULE_INC); @@ -317,6 +327,13 @@ STATIC uint8_t pop_rule(parser_t *parser, size_t *arg_i, size_t *src_line) { return rule_id; } +#if MICROPY_COMP_CONST_TUPLE +STATIC uint8_t peek_rule(parser_t *parser, size_t n) { + assert(parser->rule_stack_top > n); + return parser->rule_stack[parser->rule_stack_top - 1 - n].rule_id; +} +#endif + bool mp_parse_node_is_const_false(mp_parse_node_t pn) { return MP_PARSE_NODE_IS_TOKEN_KIND(pn, MP_TOKEN_KW_FALSE) || (MP_PARSE_NODE_IS_SMALL_INT(pn) && MP_PARSE_NODE_LEAF_SMALL_INT(pn) == 0); @@ -333,18 +350,83 @@ bool mp_parse_node_get_int_maybe(mp_parse_node_t pn, mp_obj_t *o) { return true; } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, RULE_const_object)) { mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; - #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D - // nodes are 32-bit pointers, but need to extract 64-bit object - *o = (uint64_t)pns->nodes[0] | ((uint64_t)pns->nodes[1] << 32); - #else - *o = (mp_obj_t)pns->nodes[0]; - #endif + *o = mp_parse_node_extract_const_object(pns); return mp_obj_is_int(*o); } else { return false; } } +#if MICROPY_COMP_CONST_TUPLE || MICROPY_COMP_CONST +STATIC bool mp_parse_node_is_const(mp_parse_node_t pn) { + if (MP_PARSE_NODE_IS_SMALL_INT(pn)) { + // Small integer. + return true; + } else if (MP_PARSE_NODE_IS_LEAF(pn)) { + // Possible str, or constant literal. + uintptr_t kind = MP_PARSE_NODE_LEAF_KIND(pn); + if (kind == MP_PARSE_NODE_STRING) { + return true; + } else if (kind == MP_PARSE_NODE_TOKEN) { + uintptr_t arg = MP_PARSE_NODE_LEAF_ARG(pn); + return arg == MP_TOKEN_KW_NONE + || arg == MP_TOKEN_KW_FALSE + || arg == MP_TOKEN_KW_TRUE + || arg == MP_TOKEN_ELLIPSIS; + } + } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, RULE_const_object)) { + // Constant object. + return true; + } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, RULE_atom_paren)) { + // Possible empty tuple. + mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; + return MP_PARSE_NODE_IS_NULL(pns->nodes[0]); + } + return false; +} + +STATIC mp_obj_t mp_parse_node_convert_to_obj(mp_parse_node_t pn) { + assert(mp_parse_node_is_const(pn)); + if (MP_PARSE_NODE_IS_SMALL_INT(pn)) { + mp_int_t arg = MP_PARSE_NODE_LEAF_SMALL_INT(pn); + #if MICROPY_DYNAMIC_COMPILER + mp_uint_t sign_mask = -((mp_uint_t)1 << (mp_dynamic_compiler.small_int_bits - 1)); + if (!((arg & sign_mask) == 0 || (arg & sign_mask) == sign_mask)) { + // Integer doesn't fit in a small-int, so create a multi-precision int object. + return mp_obj_new_int_from_ll(arg); + } + #endif + return MP_OBJ_NEW_SMALL_INT(arg); + } else if (MP_PARSE_NODE_IS_LEAF(pn)) { + uintptr_t kind = MP_PARSE_NODE_LEAF_KIND(pn); + uintptr_t arg = MP_PARSE_NODE_LEAF_ARG(pn); + if (kind == MP_PARSE_NODE_STRING) { + return MP_OBJ_NEW_QSTR(arg); + } else { + assert(MP_PARSE_NODE_LEAF_KIND(pn) == MP_PARSE_NODE_TOKEN); + switch (arg) { + case MP_TOKEN_KW_NONE: + return mp_const_none; + case MP_TOKEN_KW_FALSE: + return mp_const_false; + case MP_TOKEN_KW_TRUE: + return mp_const_true; + default: + assert(arg == MP_TOKEN_ELLIPSIS); + return MP_OBJ_FROM_PTR(&mp_const_ellipsis_obj); + } + } + } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, RULE_const_object)) { + mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; + return mp_parse_node_extract_const_object(pns); + } else { + assert(MP_PARSE_NODE_IS_STRUCT_KIND(pn, RULE_atom_paren)); + assert(MP_PARSE_NODE_IS_NULL(((mp_parse_node_struct_t *)pn)->nodes[0])); + return mp_const_empty_tuple; + } +} +#endif + size_t mp_parse_node_extract_list(mp_parse_node_t *pn, size_t pn_kind, mp_parse_node_t **nodes) { if (MP_PARSE_NODE_IS_NULL(*pn)) { *nodes = NULL; @@ -388,9 +470,6 @@ void mp_parse_node_print(const mp_print_t *print, mp_parse_node_t pn, size_t ind case MP_PARSE_NODE_STRING: mp_printf(print, "str(%s)\n", qstr_str(arg)); break; - case MP_PARSE_NODE_BYTES: - mp_printf(print, "bytes(%s)\n", qstr_str(arg)); - break; default: assert(MP_PARSE_NODE_LEAF_KIND(pn) == MP_PARSE_NODE_TOKEN); mp_printf(print, "tok(%u)\n", (uint)arg); @@ -400,11 +479,14 @@ void mp_parse_node_print(const mp_print_t *print, mp_parse_node_t pn, size_t ind // node must be a mp_parse_node_struct_t mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; if (MP_PARSE_NODE_STRUCT_KIND(pns) == RULE_const_object) { + mp_obj_t obj = mp_parse_node_extract_const_object(pns); #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D - mp_printf(print, "literal const(%016llx)\n", (uint64_t)pns->nodes[0] | ((uint64_t)pns->nodes[1] << 32)); + mp_printf(print, "literal const(%016llx)=", obj); #else - mp_printf(print, "literal const(%p)\n", (mp_obj_t)pns->nodes[0]); + mp_printf(print, "literal const(%p)=", obj); #endif + mp_obj_print_helper(print, obj, PRINT_REPR); + mp_printf(print, "\n"); } else { size_t n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); #if MICROPY_DEBUG_PARSE_RULE_NAME @@ -463,16 +545,28 @@ STATIC mp_parse_node_t make_node_const_object(parser_t *parser, size_t src_line, return (mp_parse_node_t)pn; } -STATIC mp_parse_node_t mp_parse_node_new_small_int_checked(parser_t *parser, mp_obj_t o_val) { - (void)parser; - mp_int_t val = MP_OBJ_SMALL_INT_VALUE(o_val); - #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D - // A parse node is only 32-bits and the small-int value must fit in 31-bits - if (((val ^ (val << 1)) & 0xffffffff80000000) != 0) { - return make_node_const_object(parser, 0, o_val); +// Create a parse node represeting a constant object, possibly optimising the case of +// an integer, by putting the (small) integer value directly in the parse node itself. +STATIC mp_parse_node_t make_node_const_object_optimised(parser_t *parser, size_t src_line, mp_obj_t obj) { + if (mp_obj_is_small_int(obj)) { + mp_int_t val = MP_OBJ_SMALL_INT_VALUE(obj); + #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D + // A parse node is only 32-bits and the small-int value must fit in 31-bits + if (((val ^ (val << 1)) & 0xffffffff80000000) != 0) { + return make_node_const_object(parser, src_line, obj); + } + #endif + #if MICROPY_DYNAMIC_COMPILER + // Check that the integer value fits in target runtime's small-int + mp_uint_t sign_mask = -((mp_uint_t)1 << (mp_dynamic_compiler.small_int_bits - 1)); + if (!((val & sign_mask) == 0 || (val & sign_mask) == sign_mask)) { + return make_node_const_object(parser, src_line, obj); + } + #endif + return mp_parse_node_new_small_int(val); + } else { + return make_node_const_object(parser, src_line, obj); } - #endif - return mp_parse_node_new_small_int(val); } STATIC void push_result_token(parser_t *parser, uint8_t rule_id) { @@ -485,11 +579,7 @@ STATIC void push_result_token(parser_t *parser, uint8_t rule_id) { mp_map_elem_t *elem; if (rule_id == RULE_atom && (elem = mp_map_lookup(&parser->consts, MP_OBJ_NEW_QSTR(id), MP_MAP_LOOKUP)) != NULL) { - if (mp_obj_is_small_int(elem->value)) { - pn = mp_parse_node_new_small_int_checked(parser, elem->value); - } else { - pn = make_node_const_object(parser, lex->tok_line, elem->value); - } + pn = make_node_const_object_optimised(parser, lex->tok_line, elem->value); } else { pn = mp_parse_node_new_leaf(MP_PARSE_NODE_ID, id); } @@ -499,16 +589,12 @@ STATIC void push_result_token(parser_t *parser, uint8_t rule_id) { #endif } else if (lex->tok_kind == MP_TOKEN_INTEGER) { mp_obj_t o = mp_parse_num_integer(lex->vstr.buf, lex->vstr.len, 0, lex); - if (mp_obj_is_small_int(o)) { - pn = mp_parse_node_new_small_int_checked(parser, o); - } else { - pn = make_node_const_object(parser, lex->tok_line, o); - } + pn = make_node_const_object_optimised(parser, lex->tok_line, o); } else if (lex->tok_kind == MP_TOKEN_FLOAT_OR_IMAG) { mp_obj_t o = mp_parse_num_decimal(lex->vstr.buf, lex->vstr.len, true, false, lex); pn = make_node_const_object(parser, lex->tok_line, o); - } else if (lex->tok_kind == MP_TOKEN_STRING || lex->tok_kind == MP_TOKEN_BYTES) { - // Don't automatically intern all strings/bytes. doc strings (which are usually large) + } else if (lex->tok_kind == MP_TOKEN_STRING) { + // Don't automatically intern all strings. Doc strings (which are usually large) // will be discarded by the compiler, and so we shouldn't intern them. qstr qst = MP_QSTRnull; if (lex->vstr.len <= MICROPY_ALLOC_PARSE_INTERN_STRING_LEN) { @@ -520,14 +606,16 @@ STATIC void push_result_token(parser_t *parser, uint8_t rule_id) { } if (qst != MP_QSTRnull) { // qstr exists, make a leaf node - pn = mp_parse_node_new_leaf(lex->tok_kind == MP_TOKEN_STRING ? MP_PARSE_NODE_STRING : MP_PARSE_NODE_BYTES, qst); + pn = mp_parse_node_new_leaf(MP_PARSE_NODE_STRING, qst); } else { - // not interned, make a node holding a pointer to the string/bytes object - mp_obj_t o = mp_obj_new_str_copy( - lex->tok_kind == MP_TOKEN_STRING ? &mp_type_str : &mp_type_bytes, - (const byte *)lex->vstr.buf, lex->vstr.len); + // not interned, make a node holding a pointer to the string object + mp_obj_t o = mp_obj_new_str_copy(&mp_type_str, (const byte *)lex->vstr.buf, lex->vstr.len); pn = make_node_const_object(parser, lex->tok_line, o); } + } else if (lex->tok_kind == MP_TOKEN_BYTES) { + // make a node holding a pointer to the bytes object + mp_obj_t o = mp_obj_new_bytes((const byte *)lex->vstr.buf, lex->vstr.len); + pn = make_node_const_object(parser, lex->tok_line, o); } else { pn = mp_parse_node_new_leaf(MP_PARSE_NODE_TOKEN, lex->tok_kind); } @@ -551,6 +639,11 @@ STATIC MP_DEFINE_CONST_MAP(mp_constants_map, mp_constants_table); STATIC void push_result_rule(parser_t *parser, size_t src_line, uint8_t rule_id, size_t num_args); #if MICROPY_COMP_CONST_FOLDING +#if MICROPY_COMP_CONST_FOLDING_COMPILER_WORKAROUND +// Some versions of the xtensa-esp32-elf-gcc compiler generate wrong code if this +// function is static, so provide a hook for them to work around this problem. +MP_NOINLINE +#endif STATIC bool fold_logical_constants(parser_t *parser, uint8_t rule_id, size_t *num_args) { if (rule_id == RULE_or_test || rule_id == RULE_and_test) { @@ -715,14 +808,14 @@ STATIC bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { // get the value mp_parse_node_t pn_value = ((mp_parse_node_struct_t *)((mp_parse_node_struct_t *)pn1)->nodes[1])->nodes[0]; - mp_obj_t value; - if (!mp_parse_node_get_int_maybe(pn_value, &value)) { + if (!mp_parse_node_is_const(pn_value)) { mp_obj_t exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, - MP_ERROR_TEXT("constant must be an integer")); + MP_ERROR_TEXT("not a constant")); mp_obj_exception_add_traceback(exc, parser->lexer->source_name, ((mp_parse_node_struct_t *)pn1)->source_line, MP_QSTRnull); nlr_raise(exc); } + mp_obj_t value = mp_parse_node_convert_to_obj(pn_value); // store the value in the table of dynamic constants mp_map_elem_t *elem = mp_map_lookup(&parser->consts, MP_OBJ_NEW_QSTR(id), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); @@ -784,21 +877,71 @@ STATIC bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { for (size_t i = num_args; i > 0; i--) { pop_result(parser); } - if (mp_obj_is_small_int(arg0)) { - push_result_node(parser, mp_parse_node_new_small_int_checked(parser, arg0)); - } else { - // TODO reuse memory for parse node struct? - push_result_node(parser, make_node_const_object(parser, 0, arg0)); - } + push_result_node(parser, make_node_const_object_optimised(parser, 0, arg0)); return true; } #endif +#if MICROPY_COMP_CONST_TUPLE +STATIC bool build_tuple_from_stack(parser_t *parser, size_t src_line, size_t num_args) { + for (size_t i = num_args; i > 0;) { + mp_parse_node_t pn = peek_result(parser, --i); + if (!mp_parse_node_is_const(pn)) { + return false; + } + } + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(num_args, NULL)); + for (size_t i = num_args; i > 0;) { + mp_parse_node_t pn = pop_result(parser); + tuple->items[--i] = mp_parse_node_convert_to_obj(pn); + if (MP_PARSE_NODE_IS_STRUCT(pn)) { + parser_free_parse_node_struct(parser, (mp_parse_node_struct_t *)pn); + } + } + push_result_node(parser, make_node_const_object(parser, src_line, MP_OBJ_FROM_PTR(tuple))); + return true; +} + +STATIC bool build_tuple(parser_t *parser, size_t src_line, uint8_t rule_id, size_t num_args) { + if (rule_id == RULE_testlist_comp) { + if (peek_rule(parser, 0) == RULE_atom_paren) { + // Tuple of the form "(a,)". + return build_tuple_from_stack(parser, src_line, num_args); + } + } + if (rule_id == RULE_testlist_comp_3c) { + assert(peek_rule(parser, 0) == RULE_testlist_comp_3b); + assert(peek_rule(parser, 1) == RULE_testlist_comp); + if (peek_rule(parser, 2) == RULE_atom_paren) { + // Tuple of the form "(a, b)". + if (build_tuple_from_stack(parser, src_line, num_args)) { + parser->rule_stack_top -= 2; // discard 2 rules + return true; + } + } + } + if (rule_id == RULE_testlist_star_expr + || rule_id == RULE_testlist + || rule_id == RULE_subscriptlist) { + // Tuple of the form: + // - x = a, b + // - return a, b + // - for x in a, b: pass + // - x[a, b] + return build_tuple_from_stack(parser, src_line, num_args); + } + + return false; +} +#endif + STATIC void push_result_rule(parser_t *parser, size_t src_line, uint8_t rule_id, size_t num_args) { - // optimise away parenthesis around an expression if possible + // Simplify and optimise certain rules, to reduce memory usage and simplify the compiler. if (rule_id == RULE_atom_paren) { - // there should be just 1 arg for this rule + // Remove parenthesis around a single expression if possible. + // This atom_paren rule always has a single argument, and after this + // optimisation that argument is either NULL or testlist_comp. mp_parse_node_t pn = peek_result(parser, 0); if (MP_PARSE_NODE_IS_NULL(pn)) { // need to keep parenthesis for () @@ -808,6 +951,34 @@ STATIC void push_result_rule(parser_t *parser, size_t src_line, uint8_t rule_id, // parenthesis around a single expression, so it's just the expression return; } + } else if (rule_id == RULE_testlist_comp) { + // The testlist_comp rule can be the sole argument to either atom_parent + // or atom_bracket, for (...) and [...] respectively. + assert(num_args == 2); + mp_parse_node_t pn = peek_result(parser, 0); + if (MP_PARSE_NODE_IS_STRUCT(pn)) { + mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; + if (MP_PARSE_NODE_STRUCT_KIND(pns) == RULE_testlist_comp_3b) { + // tuple of one item, with trailing comma + pop_result(parser); + --num_args; + } else if (MP_PARSE_NODE_STRUCT_KIND(pns) == RULE_testlist_comp_3c) { + // tuple of many items, convert testlist_comp_3c to testlist_comp + pop_result(parser); + assert(pn == peek_result(parser, 0)); + pns->kind_num_nodes = rule_id | MP_PARSE_NODE_STRUCT_NUM_NODES(pns) << 8; + return; + } else if (MP_PARSE_NODE_STRUCT_KIND(pns) == RULE_comp_for) { + // generator expression + } else { + // tuple with 2 items + } + } else { + // tuple with 2 items + } + } else if (rule_id == RULE_testlist_comp_3c) { + // steal first arg of outer testlist_comp rule + ++num_args; } #if MICROPY_COMP_CONST_FOLDING @@ -821,12 +992,23 @@ STATIC void push_result_rule(parser_t *parser, size_t src_line, uint8_t rule_id, } #endif + #if MICROPY_COMP_CONST_TUPLE + if (build_tuple(parser, src_line, rule_id, num_args)) { + // we built a tuple from this rule so return straightaway + return; + } + #endif + mp_parse_node_struct_t *pn = parser_alloc(parser, sizeof(mp_parse_node_struct_t) + sizeof(mp_parse_node_t) * num_args); pn->source_line = src_line; pn->kind_num_nodes = (rule_id & 0xff) | (num_args << 8); for (size_t i = num_args; i > 0; i--) { pn->nodes[i - 1] = pop_result(parser); } + if (rule_id == RULE_testlist_comp_3c) { + // need to push something non-null to replace stolen first arg of testlist_comp + push_result_node(parser, (mp_parse_node_t)pn); + } push_result_node(parser, (mp_parse_node_t)pn); } diff --git a/python/src/py/parse.h b/python/src/py/parse.h index a6eb380047d..5531e35cbb3 100644 --- a/python/src/py/parse.h +++ b/python/src/py/parse.h @@ -39,15 +39,13 @@ struct _mp_lexer_t; // - xxxx...xx00: pointer to mp_parse_node_struct_t // - xx...xx0010: an identifier; bits 4 and above are the qstr // - xx...xx0110: a string; bits 4 and above are the qstr holding the value -// - xx...xx1010: a string of bytes; bits 4 and above are the qstr holding the value -// - xx...xx1110: a token; bits 4 and above are mp_token_kind_t +// - xx...xx1010: a token; bits 4 and above are mp_token_kind_t #define MP_PARSE_NODE_NULL (0) #define MP_PARSE_NODE_SMALL_INT (0x1) #define MP_PARSE_NODE_ID (0x02) #define MP_PARSE_NODE_STRING (0x06) -#define MP_PARSE_NODE_BYTES (0x0a) -#define MP_PARSE_NODE_TOKEN (0x0e) +#define MP_PARSE_NODE_TOKEN (0x0a) typedef uintptr_t mp_parse_node_t; // must be pointer size @@ -79,9 +77,20 @@ typedef struct _mp_parse_node_struct_t { static inline mp_parse_node_t mp_parse_node_new_small_int(mp_int_t val) { return (mp_parse_node_t)(MP_PARSE_NODE_SMALL_INT | ((mp_uint_t)val << 1)); } + static inline mp_parse_node_t mp_parse_node_new_leaf(size_t kind, mp_int_t arg) { return (mp_parse_node_t)(kind | ((mp_uint_t)arg << 4)); } + +static inline mp_obj_t mp_parse_node_extract_const_object(mp_parse_node_struct_t *pns) { + #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D + // nodes are 32-bit pointers, but need to extract 64-bit object + return (uint64_t)pns->nodes[0] | ((uint64_t)pns->nodes[1] << 32); + #else + return (mp_obj_t)pns->nodes[0]; + #endif +} + bool mp_parse_node_is_const_false(mp_parse_node_t pn); bool mp_parse_node_is_const_true(mp_parse_node_t pn); bool mp_parse_node_get_int_maybe(mp_parse_node_t pn, mp_obj_t *o); diff --git a/python/src/py/parsenum.c b/python/src/py/parsenum.c index 54cd2bf862b..1cfe8425770 100644 --- a/python/src/py/parsenum.c +++ b/python/src/py/parsenum.c @@ -98,7 +98,7 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m break; } - // add next digit and check for overflow + // add next digi and check for overflow if (mp_small_int_mul_overflow(int_val, base)) { goto overflow; } diff --git a/python/src/py/persistentcode.c b/python/src/py/persistentcode.c index ac523990c1b..6304d1ff084 100644 --- a/python/src/py/persistentcode.c +++ b/python/src/py/persistentcode.c @@ -48,72 +48,6 @@ #define MPY_FEATURE_ARCH_DYNAMIC MPY_FEATURE_ARCH #endif -#if MICROPY_PERSISTENT_CODE_LOAD || (MICROPY_PERSISTENT_CODE_SAVE && !MICROPY_DYNAMIC_COMPILER) -// The bytecode will depend on the number of bits in a small-int, and -// this function computes that (could make it a fixed constant, but it -// would need to be defined in mpconfigport.h). -STATIC int mp_small_int_bits(void) { - mp_int_t i = MP_SMALL_INT_MAX; - int n = 1; - while (i != 0) { - i >>= 1; - ++n; - } - return n; -} -#endif - -#define QSTR_WINDOW_SIZE (32) - -typedef struct _qstr_window_t { - uint16_t idx; // indexes the head of the window - uint16_t window[QSTR_WINDOW_SIZE]; -} qstr_window_t; - -// Push a qstr to the head of the window, and the tail qstr is overwritten -STATIC void qstr_window_push(qstr_window_t *qw, qstr qst) { - qw->idx = (qw->idx + 1) % QSTR_WINDOW_SIZE; - qw->window[qw->idx] = qst; -} - -// Pull an existing qstr from within the window to the head of the window -STATIC qstr qstr_window_pull(qstr_window_t *qw, size_t idx) { - qstr qst = qw->window[idx]; - if (idx > qw->idx) { - memmove(&qw->window[idx], &qw->window[idx + 1], (QSTR_WINDOW_SIZE - idx - 1) * sizeof(uint16_t)); - qw->window[QSTR_WINDOW_SIZE - 1] = qw->window[0]; - idx = 0; - } - memmove(&qw->window[idx], &qw->window[idx + 1], (qw->idx - idx) * sizeof(uint16_t)); - qw->window[qw->idx] = qst; - return qst; -} - -#if MICROPY_PERSISTENT_CODE_LOAD - -// Access a qstr at the given index, relative to the head of the window (0=head) -STATIC qstr qstr_window_access(qstr_window_t *qw, size_t idx) { - return qstr_window_pull(qw, (qw->idx + QSTR_WINDOW_SIZE - idx) % QSTR_WINDOW_SIZE); -} - -#endif - -#if MICROPY_PERSISTENT_CODE_SAVE - -// Insert a qstr at the head of the window, either by pulling an existing one or pushing a new one -STATIC size_t qstr_window_insert(qstr_window_t *qw, qstr qst) { - for (size_t idx = 0; idx < QSTR_WINDOW_SIZE; ++idx) { - if (qw->window[idx] == qst) { - qstr_window_pull(qw, idx); - return (qw->idx + QSTR_WINDOW_SIZE - idx) % QSTR_WINDOW_SIZE; - } - } - qstr_window_push(qw, qst); - return QSTR_WINDOW_SIZE; -} - -#endif - typedef struct _bytecode_prelude_t { uint n_state; uint n_exc_stack; @@ -124,23 +58,6 @@ typedef struct _bytecode_prelude_t { uint code_info_size; } bytecode_prelude_t; -// ip will point to start of opcodes -// return value will point to simple_name, source_file qstrs -STATIC byte *extract_prelude(const byte **ip, bytecode_prelude_t *prelude) { - MP_BC_PRELUDE_SIG_DECODE(*ip); - prelude->n_state = n_state; - prelude->n_exc_stack = n_exc_stack; - prelude->scope_flags = scope_flags; - prelude->n_pos_args = n_pos_args; - prelude->n_kwonly_args = n_kwonly_args; - prelude->n_def_pos_args = n_def_pos_args; - MP_BC_PRELUDE_SIZE_DECODE(*ip); - byte *ip_info = (byte *)*ip; - *ip += n_info; - *ip += n_cell; - return ip_info; -} - #endif // MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE #if MICROPY_PERSISTENT_CODE_LOAD @@ -148,47 +65,17 @@ STATIC byte *extract_prelude(const byte **ip, bytecode_prelude_t *prelude) { #include "py/parsenum.h" STATIC int read_byte(mp_reader_t *reader); -STATIC size_t read_uint(mp_reader_t *reader, byte **out); +STATIC size_t read_uint(mp_reader_t *reader); #if MICROPY_EMIT_MACHINE_CODE typedef struct _reloc_info_t { mp_reader_t *reader; - mp_uint_t *const_table; + mp_module_context_t *context; + uint8_t *rodata; + uint8_t *bss; } reloc_info_t; -#if MICROPY_EMIT_THUMB -STATIC void asm_thumb_rewrite_mov(uint8_t *pc, uint16_t val) { - // high part - *(uint16_t *)pc = (*(uint16_t *)pc & 0xfbf0) | (val >> 1 & 0x0400) | (val >> 12); - // low part - *(uint16_t *)(pc + 2) = (*(uint16_t *)(pc + 2) & 0x0f00) | (val << 4 & 0x7000) | (val & 0x00ff); - -} -#endif - -STATIC void arch_link_qstr(uint8_t *pc, bool is_obj, qstr qst) { - mp_uint_t val = qst; - if (is_obj) { - val = (mp_uint_t)MP_OBJ_NEW_QSTR(qst); - } - #if MICROPY_EMIT_X86 || MICROPY_EMIT_X64 || MICROPY_EMIT_ARM || MICROPY_EMIT_XTENSA || MICROPY_EMIT_XTENSAWIN - pc[0] = val & 0xff; - pc[1] = (val >> 8) & 0xff; - pc[2] = (val >> 16) & 0xff; - pc[3] = (val >> 24) & 0xff; - #elif MICROPY_EMIT_THUMB - if (is_obj) { - // qstr object, movw and movt - asm_thumb_rewrite_mov(pc, val); // movw - asm_thumb_rewrite_mov(pc + 4, val >> 16); // movt - } else { - // qstr number, movw instruction - asm_thumb_rewrite_mov(pc, val); // movw - } - #endif -} - void mp_native_relocate(void *ri_in, uint8_t *text, uintptr_t reloc_text) { // Relocate native code reloc_info_t *ri = ri_in; @@ -197,13 +84,13 @@ void mp_native_relocate(void *ri_in, uint8_t *text, uintptr_t reloc_text) { while ((op = read_byte(ri->reader)) != 0xff) { if (op & 1) { // Point to new location to make adjustments - size_t addr = read_uint(ri->reader, NULL); + size_t addr = read_uint(ri->reader); if ((addr & 1) == 0) { // Point to somewhere in text addr_to_adjust = &((uintptr_t *)text)[addr >> 1]; } else { // Point to somewhere in rodata - addr_to_adjust = &((uintptr_t *)ri->const_table[1])[addr >> 1]; + addr_to_adjust = &((uintptr_t *)ri->rodata)[addr >> 1]; } } op >>= 1; @@ -212,22 +99,31 @@ void mp_native_relocate(void *ri_in, uint8_t *text, uintptr_t reloc_text) { if (op <= 5) { if (op & 1) { // Read in number of adjustments to make - n = read_uint(ri->reader, NULL); + n = read_uint(ri->reader); } op >>= 1; if (op == 0) { // Destination is text dest = reloc_text; + } else if (op == 1) { + // Destination is rodata + dest = (uintptr_t)ri->rodata; } else { - // Destination is rodata (op=1) or bss (op=1 if no rodata, else op=2) - dest = ri->const_table[op]; + // Destination is bss + dest = (uintptr_t)ri->bss; } } else if (op == 6) { + // Destination is qstr_table + dest = (uintptr_t)ri->context->constants.qstr_table; + } else if (op == 7) { + // Destination is obj_table + dest = (uintptr_t)ri->context->constants.obj_table; + } else if (op == 8) { // Destination is mp_fun_table itself dest = (uintptr_t)&mp_fun_table; } else { // Destination is an entry in mp_fun_table - dest = ((uintptr_t *)&mp_fun_table)[op - 7]; + dest = ((uintptr_t *)&mp_fun_table)[op - 9]; } while (n--) { *addr_to_adjust++ += dest; @@ -247,14 +143,10 @@ STATIC void read_bytes(mp_reader_t *reader, byte *buf, size_t len) { } } -STATIC size_t read_uint(mp_reader_t *reader, byte **out) { +STATIC size_t read_uint(mp_reader_t *reader) { size_t unum = 0; for (;;) { byte b = reader->readbyte(reader->data); - if (out != NULL) { - **out = b; - ++*out; - } unum = (unum << 7) | (b & 0x7f); if ((b & 0x80) == 0) { break; @@ -263,97 +155,69 @@ STATIC size_t read_uint(mp_reader_t *reader, byte **out) { return unum; } -STATIC qstr load_qstr(mp_reader_t *reader, qstr_window_t *qw) { - size_t len = read_uint(reader, NULL); - if (len == 0) { - // static qstr - return read_byte(reader); - } +STATIC qstr load_qstr(mp_reader_t *reader) { + size_t len = read_uint(reader); if (len & 1) { - // qstr in window - return qstr_window_access(qw, len >> 1); + // static qstr + return len >> 1; } len >>= 1; char *str = m_new(char, len); read_bytes(reader, (byte *)str, len); + read_byte(reader); // read and discard null terminator qstr qst = qstr_from_strn(str, len); m_del(char, str, len); - qstr_window_push(qw, qst); return qst; } STATIC mp_obj_t load_obj(mp_reader_t *reader) { byte obj_type = read_byte(reader); - if (obj_type == 'e') { + #if MICROPY_EMIT_MACHINE_CODE + if (obj_type == MP_PERSISTENT_OBJ_FUN_TABLE) { + return MP_OBJ_FROM_PTR(&mp_fun_table); + } else + #endif + if (obj_type == MP_PERSISTENT_OBJ_NONE) { + return mp_const_none; + } else if (obj_type == MP_PERSISTENT_OBJ_FALSE) { + return mp_const_false; + } else if (obj_type == MP_PERSISTENT_OBJ_TRUE) { + return mp_const_true; + } else if (obj_type == MP_PERSISTENT_OBJ_ELLIPSIS) { return MP_OBJ_FROM_PTR(&mp_const_ellipsis_obj); } else { - size_t len = read_uint(reader, NULL); + size_t len = read_uint(reader); + if (len == 0 && obj_type == MP_PERSISTENT_OBJ_BYTES) { + read_byte(reader); // skip null terminator + return mp_const_empty_bytes; + } else if (obj_type == MP_PERSISTENT_OBJ_TUPLE) { + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(len, NULL)); + for (size_t i = 0; i < len; ++i) { + tuple->items[i] = load_obj(reader); + } + return MP_OBJ_FROM_PTR(tuple); + } vstr_t vstr; vstr_init_len(&vstr, len); read_bytes(reader, (byte *)vstr.buf, len); - if (obj_type == 's' || obj_type == 'b') { - return mp_obj_new_str_from_vstr(obj_type == 's' ? &mp_type_str : &mp_type_bytes, &vstr); - } else if (obj_type == 'i') { + if (obj_type == MP_PERSISTENT_OBJ_STR || obj_type == MP_PERSISTENT_OBJ_BYTES) { + read_byte(reader); // skip null terminator + return mp_obj_new_str_from_vstr(obj_type == MP_PERSISTENT_OBJ_STR ? &mp_type_str : &mp_type_bytes, &vstr); + } else if (obj_type == MP_PERSISTENT_OBJ_INT) { return mp_parse_num_integer(vstr.buf, vstr.len, 10, NULL); } else { - assert(obj_type == 'f' || obj_type == 'c'); - return mp_parse_num_decimal(vstr.buf, vstr.len, obj_type == 'c', false, NULL); + assert(obj_type == MP_PERSISTENT_OBJ_FLOAT || obj_type == MP_PERSISTENT_OBJ_COMPLEX); + return mp_parse_num_decimal(vstr.buf, vstr.len, obj_type == MP_PERSISTENT_OBJ_COMPLEX, false, NULL); } } } -STATIC void load_prelude_qstrs(mp_reader_t *reader, qstr_window_t *qw, byte *ip) { - qstr simple_name = load_qstr(reader, qw); - ip[0] = simple_name; - ip[1] = simple_name >> 8; - qstr source_file = load_qstr(reader, qw); - ip[2] = source_file; - ip[3] = source_file >> 8; -} - -STATIC void load_prelude(mp_reader_t *reader, qstr_window_t *qw, byte **ip, bytecode_prelude_t *prelude) { - // Read in the prelude header - byte *ip_read = *ip; - read_uint(reader, &ip_read); // read in n_state/etc (is effectively a var-uint) - read_uint(reader, &ip_read); // read in n_info/n_cell (is effectively a var-uint) - - // Prelude header has been read into *ip, now decode and extract values from it - extract_prelude((const byte **)ip, prelude); - - // Load qstrs in prelude - load_prelude_qstrs(reader, qw, ip_read); - ip_read += 4; - - // Read remaining code info - read_bytes(reader, ip_read, *ip - ip_read); -} - -STATIC void load_bytecode(mp_reader_t *reader, qstr_window_t *qw, byte *ip, byte *ip_top) { - while (ip < ip_top) { - *ip = read_byte(reader); - size_t sz; - uint f = mp_opcode_format(ip, &sz, false); - ++ip; - --sz; - if (f == MP_BC_FORMAT_QSTR) { - qstr qst = load_qstr(reader, qw); - *ip++ = qst; - *ip++ = qst >> 8; - sz -= 2; - } else if (f == MP_BC_FORMAT_VAR_UINT) { - while ((*ip++ = read_byte(reader)) & 0x80) { - } - } - read_bytes(reader, ip, sz); - ip += sz; - } -} - -STATIC mp_raw_code_t *load_raw_code(mp_reader_t *reader, qstr_window_t *qw) { +STATIC mp_raw_code_t *load_raw_code(mp_reader_t *reader, mp_module_context_t *context) { // Load function kind and data length - size_t kind_len = read_uint(reader, NULL); + size_t kind_len = read_uint(reader); int kind = (kind_len & 3) + MP_CODE_BYTECODE; - size_t fun_data_len = kind_len >> 2; + bool has_children = !!(kind_len & 4); + size_t fun_data_len = kind_len >> 3; #if !MICROPY_EMIT_MACHINE_CODE if (kind != MP_CODE_BYTECODE) { @@ -362,23 +226,18 @@ STATIC mp_raw_code_t *load_raw_code(mp_reader_t *reader, qstr_window_t *qw) { #endif uint8_t *fun_data = NULL; - bytecode_prelude_t prelude = {0}; #if MICROPY_EMIT_MACHINE_CODE size_t prelude_offset = 0; - mp_uint_t type_sig = 0; - size_t n_qstr_link = 0; + mp_uint_t native_scope_flags = 0; + mp_uint_t native_n_pos_args = 0; + mp_uint_t native_type_sig = 0; #endif if (kind == MP_CODE_BYTECODE) { // Allocate memory for the bytecode fun_data = m_new(uint8_t, fun_data_len); - - // Load prelude - byte *ip = fun_data; - load_prelude(reader, qw, &ip, &prelude); - // Load bytecode - load_bytecode(reader, qw, ip, fun_data + fun_data_len); + read_bytes(reader, fun_data, fun_data_len); #if MICROPY_EMIT_MACHINE_CODE } else { @@ -387,135 +246,104 @@ STATIC mp_raw_code_t *load_raw_code(mp_reader_t *reader, qstr_window_t *qw) { MP_PLAT_ALLOC_EXEC(fun_data_len, (void **)&fun_data, &fun_alloc); read_bytes(reader, fun_data, fun_data_len); - if (kind == MP_CODE_NATIVE_PY || kind == MP_CODE_NATIVE_VIPER) { - // Parse qstr link table and link native code - n_qstr_link = read_uint(reader, NULL); - for (size_t i = 0; i < n_qstr_link; ++i) { - size_t off = read_uint(reader, NULL); - qstr qst = load_qstr(reader, qw); - uint8_t *dest = fun_data + (off >> 2); - if ((off & 3) == 0) { - // Generic 16-bit link - dest[0] = qst & 0xff; - dest[1] = (qst >> 8) & 0xff; - } else if ((off & 3) == 3) { - // Generic, aligned qstr-object link - *(mp_obj_t *)dest = MP_OBJ_NEW_QSTR(qst); - } else { - // Architecture-specific link - arch_link_qstr(dest, (off & 3) == 2, qst); - } - } - } - if (kind == MP_CODE_NATIVE_PY) { - // Extract prelude for later use - prelude_offset = read_uint(reader, NULL); + // Read prelude offset within fun_data, and extract scope flags. + prelude_offset = read_uint(reader); const byte *ip = fun_data + prelude_offset; - byte *ip_info = extract_prelude(&ip, &prelude); - // Load qstrs in prelude - load_prelude_qstrs(reader, qw, ip_info); + MP_BC_PRELUDE_SIG_DECODE(ip); + native_scope_flags = scope_flags; } else { - // Load basic scope info for viper and asm - prelude.scope_flags = read_uint(reader, NULL); - prelude.n_pos_args = 0; - prelude.n_kwonly_args = 0; + // Load basic scope info for viper and asm. + native_scope_flags = read_uint(reader); if (kind == MP_CODE_NATIVE_ASM) { - prelude.n_pos_args = read_uint(reader, NULL); - type_sig = read_uint(reader, NULL); + native_n_pos_args = read_uint(reader); + native_type_sig = read_uint(reader); } } #endif } - size_t n_obj = 0; - size_t n_raw_code = 0; - mp_uint_t *const_table = NULL; - - if (kind != MP_CODE_NATIVE_ASM) { - // Load constant table for bytecode, native and viper + size_t n_children = 0; + mp_raw_code_t **children = NULL; - // Number of entries in constant table - n_obj = read_uint(reader, NULL); - n_raw_code = read_uint(reader, NULL); - - // Allocate constant table - size_t n_alloc = prelude.n_pos_args + prelude.n_kwonly_args + n_obj + n_raw_code; - #if MICROPY_EMIT_MACHINE_CODE - if (kind != MP_CODE_BYTECODE) { - ++n_alloc; // additional entry for mp_fun_table - if (prelude.scope_flags & MP_SCOPE_FLAG_VIPERRODATA) { - ++n_alloc; // additional entry for rodata - } - if (prelude.scope_flags & MP_SCOPE_FLAG_VIPERBSS) { - ++n_alloc; // additional entry for BSS - } + #if MICROPY_EMIT_MACHINE_CODE + // Load optional BSS/rodata for viper. + uint8_t *rodata = NULL; + uint8_t *bss = NULL; + if (kind == MP_CODE_NATIVE_VIPER) { + size_t rodata_size = 0; + if (native_scope_flags & MP_SCOPE_FLAG_VIPERRODATA) { + rodata_size = read_uint(reader); } - #endif - const_table = m_new(mp_uint_t, n_alloc); - mp_uint_t *ct = const_table; - - // Load function argument names (initial entries in const_table) - // (viper has n_pos_args=n_kwonly_args=0 so doesn't load any qstrs here) - for (size_t i = 0; i < prelude.n_pos_args + prelude.n_kwonly_args; ++i) { - *ct++ = (mp_uint_t)MP_OBJ_NEW_QSTR(load_qstr(reader, qw)); + size_t bss_size = 0; + if (native_scope_flags & MP_SCOPE_FLAG_VIPERBSS) { + bss_size = read_uint(reader); } - #if MICROPY_EMIT_MACHINE_CODE - if (kind != MP_CODE_BYTECODE) { - // Populate mp_fun_table entry - *ct++ = (mp_uint_t)(uintptr_t)&mp_fun_table; - - // Allocate and load rodata if needed - if (prelude.scope_flags & MP_SCOPE_FLAG_VIPERRODATA) { - size_t size = read_uint(reader, NULL); - uint8_t *rodata = m_new(uint8_t, size); - read_bytes(reader, rodata, size); - *ct++ = (uintptr_t)rodata; + if (rodata_size + bss_size != 0) { + bss_size = (uintptr_t)MP_ALIGN(bss_size, sizeof(uintptr_t)); + uint8_t *data = m_new0(uint8_t, bss_size + rodata_size); + bss = data; + rodata = bss + bss_size; + if (native_scope_flags & MP_SCOPE_FLAG_VIPERRODATA) { + read_bytes(reader, rodata, rodata_size); } - // Allocate BSS if needed - if (prelude.scope_flags & MP_SCOPE_FLAG_VIPERBSS) { - size_t size = read_uint(reader, NULL); - uint8_t *bss = m_new0(uint8_t, size); - *ct++ = (uintptr_t)bss; - } + // Viper code with BSS/rodata should not have any children. + // Reuse the children pointer to reference the BSS/rodata + // memory so that it is not reclaimed by the GC. + assert(!has_children); + children = (void *)data; } - #endif + } + #endif - // Load constant objects and raw code children - for (size_t i = 0; i < n_obj; ++i) { - *ct++ = (mp_uint_t)load_obj(reader); - } - for (size_t i = 0; i < n_raw_code; ++i) { - *ct++ = (mp_uint_t)(uintptr_t)load_raw_code(reader, qw); + // Load children if any. + if (has_children) { + n_children = read_uint(reader); + children = m_new(mp_raw_code_t *, n_children + (kind == MP_CODE_NATIVE_PY)); + for (size_t i = 0; i < n_children; ++i) { + children[i] = load_raw_code(reader, context); } } // Create raw_code and return it mp_raw_code_t *rc = mp_emit_glue_new_raw_code(); if (kind == MP_CODE_BYTECODE) { + const byte *ip = fun_data; + MP_BC_PRELUDE_SIG_DECODE(ip); // Assign bytecode to raw code object mp_emit_glue_assign_bytecode(rc, fun_data, #if MICROPY_PERSISTENT_CODE_SAVE || MICROPY_DEBUG_PRINTERS fun_data_len, #endif - const_table, + children, #if MICROPY_PERSISTENT_CODE_SAVE - n_obj, n_raw_code, + n_children, #endif - prelude.scope_flags); + scope_flags); #if MICROPY_EMIT_MACHINE_CODE } else { + const uint8_t *prelude_ptr; + #if MICROPY_EMIT_NATIVE_PRELUDE_SEPARATE_FROM_MACHINE_CODE + if (kind == MP_CODE_NATIVE_PY) { + // Executable code cannot be accessed byte-wise on this architecture, so copy + // the prelude to a separate memory region that is byte-wise readable. + void *buf = fun_data + prelude_offset; + size_t n = fun_data_len - prelude_offset; + prelude_ptr = memcpy(m_new(uint8_t, n), buf, n); + } + #endif + // Relocate and commit code to executable address space - reloc_info_t ri = {reader, const_table}; + reloc_info_t ri = {reader, context, rodata, bss}; #if defined(MP_PLAT_COMMIT_EXEC) - void *opt_ri = (prelude.scope_flags & MP_SCOPE_FLAG_VIPERRELOC) ? &ri : NULL; + void *opt_ri = (native_scope_flags & MP_SCOPE_FLAG_VIPERRELOC) ? &ri : NULL; fun_data = MP_PLAT_COMMIT_EXEC(fun_data, fun_data_len, opt_ri); #else - if (prelude.scope_flags & MP_SCOPE_FLAG_VIPERRELOC) { + if (native_scope_flags & MP_SCOPE_FLAG_VIPERRELOC) { #if MICROPY_PERSISTENT_CODE_TRACK_RELOC_CODE // If native code needs relocations then it's not guaranteed that a pointer to // the head of `buf` (containing the machine code) will be retained for the GC @@ -532,28 +360,39 @@ STATIC mp_raw_code_t *load_raw_code(mp_reader_t *reader, qstr_window_t *qw) { } #endif + if (kind == MP_CODE_NATIVE_PY) { + #if !MICROPY_EMIT_NATIVE_PRELUDE_SEPARATE_FROM_MACHINE_CODE + prelude_ptr = fun_data + prelude_offset; + #endif + if (n_children == 0) { + children = (void *)prelude_ptr; + } else { + children[n_children] = (void *)prelude_ptr; + } + } + // Assign native code to raw code object mp_emit_glue_assign_native(rc, kind, - fun_data, fun_data_len, const_table, + fun_data, fun_data_len, + children, #if MICROPY_PERSISTENT_CODE_SAVE + n_children, prelude_offset, - n_obj, n_raw_code, - n_qstr_link, NULL, #endif - prelude.n_pos_args, prelude.scope_flags, type_sig); + native_scope_flags, native_n_pos_args, native_type_sig + ); #endif } return rc; } -mp_raw_code_t *mp_raw_code_load(mp_reader_t *reader) { +mp_compiled_module_t mp_raw_code_load(mp_reader_t *reader, mp_module_context_t *context) { byte header[4]; read_bytes(reader, header, sizeof(header)); if (header[0] != 'M' || header[1] != MPY_VERSION || MPY_FEATURE_DECODE_FLAGS(header[2]) != MPY_FEATURE_FLAGS - || header[3] > mp_small_int_bits() - || read_uint(reader, NULL) > QSTR_WINDOW_SIZE) { + || header[3] > MP_SMALL_INT_BITS) { mp_raise_ValueError(MP_ERROR_TEXT("incompatible .mpy file")); } if (MPY_FEATURE_DECODE_ARCH(header[2]) != MP_NATIVE_ARCH_NONE) { @@ -562,25 +401,49 @@ mp_raw_code_t *mp_raw_code_load(mp_reader_t *reader) { mp_raise_ValueError(MP_ERROR_TEXT("incompatible .mpy arch")); } } - qstr_window_t qw; - qw.idx = 0; - mp_raw_code_t *rc = load_raw_code(reader, &qw); + + size_t n_qstr = read_uint(reader); + size_t n_obj = read_uint(reader); + mp_module_context_alloc_tables(context, n_qstr, n_obj); + + // Load qstrs. + for (size_t i = 0; i < n_qstr; ++i) { + context->constants.qstr_table[i] = load_qstr(reader); + } + + // Load constant objects. + for (size_t i = 0; i < n_obj; ++i) { + context->constants.obj_table[i] = load_obj(reader); + } + + // Load top-level module. + mp_compiled_module_t cm2; + cm2.rc = load_raw_code(reader, context); + cm2.context = context; + + #if MICROPY_PERSISTENT_CODE_SAVE + cm2.has_native = MPY_FEATURE_DECODE_ARCH(header[2]) != MP_NATIVE_ARCH_NONE; + cm2.n_qstr = n_qstr; + cm2.n_obj = n_obj; + #endif + reader->close(reader->data); - return rc; + + return cm2; } -mp_raw_code_t *mp_raw_code_load_mem(const byte *buf, size_t len) { +mp_compiled_module_t mp_raw_code_load_mem(const byte *buf, size_t len, mp_module_context_t *context) { mp_reader_t reader; mp_reader_new_mem(&reader, buf, len, 0); - return mp_raw_code_load(&reader); + return mp_raw_code_load(&reader, context); } #if MICROPY_HAS_FILE_READER -mp_raw_code_t *mp_raw_code_load_file(const char *filename) { +mp_compiled_module_t mp_raw_code_load_file(const char *filename, mp_module_context_t *context) { mp_reader_t reader; mp_reader_new_file(&reader, filename); - return mp_raw_code_load(&reader); + return mp_raw_code_load(&reader, context); } #endif // MICROPY_HAS_FILE_READER @@ -607,54 +470,72 @@ STATIC void mp_print_uint(mp_print_t *print, size_t n) { print->print_strn(print->data, (char *)p, buf + sizeof(buf) - p); } -STATIC void save_qstr(mp_print_t *print, qstr_window_t *qw, qstr qst) { +STATIC void save_qstr(mp_print_t *print, qstr qst) { if (qst <= QSTR_LAST_STATIC) { // encode static qstr - byte buf[2] = {0, qst & 0xff}; - mp_print_bytes(print, buf, 2); - return; - } - size_t idx = qstr_window_insert(qw, qst); - if (idx < QSTR_WINDOW_SIZE) { - // qstr found in window, encode index to it - mp_print_uint(print, idx << 1 | 1); + mp_print_uint(print, qst << 1 | 1); return; } size_t len; const byte *str = qstr_data(qst, &len); mp_print_uint(print, len << 1); - mp_print_bytes(print, str, len); + mp_print_bytes(print, str, len + 1); // +1 to store null terminator } STATIC void save_obj(mp_print_t *print, mp_obj_t o) { + #if MICROPY_EMIT_MACHINE_CODE + if (o == MP_OBJ_FROM_PTR(&mp_fun_table)) { + byte obj_type = MP_PERSISTENT_OBJ_FUN_TABLE; + mp_print_bytes(print, &obj_type, 1); + } else + #endif if (mp_obj_is_str_or_bytes(o)) { byte obj_type; if (mp_obj_is_str(o)) { - obj_type = 's'; + obj_type = MP_PERSISTENT_OBJ_STR; } else { - obj_type = 'b'; + obj_type = MP_PERSISTENT_OBJ_BYTES; } size_t len; const char *str = mp_obj_str_get_data(o, &len); mp_print_bytes(print, &obj_type, 1); mp_print_uint(print, len); - mp_print_bytes(print, (const byte *)str, len); + mp_print_bytes(print, (const byte *)str, len + 1); // +1 to store null terminator + } else if (o == mp_const_none) { + byte obj_type = MP_PERSISTENT_OBJ_NONE; + mp_print_bytes(print, &obj_type, 1); + } else if (o == mp_const_false) { + byte obj_type = MP_PERSISTENT_OBJ_FALSE; + mp_print_bytes(print, &obj_type, 1); + } else if (o == mp_const_true) { + byte obj_type = MP_PERSISTENT_OBJ_TRUE; + mp_print_bytes(print, &obj_type, 1); } else if (MP_OBJ_TO_PTR(o) == &mp_const_ellipsis_obj) { - byte obj_type = 'e'; + byte obj_type = MP_PERSISTENT_OBJ_ELLIPSIS; + mp_print_bytes(print, &obj_type, 1); + } else if (mp_obj_is_type(o, &mp_type_tuple)) { + size_t len; + mp_obj_t *items; + mp_obj_tuple_get(o, &len, &items); + byte obj_type = MP_PERSISTENT_OBJ_TUPLE; mp_print_bytes(print, &obj_type, 1); + mp_print_uint(print, len); + for (size_t i = 0; i < len; ++i) { + save_obj(print, items[i]); + } } else { // we save numbers using a simplistic text representation // TODO could be improved byte obj_type; - if (mp_obj_is_type(o, &mp_type_int)) { - obj_type = 'i'; + if (mp_obj_is_int(o)) { + obj_type = MP_PERSISTENT_OBJ_INT; #if MICROPY_PY_BUILTINS_COMPLEX } else if (mp_obj_is_type(o, &mp_type_complex)) { - obj_type = 'c'; + obj_type = MP_PERSISTENT_OBJ_COMPLEX; #endif } else { assert(mp_obj_is_float(o)); - obj_type = 'f'; + obj_type = MP_PERSISTENT_OBJ_FLOAT; } vstr_t vstr; mp_print_t pr; @@ -667,142 +548,41 @@ STATIC void save_obj(mp_print_t *print, mp_obj_t o) { } } -STATIC void save_prelude_qstrs(mp_print_t *print, qstr_window_t *qw, const byte *ip) { - save_qstr(print, qw, ip[0] | (ip[1] << 8)); // simple_name - save_qstr(print, qw, ip[2] | (ip[3] << 8)); // source_file -} - -STATIC void save_bytecode(mp_print_t *print, qstr_window_t *qw, const byte *ip, const byte *ip_top) { - while (ip < ip_top) { - size_t sz; - uint f = mp_opcode_format(ip, &sz, true); - if (f == MP_BC_FORMAT_QSTR) { - mp_print_bytes(print, ip, 1); - qstr qst = ip[1] | (ip[2] << 8); - save_qstr(print, qw, qst); - ip += 3; - sz -= 3; - } - mp_print_bytes(print, ip, sz); - ip += sz; - } -} - -STATIC void save_raw_code(mp_print_t *print, mp_raw_code_t *rc, qstr_window_t *qstr_window) { +STATIC void save_raw_code(mp_print_t *print, const mp_raw_code_t *rc) { // Save function kind and data length - mp_print_uint(print, (rc->fun_data_len << 2) | (rc->kind - MP_CODE_BYTECODE)); - - bytecode_prelude_t prelude; + mp_print_uint(print, (rc->fun_data_len << 3) | ((rc->n_children != 0) << 2) | (rc->kind - MP_CODE_BYTECODE)); - if (rc->kind == MP_CODE_BYTECODE) { - // Extract prelude - const byte *ip = rc->fun_data; - const byte *ip_info = extract_prelude(&ip, &prelude); + // Save function code. + mp_print_bytes(print, rc->fun_data, rc->fun_data_len); - // Save prelude - mp_print_bytes(print, rc->fun_data, ip_info - (const byte *)rc->fun_data); - save_prelude_qstrs(print, qstr_window, ip_info); - ip_info += 4; - mp_print_bytes(print, ip_info, ip - ip_info); - - // Save bytecode - const byte *ip_top = (const byte *)rc->fun_data + rc->fun_data_len; - save_bytecode(print, qstr_window, ip, ip_top); #if MICROPY_EMIT_MACHINE_CODE - } else { - // Save native code - mp_print_bytes(print, rc->fun_data, rc->fun_data_len); - - if (rc->kind == MP_CODE_NATIVE_PY || rc->kind == MP_CODE_NATIVE_VIPER) { - // Save qstr link table for native code - mp_print_uint(print, rc->n_qstr); - for (size_t i = 0; i < rc->n_qstr; ++i) { - mp_print_uint(print, rc->qstr_link[i].off); - save_qstr(print, qstr_window, rc->qstr_link[i].qst); - } + if (rc->kind == MP_CODE_NATIVE_PY) { + // Save prelude size + mp_print_uint(print, rc->prelude_offset); + } else if (rc->kind == MP_CODE_NATIVE_VIPER || rc->kind == MP_CODE_NATIVE_ASM) { + // Save basic scope info for viper and asm + mp_print_uint(print, rc->scope_flags & MP_SCOPE_FLAG_ALL_SIG); + if (rc->kind == MP_CODE_NATIVE_ASM) { + mp_print_uint(print, rc->n_pos_args); + mp_print_uint(print, rc->type_sig); } - - if (rc->kind == MP_CODE_NATIVE_PY) { - // Save prelude size - mp_print_uint(print, rc->prelude_offset); - - // Extract prelude and save qstrs in prelude - const byte *ip = (const byte *)rc->fun_data + rc->prelude_offset; - const byte *ip_info = extract_prelude(&ip, &prelude); - save_prelude_qstrs(print, qstr_window, ip_info); - } else { - // Save basic scope info for viper and asm - mp_print_uint(print, rc->scope_flags & MP_SCOPE_FLAG_ALL_SIG); - prelude.n_pos_args = 0; - prelude.n_kwonly_args = 0; - if (rc->kind == MP_CODE_NATIVE_ASM) { - mp_print_uint(print, rc->n_pos_args); - mp_print_uint(print, rc->type_sig); - } - } - #endif } + #endif - if (rc->kind != MP_CODE_NATIVE_ASM) { - // Save constant table for bytecode, native and viper - - // Number of entries in constant table - mp_print_uint(print, rc->n_obj); - mp_print_uint(print, rc->n_raw_code); - - const mp_uint_t *const_table = rc->const_table; - - // Save function argument names (initial entries in const_table) - // (viper has n_pos_args=n_kwonly_args=0 so doesn't save any qstrs here) - for (size_t i = 0; i < prelude.n_pos_args + prelude.n_kwonly_args; ++i) { - mp_obj_t o = (mp_obj_t)*const_table++; - save_qstr(print, qstr_window, MP_OBJ_QSTR_VALUE(o)); - } - - if (rc->kind != MP_CODE_BYTECODE) { - // Skip saving mp_fun_table entry - ++const_table; - } - - // Save constant objects and raw code children - for (size_t i = 0; i < rc->n_obj; ++i) { - save_obj(print, (mp_obj_t)*const_table++); - } - for (size_t i = 0; i < rc->n_raw_code; ++i) { - save_raw_code(print, (mp_raw_code_t *)(uintptr_t)*const_table++, qstr_window); - } - } -} - -STATIC bool mp_raw_code_has_native(mp_raw_code_t *rc) { - if (rc->kind != MP_CODE_BYTECODE) { - return true; - } - - const byte *ip = rc->fun_data; - bytecode_prelude_t prelude; - extract_prelude(&ip, &prelude); - - const mp_uint_t *const_table = rc->const_table - + prelude.n_pos_args + prelude.n_kwonly_args - + rc->n_obj; - - for (size_t i = 0; i < rc->n_raw_code; ++i) { - if (mp_raw_code_has_native((mp_raw_code_t *)(uintptr_t)*const_table++)) { - return true; + if (rc->n_children) { + mp_print_uint(print, rc->n_children); + for (size_t i = 0; i < rc->n_children; ++i) { + save_raw_code(print, rc->children[i]); } } - - return false; } -void mp_raw_code_save(mp_raw_code_t *rc, mp_print_t *print) { +void mp_raw_code_save(mp_compiled_module_t *cm, mp_print_t *print) { // header contains: // byte 'M' // byte version // byte feature flags // byte number of bits in a small int - // uint size of qstr window byte header[4] = { 'M', MPY_VERSION, @@ -810,19 +590,30 @@ void mp_raw_code_save(mp_raw_code_t *rc, mp_print_t *print) { #if MICROPY_DYNAMIC_COMPILER mp_dynamic_compiler.small_int_bits, #else - mp_small_int_bits(), + MP_SMALL_INT_BITS, #endif }; - if (mp_raw_code_has_native(rc)) { + if (cm->has_native) { header[2] |= MPY_FEATURE_ENCODE_ARCH(MPY_FEATURE_ARCH_DYNAMIC); } mp_print_bytes(print, header, sizeof(header)); - mp_print_uint(print, QSTR_WINDOW_SIZE); - qstr_window_t qw; - qw.idx = 0; - memset(qw.window, 0, sizeof(qw.window)); - save_raw_code(print, rc, &qw); + // Number of entries in constant table. + mp_print_uint(print, cm->n_qstr); + mp_print_uint(print, cm->n_obj); + + // Save qstrs. + for (size_t i = 0; i < cm->n_qstr; ++i) { + save_qstr(print, cm->context->constants.qstr_table[i]); + } + + // Save constant objects. + for (size_t i = 0; i < cm->n_obj; ++i) { + save_obj(print, (mp_obj_t)cm->context->constants.obj_table[i]); + } + + // Save outer raw code, which will save all its child raw codes. + save_raw_code(print, cm->rc); } #if MICROPY_PERSISTENT_CODE_SAVE_FILE @@ -839,12 +630,12 @@ STATIC void fd_print_strn(void *env, const char *str, size_t len) { (void)ret; } -void mp_raw_code_save_file(mp_raw_code_t *rc, const char *filename) { +void mp_raw_code_save_file(mp_compiled_module_t *cm, const char *filename) { MP_THREAD_GIL_EXIT(); int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); MP_THREAD_GIL_ENTER(); mp_print_t fd_print = {(void *)(intptr_t)fd, fd_print_strn}; - mp_raw_code_save(rc, &fd_print); + mp_raw_code_save(cm, &fd_print); MP_THREAD_GIL_EXIT(); close(fd); MP_THREAD_GIL_ENTER(); diff --git a/python/src/py/persistentcode.h b/python/src/py/persistentcode.h index 8769ef584d2..29ccce4a3d9 100644 --- a/python/src/py/persistentcode.h +++ b/python/src/py/persistentcode.h @@ -31,7 +31,7 @@ #include "py/emitglue.h" // The current version of .mpy files -#define MPY_VERSION 5 +#define MPY_VERSION 6 // Macros to encode/decode flags to/from the feature byte #define MPY_FEATURE_ENCODE_FLAGS(flags) (flags) @@ -41,17 +41,12 @@ #define MPY_FEATURE_ENCODE_ARCH(arch) ((arch) << 2) #define MPY_FEATURE_DECODE_ARCH(feat) ((feat) >> 2) -// The feature flag bits encode the compile-time config options that -// affect the generate bytecode. -#define MPY_FEATURE_FLAGS ( \ - ((MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) << 0) \ - | ((MICROPY_PY_BUILTINS_STR_UNICODE) << 1) \ - ) +// The feature flag bits encode the compile-time config options that affect +// the generate bytecode. Note: no longer used. +// (formerly MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE and MICROPY_PY_BUILTINS_STR_UNICODE). +#define MPY_FEATURE_FLAGS (0) // This is a version of the flags that can be configured at runtime. -#define MPY_FEATURE_FLAGS_DYNAMIC ( \ - ((MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE_DYNAMIC) << 0) \ - | ((MICROPY_PY_BUILTINS_STR_UNICODE_DYNAMIC) << 1) \ - ) +#define MPY_FEATURE_FLAGS_DYNAMIC (0) // Define the host architecture #if MICROPY_EMIT_X86 @@ -68,7 +63,7 @@ #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV7EM) #endif #else - #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV7M) + #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV6M) #endif #define MPY_FEATURE_ARCH_TEST(x) (MP_NATIVE_ARCH_ARMV6M <= (x) && (x) <= MPY_FEATURE_ARCH) #elif MICROPY_EMIT_ARM @@ -103,12 +98,26 @@ enum { MP_NATIVE_ARCH_XTENSAWIN, }; -mp_raw_code_t *mp_raw_code_load(mp_reader_t *reader); -mp_raw_code_t *mp_raw_code_load_mem(const byte *buf, size_t len); -mp_raw_code_t *mp_raw_code_load_file(const char *filename); +enum { + MP_PERSISTENT_OBJ_FUN_TABLE = 0, + MP_PERSISTENT_OBJ_NONE, + MP_PERSISTENT_OBJ_FALSE, + MP_PERSISTENT_OBJ_TRUE, + MP_PERSISTENT_OBJ_ELLIPSIS, + MP_PERSISTENT_OBJ_STR, + MP_PERSISTENT_OBJ_BYTES, + MP_PERSISTENT_OBJ_INT, + MP_PERSISTENT_OBJ_FLOAT, + MP_PERSISTENT_OBJ_COMPLEX, + MP_PERSISTENT_OBJ_TUPLE, +}; + +mp_compiled_module_t mp_raw_code_load(mp_reader_t *reader, mp_module_context_t *ctx); +mp_compiled_module_t mp_raw_code_load_mem(const byte *buf, size_t len, mp_module_context_t *ctx); +mp_compiled_module_t mp_raw_code_load_file(const char *filename, mp_module_context_t *ctx); -void mp_raw_code_save(mp_raw_code_t *rc, mp_print_t *print); -void mp_raw_code_save_file(mp_raw_code_t *rc, const char *filename); +void mp_raw_code_save(mp_compiled_module_t *cm, mp_print_t *print); +void mp_raw_code_save_file(mp_compiled_module_t *cm, const char *filename); void mp_native_relocate(void *reloc, uint8_t *text, uintptr_t reloc_text); diff --git a/python/src/py/profile.c b/python/src/py/profile.c index 054a0f9e614..4e23e9eac43 100644 --- a/python/src/py/profile.c +++ b/python/src/py/profile.c @@ -27,14 +27,16 @@ #include "py/profile.h" #include "py/bc0.h" #include "py/gc.h" +#include "py/objfun.h" #if MICROPY_PY_SYS_SETTRACE #define prof_trace_cb MP_STATE_THREAD(prof_trace_callback) +#define QSTR_MAP(context, idx) (context->constants.qstr_table[idx]) STATIC uint mp_prof_bytecode_lineno(const mp_raw_code_t *rc, size_t bc) { const mp_bytecode_prelude_t *prelude = &rc->prelude; - return mp_bytecode_get_source_line(prelude->line_info, bc); + return mp_bytecode_get_source_line(prelude->line_info, prelude->line_info_top, bc); } void mp_prof_extract_prelude(const byte *bytecode, mp_bytecode_prelude_t *prelude) { @@ -50,13 +52,14 @@ void mp_prof_extract_prelude(const byte *bytecode, mp_bytecode_prelude_t *prelud MP_BC_PRELUDE_SIZE_DECODE(ip); - prelude->line_info = ip + 4; + prelude->line_info_top = ip + n_info; prelude->opcodes = ip + n_info + n_cell; - qstr block_name = ip[0] | (ip[1] << 8); - qstr source_file = ip[2] | (ip[3] << 8); - prelude->qstr_block_name = block_name; - prelude->qstr_source_file = source_file; + prelude->qstr_block_name_idx = mp_decode_uint_value(ip); + for (size_t i = 0; i < 1 + n_pos_args + n_kwonly_args; ++i) { + ip = mp_decode_uint_skip(ip); + } + prelude->line_info = ip; } /******************************************************************************/ @@ -69,22 +72,19 @@ STATIC void code_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t k const mp_bytecode_prelude_t *prelude = &rc->prelude; mp_printf(print, "", - prelude->qstr_block_name, + QSTR_MAP(o->context, prelude->qstr_block_name_idx), o, - prelude->qstr_source_file, + QSTR_MAP(o->context, 0), rc->line_of_definition ); } -STATIC mp_obj_tuple_t *code_consts(const mp_raw_code_t *rc) { - const mp_bytecode_prelude_t *prelude = &rc->prelude; - int start = prelude->n_pos_args + prelude->n_kwonly_args + rc->n_obj; - int stop = prelude->n_pos_args + prelude->n_kwonly_args + rc->n_obj + rc->n_raw_code; - mp_obj_tuple_t *consts = MP_OBJ_TO_PTR(mp_obj_new_tuple(stop - start + 1, NULL)); +STATIC mp_obj_tuple_t *code_consts(const mp_module_context_t *context, const mp_raw_code_t *rc) { + mp_obj_tuple_t *consts = MP_OBJ_TO_PTR(mp_obj_new_tuple(rc->n_children + 1, NULL)); size_t const_no = 0; - for (int i = start; i < stop; ++i) { - mp_obj_t code = mp_obj_new_code((const mp_raw_code_t *)MP_OBJ_TO_PTR(rc->const_table[i])); + for (size_t i = 0; i < rc->n_children; ++i) { + mp_obj_t code = mp_obj_new_code(context, rc->children[i]); if (code == MP_OBJ_NULL) { m_malloc_fail(sizeof(mp_obj_code_t)); } @@ -149,16 +149,16 @@ STATIC void code_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { ); break; case MP_QSTR_co_consts: - dest[0] = MP_OBJ_FROM_PTR(code_consts(rc)); + dest[0] = MP_OBJ_FROM_PTR(code_consts(o->context, rc)); break; case MP_QSTR_co_filename: - dest[0] = MP_OBJ_NEW_QSTR(prelude->qstr_source_file); + dest[0] = MP_OBJ_NEW_QSTR(QSTR_MAP(o->context, 0)); break; case MP_QSTR_co_firstlineno: dest[0] = MP_OBJ_NEW_SMALL_INT(mp_prof_bytecode_lineno(rc, 0)); break; case MP_QSTR_co_name: - dest[0] = MP_OBJ_NEW_QSTR(prelude->qstr_block_name); + dest[0] = MP_OBJ_NEW_QSTR(QSTR_MAP(o->context, prelude->qstr_block_name_idx)); break; case MP_QSTR_co_names: dest[0] = MP_OBJ_FROM_PTR(o->dict_locals); @@ -180,12 +180,13 @@ const mp_obj_type_t mp_type_settrace_codeobj = { .attr = code_attr, }; -mp_obj_t mp_obj_new_code(const mp_raw_code_t *rc) { +mp_obj_t mp_obj_new_code(const mp_module_context_t *context, const mp_raw_code_t *rc) { mp_obj_code_t *o = m_new_obj_maybe(mp_obj_code_t); if (o == NULL) { return MP_OBJ_NULL; } o->base.type = &mp_type_settrace_codeobj; + o->context = context; o->rc = rc; o->dict_locals = mp_locals_get(); // this is a wrong! how to do this properly? o->lnotab = MP_OBJ_NULL; @@ -204,9 +205,9 @@ STATIC void frame_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t mp_printf(print, "", frame, - prelude->qstr_source_file, + QSTR_MAP(code->context, 0), frame->lineno, - prelude->qstr_block_name + QSTR_MAP(code->context, prelude->qstr_block_name_idx) ); } @@ -229,7 +230,7 @@ STATIC void frame_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { dest[0] = MP_OBJ_FROM_PTR(o->code); break; case MP_QSTR_f_globals: - dest[0] = MP_OBJ_FROM_PTR(o->code_state->fun_bc->globals); + dest[0] = MP_OBJ_FROM_PTR(o->code_state->fun_bc->context->module.globals); break; case MP_QSTR_f_lasti: dest[0] = MP_OBJ_NEW_SMALL_INT(o->lasti); @@ -258,7 +259,7 @@ mp_obj_t mp_obj_new_frame(const mp_code_state_t *code_state) { return MP_OBJ_NULL; } - mp_obj_code_t *code = o->code = MP_OBJ_TO_PTR(mp_obj_new_code(code_state->fun_bc->rc)); + mp_obj_code_t *code = o->code = MP_OBJ_TO_PTR(mp_obj_new_code(code_state->fun_bc->context, code_state->fun_bc->rc)); if (code == NULL) { return MP_OBJ_NULL; } @@ -540,9 +541,6 @@ STATIC const byte *mp_prof_opcode_decode(const byte *ip, const mp_uint_t *const_ instruction->qstr_opname = MP_QSTR_LOAD_NAME; instruction->arg = qst; instruction->argobj = MP_OBJ_NEW_QSTR(qst); - if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) { - instruction->argobjex_cache = MP_OBJ_NEW_SMALL_INT(*ip++); - } break; case MP_BC_LOAD_GLOBAL: @@ -550,9 +548,6 @@ STATIC const byte *mp_prof_opcode_decode(const byte *ip, const mp_uint_t *const_ instruction->qstr_opname = MP_QSTR_LOAD_GLOBAL; instruction->arg = qst; instruction->argobj = MP_OBJ_NEW_QSTR(qst); - if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) { - instruction->argobjex_cache = MP_OBJ_NEW_SMALL_INT(*ip++); - } break; case MP_BC_LOAD_ATTR: @@ -560,9 +555,6 @@ STATIC const byte *mp_prof_opcode_decode(const byte *ip, const mp_uint_t *const_ instruction->qstr_opname = MP_QSTR_LOAD_ATTR; instruction->arg = qst; instruction->argobj = MP_OBJ_NEW_QSTR(qst); - if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) { - instruction->argobjex_cache = MP_OBJ_NEW_SMALL_INT(*ip++); - } break; case MP_BC_LOAD_METHOD: @@ -618,9 +610,6 @@ STATIC const byte *mp_prof_opcode_decode(const byte *ip, const mp_uint_t *const_ instruction->qstr_opname = MP_QSTR_STORE_ATTR; instruction->arg = qst; instruction->argobj = MP_OBJ_NEW_QSTR(qst); - if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) { - instruction->argobjex_cache = MP_OBJ_NEW_SMALL_INT(*ip++); - } break; case MP_BC_STORE_SUBSCR: diff --git a/python/src/py/profile.h b/python/src/py/profile.h index 64e207d04fb..7f3f9140346 100644 --- a/python/src/py/profile.h +++ b/python/src/py/profile.h @@ -34,7 +34,9 @@ #define mp_prof_is_executing MP_STATE_THREAD(prof_callback_is_executing) typedef struct _mp_obj_code_t { + // TODO this was 4 words mp_obj_base_t base; + const mp_module_context_t *context; const mp_raw_code_t *rc; mp_obj_dict_t *dict_locals; mp_obj_t lnotab; @@ -53,7 +55,7 @@ typedef struct _mp_obj_frame_t { void mp_prof_extract_prelude(const byte *bytecode, mp_bytecode_prelude_t *prelude); -mp_obj_t mp_obj_new_code(const mp_raw_code_t *rc); +mp_obj_t mp_obj_new_code(const mp_module_context_t *mc, const mp_raw_code_t *rc); mp_obj_t mp_obj_new_frame(const mp_code_state_t *code_state); // This is the implementation for the sys.settrace diff --git a/python/src/py/py.mk b/python/src/py/py.mk index 609ba6cae3c..57fdb6d9b5d 100644 --- a/python/src/py/py.mk +++ b/python/src/py/py.mk @@ -176,6 +176,7 @@ PY_EXTMOD_O_BASENAME = \ extmod/moduasyncio.o \ extmod/moductypes.o \ extmod/modujson.o \ + extmod/moduos.o \ extmod/modure.o \ extmod/moduzlib.o \ extmod/moduheapq.o \ @@ -189,11 +190,13 @@ PY_EXTMOD_O_BASENAME = \ extmod/machine_pinbase.o \ extmod/machine_signal.o \ extmod/machine_pulse.o \ + extmod/machine_pwm.o \ extmod/machine_i2c.o \ extmod/machine_spi.o \ extmod/modbluetooth.o \ extmod/modussl_axtls.o \ extmod/modussl_mbedtls.o \ + extmod/moduplatform.o\ extmod/modurandom.o \ extmod/moduselect.o \ extmod/moduwebsocket.o \ @@ -225,16 +228,6 @@ ifneq ($(FROZEN_MANIFEST),) PY_O += $(BUILD)/$(BUILD)/frozen_content.o endif -# object file for frozen files -ifneq ($(FROZEN_DIR),) -PY_O += $(BUILD)/$(BUILD)/frozen.o -endif - -# object file for frozen bytecode (frozen .mpy files) -ifneq ($(FROZEN_MPY_DIR),) -PY_O += $(BUILD)/$(BUILD)/frozen_mpy.o -endif - # Sources that may contain qstrings SRC_QSTR_IGNORE = py/nlr% SRC_QSTR += $(SRC_MOD) $(filter-out $(SRC_QSTR_IGNORE),$(PY_CORE_O_BASENAME:.o=.c)) $(PY_EXTMOD_O_BASENAME:.o=.c) @@ -266,9 +259,9 @@ $(HEADER_BUILD)/compressed.data.h: $(HEADER_BUILD)/compressed.collected $(Q)$(PYTHON) $(PY_SRC)/makecompresseddata.py $< > $@ # build a list of registered modules for py/objmodule.c. -$(HEADER_BUILD)/moduledefs.h: $(SRC_QSTR) $(QSTR_GLOBAL_DEPENDENCIES) | $(HEADER_BUILD)/mpversion.h +$(HEADER_BUILD)/moduledefs.h: $(HEADER_BUILD)/moduledefs.collected @$(ECHO) "GEN $@" - $(Q)$(PYTHON) $(PY_SRC)/makemoduledefs.py --vpath="., $(TOP), $(USER_C_MODULES)" $(SRC_QSTR) > $@ + $(Q)$(PYTHON) $(PY_SRC)/makemoduledefs.py $< > $@ # Standard C functions like memset need to be compiled with special flags so # the compiler does not optimise these functions in terms of themselves. diff --git a/python/src/py/qstr.c b/python/src/py/qstr.c index c14ec5ae007..f9ca1068374 100644 --- a/python/src/py/qstr.c +++ b/python/src/py/qstr.c @@ -35,7 +35,6 @@ // NOTE: we are using linear arrays to store and search for qstr's (unique strings, interned strings) // ultimately we will replace this with a static hash table of some kind -// also probably need to include the length in the string data, to allow null bytes in the string #if MICROPY_DEBUG_VERBOSE // print debugging info #define DEBUG_printf DEBUG_printf @@ -44,34 +43,9 @@ #endif // A qstr is an index into the qstr pool. -// The data for a qstr contains (hash, length, data): -// - hash (configurable number of bytes) -// - length (configurable number of bytes) -// - data ("length" number of bytes) -// - \0 terminated (so they can be printed using printf) - -#if MICROPY_QSTR_BYTES_IN_HASH == 1 - #define Q_HASH_MASK (0xff) - #define Q_GET_HASH(q) ((mp_uint_t)(q)[0]) - #define Q_SET_HASH(q, hash) do { (q)[0] = (hash); } while (0) -#elif MICROPY_QSTR_BYTES_IN_HASH == 2 - #define Q_HASH_MASK (0xffff) - #define Q_GET_HASH(q) ((mp_uint_t)(q)[0] | ((mp_uint_t)(q)[1] << 8)) - #define Q_SET_HASH(q, hash) do { (q)[0] = (hash); (q)[1] = (hash) >> 8; } while (0) -#else - #error unimplemented qstr hash decoding -#endif -#define Q_GET_ALLOC(q) (MICROPY_QSTR_BYTES_IN_HASH + MICROPY_QSTR_BYTES_IN_LEN + Q_GET_LENGTH(q) + 1) -#define Q_GET_DATA(q) ((q) + MICROPY_QSTR_BYTES_IN_HASH + MICROPY_QSTR_BYTES_IN_LEN) -#if MICROPY_QSTR_BYTES_IN_LEN == 1 - #define Q_GET_LENGTH(q) ((q)[MICROPY_QSTR_BYTES_IN_HASH]) - #define Q_SET_LENGTH(q, len) do { (q)[MICROPY_QSTR_BYTES_IN_HASH] = (len); } while (0) -#elif MICROPY_QSTR_BYTES_IN_LEN == 2 - #define Q_GET_LENGTH(q) ((q)[MICROPY_QSTR_BYTES_IN_HASH] | ((q)[MICROPY_QSTR_BYTES_IN_HASH + 1] << 8)) - #define Q_SET_LENGTH(q, len) do { (q)[MICROPY_QSTR_BYTES_IN_HASH] = (len); (q)[MICROPY_QSTR_BYTES_IN_HASH + 1] = (len) >> 8; } while (0) -#else - #error unimplemented qstr length decoding -#endif +// The data for a qstr is \0 terminated (so they can be printed using printf) + +#define Q_HASH_MASK ((1 << (8 * MICROPY_QSTR_BYTES_IN_HASH)) - 1) #if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL #define QSTR_ENTER() mp_thread_mutex_lock(&MP_STATE_VM(qstr_mutex), 1) @@ -100,14 +74,32 @@ mp_uint_t qstr_compute_hash(const byte *data, size_t len) { return hash; } +const qstr_hash_t mp_qstr_const_hashes[] = { + #ifndef NO_QSTR +#define QDEF(id, hash, len, str) hash, + #include "genhdr/qstrdefs.generated.h" +#undef QDEF + #endif +}; + +const qstr_len_t mp_qstr_const_lengths[] = { + #ifndef NO_QSTR +#define QDEF(id, hash, len, str) len, + #include "genhdr/qstrdefs.generated.h" +#undef QDEF + #endif +}; + const qstr_pool_t mp_qstr_const_pool = { NULL, // no previous pool 0, // no previous pool MICROPY_ALLOC_QSTR_ENTRIES_INIT, MP_QSTRnumber_of, // corresponds to number of strings in array just below + (qstr_hash_t *)mp_qstr_const_hashes, + (qstr_len_t *)mp_qstr_const_lengths, { #ifndef NO_QSTR -#define QDEF(id, str) str, +#define QDEF(id, hash, len, str) str, #include "genhdr/qstrdefs.generated.h" #undef QDEF #endif @@ -130,19 +122,21 @@ void qstr_init(void) { #endif } -STATIC const byte *find_qstr(qstr q) { +STATIC const qstr_pool_t *find_qstr(qstr *q) { // search pool for this qstr // total_prev_len==0 in the final pool, so the loop will always terminate - qstr_pool_t *pool = MP_STATE_VM(last_pool); - while (q < pool->total_prev_len) { + const qstr_pool_t *pool = MP_STATE_VM(last_pool); + while (*q < pool->total_prev_len) { pool = pool->prev; } - return pool->qstrs[q - pool->total_prev_len]; + *q -= pool->total_prev_len; + assert(*q < pool->len); + return pool; } // qstr_mutex must be taken while in this function -STATIC qstr qstr_add(const byte *q_ptr) { - DEBUG_printf("QSTR: add hash=%d len=%d data=%.*s\n", Q_GET_HASH(q_ptr), Q_GET_LENGTH(q_ptr), Q_GET_LENGTH(q_ptr), Q_GET_DATA(q_ptr)); +STATIC qstr qstr_add(mp_uint_t hash, mp_uint_t len, const char *q_ptr) { + DEBUG_printf("QSTR: add hash=%d len=%d data=%.*s\n", hash, len, len, q_ptr); // make sure we have room in the pool for a new qstr if (MP_STATE_VM(last_pool)->len >= MP_STATE_VM(last_pool)->alloc) { @@ -151,11 +145,21 @@ STATIC qstr qstr_add(const byte *q_ptr) { // Put a lower bound on the allocation size in case the extra qstr pool has few entries new_alloc = MAX(MICROPY_ALLOC_QSTR_ENTRIES_INIT, new_alloc); #endif - qstr_pool_t *pool = m_new_obj_var_maybe(qstr_pool_t, const char *, new_alloc); + mp_uint_t pool_size = sizeof(qstr_pool_t) + + (sizeof(const char *) + sizeof(qstr_hash_t) + sizeof(qstr_len_t)) * new_alloc; + qstr_pool_t *pool = (qstr_pool_t *)m_malloc_maybe(pool_size); if (pool == NULL) { + // Keep qstr_last_chunk consistent with qstr_pool_t: qstr_last_chunk is not scanned + // at garbage collection since it's reachable from a qstr_pool_t. And the caller of + // this function expects q_ptr to be stored in a qstr_pool_t so it can be reached + // by the collector. If qstr_pool_t allocation failed, qstr_last_chunk needs to be + // NULL'd. Otherwise it may become a dangling pointer at the next garbage collection. + MP_STATE_VM(qstr_last_chunk) = NULL; QSTR_EXIT(); m_malloc_fail(new_alloc); } + pool->hashes = (qstr_hash_t *)(pool->qstrs + new_alloc); + pool->lengths = (qstr_len_t *)(pool->hashes + new_alloc); pool->prev = MP_STATE_VM(last_pool); pool->total_prev_len = MP_STATE_VM(last_pool)->total_prev_len + MP_STATE_VM(last_pool)->len; pool->alloc = new_alloc; @@ -165,10 +169,14 @@ STATIC qstr qstr_add(const byte *q_ptr) { } // add the new qstr - MP_STATE_VM(last_pool)->qstrs[MP_STATE_VM(last_pool)->len++] = q_ptr; + mp_uint_t at = MP_STATE_VM(last_pool)->len; + MP_STATE_VM(last_pool)->hashes[at] = hash; + MP_STATE_VM(last_pool)->lengths[at] = len; + MP_STATE_VM(last_pool)->qstrs[at] = q_ptr; + MP_STATE_VM(last_pool)->len++; // return id for the newly-added qstr - return MP_STATE_VM(last_pool)->total_prev_len + MP_STATE_VM(last_pool)->len - 1; + return MP_STATE_VM(last_pool)->total_prev_len + at; } qstr qstr_find_strn(const char *str, size_t str_len) { @@ -176,10 +184,11 @@ qstr qstr_find_strn(const char *str, size_t str_len) { mp_uint_t str_hash = qstr_compute_hash((const byte *)str, str_len); // search pools for the data - for (qstr_pool_t *pool = MP_STATE_VM(last_pool); pool != NULL; pool = pool->prev) { - for (const byte **q = pool->qstrs, **q_top = pool->qstrs + pool->len; q < q_top; q++) { - if (Q_GET_HASH(*q) == str_hash && Q_GET_LENGTH(*q) == str_len && memcmp(Q_GET_DATA(*q), str, str_len) == 0) { - return pool->total_prev_len + (q - pool->qstrs); + for (const qstr_pool_t *pool = MP_STATE_VM(last_pool); pool != NULL; pool = pool->prev) { + for (mp_uint_t at = 0, top = pool->len; at < top; at++) { + if (pool->hashes[at] == str_hash && pool->lengths[at] == str_len + && memcmp(pool->qstrs[at], str, str_len) == 0) { + return pool->total_prev_len + at; } } } @@ -205,14 +214,14 @@ qstr qstr_from_strn(const char *str, size_t len) { } // compute number of bytes needed to intern this string - size_t n_bytes = MICROPY_QSTR_BYTES_IN_HASH + MICROPY_QSTR_BYTES_IN_LEN + len + 1; + size_t n_bytes = len + 1; if (MP_STATE_VM(qstr_last_chunk) != NULL && MP_STATE_VM(qstr_last_used) + n_bytes > MP_STATE_VM(qstr_last_alloc)) { // not enough room at end of previously interned string so try to grow - byte *new_p = m_renew_maybe(byte, MP_STATE_VM(qstr_last_chunk), MP_STATE_VM(qstr_last_alloc), MP_STATE_VM(qstr_last_alloc) + n_bytes, false); + char *new_p = m_renew_maybe(char, MP_STATE_VM(qstr_last_chunk), MP_STATE_VM(qstr_last_alloc), MP_STATE_VM(qstr_last_alloc) + n_bytes, false); if (new_p == NULL) { // could not grow existing memory; shrink it to fit previous - (void)m_renew_maybe(byte, MP_STATE_VM(qstr_last_chunk), MP_STATE_VM(qstr_last_alloc), MP_STATE_VM(qstr_last_used), false); + (void)m_renew_maybe(char, MP_STATE_VM(qstr_last_chunk), MP_STATE_VM(qstr_last_alloc), MP_STATE_VM(qstr_last_used), false); MP_STATE_VM(qstr_last_chunk) = NULL; } else { // could grow existing memory @@ -226,10 +235,10 @@ qstr qstr_from_strn(const char *str, size_t len) { if (al < MICROPY_ALLOC_QSTR_CHUNK_INIT) { al = MICROPY_ALLOC_QSTR_CHUNK_INIT; } - MP_STATE_VM(qstr_last_chunk) = m_new_maybe(byte, al); + MP_STATE_VM(qstr_last_chunk) = m_new_maybe(char, al); if (MP_STATE_VM(qstr_last_chunk) == NULL) { // failed to allocate a large chunk so try with exact size - MP_STATE_VM(qstr_last_chunk) = m_new_maybe(byte, n_bytes); + MP_STATE_VM(qstr_last_chunk) = m_new_maybe(char, n_bytes); if (MP_STATE_VM(qstr_last_chunk) == NULL) { QSTR_EXIT(); m_malloc_fail(n_bytes); @@ -241,40 +250,38 @@ qstr qstr_from_strn(const char *str, size_t len) { } // allocate memory from the chunk for this new interned string's data - byte *q_ptr = MP_STATE_VM(qstr_last_chunk) + MP_STATE_VM(qstr_last_used); + char *q_ptr = MP_STATE_VM(qstr_last_chunk) + MP_STATE_VM(qstr_last_used); MP_STATE_VM(qstr_last_used) += n_bytes; // store the interned strings' data mp_uint_t hash = qstr_compute_hash((const byte *)str, len); - Q_SET_HASH(q_ptr, hash); - Q_SET_LENGTH(q_ptr, len); - memcpy(q_ptr + MICROPY_QSTR_BYTES_IN_HASH + MICROPY_QSTR_BYTES_IN_LEN, str, len); - q_ptr[MICROPY_QSTR_BYTES_IN_HASH + MICROPY_QSTR_BYTES_IN_LEN + len] = '\0'; - q = qstr_add(q_ptr); + memcpy(q_ptr, str, len); + q_ptr[len] = '\0'; + q = qstr_add(hash, len, q_ptr); } QSTR_EXIT(); return q; } mp_uint_t qstr_hash(qstr q) { - const byte *qd = find_qstr(q); - return Q_GET_HASH(qd); + const qstr_pool_t *pool = find_qstr(&q); + return pool->hashes[q]; } size_t qstr_len(qstr q) { - const byte *qd = find_qstr(q); - return Q_GET_LENGTH(qd); + const qstr_pool_t *pool = find_qstr(&q); + return pool->lengths[q]; } const char *qstr_str(qstr q) { - const byte *qd = find_qstr(q); - return (const char *)Q_GET_DATA(qd); + const qstr_pool_t *pool = find_qstr(&q); + return pool->qstrs[q]; } const byte *qstr_data(qstr q, size_t *len) { - const byte *qd = find_qstr(q); - *len = Q_GET_LENGTH(qd); - return Q_GET_DATA(qd); + const qstr_pool_t *pool = find_qstr(&q); + *len = pool->lengths[q]; + return (byte *)pool->qstrs[q]; } void qstr_pool_info(size_t *n_pool, size_t *n_qstr, size_t *n_str_data_bytes, size_t *n_total_bytes) { @@ -283,16 +290,17 @@ void qstr_pool_info(size_t *n_pool, size_t *n_qstr, size_t *n_str_data_bytes, si *n_qstr = 0; *n_str_data_bytes = 0; *n_total_bytes = 0; - for (qstr_pool_t *pool = MP_STATE_VM(last_pool); pool != NULL && pool != &CONST_POOL; pool = pool->prev) { + for (const qstr_pool_t *pool = MP_STATE_VM(last_pool); pool != NULL && pool != &CONST_POOL; pool = pool->prev) { *n_pool += 1; *n_qstr += pool->len; - for (const byte **q = pool->qstrs, **q_top = pool->qstrs + pool->len; q < q_top; q++) { - *n_str_data_bytes += Q_GET_ALLOC(*q); + for (qstr_len_t *l = pool->lengths, *l_top = pool->lengths + pool->len; l < l_top; l++) { + *n_str_data_bytes += *l + 1; } #if MICROPY_ENABLE_GC *n_total_bytes += gc_nbytes(pool); // this counts actual bytes used in heap #else - *n_total_bytes += sizeof(qstr_pool_t) + sizeof(qstr) * pool->alloc; + *n_total_bytes += sizeof(qstr_pool_t) + + (sizeof(const char *) + sizeof(qstr_hash_t) + sizeof(qstr_len_t)) * pool->alloc; #endif } *n_total_bytes += *n_str_data_bytes; @@ -302,9 +310,9 @@ void qstr_pool_info(size_t *n_pool, size_t *n_qstr, size_t *n_str_data_bytes, si #if MICROPY_PY_MICROPYTHON_MEM_INFO void qstr_dump_data(void) { QSTR_ENTER(); - for (qstr_pool_t *pool = MP_STATE_VM(last_pool); pool != NULL && pool != &CONST_POOL; pool = pool->prev) { - for (const byte **q = pool->qstrs, **q_top = pool->qstrs + pool->len; q < q_top; q++) { - mp_printf(&mp_plat_print, "Q(%s)\n", Q_GET_DATA(*q)); + for (const qstr_pool_t *pool = MP_STATE_VM(last_pool); pool != NULL && pool != &CONST_POOL; pool = pool->prev) { + for (const char *const *q = pool->qstrs, *const *q_top = pool->qstrs + pool->len; q < q_top; q++) { + mp_printf(&mp_plat_print, "Q(%s)\n", *q); } } QSTR_EXIT(); diff --git a/python/src/py/qstr.h b/python/src/py/qstr.h index 0b6fb12b084..fa634f90b0e 100644 --- a/python/src/py/qstr.h +++ b/python/src/py/qstr.h @@ -38,7 +38,7 @@ // first entry in enum will be MP_QSTRnull=0, which indicates invalid/no qstr enum { #ifndef NO_QSTR -#define QDEF(id, str) id, +#define QDEF(id, hash, len, str) id, #include "genhdr/qstrdefs.generated.h" #undef QDEF #endif @@ -46,13 +46,32 @@ enum { }; typedef size_t qstr; +typedef uint16_t qstr_short_t; + +#if MICROPY_QSTR_BYTES_IN_HASH == 1 +typedef uint8_t qstr_hash_t; +#elif MICROPY_QSTR_BYTES_IN_HASH == 2 +typedef uint16_t qstr_hash_t; +#else +#error unimplemented qstr hash decoding +#endif + +#if MICROPY_QSTR_BYTES_IN_LEN == 1 +typedef uint8_t qstr_len_t; +#elif MICROPY_QSTR_BYTES_IN_LEN == 2 +typedef uint16_t qstr_len_t; +#else +#error unimplemented qstr length decoding +#endif typedef struct _qstr_pool_t { - struct _qstr_pool_t *prev; + const struct _qstr_pool_t *prev; size_t total_prev_len; size_t alloc; size_t len; - const byte *qstrs[]; + qstr_hash_t *hashes; + qstr_len_t *lengths; + const char *qstrs[]; } qstr_pool_t; #define QSTR_TOTAL() (MP_STATE_VM(last_pool)->total_prev_len + MP_STATE_VM(last_pool)->len) diff --git a/python/src/py/qstrdefs.h b/python/src/py/qstrdefs.h index 5b4e0dc48e0..5003636df3d 100644 --- a/python/src/py/qstrdefs.h +++ b/python/src/py/qstrdefs.h @@ -39,6 +39,10 @@ Q() Q(*) Q(_) Q(/) +#if MICROPY_PY_SYS_PS1_PS2 +Q(>>> ) +Q(... ) +#endif #if MICROPY_PY_BUILTINS_STR_OP_MODULO Q(%#o) Q(%#x) @@ -60,6 +64,10 @@ Q() Q() Q(utf-8) +#if MICROPY_MODULE_FROZEN +Q(.frozen) +#endif + #if MICROPY_ENABLE_PYSTACK Q(pystack exhausted) #endif diff --git a/python/src/py/repl.c b/python/src/py/repl.c index 822e385abd2..0369b021951 100644 --- a/python/src/py/repl.c +++ b/python/src/py/repl.c @@ -33,6 +33,16 @@ #if MICROPY_HELPER_REPL +#if MICROPY_PY_SYS_PS1_PS2 +const char *mp_repl_get_psx(unsigned int entry) { + if (mp_obj_is_str(MP_STATE_VM(sys_mutable)[entry])) { + return mp_obj_str_get_str(MP_STATE_VM(sys_mutable)[entry]); + } else { + return ""; + } +} +#endif + STATIC bool str_startswith_word(const char *str, const char *head) { size_t i; for (i = 0; str[i] && head[i]; i++) { @@ -303,10 +313,7 @@ size_t mp_repl_autocomplete(const char *str, size_t len, const mp_print_t *print return sizeof(import_str) - 1 - s_len; } } - if (q_first == 0) { - *compl_str = " "; - return s_len ? 0 : 4; - } + return 0; } // 1 match found, or multiple matches with a common prefix diff --git a/python/src/py/repl.h b/python/src/py/repl.h index a7a4136cad1..9e8f7f1ddae 100644 --- a/python/src/py/repl.h +++ b/python/src/py/repl.h @@ -31,8 +31,34 @@ #include "py/mpprint.h" #if MICROPY_HELPER_REPL + +#if MICROPY_PY_SYS_PS1_PS2 + +const char *mp_repl_get_psx(unsigned int entry); + +static inline const char *mp_repl_get_ps1(void) { + return mp_repl_get_psx(MP_SYS_MUTABLE_PS1); +} + +static inline const char *mp_repl_get_ps2(void) { + return mp_repl_get_psx(MP_SYS_MUTABLE_PS2); +} + +#else + +static inline const char *mp_repl_get_ps1(void) { + return ">>> "; +} + +static inline const char *mp_repl_get_ps2(void) { + return "... "; +} + +#endif + bool mp_repl_continue_with_input(const char *input); size_t mp_repl_autocomplete(const char *str, size_t len, const mp_print_t *print, const char **compl_str); + #endif #endif // MICROPY_INCLUDED_PY_REPL_H diff --git a/python/src/py/runtime.c b/python/src/py/runtime.c index 27e5bc13eb1..e6d8c68070e 100644 --- a/python/src/py/runtime.c +++ b/python/src/py/runtime.c @@ -25,10 +25,11 @@ * THE SOFTWARE. */ +#include #include #include #include -#include +#include #include "py/parsenum.h" #include "py/compile.h" @@ -58,13 +59,23 @@ const mp_obj_module_t mp_module___main__ = { .globals = (mp_obj_dict_t *)&MP_STATE_VM(dict_main), }; +MP_REGISTER_MODULE(MP_QSTR___main__, mp_module___main__); + void mp_init(void) { qstr_init(); // no pending exceptions to start with MP_STATE_THREAD(mp_pending_exception) = MP_OBJ_NULL; #if MICROPY_ENABLE_SCHEDULER - MP_STATE_VM(sched_state) = MP_SCHED_IDLE; + #if MICROPY_SCHEDULER_STATIC_NODES + if (MP_STATE_VM(sched_head) == NULL) { + // no pending callbacks to start with + MP_STATE_VM(sched_state) = MP_SCHED_IDLE; + } else { + // pending callbacks are on the list, eg from before a soft reset + MP_STATE_VM(sched_state) = MP_SCHED_PENDING; + } + #endif MP_STATE_VM(sched_idx) = 0; MP_STATE_VM(sched_len) = 0; #endif @@ -122,16 +133,34 @@ void mp_init(void) { MP_STATE_VM(vfs_mount_table) = NULL; #endif + #if MICROPY_PY_SYS_PATH_ARGV_DEFAULTS + mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_path), 0); + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR_)); // current dir (or base dir of the script) + #if MICROPY_MODULE_FROZEN + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__dot_frozen)); + #endif + mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_argv), 0); + #endif + #if MICROPY_PY_SYS_ATEXIT MP_STATE_VM(sys_exitfunc) = mp_const_none; #endif + #if MICROPY_PY_SYS_PS1_PS2 + MP_STATE_VM(sys_mutable[MP_SYS_MUTABLE_PS1]) = MP_OBJ_NEW_QSTR(MP_QSTR__gt__gt__gt__space_); + MP_STATE_VM(sys_mutable[MP_SYS_MUTABLE_PS2]) = MP_OBJ_NEW_QSTR(MP_QSTR__dot__dot__dot__space_); + #endif + #if MICROPY_PY_SYS_SETTRACE MP_STATE_THREAD(prof_trace_callback) = MP_OBJ_NULL; MP_STATE_THREAD(prof_callback_is_executing) = false; MP_STATE_THREAD(current_code_state) = NULL; #endif + #if MICROPY_PY_SYS_TRACEBACKLIMIT + MP_STATE_VM(sys_mutable[MP_SYS_MUTABLE_TRACEBACKLIMIT]) = MP_OBJ_NEW_SMALL_INT(1000); + #endif + #if MICROPY_PY_BLUETOOTH MP_STATE_VM(bluetooth) = MP_OBJ_NULL; #endif @@ -157,7 +186,7 @@ void mp_deinit(void) { #endif } -mp_obj_t mp_load_name(qstr qst) { +mp_obj_t MICROPY_WRAP_MP_LOAD_NAME(mp_load_name)(qstr qst) { // logic: search locals, globals, builtins DEBUG_OP_printf("load name %s\n", qstr_str(qst)); // If we're at the outer scope (locals == globals), dispatch to load_global right away @@ -170,7 +199,7 @@ mp_obj_t mp_load_name(qstr qst) { return mp_load_global(qst); } -mp_obj_t mp_load_global(qstr qst) { +mp_obj_t MICROPY_WRAP_MP_LOAD_GLOBAL(mp_load_global)(qstr qst) { // logic: search globals, builtins DEBUG_OP_printf("load global %s\n", qstr_str(qst)); mp_map_elem_t *elem = mp_map_lookup(&mp_globals_get()->map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP); @@ -311,7 +340,7 @@ mp_obj_t mp_unary_op(mp_unary_op_t op, mp_obj_t arg) { } } -mp_obj_t mp_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { +mp_obj_t MICROPY_WRAP_MP_BINARY_OP(mp_binary_op)(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { DEBUG_OP_printf("binary " UINT_FMT " %q %p %p\n", op, mp_binary_op_method_name[op], lhs, rhs); // TODO correctly distinguish inplace operators for mutable objects @@ -681,12 +710,11 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_ if (have_self) { self = *args++; // may be MP_OBJ_NULL } - uint n_args = n_args_n_kw & 0xff; - uint n_kw = (n_args_n_kw >> 8) & 0xff; - mp_obj_t pos_seq = args[n_args + 2 * n_kw]; // may be MP_OBJ_NULL - mp_obj_t kw_dict = args[n_args + 2 * n_kw + 1]; // may be MP_OBJ_NULL + size_t n_args = n_args_n_kw & 0xff; + size_t n_kw = (n_args_n_kw >> 8) & 0xff; + mp_uint_t star_args = MP_OBJ_SMALL_INT_VALUE(args[n_args + 2 * n_kw]); - DEBUG_OP_printf("call method var (fun=%p, self=%p, n_args=%u, n_kw=%u, args=%p, seq=%p, dict=%p)\n", fun, self, n_args, n_kw, args, pos_seq, kw_dict); + DEBUG_OP_printf("call method var (fun=%p, self=%p, n_args=%u, n_kw=%u, args=%p, map=%u)\n", fun, self, n_args, n_kw, args, star_args); // We need to create the following array of objects: // args[0 .. n_args] unpacked(pos_seq) args[n_args .. n_args + 2 * n_kw] unpacked(kw_dict) @@ -694,19 +722,40 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_ // The new args array mp_obj_t *args2; - uint args2_alloc; - uint args2_len = 0; + size_t args2_alloc; + size_t args2_len = 0; + + // Try to get a hint for unpacked * args length + ssize_t list_len = 0; + + if (star_args != 0) { + for (size_t i = 0; i < n_args; i++) { + if ((star_args >> i) & 1) { + mp_obj_t len = mp_obj_len_maybe(args[i]); + if (len != MP_OBJ_NULL) { + // -1 accounts for 1 of n_args occupied by this arg + list_len += mp_obj_get_int(len) - 1; + } + } + } + } // Try to get a hint for the size of the kw_dict - uint kw_dict_len = 0; - if (kw_dict != MP_OBJ_NULL && mp_obj_is_type(kw_dict, &mp_type_dict)) { - kw_dict_len = mp_obj_dict_len(kw_dict); + ssize_t kw_dict_len = 0; + + for (size_t i = 0; i < n_kw; i++) { + mp_obj_t key = args[n_args + i * 2]; + mp_obj_t value = args[n_args + i * 2 + 1]; + if (key == MP_OBJ_NULL && value != MP_OBJ_NULL && mp_obj_is_type(value, &mp_type_dict)) { + // -1 accounts for 1 of n_kw occupied by this arg + kw_dict_len += mp_obj_dict_len(value) - 1; + } } // Extract the pos_seq sequence to the new args array. // Note that it can be arbitrary iterator. - if (pos_seq == MP_OBJ_NULL) { - // no sequence + if (star_args == 0) { + // no star args to unpack // allocate memory for the new array of args args2_alloc = 1 + n_args + 2 * (n_kw + kw_dict_len); @@ -720,33 +769,11 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_ // copy the fixed pos args mp_seq_copy(args2 + args2_len, args, n_args, mp_obj_t); args2_len += n_args; - - } else if (mp_obj_is_type(pos_seq, &mp_type_tuple) || mp_obj_is_type(pos_seq, &mp_type_list)) { - // optimise the case of a tuple and list - - // get the items - size_t len; - mp_obj_t *items; - mp_obj_get_array(pos_seq, &len, &items); - - // allocate memory for the new array of args - args2_alloc = 1 + n_args + len + 2 * (n_kw + kw_dict_len); - args2 = mp_nonlocal_alloc(args2_alloc * sizeof(mp_obj_t)); - - // copy the self - if (self != MP_OBJ_NULL) { - args2[args2_len++] = self; - } - - // copy the fixed and variable position args - mp_seq_cat(args2 + args2_len, args, n_args, items, len, mp_obj_t); - args2_len += n_args + len; - } else { - // generic iterator + // at least one star arg to unpack // allocate memory for the new array of args - args2_alloc = 1 + n_args + 2 * (n_kw + kw_dict_len) + 3; + args2_alloc = 1 + n_args + list_len + 2 * (n_kw + kw_dict_len); args2 = mp_nonlocal_alloc(args2_alloc * sizeof(mp_obj_t)); // copy the self @@ -754,84 +781,118 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_ args2[args2_len++] = self; } - // copy the fixed position args - mp_seq_copy(args2 + args2_len, args, n_args, mp_obj_t); - args2_len += n_args; - - // extract the variable position args from the iterator - mp_obj_iter_buf_t iter_buf; - mp_obj_t iterable = mp_getiter(pos_seq, &iter_buf); - mp_obj_t item; - while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { - if (args2_len >= args2_alloc) { - args2 = mp_nonlocal_realloc(args2, args2_alloc * sizeof(mp_obj_t), args2_alloc * 2 * sizeof(mp_obj_t)); - args2_alloc *= 2; + for (size_t i = 0; i < n_args; i++) { + mp_obj_t arg = args[i]; + if ((star_args >> i) & 1) { + // star arg + if (mp_obj_is_type(arg, &mp_type_tuple) || mp_obj_is_type(arg, &mp_type_list)) { + // optimise the case of a tuple and list + + // get the items + size_t len; + mp_obj_t *items; + mp_obj_get_array(arg, &len, &items); + + // copy the items + assert(args2_len + len <= args2_alloc); + mp_seq_copy(args2 + args2_len, items, len, mp_obj_t); + args2_len += len; + } else { + // generic iterator + + // extract the variable position args from the iterator + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(arg, &iter_buf); + mp_obj_t item; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + if (args2_len >= args2_alloc) { + args2 = mp_nonlocal_realloc(args2, args2_alloc * sizeof(mp_obj_t), + args2_alloc * 2 * sizeof(mp_obj_t)); + args2_alloc *= 2; + } + args2[args2_len++] = item; + } + } + } else { + // normal argument + assert(args2_len < args2_alloc); + args2[args2_len++] = arg; } - args2[args2_len++] = item; } } // The size of the args2 array now is the number of positional args. - uint pos_args_len = args2_len; - - // Copy the fixed kw args. - mp_seq_copy(args2 + args2_len, args + n_args, 2 * n_kw, mp_obj_t); - args2_len += 2 * n_kw; + size_t pos_args_len = args2_len; + + // ensure there is still enough room for kw args + if (args2_len + 2 * (n_kw + kw_dict_len) > args2_alloc) { + size_t new_alloc = args2_len + 2 * (n_kw + kw_dict_len); + args2 = mp_nonlocal_realloc(args2, args2_alloc * sizeof(mp_obj_t), + new_alloc * sizeof(mp_obj_t)); + args2_alloc = new_alloc; + } - // Extract (key,value) pairs from kw_dict dictionary and append to args2. - // Note that it can be arbitrary iterator. - if (kw_dict == MP_OBJ_NULL) { - // pass - } else if (mp_obj_is_type(kw_dict, &mp_type_dict)) { - // dictionary - mp_map_t *map = mp_obj_dict_get_map(kw_dict); - assert(args2_len + 2 * map->used <= args2_alloc); // should have enough, since kw_dict_len is in this case hinted correctly above - for (size_t i = 0; i < map->alloc; i++) { - if (mp_map_slot_is_filled(map, i)) { - // the key must be a qstr, so intern it if it's a string - mp_obj_t key = map->table[i].key; - if (!mp_obj_is_qstr(key)) { - key = mp_obj_str_intern_checked(key); - } - args2[args2_len++] = key; - args2[args2_len++] = map->table[i].value; - } - } - } else { - // generic mapping: - // - call keys() to get an iterable of all keys in the mapping - // - call __getitem__ for each key to get the corresponding value - - // get the keys iterable - mp_obj_t dest[3]; - mp_load_method(kw_dict, MP_QSTR_keys, dest); - mp_obj_t iterable = mp_getiter(mp_call_method_n_kw(0, 0, dest), NULL); - - mp_obj_t key; - while ((key = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { - // expand size of args array if needed - if (args2_len + 1 >= args2_alloc) { - uint new_alloc = args2_alloc * 2; - if (new_alloc < 4) { - new_alloc = 4; + // Copy the kw args. + for (size_t i = 0; i < n_kw; i++) { + mp_obj_t kw_key = args[n_args + i * 2]; + mp_obj_t kw_value = args[n_args + i * 2 + 1]; + if (kw_key == MP_OBJ_NULL) { + // double-star args + if (mp_obj_is_type(kw_value, &mp_type_dict)) { + // dictionary + mp_map_t *map = mp_obj_dict_get_map(kw_value); + // should have enough, since kw_dict_len is in this case hinted correctly above + assert(args2_len + 2 * map->used <= args2_alloc); + for (size_t j = 0; j < map->alloc; j++) { + if (mp_map_slot_is_filled(map, j)) { + // the key must be a qstr, so intern it if it's a string + mp_obj_t key = map->table[j].key; + if (!mp_obj_is_qstr(key)) { + key = mp_obj_str_intern_checked(key); + } + args2[args2_len++] = key; + args2[args2_len++] = map->table[j].value; + } } - args2 = mp_nonlocal_realloc(args2, args2_alloc * sizeof(mp_obj_t), new_alloc * sizeof(mp_obj_t)); - args2_alloc = new_alloc; - } + } else { + // generic mapping: + // - call keys() to get an iterable of all keys in the mapping + // - call __getitem__ for each key to get the corresponding value + + // get the keys iterable + mp_obj_t dest[3]; + mp_load_method(kw_value, MP_QSTR_keys, dest); + mp_obj_t iterable = mp_getiter(mp_call_method_n_kw(0, 0, dest), NULL); + + mp_obj_t key; + while ((key = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + // expand size of args array if needed + if (args2_len + 1 >= args2_alloc) { + size_t new_alloc = args2_alloc * 2; + args2 = mp_nonlocal_realloc(args2, args2_alloc * sizeof(mp_obj_t), new_alloc * sizeof(mp_obj_t)); + args2_alloc = new_alloc; + } - // the key must be a qstr, so intern it if it's a string - if (!mp_obj_is_qstr(key)) { - key = mp_obj_str_intern_checked(key); - } + // the key must be a qstr, so intern it if it's a string + if (!mp_obj_is_qstr(key)) { + key = mp_obj_str_intern_checked(key); + } - // get the value corresponding to the key - mp_load_method(kw_dict, MP_QSTR___getitem__, dest); - dest[2] = key; - mp_obj_t value = mp_call_method_n_kw(1, 0, dest); + // get the value corresponding to the key + mp_load_method(kw_value, MP_QSTR___getitem__, dest); + dest[2] = key; + mp_obj_t value = mp_call_method_n_kw(1, 0, dest); - // store the key/value pair in the argument array - args2[args2_len++] = key; - args2[args2_len++] = value; + // store the key/value pair in the argument array + args2[args2_len++] = key; + args2[args2_len++] = value; + } + } + } else { + // normal kwarg + assert(args2_len + 2 <= args2_alloc); + args2[args2_len++] = kw_key; + args2[args2_len++] = kw_value; } } @@ -1008,8 +1069,7 @@ STATIC const mp_obj_type_t mp_type_checked_fun = { }; STATIC mp_obj_t mp_obj_new_checked_fun(const mp_obj_type_t *type, mp_obj_t fun) { - mp_obj_checked_fun_t *o = m_new_obj(mp_obj_checked_fun_t); - o->base.type = &mp_type_checked_fun; + mp_obj_checked_fun_t *o = mp_obj_malloc(mp_obj_checked_fun_t, &mp_type_checked_fun); o->type = type; o->fun = fun; return MP_OBJ_FROM_PTR(o); @@ -1081,6 +1141,10 @@ void mp_load_method_maybe(mp_obj_t obj, qstr attr, mp_obj_t *dest) { dest[0] = MP_OBJ_NULL; dest[1] = MP_OBJ_NULL; + // Note: the specific case of obj being an instance type is fast-path'ed in the VM + // for the MP_BC_LOAD_ATTR opcode. Instance types handle type->attr and look up directly + // in their member's map. + // get the type const mp_obj_type_t *type = mp_obj_get_type(obj); @@ -1096,12 +1160,20 @@ void mp_load_method_maybe(mp_obj_t obj, qstr attr, mp_obj_t *dest) { if (attr == MP_QSTR___next__ && type->iternext != NULL) { dest[0] = MP_OBJ_FROM_PTR(&mp_builtin_next_obj); dest[1] = obj; - - } else if (type->attr != NULL) { + return; + } + if (type->attr != NULL) { // this type can do its own load, so call it type->attr(obj, attr, dest); - - } else if (type->locals_dict != NULL) { + // If type->attr has set dest[1] = MP_OBJ_SENTINEL, we should proceed + // with lookups below (i.e. in locals_dict). If not, return right away. + if (dest[1] != MP_OBJ_SENTINEL) { + return; + } + // Clear the fail flag set by type->attr so it's like it never ran. + dest[1] = MP_OBJ_NULL; + } + if (type->locals_dict != NULL) { // generic method lookup // this is a lookup in the object (ie not class or type) assert(type->locals_dict->base.type == &mp_type_dict); // MicroPython restriction, for now @@ -1110,6 +1182,7 @@ void mp_load_method_maybe(mp_obj_t obj, qstr attr, mp_obj_t *dest) { if (elem != NULL) { mp_convert_member_lookup(obj, type, elem->value, dest); } + return; } } @@ -1368,8 +1441,10 @@ mp_obj_t mp_make_raise_obj(mp_obj_t o) { // create and return a new exception instance by calling o // TODO could have an option to disable traceback, then builtin exceptions (eg TypeError) // could have const instances in ROM which we return here instead - return mp_call_function_n_kw(o, 0, 0, NULL); - } else if (mp_obj_is_exception_instance(o)) { + o = mp_call_function_n_kw(o, 0, 0, NULL); + } + + if (mp_obj_is_exception_instance(o)) { // o is an instance of an exception, so use it as the exception return o; } else { diff --git a/python/src/py/runtime.h b/python/src/py/runtime.h index f0d41f38df7..4393fbfa82d 100644 --- a/python/src/py/runtime.h +++ b/python/src/py/runtime.h @@ -57,6 +57,15 @@ typedef struct _mp_arg_t { mp_arg_val_t defval; } mp_arg_t; +struct _mp_sched_node_t; + +typedef void (*mp_sched_callback_t)(struct _mp_sched_node_t *); + +typedef struct _mp_sched_node_t { + mp_sched_callback_t callback; + struct _mp_sched_node_t *next; +} mp_sched_node_t; + // Tables mapping operator enums to qstrs, defined in objtype.c extern const byte mp_unary_op_method_name[]; extern const byte mp_binary_op_method_name[]; @@ -74,6 +83,7 @@ void mp_sched_lock(void); void mp_sched_unlock(void); #define mp_sched_num_pending() (MP_STATE_VM(sched_len)) bool mp_sched_schedule(mp_obj_t function, mp_obj_t arg); +bool mp_sched_schedule_node(mp_sched_node_t *node, mp_sched_callback_t callback); #endif // extra printing method specifically for mp_obj_t's which are integral type diff --git a/python/src/py/scheduler.c b/python/src/py/scheduler.c index bd0bbf2078f..3966da29751 100644 --- a/python/src/py/scheduler.c +++ b/python/src/py/scheduler.c @@ -90,6 +90,24 @@ void mp_handle_pending(bool raise_exc) { // or by the VM's inlined version of that function. void mp_handle_pending_tail(mp_uint_t atomic_state) { MP_STATE_VM(sched_state) = MP_SCHED_LOCKED; + + #if MICROPY_SCHEDULER_STATIC_NODES + // Run all pending C callbacks. + while (MP_STATE_VM(sched_head) != NULL) { + mp_sched_node_t *node = MP_STATE_VM(sched_head); + MP_STATE_VM(sched_head) = node->next; + if (MP_STATE_VM(sched_head) == NULL) { + MP_STATE_VM(sched_tail) = NULL; + } + mp_sched_callback_t callback = node->callback; + node->callback = NULL; + MICROPY_END_ATOMIC_SECTION(atomic_state); + callback(node); + atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + } + #endif + + // Run at most one pending Python callback. if (!mp_sched_empty()) { mp_sched_item_t item = MP_STATE_VM(sched_queue)[MP_STATE_VM(sched_idx)]; MP_STATE_VM(sched_idx) = IDX_MASK(MP_STATE_VM(sched_idx) + 1); @@ -99,6 +117,7 @@ void mp_handle_pending_tail(mp_uint_t atomic_state) { } else { MICROPY_END_ATOMIC_SECTION(atomic_state); } + mp_sched_unlock(); } @@ -117,7 +136,11 @@ void mp_sched_unlock(void) { assert(MP_STATE_VM(sched_state) < 0); if (++MP_STATE_VM(sched_state) == 0) { // vm became unlocked - if (MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL || mp_sched_num_pending()) { + if (MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL + #if MICROPY_SCHEDULER_STATIC_NODES + || MP_STATE_VM(sched_head) != NULL + #endif + || mp_sched_num_pending()) { MP_STATE_VM(sched_state) = MP_SCHED_PENDING; } else { MP_STATE_VM(sched_state) = MP_SCHED_IDLE; @@ -146,6 +169,33 @@ bool MICROPY_WRAP_MP_SCHED_SCHEDULE(mp_sched_schedule)(mp_obj_t function, mp_obj return ret; } +#if MICROPY_SCHEDULER_STATIC_NODES +bool mp_sched_schedule_node(mp_sched_node_t *node, mp_sched_callback_t callback) { + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + bool ret; + if (node->callback == NULL) { + if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) { + MP_STATE_VM(sched_state) = MP_SCHED_PENDING; + } + node->callback = callback; + node->next = NULL; + if (MP_STATE_VM(sched_tail) == NULL) { + MP_STATE_VM(sched_head) = node; + } else { + MP_STATE_VM(sched_tail)->next = node; + } + MP_STATE_VM(sched_tail) = node; + MICROPY_SCHED_HOOK_SCHEDULED; + ret = true; + } else { + // already scheduled + ret = false; + } + MICROPY_END_ATOMIC_SECTION(atomic_state); + return ret; +} +#endif + #else // MICROPY_ENABLE_SCHEDULER // A variant of this is inlined in the VM at the pending exception check diff --git a/python/src/py/scope.c b/python/src/py/scope.c index 98e02fb53fe..8fc0943289b 100644 --- a/python/src/py/scope.c +++ b/python/src/py/scope.c @@ -40,7 +40,7 @@ STATIC const uint8_t scope_simple_name_table[] = { [SCOPE_GEN_EXPR] = MP_QSTR__lt_genexpr_gt_, }; -scope_t *scope_new(scope_kind_t kind, mp_parse_node_t pn, qstr source_file, mp_uint_t emit_options) { +scope_t *scope_new(scope_kind_t kind, mp_parse_node_t pn, mp_uint_t emit_options) { // Make sure those qstrs indeed fit in an uint8_t. MP_STATIC_ASSERT(MP_QSTR__lt_module_gt_ <= UINT8_MAX); MP_STATIC_ASSERT(MP_QSTR__lt_lambda_gt_ <= UINT8_MAX); @@ -52,7 +52,6 @@ scope_t *scope_new(scope_kind_t kind, mp_parse_node_t pn, qstr source_file, mp_u scope_t *scope = m_new0(scope_t, 1); scope->kind = kind; scope->pn = pn; - scope->source_file = source_file; if (kind == SCOPE_FUNCTION || kind == SCOPE_CLASS) { assert(MP_PARSE_NODE_IS_STRUCT(pn)); scope->simple_name = MP_PARSE_NODE_LEAF_ARG(((mp_parse_node_struct_t *)pn)->nodes[0]); diff --git a/python/src/py/scope.h b/python/src/py/scope.h index edf164c4ada..b781dde4271 100644 --- a/python/src/py/scope.h +++ b/python/src/py/scope.h @@ -32,6 +32,7 @@ typedef enum { ID_INFO_KIND_UNDECIDED, ID_INFO_KIND_GLOBAL_IMPLICIT, + ID_INFO_KIND_GLOBAL_IMPLICIT_ASSIGNED, ID_INFO_KIND_GLOBAL_EXPLICIT, ID_INFO_KIND_LOCAL, // in a function f, written and only referenced by f ID_INFO_KIND_CELL, // in a function f, read/written by children of f @@ -75,7 +76,6 @@ typedef struct _scope_t { struct _scope_t *next; mp_parse_node_t pn; mp_raw_code_t *raw_code; - uint16_t source_file; // a qstr uint16_t simple_name; // a qstr uint16_t scope_flags; // see runtime0.h uint16_t emit_options; // see emitglue.h @@ -90,7 +90,7 @@ typedef struct _scope_t { id_info_t *id_info; } scope_t; -scope_t *scope_new(scope_kind_t kind, mp_parse_node_t pn, qstr source_file, mp_uint_t emit_options); +scope_t *scope_new(scope_kind_t kind, mp_parse_node_t pn, mp_uint_t emit_options); void scope_free(scope_t *scope); id_info_t *scope_find_or_add_id(scope_t *scope, qstr qstr, id_info_kind_t kind); id_info_t *scope_find(scope_t *scope, qstr qstr); diff --git a/python/src/py/showbc.c b/python/src/py/showbc.c index cb81b883596..f9c334b93bb 100644 --- a/python/src/py/showbc.c +++ b/python/src/py/showbc.c @@ -28,7 +28,7 @@ #include #include "py/bc0.h" -#include "py/bc.h" +#include "py/emitglue.h" #if MICROPY_DEBUG_PRINTERS @@ -38,80 +38,90 @@ unum = (unum << 7) + (*ip & 0x7f); \ } while ((*ip++ & 0x80) != 0); \ } -#define DECODE_ULABEL do { unum = (ip[0] | (ip[1] << 8)); ip += 2; } while (0) -#define DECODE_SLABEL do { unum = (ip[0] | (ip[1] << 8)) - 0x8000; ip += 2; } while (0) -#if MICROPY_PERSISTENT_CODE +#define DECODE_ULABEL \ + do { \ + if (ip[0] & 0x80) { \ + unum = ((ip[0] & 0x7f) | (ip[1] << 7)); \ + ip += 2; \ + } else { \ + unum = ip[0]; \ + ip += 1; \ + } \ + } while (0) + +#define DECODE_SLABEL \ + do { \ + if (ip[0] & 0x80) { \ + unum = ((ip[0] & 0x7f) | (ip[1] << 7)) - 0x4000; \ + ip += 2; \ + } else { \ + unum = ip[0] - 0x40; \ + ip += 1; \ + } \ + } while (0) + +#if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE #define DECODE_QSTR \ - qst = ip[0] | ip[1] << 8; \ - ip += 2; -#define DECODE_PTR \ - DECODE_UINT; \ - unum = mp_showbc_const_table[unum] -#define DECODE_OBJ \ DECODE_UINT; \ - unum = mp_showbc_const_table[unum] + qst = qstr_table[unum] #else -#define DECODE_QSTR { \ - qst = 0; \ - do { \ - qst = (qst << 7) + (*ip & 0x7f); \ - } while ((*ip++ & 0x80) != 0); \ -} -#define DECODE_PTR do { \ - ip = (byte *)MP_ALIGN(ip, sizeof(void *)); \ - unum = (uintptr_t)*(void **)ip; \ - ip += sizeof(void *); \ -} while (0) -#define DECODE_OBJ do { \ - ip = (byte *)MP_ALIGN(ip, sizeof(mp_obj_t)); \ - unum = (mp_uint_t)*(mp_obj_t *)ip; \ - ip += sizeof(mp_obj_t); \ -} while (0) +#define DECODE_QSTR \ + DECODE_UINT; \ + qst = unum; #endif -const byte *mp_showbc_code_start; -const mp_uint_t *mp_showbc_const_table; +#define DECODE_PTR \ + DECODE_UINT; \ + unum = (mp_uint_t)(uintptr_t)child_table[unum] -void mp_bytecode_print(const mp_print_t *print, const void *descr, const byte *ip, mp_uint_t len, const mp_uint_t *const_table) { - mp_showbc_code_start = ip; +#define DECODE_OBJ \ + DECODE_UINT; \ + unum = (mp_uint_t)obj_table[unum] + +void mp_bytecode_print(const mp_print_t *print, const mp_raw_code_t *rc, const mp_module_constants_t *cm) { + const byte *ip_start = rc->fun_data; + const byte *ip = rc->fun_data; // Decode prelude MP_BC_PRELUDE_SIG_DECODE(ip); MP_BC_PRELUDE_SIZE_DECODE(ip); const byte *code_info = ip; - #if MICROPY_PERSISTENT_CODE - qstr block_name = code_info[0] | (code_info[1] << 8); - qstr source_file = code_info[2] | (code_info[3] << 8); - code_info += 4; - #else qstr block_name = mp_decode_uint(&code_info); - qstr source_file = mp_decode_uint(&code_info); + #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE + block_name = cm->qstr_table[block_name]; + qstr source_file = cm->qstr_table[0]; + #else + qstr source_file = cm->source_file; #endif - mp_printf(print, "File %s, code block '%s' (descriptor: %p, bytecode @%p " UINT_FMT " bytes)\n", - qstr_str(source_file), qstr_str(block_name), descr, mp_showbc_code_start, len); + mp_printf(print, "File %s, code block '%s' (descriptor: %p, bytecode @%p %u bytes)\n", + qstr_str(source_file), qstr_str(block_name), rc, ip_start, (unsigned)rc->fun_data_len); // raw bytecode dump - size_t prelude_size = ip - mp_showbc_code_start + n_info + n_cell; - mp_printf(print, "Raw bytecode (code_info_size=" UINT_FMT ", bytecode_size=" UINT_FMT "):\n", - prelude_size, len - prelude_size); - for (mp_uint_t i = 0; i < len; i++) { + size_t prelude_size = ip - ip_start + n_info + n_cell; + mp_printf(print, "Raw bytecode (code_info_size=%u, bytecode_size=%u):\n", + (unsigned)prelude_size, (unsigned)(rc->fun_data_len - prelude_size)); + for (size_t i = 0; i < rc->fun_data_len; i++) { if (i > 0 && i % 16 == 0) { mp_printf(print, "\n"); } - mp_printf(print, " %02x", mp_showbc_code_start[i]); + mp_printf(print, " %02x", ip_start[i]); } mp_printf(print, "\n"); // bytecode prelude: arg names (as qstr objects) mp_printf(print, "arg names:"); for (mp_uint_t i = 0; i < n_pos_args + n_kwonly_args; i++) { - mp_printf(print, " %s", qstr_str(MP_OBJ_QSTR_VALUE(const_table[i]))); + qstr qst = mp_decode_uint(&code_info); + #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE + qst = cm->qstr_table[qst]; + #endif + mp_printf(print, " %s", qstr_str(qst)); } mp_printf(print, "\n"); @@ -120,6 +130,7 @@ void mp_bytecode_print(const mp_print_t *print, const void *descr, const byte *i // skip over code_info ip += n_info; + const byte *line_info_top = ip; // bytecode prelude: initialise closed over variables for (size_t i = 0; i < n_cell; ++i) { @@ -132,7 +143,7 @@ void mp_bytecode_print(const mp_print_t *print, const void *descr, const byte *i mp_int_t bc = 0; mp_uint_t source_line = 1; mp_printf(print, " bc=" INT_FMT " line=" UINT_FMT "\n", bc, source_line); - for (const byte *ci = code_info; *ci;) { + for (const byte *ci = code_info; ci < line_info_top;) { if ((ci[0] & 0x80) == 0) { // 0b0LLBBBBB encoding bc += ci[0] & 0x1f; @@ -147,10 +158,14 @@ void mp_bytecode_print(const mp_print_t *print, const void *descr, const byte *i mp_printf(print, " bc=" INT_FMT " line=" UINT_FMT "\n", bc, source_line); } } - mp_bytecode_print2(print, ip, len - prelude_size, const_table); + mp_bytecode_print2(print, ip, rc->fun_data_len - prelude_size, rc->children, cm); } -const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip) { +const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip_start, const byte *ip, mp_raw_code_t *const *child_table, const mp_module_constants_t *cm) { + #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE + const qstr_short_t *qstr_table = cm->qstr_table; + #endif + const mp_obj_t *obj_table = cm->obj_table; mp_uint_t unum; qstr qst; @@ -208,25 +223,16 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip) { case MP_BC_LOAD_NAME: DECODE_QSTR; mp_printf(print, "LOAD_NAME %s", qstr_str(qst)); - if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) { - mp_printf(print, " (cache=%u)", *ip++); - } break; case MP_BC_LOAD_GLOBAL: DECODE_QSTR; mp_printf(print, "LOAD_GLOBAL %s", qstr_str(qst)); - if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) { - mp_printf(print, " (cache=%u)", *ip++); - } break; case MP_BC_LOAD_ATTR: DECODE_QSTR; mp_printf(print, "LOAD_ATTR %s", qstr_str(qst)); - if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) { - mp_printf(print, " (cache=%u)", *ip++); - } break; case MP_BC_LOAD_METHOD: @@ -270,9 +276,6 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip) { case MP_BC_STORE_ATTR: DECODE_QSTR; mp_printf(print, "STORE_ATTR %s", qstr_str(qst)); - if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) { - mp_printf(print, " (cache=%u)", *ip++); - } break; case MP_BC_STORE_SUBSCR: @@ -321,32 +324,32 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip) { case MP_BC_JUMP: DECODE_SLABEL; - mp_printf(print, "JUMP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "JUMP " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_POP_JUMP_IF_TRUE: DECODE_SLABEL; - mp_printf(print, "POP_JUMP_IF_TRUE " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "POP_JUMP_IF_TRUE " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_POP_JUMP_IF_FALSE: DECODE_SLABEL; - mp_printf(print, "POP_JUMP_IF_FALSE " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "POP_JUMP_IF_FALSE " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_JUMP_IF_TRUE_OR_POP: - DECODE_SLABEL; - mp_printf(print, "JUMP_IF_TRUE_OR_POP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + DECODE_ULABEL; + mp_printf(print, "JUMP_IF_TRUE_OR_POP " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_JUMP_IF_FALSE_OR_POP: - DECODE_SLABEL; - mp_printf(print, "JUMP_IF_FALSE_OR_POP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + DECODE_ULABEL; + mp_printf(print, "JUMP_IF_FALSE_OR_POP " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_SETUP_WITH: DECODE_ULABEL; // loop-like labels are always forward - mp_printf(print, "SETUP_WITH " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "SETUP_WITH " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_WITH_CLEANUP: @@ -355,18 +358,18 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip) { case MP_BC_UNWIND_JUMP: DECODE_SLABEL; - mp_printf(print, "UNWIND_JUMP " UINT_FMT " %d", (mp_uint_t)(ip + unum - mp_showbc_code_start), *ip); + mp_printf(print, "UNWIND_JUMP " UINT_FMT " %d", (mp_uint_t)(ip + unum - ip_start), *ip); ip += 1; break; case MP_BC_SETUP_EXCEPT: DECODE_ULABEL; // except labels are always forward - mp_printf(print, "SETUP_EXCEPT " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "SETUP_EXCEPT " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_SETUP_FINALLY: DECODE_ULABEL; // except labels are always forward - mp_printf(print, "SETUP_FINALLY " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "SETUP_FINALLY " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_END_FINALLY: @@ -387,12 +390,12 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip) { case MP_BC_FOR_ITER: DECODE_ULABEL; // the jump offset if iteration finishes; for labels are always forward - mp_printf(print, "FOR_ITER " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "FOR_ITER " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_POP_EXCEPT_JUMP: DECODE_ULABEL; // these labels are always forward - mp_printf(print, "POP_EXCEPT_JUMP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "POP_EXCEPT_JUMP " UINT_FMT, (mp_uint_t)(ip + unum - ip_start)); break; case MP_BC_BUILD_TUPLE: @@ -531,7 +534,8 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip) { } else if (ip[-1] < MP_BC_STORE_FAST_MULTI + 16) { mp_printf(print, "STORE_FAST " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_STORE_FAST_MULTI); } else if (ip[-1] < MP_BC_UNARY_OP_MULTI + MP_UNARY_OP_NUM_BYTECODE) { - mp_printf(print, "UNARY_OP " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_UNARY_OP_MULTI); + mp_uint_t op = ip[-1] - MP_BC_UNARY_OP_MULTI; + mp_printf(print, "UNARY_OP " UINT_FMT " %s", op, qstr_str(mp_unary_op_method_name[op])); } else if (ip[-1] < MP_BC_BINARY_OP_MULTI + MP_BINARY_OP_NUM_BYTECODE) { mp_uint_t op = ip[-1] - MP_BC_BINARY_OP_MULTI; mp_printf(print, "BINARY_OP " UINT_FMT " %s", op, qstr_str(mp_binary_op_method_name[op])); @@ -546,12 +550,11 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip) { return ip; } -void mp_bytecode_print2(const mp_print_t *print, const byte *ip, size_t len, const mp_uint_t *const_table) { - mp_showbc_code_start = ip; - mp_showbc_const_table = const_table; - while (ip < len + mp_showbc_code_start) { - mp_printf(print, "%02u ", (uint)(ip - mp_showbc_code_start)); - ip = mp_bytecode_print_str(print, ip); +void mp_bytecode_print2(const mp_print_t *print, const byte *ip, size_t len, mp_raw_code_t *const *child_table, const mp_module_constants_t *cm) { + const byte *ip_start = ip; + while (ip < ip_start + len) { + mp_printf(print, "%02u ", (uint)(ip - ip_start)); + ip = mp_bytecode_print_str(print, ip_start, ip, child_table, cm); mp_printf(print, "\n"); } } diff --git a/python/src/py/smallint.h b/python/src/py/smallint.h index 67daf9b9fa1..584e0018d1b 100644 --- a/python/src/py/smallint.h +++ b/python/src/py/smallint.h @@ -61,6 +61,13 @@ #define MP_SMALL_INT_MAX ((mp_int_t)(~(MP_SMALL_INT_MIN))) +// https://stackoverflow.com/a/4589384/1976323 +// Number of bits in inttype_MAX, or in any (1<state[0] + 1)) #endif -#define TRACE(ip) TRACE_PREFIX; mp_bytecode_print2(&mp_plat_print, ip, 1, code_state->fun_bc->const_table); +#define TRACE(ip) TRACE_PREFIX; mp_bytecode_print2(&mp_plat_print, ip, 1, code_state->fun_bc->child_table, &code_state->fun_bc->context->constants); #else #define TRACE(ip) #endif @@ -61,38 +61,53 @@ do { \ unum = (unum << 7) + (*ip & 0x7f); \ } while ((*ip++ & 0x80) != 0) -#define DECODE_ULABEL size_t ulab = (ip[0] | (ip[1] << 8)); ip += 2 -#define DECODE_SLABEL size_t slab = (ip[0] | (ip[1] << 8)) - 0x8000; ip += 2 -#if MICROPY_PERSISTENT_CODE +#define DECODE_ULABEL \ + size_t ulab; \ + do { \ + if (ip[0] & 0x80) { \ + ulab = ((ip[0] & 0x7f) | (ip[1] << 7)); \ + ip += 2; \ + } else { \ + ulab = ip[0]; \ + ip += 1; \ + } \ + } while (0) + +#define DECODE_SLABEL \ + size_t slab; \ + do { \ + if (ip[0] & 0x80) { \ + slab = ((ip[0] & 0x7f) | (ip[1] << 7)) - 0x4000; \ + ip += 2; \ + } else { \ + slab = ip[0] - 0x40; \ + ip += 1; \ + } \ + } while (0) + +#if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE #define DECODE_QSTR \ - qstr qst = ip[0] | ip[1] << 8; \ - ip += 2; -#define DECODE_PTR \ - DECODE_UINT; \ - void *ptr = (void*)(uintptr_t)code_state->fun_bc->const_table[unum] -#define DECODE_OBJ \ DECODE_UINT; \ - mp_obj_t obj = (mp_obj_t)code_state->fun_bc->const_table[unum] + qstr qst = qstr_table[unum] #else -#define DECODE_QSTR qstr qst = 0; \ - do { \ - qst = (qst << 7) + (*ip & 0x7f); \ - } while ((*ip++ & 0x80) != 0) -#define DECODE_PTR \ - ip = (byte*)MP_ALIGN(ip, sizeof(void*)); \ - void *ptr = *(void**)ip; \ - ip += sizeof(void*) -#define DECODE_OBJ \ - ip = (byte*)MP_ALIGN(ip, sizeof(mp_obj_t)); \ - mp_obj_t obj = *(mp_obj_t*)ip; \ - ip += sizeof(mp_obj_t) +#define DECODE_QSTR \ + DECODE_UINT; \ + qstr qst = unum; #endif +#define DECODE_PTR \ + DECODE_UINT; \ + void *ptr = (void *)(uintptr_t)code_state->fun_bc->child_table[unum] + +#define DECODE_OBJ \ + DECODE_UINT; \ + mp_obj_t obj = (mp_obj_t)code_state->fun_bc->context->constants.obj_table[unum] + #define PUSH(val) *++sp = (val) #define POP() (*sp--) #define TOP() (*sp) @@ -180,30 +195,13 @@ #define TRACE_TICK(current_ip, current_sp, is_exception) #endif // MICROPY_PY_SYS_SETTRACE -#if MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE -static inline mp_map_elem_t *mp_map_cached_lookup(mp_map_t *map, qstr qst, uint8_t *idx_cache) { - size_t idx = *idx_cache; - mp_obj_t key = MP_OBJ_NEW_QSTR(qst); - mp_map_elem_t *elem = NULL; - if (idx < map->alloc && map->table[idx].key == key) { - elem = &map->table[idx]; - } else { - elem = mp_map_lookup(map, key, MP_MAP_LOOKUP); - if (elem != NULL) { - *idx_cache = (elem - &map->table[0]) & 0xff; - } - } - return elem; -} -#endif - // fastn has items in reverse order (fastn[0] is local[0], fastn[-1] is local[1], etc) // sp points to bottom of stack which grows up // returns: // MP_VM_RETURN_NORMAL, sp valid, return value in *sp // MP_VM_RETURN_YIELD, ip, sp valid, yielded value in *sp // MP_VM_RETURN_EXCEPTION, exception in state[0] -mp_vm_return_kind_t mp_execute_bytecode(mp_code_state_t *code_state, volatile mp_obj_t inject_exc) { +mp_vm_return_kind_t MICROPY_WRAP_MP_EXECUTE_BYTECODE(mp_execute_bytecode)(mp_code_state_t *code_state, volatile mp_obj_t inject_exc) { #define SELECTIVE_EXC_IP (0) #if SELECTIVE_EXC_IP #define MARK_EXC_IP_SELECTIVE() { code_state->ip = ip; } /* stores ip 1 byte past last opcode */ @@ -272,6 +270,9 @@ FRAME_SETUP(); // local variables that are not visible to the exception handler const byte *ip = code_state->ip; mp_obj_t *sp = code_state->sp; + #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE + const qstr_short_t *qstr_table = code_state->fun_bc->context->constants.qstr_table; + #endif mp_obj_t obj_shared; MICROPY_VM_HOOK_INIT @@ -361,84 +362,46 @@ FRAME_SETUP(); goto load_check; } - #if !MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE ENTRY(MP_BC_LOAD_NAME): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; PUSH(mp_load_name(qst)); DISPATCH(); } - #else - ENTRY(MP_BC_LOAD_NAME): { - MARK_EXC_IP_SELECTIVE(); - DECODE_QSTR; - mp_map_elem_t *elem = mp_map_cached_lookup(&mp_locals_get()->map, qst, (uint8_t*)ip); - mp_obj_t obj; - if (elem != NULL) { - obj = elem->value; - } else { - obj = mp_load_name(qst); - } - PUSH(obj); - ip++; - DISPATCH(); - } - #endif - #if !MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE ENTRY(MP_BC_LOAD_GLOBAL): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; PUSH(mp_load_global(qst)); DISPATCH(); } - #else - ENTRY(MP_BC_LOAD_GLOBAL): { - MARK_EXC_IP_SELECTIVE(); - DECODE_QSTR; - mp_map_elem_t *elem = mp_map_cached_lookup(&mp_globals_get()->map, qst, (uint8_t*)ip); - mp_obj_t obj; - if (elem != NULL) { - obj = elem->value; - } else { - obj = mp_load_global(qst); - } - PUSH(obj); - ip++; - DISPATCH(); - } - #endif - #if !MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE - ENTRY(MP_BC_LOAD_ATTR): { - FRAME_UPDATE(); - MARK_EXC_IP_SELECTIVE(); - DECODE_QSTR; - SET_TOP(mp_load_attr(TOP(), qst)); - DISPATCH(); - } - #else ENTRY(MP_BC_LOAD_ATTR): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_obj_t top = TOP(); + mp_obj_t obj; + #if MICROPY_OPT_LOAD_ATTR_FAST_PATH + // For the specific case of an instance type, it implements .attr + // and forwards to its members map. Attribute lookups on instance + // types are extremely common, so avoid all the other checks and + // calls that normally happen first. mp_map_elem_t *elem = NULL; if (mp_obj_is_instance_type(mp_obj_get_type(top))) { mp_obj_instance_t *self = MP_OBJ_TO_PTR(top); - elem = mp_map_cached_lookup(&self->members, qst, (uint8_t*)ip); + elem = mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP); } - mp_obj_t obj; - if (elem != NULL) { + if (elem) { obj = elem->value; - } else { + } else + #endif + { obj = mp_load_attr(top, qst); } SET_TOP(obj); - ip++; DISPATCH(); } - #endif ENTRY(MP_BC_LOAD_METHOD): { MARK_EXC_IP_SELECTIVE(); @@ -494,7 +457,6 @@ FRAME_SETUP(); DISPATCH(); } - #if !MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE ENTRY(MP_BC_STORE_ATTR): { FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); @@ -503,32 +465,6 @@ FRAME_SETUP(); sp -= 2; DISPATCH(); } - #else - // This caching code works with MICROPY_PY_BUILTINS_PROPERTY and/or - // MICROPY_PY_DESCRIPTORS enabled because if the attr exists in - // self->members then it can't be a property or have descriptors. A - // consequence of this is that we can't use MP_MAP_LOOKUP_ADD_IF_NOT_FOUND - // in the fast-path below, because that store could override a property. - ENTRY(MP_BC_STORE_ATTR): { - FRAME_UPDATE(); - MARK_EXC_IP_SELECTIVE(); - DECODE_QSTR; - mp_map_elem_t *elem = NULL; - mp_obj_t top = TOP(); - if (mp_obj_is_instance_type(mp_obj_get_type(top)) && sp[-1] != MP_OBJ_NULL) { - mp_obj_instance_t *self = MP_OBJ_TO_PTR(top); - elem = mp_map_cached_lookup(&self->members, qst, (uint8_t*)ip); - } - if (elem != NULL) { - elem->value = sp[-1]; - } else { - mp_store_attr(sp[0], qst, sp[-1]); - } - sp -= 2; - ip++; - DISPATCH(); - } - #endif ENTRY(MP_BC_STORE_SUBSCR): MARK_EXC_IP_SELECTIVE(); @@ -624,9 +560,9 @@ FRAME_SETUP(); } ENTRY(MP_BC_JUMP_IF_TRUE_OR_POP): { - DECODE_SLABEL; + DECODE_ULABEL; if (mp_obj_is_true(TOP())) { - ip += slab; + ip += ulab; } else { sp--; } @@ -634,11 +570,11 @@ FRAME_SETUP(); } ENTRY(MP_BC_JUMP_IF_FALSE_OR_POP): { - DECODE_SLABEL; + DECODE_ULABEL; if (mp_obj_is_true(TOP())) { sp--; } else { - ip += slab; + ip += ulab; } DISPATCH_WITH_PEND_EXC_CHECK(); } @@ -809,8 +745,8 @@ unwind_jump:; obj = mp_getiter(obj, iter_buf); if (obj != MP_OBJ_FROM_PTR(iter_buf)) { // Iterator didn't use the stack so indicate that with MP_OBJ_NULL. - sp[-MP_OBJ_ITER_BUF_NSLOTS + 1] = MP_OBJ_NULL; - sp[-MP_OBJ_ITER_BUF_NSLOTS + 2] = obj; + *(sp - MP_OBJ_ITER_BUF_NSLOTS + 1) = MP_OBJ_NULL; + *(sp - MP_OBJ_ITER_BUF_NSLOTS + 2) = obj; } DISPATCH(); } @@ -821,8 +757,8 @@ unwind_jump:; DECODE_ULABEL; // the jump offset if iteration finishes; for labels are always forward code_state->sp = sp; mp_obj_t obj; - if (sp[-MP_OBJ_ITER_BUF_NSLOTS + 1] == MP_OBJ_NULL) { - obj = sp[-MP_OBJ_ITER_BUF_NSLOTS + 2]; + if (*(sp - MP_OBJ_ITER_BUF_NSLOTS + 1) == MP_OBJ_NULL) { + obj = *(sp - MP_OBJ_ITER_BUF_NSLOTS + 2); } else { obj = MP_OBJ_FROM_PTR(&sp[-MP_OBJ_ITER_BUF_NSLOTS + 1]); } @@ -941,15 +877,15 @@ unwind_jump:; ENTRY(MP_BC_MAKE_FUNCTION): { DECODE_PTR; - PUSH(mp_make_function_from_raw_code(ptr, MP_OBJ_NULL, MP_OBJ_NULL)); + PUSH(mp_make_function_from_raw_code(ptr, code_state->fun_bc->context, NULL)); DISPATCH(); } ENTRY(MP_BC_MAKE_FUNCTION_DEFARGS): { DECODE_PTR; // Stack layout: def_tuple def_dict <- TOS - mp_obj_t def_dict = POP(); - SET_TOP(mp_make_function_from_raw_code(ptr, TOP(), def_dict)); + sp -= 1; + SET_TOP(mp_make_function_from_raw_code(ptr, code_state->fun_bc->context, sp)); DISPATCH(); } @@ -958,7 +894,7 @@ unwind_jump:; size_t n_closed_over = *ip++; // Stack layout: closed_overs <- TOS sp -= n_closed_over - 1; - SET_TOP(mp_make_closure_from_raw_code(ptr, n_closed_over, sp)); + SET_TOP(mp_make_closure_from_raw_code(ptr, code_state->fun_bc->context, n_closed_over, sp)); DISPATCH(); } @@ -967,7 +903,7 @@ unwind_jump:; size_t n_closed_over = *ip++; // Stack layout: def_tuple def_dict closed_overs <- TOS sp -= 2 + n_closed_over - 1; - SET_TOP(mp_make_closure_from_raw_code(ptr, 0x100 | n_closed_over, sp)); + SET_TOP(mp_make_closure_from_raw_code(ptr, code_state->fun_bc->context, 0x100 | n_closed_over, sp)); DISPATCH(); } @@ -1013,8 +949,8 @@ unwind_jump:; // unum & 0xff == n_positional // (unum >> 8) & 0xff == n_keyword // We have following stack layout here: - // fun arg0 arg1 ... kw0 val0 kw1 val1 ... seq dict <- TOS - sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 2; + // fun arg0 arg1 ... kw0 val0 kw1 val1 ... bitmap <- TOS + sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 1; #if MICROPY_STACKLESS if (mp_obj_get_type(*sp) == &mp_type_fun_bc) { code_state->ip = ip; @@ -1098,8 +1034,8 @@ unwind_jump:; // unum & 0xff == n_positional // (unum >> 8) & 0xff == n_keyword // We have following stack layout here: - // fun self arg0 arg1 ... kw0 val0 kw1 val1 ... seq dict <- TOS - sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 3; + // fun self arg0 arg1 ... kw0 val0 kw1 val1 ... bitmap <- TOS + sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 2; #if MICROPY_STACKLESS if (mp_obj_get_type(*sp) == &mp_type_fun_bc) { code_state->ip = ip; @@ -1466,23 +1402,20 @@ unwind_jump:; const byte *ip = code_state->fun_bc->bytecode; MP_BC_PRELUDE_SIG_DECODE(ip); MP_BC_PRELUDE_SIZE_DECODE(ip); + const byte *line_info_top = ip + n_info; const byte *bytecode_start = ip + n_info + n_cell; - #if !MICROPY_PERSISTENT_CODE - // so bytecode is aligned - bytecode_start = MP_ALIGN(bytecode_start, sizeof(mp_uint_t)); - #endif size_t bc = code_state->ip - bytecode_start; - #if MICROPY_PERSISTENT_CODE - qstr block_name = ip[0] | (ip[1] << 8); - qstr source_file = ip[2] | (ip[3] << 8); - ip += 4; - #else qstr block_name = mp_decode_uint_value(ip); - ip = mp_decode_uint_skip(ip); - qstr source_file = mp_decode_uint_value(ip); - ip = mp_decode_uint_skip(ip); + for (size_t i = 0; i < 1 + n_pos_args + n_kwonly_args; ++i) { + ip = mp_decode_uint_skip(ip); + } + #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE + block_name = code_state->fun_bc->context->constants.qstr_table[block_name]; + qstr source_file = code_state->fun_bc->context->constants.qstr_table[0]; + #else + qstr source_file = code_state->fun_bc->context->constants.source_file; #endif - size_t source_line = mp_bytecode_get_source_line(ip, bc); + size_t source_line = mp_bytecode_get_source_line(ip, line_info_top, bc); mp_obj_exception_add_traceback(MP_OBJ_FROM_PTR(nlr.ret_val), source_file, source_line, block_name); } diff --git a/python/test/ulab.cpp b/python/test/ulab.cpp new file mode 100644 index 00000000000..9246c1d3e2c --- /dev/null +++ b/python/test/ulab.cpp @@ -0,0 +1,52 @@ +#include +#include "execution_environment.h" + +QUIZ_CASE(python_ulab) { + TestExecutionEnvironment env = init_environnement(); + // Try to import ulab module and submodules + assert_command_execution_succeeds(env, "import ulab"); + assert_command_execution_succeeds(env, "import ulab as ul"); + assert_command_execution_succeeds(env, "from ulab import *"); + assert_command_execution_succeeds(env, "from ulab import numpy"); + assert_command_execution_succeeds(env, "from ulab import numpy as np"); + assert_command_execution_succeeds(env, "from ulab import scipy"); + assert_command_execution_succeeds(env, "from ulab import scipy as sp"); + assert_command_execution_succeeds(env, "from ulab import scipy as spy"); + // NumPy tests + assert_command_execution_succeeds(env, "np.array([1, 2, 3])"); + // Store an array in a variable and use it + assert_command_execution_succeeds(env, "a = np.array([1, 2, 3])"); + assert_command_execution_succeeds(env, "a[0]"); + assert_command_execution_succeeds(env, "a[1]"); + assert_command_execution_succeeds(env, "a[2]"); + assert_command_execution_fails(env, "a[3]"); + // Test np.all + assert_command_execution_succeeds(env, "np.all([1, 2, 3])"); + // SciPy tests + // Test ulab.scipy.linalg using spy prefix + assert_command_execution_succeeds(env, "spy.linalg.solve_triangular(np.array([[1, 2], [3, 4]]), np.array([5, 6]))"); + assert_command_execution_fails(env, "spy.linalg.solve_triangular([[1, 2], [3, 4]], [1, 2, 3])"); + // Test ulab.scipy.optimize using spy prefix + assert_command_execution_succeeds(env, "spy.optimize.fmin(lambda x: x**2, 1)"); + assert_command_execution_fails(env, "spy.optimize.fmin(lambda x: x**2, 1, maxiter=0)"); + assert_command_execution_succeeds(env, "spy.optimize.fmin(lambda x: x**2, 1, maxiter=1)"); + assert_command_execution_fails(env, "spy.optimize.bisect(lambda x: x**2, 1, 2, maxiter=0)"); + assert_command_execution_succeeds(env, "spy.optimize.newton(lambda x: x**2, 1)"); + assert_command_execution_fails(env, "spy.optimize.newton(lambda x: x**2, 1, maxiter=0)"); + assert_command_execution_succeeds(env, "spy.optimize.newton(lambda x: x**2, 1, maxiter=1)"); + // Test ulab.scipy.signal using spy prefix + // TODO: Find a way to test this, maybe in a future ulab release ? + // assert_command_execution_succeeds(env, "spy.signal.sosfilt(np.array([1, 2, 3]), np.array([7, 8, 9]))"); + assert_command_execution_fails(env, "spy.signal.spectrogram(np.array([1, 2, 3]), np.array([7, 8, 9]))"); + // Test ulab.scipy.special using spy prefix + assert_command_execution_succeeds(env, "spy.special.erf(1)"); + assert_command_execution_fails(env, "spy.special.erf(1, 2)"); + assert_command_execution_succeeds(env, "spy.special.erfc(1)"); + assert_command_execution_fails(env, "spy.special.erfc(1, 2)"); + assert_command_execution_succeeds(env, "spy.special.gamma(1)"); + assert_command_execution_fails(env, "spy.special.gamma(1, 2)"); + assert_command_execution_succeeds(env, "spy.special.gammaln(1)"); + assert_command_execution_fails(env, "spy.special.gammaln(1, 2)"); + deinit_environment(); +} + diff --git a/python/upgrade.md b/python/upgrade.md index 80cb7c37760..12cb3568b0d 100644 --- a/python/upgrade.md +++ b/python/upgrade.md @@ -1,14 +1,14 @@ -Steps to upgrade MicroPython: -- Clone the micropython project and checkout the current version -- Find the current patches and save them or make sure they were integrated to the next micropython version - git diff Path/to/epsilon/py Path/to/micropython/py -- Checkout the new version in the micropython project -- Copy the micropython py files in epsilon py folder -- Update epsilon/python/Makefile and epsilon/python/port/genhdr/qstrdefs.in.h following the instructions in the files -- Update other epsilon/python/port/genhdr/ files : +# Steps to upgrade MicroPython + +- Clone the MicroPython project and checkout the current version +- Find the current patches and save them or make sure they were integrated to the next MicroPython version + git diff Path/to/Upsilon/python/src/py Path/to/MicroPython/py +- Checkout the new version in the MicroPython project +- Copy the MicroPython py files in Upsilon python/src/py folder +- Update Upsilon/python/Makefile, Upsilon/python/port/genhdr/qstrdefs.in.h and Upsilon/python/port/genhdr/moduledefs.h following the instructions in the files +- Update other Upsilon/python/port/genhdr/ files : Get a clean copy of MicroPython Copy our mpconfigport.h over the "bare-arm" port of MicroPython "make" the bare-arm port of MicroPython (don't worry if it doesn't finish) - Copy the wanted build/genhdr files to epsilon/python/port/genhdr/ + Copy the wanted build/genhdr files to Upsilon/python/port/genhdr/ - Put back the patches from the first step if needed - From 891afff4bbc0ca09b8009fa90b62f6ac92147a82 Mon Sep 17 00:00:00 2001 From: Laury Date: Sun, 26 Jun 2022 14:30:50 +0200 Subject: [PATCH 302/355] [code] Added ulab.utils in toolbox --- apps/code/catalog.de.i18n | 2 ++ apps/code/catalog.en.i18n | 2 ++ apps/code/catalog.es.i18n | 2 ++ apps/code/catalog.fr.i18n | 2 ++ apps/code/catalog.hu.i18n | 2 ++ apps/code/catalog.it.i18n | 2 ++ apps/code/catalog.nl.i18n | 2 ++ apps/code/catalog.pt.i18n | 2 ++ apps/code/catalog.universal.i18n | 8 ++++++++ apps/code/python_toolbox.cpp | 11 +++++++++++ apps/code/toolbox.universal.i18n | 1 + 11 files changed, 36 insertions(+) diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index 799ed941337..5ead158750a 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -254,3 +254,5 @@ PythonFileName = "Enthält den Namen der Datei" PythonFileMode = "Enthält den Öffnungsmodus der Datei" PythonFileReadable = "Kann Datei gelesen werden?" PythonFileWritable = "Kann Datei geschrieben werden?" +PythonImportUtils = "Importieren von ulab.utils" +PythonUtilsFunction = "Funktionspräfix des utils-Moduls" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index af439aeb8f0..62a9400577f 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -254,3 +254,5 @@ PythonFileName = "Contains file's name" PythonFileMode = "Contains file's open mode" PythonFileReadable = "Tells if read can be used on a file" PythonFileWritable = "Tells if write can be used on a file" +PythonImportUtils = "Importing ulab.utils" +PythonUtilsFunction = "utils module function prefix" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index c7d94d3f447..883b47cf481 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -254,3 +254,5 @@ PythonFileName = "Contains file's name" PythonFileMode = "Contains file's open mode" PythonFileReadable = "Tells if read can be used on a file" PythonFileWritable = "Tells if write can be used on a file" +PythonImportUtils = "Importando ulab.utils" +PythonUtilsFunction = "prefijo de función del módulo utils" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index aae8345d317..99377015773 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -78,6 +78,7 @@ PythonImportMath = "Importation du module math" PythonImportMatplotlibPyplot = "Importation de matplotlib.pyplot" PythonImportNumpy = "Importation de ulab.numpy" PythonImportScipy = "Importation de ulab.scipy" +PythonImportUtils = "Importation de ulab.utils" PythonImportTurtle = "Importation du module turtle" PythonImportTime = "Importation du module time" PythonIndex = "Indice première occurrence de x" @@ -161,6 +162,7 @@ PythonScipyLinalgFunction = "Préfixe fonction du module scipy.linalg" PythonScipyOptimizeFunction = "Préfixe fonction du module scipy.optimize" PythonScipySignalFunction = "Préfixe fonction du module scipy.signal" PythonScipySpecialFunction = "Préfixe fonction du module scipy.special" +PythonUtilsFunction = "Préfixe fonction du module utils" PythonOct = "Conversion en octal" PythonPhase = "Argument de z" PythonPlot = "Trace y en fonction de x" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index 3dac494700e..6242e4dc35a 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -254,3 +254,5 @@ PythonKeyDot = "DOT gomb" PythonKeyEe = "10 POWER X gomb" PythonKeyAns = "ANS kulcs" PythonKeyExe = "EXE kulcs" +PythonImportUtils = "Az ulab.utils importálása" +PythonUtilsFunction = "utils modul függvény előtagja" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index 7e797a39932..d8a24f355b9 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -254,3 +254,5 @@ PythonFileName = "Contains file's name" PythonFileMode = "Contains file's open mode" PythonFileReadable = "Tells if read can be used on a file" PythonFileWritable = "Tells if write can be used on a file" +PythonImportUtils = "Importazione di ulab.utils" +PythonUtilsFunction = "prefisso della funzione del modulo utils" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index 9d98d756e3f..876073d95b9 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -255,3 +255,5 @@ PythonFileName = "Contains file's name" PythonFileMode = "Contains file's open mode" PythonFileReadable = "Tells if read can be used on a file" PythonFileWritable = "Tells if write can be used on a file" +PythonImportUtils = "Ulab.utils importeren" +PythonUtilsFunction = "utils module functie prefix" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index d3a49aa559b..155aa785f56 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -254,3 +254,5 @@ PythonFileName = "Contains file's name" PythonFileMode = "Contains file's open mode" PythonFileReadable = "Tells if read can be used on a file" PythonFileWritable = "Tells if write can be used on a file" +PythonImportUtils = "Importando ulab.utils" +PythonUtilsFunction = "prefixo de função do módulo utils" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index 05a79de125b..ca6e19c4576 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -295,6 +295,14 @@ PythonCommandNumpyFftFunction = "np.fft.function" PythonCommandNumpyFftFunctionWithoutArg = "np.fft.\x11" PythonCommandNumpyLinalgFunction = "np.linalg.function" PythonCommandNumpyLinalgFunctionWithoutArg = "np.linalg.\x11" +PythonCommandImportFromUtils = "from ulab import utils" +PythonCommandUtilsFunction = "utils.function" +PythonCommandUtilsFunctionWithoutArg = "utils.\x11" +PythonCommandUtilsSpectrogram = "spectrogram(a)" +PythonCommandUtilsFromInt16Buffer = "from_int16_buffer(b)" +PythonCommandUtilsFromUint16Buffer = "from_uint16_buffer(b)" +PythonCommandUtilsFromInt32Buffer = "from_int32_buffer(b)" +PythonCommandUtilsFromUint32Buffer = "from_uint32_buffer(b)" PythonCommandOct = "oct(x)" PythonCommandPhase = "phase(z)" PythonCommandPlot = "plot(x,y,color)" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index b4525591da7..1f75378d332 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -295,9 +295,20 @@ const ToolboxMessageTree ScipyModuleChildren[] = { ToolboxMessageTree::Node(I18n::Message::ScipySpecialModule, ScipySpecialModuleChildren), }; +const ToolboxMessageTree UtilsModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromUtils, I18n::Message::PythonImportUtils, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandUtilsFunction, I18n::Message::PythonUtilsFunction, false, I18n::Message::PythonCommandUtilsFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandUtilsSpectrogram), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandUtilsFromInt16Buffer), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandUtilsFromUint16Buffer), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandUtilsFromInt32Buffer), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandUtilsFromUint32Buffer), +}; + const ToolboxMessageTree UlabModuleChildren[] = { ToolboxMessageTree::Node(I18n::Message::NumpyModule, NumpyModuleChildren), ToolboxMessageTree::Node(I18n::Message::ScipyModule, ScipyModuleChildren), + ToolboxMessageTree::Node(I18n::Message::UtilsModule, UtilsModuleChildren), ToolboxMessageTree::Leaf(I18n::Message::UlabDocumentation, I18n::Message::UlabDocumentationLink) }; diff --git a/apps/code/toolbox.universal.i18n b/apps/code/toolbox.universal.i18n index 1e757f43a35..c05493a0564 100644 --- a/apps/code/toolbox.universal.i18n +++ b/apps/code/toolbox.universal.i18n @@ -12,6 +12,7 @@ ScipyOptimizeModule = "optimize" ScipySignalModule = "signal" ScipySpecialModule = "special" NumpyNdarray = "ndarray" +UtilsModule = "utils" OsModule = "os" SysModule = "sys" TimeModule = "time" From 992fb5dc85c9abe85f3a9b9eecec24b6839b54ee Mon Sep 17 00:00:00 2001 From: Laury Date: Mon, 27 Jun 2022 11:44:53 +0200 Subject: [PATCH 303/355] [escher] Fixed icon view --- escher/src/icon_view.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/escher/src/icon_view.cpp b/escher/src/icon_view.cpp index cb9a31749c4..7a5453d2a4c 100644 --- a/escher/src/icon_view.cpp +++ b/escher/src/icon_view.cpp @@ -45,7 +45,7 @@ void IconView::drawRect(KDContext * ctx, KDRect rect) const { //We push the first 6 lines of the image so that they are truncated on the sides ctx->fillRectWithPixels(KDRect(6, 0, m_frame.width()-12, 1),pixelBuffer+6, nullptr); - ctx->fillRectWithPixels(KDRect(4, 1, m_frame.width()-8, 1),pixelBuffer+6+55, nullptr); + ctx->fillRectWithPixels(KDRect(4, 1, m_frame.width()-8, 1),pixelBuffer+4+55, nullptr); ctx->fillRectWithPixels(KDRect(3, 2, m_frame.width()-6, 1),pixelBuffer+3+(2*55), nullptr); ctx->fillRectWithPixels(KDRect(2, 3, m_frame.width()-4, 1),pixelBuffer+2+(3*55), nullptr); ctx->fillRectWithPixels(KDRect(1, 4, m_frame.width()-2, 1),pixelBuffer+1+(4*55), nullptr); From 2a234305f331b38199c7d61798e06a9ab050aa3d Mon Sep 17 00:00:00 2001 From: Laury Date: Mon, 27 Jun 2022 13:00:30 +0200 Subject: [PATCH 304/355] [reader] FIxed "syntax error" with colors when going backward --- apps/reader/word_wrap_view.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index c13400e214b..b02c4ed03c2 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -706,7 +706,11 @@ bool WordWrapTextView::updateTextColorForward(const char * colorStart) const { bool WordWrapTextView::updateTextColorBackward(const char * colorStart) const { - if (*(colorStart++) != '\\') { + if (*(++colorStart) != '\\') { + if (*(colorStart + 1) == '%' || *(colorStart + 2) == '%') { + m_textColor = Palette::PrimaryText; + return true; + } return false; } From 99b070a30dc89685057e399293df6a121c4d2334 Mon Sep 17 00:00:00 2001 From: Oreig403 <97249553+Oreig403@users.noreply.github.com> Date: Fri, 1 Jul 2022 11:39:23 +0200 Subject: [PATCH 305/355] New font and italic in python keywords (#232) --- apps/code/python_text_area.cpp | 119 ++++++++++++++++-- .../list/text_field_function_title_cell.h | 2 +- apps/reader/tex_parser.cpp | 19 ++- apps/reader/tex_parser.h | 3 - apps/shared/banner_view.h | 2 +- escher/include/escher/text_area.h | 4 +- escher/src/text_area.cpp | 34 ++--- kandinsky/Makefile | 13 +- kandinsky/fonts/ItalicLargeFont.ttf | Bin 0 -> 64048 bytes kandinsky/fonts/ItalicSmallFont.ttf | Bin 0 -> 64048 bytes kandinsky/fonts/LargeFont.ttf | Bin 500292 -> 136344 bytes kandinsky/fonts/SmallFont.ttf | Bin 478328 -> 136344 bytes kandinsky/fonts/code_points.h | 13 +- kandinsky/fonts/rasterizer.c | 3 + kandinsky/include/kandinsky/font.h | 6 +- 15 files changed, 183 insertions(+), 35 deletions(-) create mode 100644 kandinsky/fonts/ItalicLargeFont.ttf create mode 100644 kandinsky/fonts/ItalicSmallFont.ttf diff --git a/apps/code/python_text_area.cpp b/apps/code/python_text_area.cpp index 788753f968a..a0e7a5a407d 100644 --- a/apps/code/python_text_area.cpp +++ b/apps/code/python_text_area.cpp @@ -24,6 +24,105 @@ constexpr KDColor BackgroundColor = Palette::CodeBackground; constexpr KDColor HighlightColor = Palette::CodeBackgroundSelected; constexpr KDColor AutocompleteColor = KDColor::RGB24(0xC6C6C6); // TODO Palette change +bool isItalic(mp_token_kind_t tokenKind) { + if (!GlobalPreferences::sharedGlobalPreferences()->syntaxhighlighting()) { + return false; + } + if (tokenKind == MP_TOKEN_STRING) { + return true; + } + + static_assert(MP_TOKEN_ELLIPSIS + 1 == MP_TOKEN_KW_FALSE + && MP_TOKEN_KW_FALSE + 1 == MP_TOKEN_KW_NONE + && MP_TOKEN_KW_NONE + 1 == MP_TOKEN_KW_TRUE + && MP_TOKEN_KW_TRUE + 1 == MP_TOKEN_KW___DEBUG__ + && MP_TOKEN_KW___DEBUG__ + 1 == MP_TOKEN_KW_AND + && MP_TOKEN_KW_AND + 1 == MP_TOKEN_KW_AS + && MP_TOKEN_KW_AS + 1 == MP_TOKEN_KW_ASSERT + /* Here there are keywords that depend on MICROPY_PY_ASYNC_AWAIT, we do + * not test them */ + && MP_TOKEN_KW_BREAK + 1 == MP_TOKEN_KW_CLASS + && MP_TOKEN_KW_CLASS + 1 == MP_TOKEN_KW_CONTINUE + && MP_TOKEN_KW_CONTINUE + 1 == MP_TOKEN_KW_DEF + && MP_TOKEN_KW_DEF + 1 == MP_TOKEN_KW_DEL + && MP_TOKEN_KW_DEL + 1 == MP_TOKEN_KW_ELIF + && MP_TOKEN_KW_ELIF + 1 == MP_TOKEN_KW_ELSE + && MP_TOKEN_KW_ELSE + 1 == MP_TOKEN_KW_EXCEPT + && MP_TOKEN_KW_EXCEPT + 1 == MP_TOKEN_KW_FINALLY + && MP_TOKEN_KW_FINALLY + 1 == MP_TOKEN_KW_FOR + && MP_TOKEN_KW_FOR + 1 == MP_TOKEN_KW_FROM + && MP_TOKEN_KW_FROM + 1 == MP_TOKEN_KW_GLOBAL + && MP_TOKEN_KW_GLOBAL + 1 == MP_TOKEN_KW_IF + && MP_TOKEN_KW_IF + 1 == MP_TOKEN_KW_IMPORT + && MP_TOKEN_KW_IMPORT + 1 == MP_TOKEN_KW_IN + && MP_TOKEN_KW_IN + 1 == MP_TOKEN_KW_IS + && MP_TOKEN_KW_IS + 1 == MP_TOKEN_KW_LAMBDA + && MP_TOKEN_KW_LAMBDA + 1 == MP_TOKEN_KW_NONLOCAL + && MP_TOKEN_KW_NONLOCAL + 1 == MP_TOKEN_KW_NOT + && MP_TOKEN_KW_NOT + 1 == MP_TOKEN_KW_OR + && MP_TOKEN_KW_OR + 1 == MP_TOKEN_KW_PASS + && MP_TOKEN_KW_PASS + 1 == MP_TOKEN_KW_RAISE + && MP_TOKEN_KW_RAISE + 1 == MP_TOKEN_KW_RETURN + && MP_TOKEN_KW_RETURN + 1 == MP_TOKEN_KW_TRY + && MP_TOKEN_KW_TRY + 1 == MP_TOKEN_KW_WHILE + && MP_TOKEN_KW_WHILE + 1 == MP_TOKEN_KW_WITH + && MP_TOKEN_KW_WITH + 1 == MP_TOKEN_KW_YIELD + && MP_TOKEN_KW_YIELD + 1 == MP_TOKEN_OP_ASSIGN + && MP_TOKEN_OP_ASSIGN + 1 == MP_TOKEN_OP_TILDE, + "MP_TOKEN order changed, so Code::PythonTextArea::TokenColor might need to change too."); + if (tokenKind >= MP_TOKEN_KW_FALSE && tokenKind <= MP_TOKEN_KW_YIELD) { + return true; + } + static_assert(MP_TOKEN_OP_TILDE + 1 == MP_TOKEN_OP_LESS + && MP_TOKEN_OP_LESS + 1 == MP_TOKEN_OP_MORE + && MP_TOKEN_OP_MORE + 1 == MP_TOKEN_OP_DBL_EQUAL + && MP_TOKEN_OP_DBL_EQUAL + 1 == MP_TOKEN_OP_LESS_EQUAL + && MP_TOKEN_OP_LESS_EQUAL + 1 == MP_TOKEN_OP_MORE_EQUAL + && MP_TOKEN_OP_MORE_EQUAL + 1 == MP_TOKEN_OP_NOT_EQUAL + && MP_TOKEN_OP_NOT_EQUAL + 1 == MP_TOKEN_OP_PIPE + && MP_TOKEN_OP_PIPE + 1 == MP_TOKEN_OP_CARET + && MP_TOKEN_OP_CARET + 1 == MP_TOKEN_OP_AMPERSAND + && MP_TOKEN_OP_AMPERSAND + 1 == MP_TOKEN_OP_DBL_LESS + && MP_TOKEN_OP_DBL_LESS + 1 == MP_TOKEN_OP_DBL_MORE + && MP_TOKEN_OP_DBL_MORE + 1 == MP_TOKEN_OP_PLUS + && MP_TOKEN_OP_PLUS + 1 == MP_TOKEN_OP_MINUS + && MP_TOKEN_OP_MINUS + 1 == MP_TOKEN_OP_STAR + && MP_TOKEN_OP_STAR + 1 == MP_TOKEN_OP_AT + && MP_TOKEN_OP_AT + 1 == MP_TOKEN_OP_DBL_SLASH + && MP_TOKEN_OP_DBL_SLASH + 1 == MP_TOKEN_OP_SLASH + && MP_TOKEN_OP_SLASH + 1 == MP_TOKEN_OP_PERCENT + && MP_TOKEN_OP_PERCENT + 1 == MP_TOKEN_OP_DBL_STAR + && MP_TOKEN_OP_DBL_STAR + 1 == MP_TOKEN_DEL_PIPE_EQUAL + && MP_TOKEN_DEL_PIPE_EQUAL + 1 == MP_TOKEN_DEL_CARET_EQUAL + && MP_TOKEN_DEL_CARET_EQUAL + 1 == MP_TOKEN_DEL_AMPERSAND_EQUAL + && MP_TOKEN_DEL_AMPERSAND_EQUAL + 1 == MP_TOKEN_DEL_DBL_LESS_EQUAL + && MP_TOKEN_DEL_DBL_LESS_EQUAL + 1 == MP_TOKEN_DEL_DBL_MORE_EQUAL + && MP_TOKEN_DEL_DBL_MORE_EQUAL + 1 == MP_TOKEN_DEL_PLUS_EQUAL + && MP_TOKEN_DEL_PLUS_EQUAL + 1 == MP_TOKEN_DEL_MINUS_EQUAL + && MP_TOKEN_DEL_MINUS_EQUAL + 1 == MP_TOKEN_DEL_STAR_EQUAL + && MP_TOKEN_DEL_STAR_EQUAL + 1 == MP_TOKEN_DEL_AT_EQUAL + && MP_TOKEN_DEL_AT_EQUAL + 1 == MP_TOKEN_DEL_DBL_SLASH_EQUAL + && MP_TOKEN_DEL_DBL_SLASH_EQUAL + 1 == MP_TOKEN_DEL_SLASH_EQUAL + && MP_TOKEN_DEL_SLASH_EQUAL + 1 == MP_TOKEN_DEL_PERCENT_EQUAL + && MP_TOKEN_DEL_PERCENT_EQUAL + 1 == MP_TOKEN_DEL_DBL_STAR_EQUAL + && MP_TOKEN_DEL_DBL_STAR_EQUAL + 1 == MP_TOKEN_DEL_PAREN_OPEN + && MP_TOKEN_DEL_PAREN_OPEN + 1 == MP_TOKEN_DEL_PAREN_CLOSE + && MP_TOKEN_DEL_PAREN_CLOSE + 1 == MP_TOKEN_DEL_BRACKET_OPEN + && MP_TOKEN_DEL_BRACKET_OPEN + 1 == MP_TOKEN_DEL_BRACKET_CLOSE + && MP_TOKEN_DEL_BRACKET_CLOSE + 1 == MP_TOKEN_DEL_BRACE_OPEN + && MP_TOKEN_DEL_BRACE_OPEN + 1 == MP_TOKEN_DEL_BRACE_CLOSE + && MP_TOKEN_DEL_BRACE_CLOSE + 1 == MP_TOKEN_DEL_COMMA + && MP_TOKEN_DEL_COMMA + 1 == MP_TOKEN_DEL_COLON + && MP_TOKEN_DEL_COLON + 1 == MP_TOKEN_DEL_PERIOD + && MP_TOKEN_DEL_PERIOD + 1 == MP_TOKEN_DEL_SEMICOLON + && MP_TOKEN_DEL_SEMICOLON + 1 == MP_TOKEN_DEL_EQUAL + && MP_TOKEN_DEL_EQUAL + 1 == MP_TOKEN_DEL_MINUS_MORE, + "MP_TOKEN order changed, so Code::PythonTextArea::TokenColor might need to change too."); + + + return false; +} + static inline KDColor TokenColor(mp_token_kind_t tokenKind) { if (!GlobalPreferences::sharedGlobalPreferences()->syntaxhighlighting()) { return Palette::CodeText; @@ -255,7 +354,8 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char BackgroundColor, selectionStart, selectionEnd, - HighlightColor); + HighlightColor, + false); } if (UTF8Helper::CodePointIs(firstNonSpace, UCodePointNull)) { return; @@ -285,14 +385,16 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char BackgroundColor, selectionStart, selectionEnd, - HighlightColor); + HighlightColor, + false); } tokenLength = TokenLength(lex, tokenFrom); tokenEnd = tokenFrom + tokenLength; - // If the token is being autocompleted, use DefaultColor + // If the token is being autocompleted, use DefaultColor/Font KDColor color = (tokenFrom <= autocompleteStart && autocompleteStart < tokenEnd) ? Palette::CodeText : TokenColor(lex->tok_kind); - + bool font = (tokenFrom <= autocompleteStart && autocompleteStart < tokenEnd) ? false:isItalic(lex->tok_kind); + LOG_DRAW("Draw \"%.*s\" for token %d\n", tokenLength, tokenFrom, lex->tok_kind); drawStringAt(ctx, line, UTF8Helper::GlyphOffsetAtCodePoint(text, tokenFrom), @@ -302,7 +404,8 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char BackgroundColor, selectionStart, selectionEnd, - HighlightColor); + HighlightColor, + font); mp_lexer_to_next(lex); LOG_DRAW("Pop token %d\n", lex->tok_kind); @@ -325,7 +428,8 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char BackgroundColor, selectionStart, selectionEnd, - HighlightColor); + HighlightColor, + true); } mp_lexer_free(lex); @@ -345,7 +449,8 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char BackgroundColor, nullptr, nullptr, - HighlightColor); + HighlightColor, + false); } } diff --git a/apps/graph/list/text_field_function_title_cell.h b/apps/graph/list/text_field_function_title_cell.h index 05d6dcab7e7..7dc4d962a39 100644 --- a/apps/graph/list/text_field_function_title_cell.h +++ b/apps/graph/list/text_field_function_title_cell.h @@ -11,7 +11,7 @@ class ListController; class TextFieldFunctionTitleCell : public Shared::FunctionTitleCell, public Responder { public: - TextFieldFunctionTitleCell(ListController * listController, Orientation orientation = Orientation::VerticalIndicator, const KDFont * font = KDFont::LargeFont); + TextFieldFunctionTitleCell(ListController * listController, Orientation orientation = Orientation::VerticalIndicator, const KDFont * font = KDFont::ItalicLargeFont); TextField * textField() { return &m_textField; } void setEditing(bool editing); bool isEditing() const; diff --git a/apps/reader/tex_parser.cpp b/apps/reader/tex_parser.cpp index 3008db6d913..339ee8022d7 100644 --- a/apps/reader/tex_parser.cpp +++ b/apps/reader/tex_parser.cpp @@ -5,7 +5,8 @@ namespace Reader { // List of available Symbols static constexpr char const * k_SymbolsCommands[] = { - "times", "div", "forall", "partial", "exists", "pm", "approx", "infty", "neq", "equiv", "leq", "geq", + "times", "div", "forall", "partial", "exists", "nexists", "pm", "approx", "infty", "neq", "equiv", "leq", "geq", + "cap", "cup", "Cap", "Cup", "subset", "nsubset", "In", "Notin", "leftarrow", "uparrow", "rightarrow", "downarrow","leftrightarrow", "updownarrow", "Leftarrow", "Uparrow", "Rightarrow", "Downarrow", "nwarrow", "nearrow", "swarrow", "searrow", "in", "cdot", "cdots", "ldots", "Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta", "Eta", "Theta", "Iota", "Kappa", "Lambda", @@ -19,7 +20,8 @@ namespace Reader { // List of the available Symbol's CodePoints in the same order of the Symbol's list static constexpr uint32_t const k_SymbolsCodePoints[] = { - 0xd7, 0xf7, 0x2200, 0x2202, 0x2203, 0xb1, 0x2248, 0x221e, 0x2260, 0x2261, 0x2264, 0x2265, + 0xd7, 0xf7, 0x2200, 0x2202, 0x2203, 0x2204, 0xb1, 0x2248, 0x221e, 0x2260, 0x2261, 0x2264, 0x2265, + 0x2229, 0x222a, 0x22c2, 0x22c3, 0x2282, 0x2284, 0x2208, 0x2209, 0x2190, 0x2191, 0x2192, 0x2193, 0x2194, 0x2195, 0x21d0, 0x21d1, 0x21d2, 0x21d3, 0x2196, 0x2197, 0x2198, 0x2199, 0x454, 0xb7, 0x2505, 0x2026, 0x391, 0x392, 0x393, 0x394, 0x395, 0x396, 0x397, 0x398, 0x399, 0x39a, 0x39b, @@ -225,7 +227,18 @@ Layout TexParser::popCommand() { return popSumCommand(); } } - + if (strncmp(k_overlineCommand, m_text, strlen(k_overlineCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_overlineCommand)))) { + m_text += strlen(k_overlineCommand); + return popOverlineCommand(); + } + } + if (strncmp(k_intsetCommand, m_text, strlen(k_intsetCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_intsetCommand)))) { + m_text += strlen(k_intsetCommand); + return popIntsetCommand(); + } + } for (int i = 0; i < k_NumberOfSymbols; i++) { if (strncmp(k_SymbolsCommands[i], m_text, strlen(k_SymbolsCommands[i])) == 0) { if (isCommandEnded(*(m_text + strlen(k_SymbolsCommands[i])))) { diff --git a/apps/reader/tex_parser.h b/apps/reader/tex_parser.h index 088b3385b3f..65cb0cc6ab8 100644 --- a/apps/reader/tex_parser.h +++ b/apps/reader/tex_parser.h @@ -35,7 +35,6 @@ class TexParser { Layout popSumCommand(); Layout popSpaceCommand(); - //Symbols Layout popSymbolCommand(int SymbolIndex); @@ -60,8 +59,6 @@ class TexParser { static constexpr char const * k_spaceCommand = "space"; static constexpr char const * k_sqrtCommand = "sqrt"; static constexpr char const * k_sumCommand = "sum"; - - }; } diff --git a/apps/shared/banner_view.h b/apps/shared/banner_view.h index b1ad6e5fc5d..23424dec4e9 100644 --- a/apps/shared/banner_view.h +++ b/apps/shared/banner_view.h @@ -12,7 +12,7 @@ class BannerView : public View { KDSize minimalSizeForOptimalDisplay() const override; KDCoordinate minimalHeightForOptimalDisplayGivenWidth(KDCoordinate width) const; void reload() { layoutSubviews(); } - static constexpr const KDFont * Font() { return KDFont::SmallFont; } + static constexpr const KDFont * Font() { return KDFont::ItalicSmallFont; } static constexpr KDColor TextColor() { return Palette::PrimaryText; } static constexpr KDColor BackgroundColor() { return Palette::SubMenuBackground; } private: diff --git a/escher/include/escher/text_area.h b/escher/include/escher/text_area.h index 13bd84e03ed..827ea73141a 100644 --- a/escher/include/escher/text_area.h +++ b/escher/include/escher/text_area.h @@ -111,10 +111,12 @@ class TextArea : public TextInput, public InputEventHandler { m_cursorLocation = m_text.text(); } void drawRect(KDContext * ctx, KDRect rect) const override; - void drawStringAt(KDContext * ctx, int line, int column, const char * text, int length, KDColor textColor, KDColor backgroundColor, const char * selectionStart, const char * selectionEnd, KDColor backgroundHighlightColor) const; + void drawStringAt(KDContext * ctx, int line, int column, const char * text, int length, KDColor textColor, KDColor backgroundColor, const char * selectionStart, const char * selectionEnd, KDColor backgroundHighlightColor,bool isItalic = false) const; virtual void drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn, const char * selectionStart, const char * selectionEnd) const = 0; virtual void clearRect(KDContext * ctx, KDRect rect) const = 0; KDSize minimalSizeForOptimalDisplay() const override; + KDFont * usedFont; + KDFont * ItalicFont; void setText(char * textBuffer, size_t textBufferSize); const char * text() const override { return m_text.text(); } const char * editedText() const override { return m_text.text(); } diff --git a/escher/src/text_area.cpp b/escher/src/text_area.cpp index 7c88640d051..ddbe3170171 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -513,21 +513,27 @@ void TextArea::ContentView::drawRect(KDContext * ctx, KDRect rect) const { } } -void TextArea::ContentView::drawStringAt(KDContext * ctx, int line, int column, const char * text, int length, KDColor textColor, KDColor backgroundColor, const char * selectionStart, const char * selectionEnd, KDColor backgroundHighlightColor) const { +void TextArea::ContentView::drawStringAt(KDContext * ctx, int line, int column, const char * text, int length, KDColor textColor, KDColor backgroundColor, const char * selectionStart, const char * selectionEnd, KDColor backgroundHighlightColor, bool isItalic) const { if (length < 0) { return; - } - KDSize glyphSize = m_font->glyphSize(); - + } + const KDFont * ItalicFont = (m_font == KDFont::LargeFont) ? KDFont::ItalicLargeFont : KDFont::ItalicSmallFont; + const KDFont * usedFont = isItalic ? ItalicFont : m_font; + + + KDSize glyphSize = usedFont->glyphSize(); + bool drawSelection = selectionStart != nullptr && selectionEnd > text && selectionStart < text + length; + KDPoint nextPoint = ctx->drawString( - text, - KDPoint(column*glyphSize.width(), line*glyphSize.height()), - m_font, - textColor, - backgroundColor, - drawSelection ? (selectionStart >= text ? std::min(length, selectionStart - text) : 0) : length - ); + text, + KDPoint(column*glyphSize.width(), line*glyphSize.height()), + usedFont, + textColor, + backgroundColor, + drawSelection ? (selectionStart >= text ? std::min(length, selectionStart - text) : 0) : length + ); + if (!drawSelection) { return; } @@ -537,7 +543,7 @@ void TextArea::ContentView::drawStringAt(KDContext * ctx, int line, int column, nextPoint = ctx->drawString( highlightedDrawStart, nextPoint, - m_font, + usedFont, textColor, backgroundHighlightColor, highlightedDrawLength); @@ -546,7 +552,7 @@ void TextArea::ContentView::drawStringAt(KDContext * ctx, int line, int column, ctx->drawString( notHighlightedDrawStart, nextPoint, - m_font, + usedFont, textColor, backgroundColor, length - (notHighlightedDrawStart - text)); @@ -557,7 +563,7 @@ KDSize TextArea::ContentView::minimalSizeForOptimalDisplay() const { return KDSize( /* We take into account the space required to draw a cursor at the end of * line by adding glyphSize.width() to the width. */ - span.width() + m_font->glyphSize().width(), + span.width() + m_font->glyphSize().width() + 4, span.height() ); } diff --git a/kandinsky/Makefile b/kandinsky/Makefile index 3b3f45628eb..c67fc3e0107 100644 --- a/kandinsky/Makefile +++ b/kandinsky/Makefile @@ -66,14 +66,18 @@ ifdef HAS_READER kandinsky_src += $(addprefix kandinsky/fonts/, \ LargeFontExtended.ttf \ + ItalicLargeFontExtended.ttf \ SmallFontExtended.ttf \ + ItalicSmallFontExtended.ttf \ LargeFontSimple.ttf \ SmallFontSimple.ttf \ ) default_kandinsky_src += $(addprefix kandinsky/fonts/, \ LargeFontExtended.ttf \ + ItalicLargeFontExtended.ttf \ SmallFontExtended.ttf \ + ItalicSmallFontExtended.ttf \ ) simple_kandinsky_src += $(addprefix kandinsky/fonts/, \ @@ -81,23 +85,28 @@ simple_kandinsky_src += $(addprefix kandinsky/fonts/, \ SmallFontSimple.ttf \ ) + $(eval $(call raster_font,SmallFont,SmallFontExtended,1,12,7,14)) +$(eval $(call raster_font,ItalicSmallFont,ItalicSmallFontExtended,1,12,7,14)) $(eval $(call raster_font,LargeFont,LargeFontExtended,1,16,10,18)) +$(eval $(call raster_font,ItalicLargeFont,ItalicLargeFontExtended,1,16,10,18)) $(eval $(call raster_font,SmallFont,SmallFontSimple,0,12,7,14)) $(eval $(call raster_font,LargeFont,LargeFontSimple,0,16,10,18)) - else kandinsky_src += $(addprefix kandinsky/fonts/, \ LargeFont.ttf \ + ItalicLargeFont.ttf \ SmallFont.ttf \ + ItalicSmallFont.ttf \ ) default_kandinsky_src = $(kandinsky_src) simple_kandinsky_src = $(kandinsky_src) $(eval $(call raster_font,SmallFont,SmallFontSimple,0,12,7,14)) +$(eval $(call raster_font,ItalicSmallFont,ItalicSmallFontSimple,0,12,7,14)) $(eval $(call raster_font,LargeFont,LargeFontSimple,0,16,10,18)) - +$(eval $(call raster_font,ItalicLargeFont,ItalicLargeFontSimple,0,16,10,18)) endif diff --git a/kandinsky/fonts/ItalicLargeFont.ttf b/kandinsky/fonts/ItalicLargeFont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e5501589d566c5f1e233846ca97f0e38e6f54b45 GIT binary patch literal 64048 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

    (eWxv?`PI<& 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

    (eWxv?`PI<& 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�`N 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 C^!qUY literal 500292 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
    k)3fw)wwy8_N6)rc=`1 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�P<;06knG=)k~sKoG= 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�`N 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 C^!qUY literal 478328 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

    <@iq_qPWK4QR#$u98@ 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


    i_ zZA~;Z+}roWo_8F+b~4pc?A#A?Jgx-x-~hh1%yix5+HL%rQ+}+vIu)y66#%PTaB57S z8o!ZJlsa`krF?Sn)!P+kMeMh#l!ey23A$Gj@g=%da>J8)1&p5bS!V^r{e-|G_!-8~ z1z@r|SG0~qYLeb;(!O~ozImtZn|I=ycj5(h;+uEkn|I=ycjB9O;+uEkn|I=ycjB9O z;+uEkn|EsDZ9y@F=jgO?S4J7oX}GjXADxcBY6Jk z;rVkSgQ$L^%~M+&sq?g!?i=uVJT)~jIe*URPo;W$Q%rU2W4^+d!=Y?8911b3DH!_n zXZuSJ8wYctCNt5w`i$}D!->+vz|e z&L)zfwLJo+nDWzPs+-h06)fx7L8ijAN<~V<6uX)@TPdG*;3o-H6&kh!Kgzj6Qhs#- zItrbF?uH(O9)X^Ko`qh7R0!}g4|kPwa+vGDtM3)#dy%>HGI`e0P-@+Z#LhkW zj*k4Er5jq3L*tpu{l>}XqmjPOVDR(e7ql6-n&al<+-B#TROZtP1Wn6kpvT z`PLnd8qQWtUcMq@l9KBYNgY)hMFuljATG-fn^}Hn?Sbd#BnEXtEZEWKTzu!qD5>HDVgpsqcg#ZB$Z*xj zSk9MYcnn~n0~tvfYWjCIsX^wxBobXM!DyhlqczpoR96!lYz+hwE#0|zvN;qE1(WH< zCS%FtInsK>gSH#ZcrwE!<3H5|5)I9bnS3}@QyYk60)a>)>xvWgp-?&-jn&tMLt$Bb z9gDu>^EbWx@=c#N2Jc&0xv%_9!Ia5L#c{@5eB?HN(}^i7V_dgv?H7kK|MXb!p_x()gO^kL|e(C471p|3*e1(K1Qaax<;eJ3Q7G6~Ye z`S#Fjll(z)a%nn(4J572#@MM_hKzbZcMs_90o^^Iy9adlfbJg9-2=LNKz9%5?g8CB zpj%4RBhxQ&auzaW@qq;20b=tt4LCL=fx1TQ+SJ9NLAiz_rVlAEB!zrWF`1gro_i$pc zwPgrY)&+yvxl(t7r_edMb7K48KsHnDxiOe-%|@f!x{N?D+z>E4VtWbJy!+LB-CDFz za&4WsQg;Bi7^W9Yjv^xYWx zZVY`lhQ1p^-;JT~#?W_T=({oW-C&aFj>p#Wu3Eh6YO$J?hK#i7$Z-8KFl*ts#c9bG zTJu*_oWiOM&1~bZQ}rm)Y_30(b`KB^Ceq1)wq#4Fso44EpJ^Z*_O!%{xllORTxbo( zR$FW+l&|wu*TiDEw#7nAs=Lfw{RlqRGG5jM0>@L%!qN%IHt}z;U800mUigmPOY*!QCZZVgk(XC-h}0%s*~Rsv@wa8?3mC2&^a4M@qsazclktf@MGLN&9p)!(94xN0w< zxLq;Y_?j$}GCsF%Wz==*Pd{F_BI^XaYfQw`EQfpl z-?S-qnk#EE9BfXF*VG1^!wvP}aL2Z=Hxy2HMWexBqB$O5X-j76my|HqYXSC9$HeAo zjs=>BjCE`0r-g0P4k2}>8rrP|owelrY+EHuESr4d?NAB|7@m< zrPaY;dyD5^YzA1XAG8tmPq8Pd`o?&Cpgq^qA5DbD+EcMatSuP);N`J)r=U#hje|;W z)FSJ{#x+jgb%P(YVJnu(YHpeKCSx|@VrvH}N?$r1BpKlI0y@bVBvfw;9J@8>y7^i+ zpz8*7-GHte&~*d4Za~)!=(+)2H=yeVblreXEIn%D3@5KZDwioUsU)8%GpVXiL!5g# zinWu$7FBC&%gV;J0-P8ry;k)ql0UVFeB@kbGS@qSRmw47s0JWa3f-I+&8_(hwsSZW zEynAEO`+z`|7t^P`!Gw+(j&!GOFR~eMx`pts>{ZMb{KCC`KlWl9!j2QZfPM!lx%8D zHz(^^ao3jrWzBQZE1KsyqV6T*5hu?lM6XO-P_I}9!z#t9oZ&$PT6o^0Mh%|B(Qqmp zSy|qpcEr+oldY=5OjxrYp3Xu0>Fd_h5a;&DvJBw{1Xf=3Mg;1BMxnjX5$IOvPUwE< zA?PvaDd>5~3TkYnqZ96qJm?%|YR)!Oa~PO8%+wqzWDYYmhnbqgOwD1Y<}g!pn5j9; z)Es7N4l^}J^E@}tHwt?CQIewyJ#xiP3&^?THpZnKAzH&KA#`yybY-ZzxcaFIZW?J9 z*E}D+4EiMY`tln7`dx=zM~(ZP_^Y@h7To3pkaO$(6Fc4b)Adty05k+IhCtUYIJSeT z3%eA@y5Q15$v$ZhMs|HVWLvr@po;>!D4>f1x+tKF0=g)mivqeRpo;>!D4>f1I%{0! zB~H#lGR@@jo{T(9ziT%Sqr9g>!19m{%R|8O5U@N1EDr(8L%{M7usj4T4*|FaeZYCi_KV`?Z)*^ELth5O75mI zJ=_APbBBn2f(y}w-Gm3?bOnHj51S_w|76fhhSFq=r=Dw~P-KuRsjXiUwkNTENuD7I z*ppa4_N0J50DTzxB=kAxY3S>aW&M&^zodnjcID|>DIXa{lXQ6Jxg8&!ZyiMgs$9uiuiYDY|Gmk9f#heaI-T|} zvJo(=S=%I*|IJGczkl*%?9ic`9CnbsUe=jNqhT#(mN@E&DeJf;x6w^3p86?SmaW>#a9wUi+%QRt%g~Hc5;bsKYUN-WWjCZaRD`xc z3(!&M6m&QAAoK|I1oSNQA|xCi;$d|eUe)3jY*{B9R=E{>1&}?9vf?7iMm=^2mY1o} zYq{@2hXRM$kxjKWHW!OivF2=*%CThK10y5xL~G{FKd1y^?)RpHq}o!+j%+m498~M@ z%hxqErByP;8>o^T%f3z=^UhSt%B@H^cA};>-jj%jLK#vlj0;yO8)oBV&b80=4&&2K z%~8p!SzyVD70YQW*5X=HoS9;{OgLl@SZHYIX_6(a7^Jvh3B^!CF_cgYB@{ym#ZW>q zlu!&M6hjHcP(m@3Pz;ioP;PMv#USYonQ^$JGTzU)4vD>*#cIe+55iClj}?wv1&@Oj zc!>KoizS@J63$`?XR(B{Si)H>;VhPL7E3sbC7i_)&SD8?v4pew@n7QPEM&!eE5LTe zhV2TlT>-W$z;*@Lt^nH=V7mfrSAgvbuw4PRE5LRI*sjo|wr!UIS$4DP=!y~eH=)bu zE8{<)udWE2ms4iv0oJSXL)R(2CL6qC>G1pQaSGY6K#CYBtyWpIYF)6lEK}}&orTyi z`NU48?`65el*Z~5Se*i^Q#3yXR;R$~6j+@Ct5aZg3an0n)hVz#1y-kQtWJT|DfkOI z?KRGms4YQf6ckwKOoIYhZ>W@48Wf~?ku)esgMu_DNP~hjC`f~XG$=@ef;1>dg8~_f z(_aqQLQo~i}oRr*L_1>>i4r|u3291o(gy}_X8^m09VY(&TYV9Y{Pe1hA{m@T8 z^wSTrD7l}0=%*k0>4$#$p`U)}ryu(1hkp8@pML1CNP{)2)s8EC;kE1+6WZ;hmm1Bs z@*O^}#HQAAA9bxyreP`tY`hY02&6N`Qic)6=yY4#;9xF0`#1ec^?IIZN%jneg88;k zX!W-Q>fS`6J(FsQMp7N=rbeZP&$$9kO_8=pG#Y7ZX>M-tB!=^`cp}j@oOz#0i`9`N zt50=CDLxr6(aDYRrsk%mc&Z`budNRS(KZrSXpHxh&^hM1$@R15hn!k@Lu@fGT6unb zZ1K~MDa7sDMjB)WW_T1BW%TIiX+7Eb-OiZexMe70?_ev7_O}vSC{!ZGEmRTX;#~EXWj-O$g=crXnuS?_5Z9W1A4!O$1;R zBfp4H@4JjRuaoC2zQZ9*`6+YJDbE$tWZ5cbal`fDn3My!R+*Gu!ZbZ3AmilI+ICH2 zX{OP-)7Y+QY}YilYZ}`%jqRGoc1>furm5>T40gXa?p(D_((4Elz&_mE;&{NR!kd=ZP#s?f$ zT`N{_&a#60wN&osnfKezyq{;@&ol4mnfLR|`+4U5JoA2@c|Xs*pJ(3BGw z^epruBvz}Dqf^S#4ll9Kk`IbiiMF|FOKZhUmo!A{XSH0N9Q>tY4vhP+t`74b)N^Kb zP9=6<7UIP6kArbNb{+)ZGSd1LC&D+(p&1abaX@>GA-*99&oTTZ2$v;}4S07&uIpt! z16-NVn4bXi6JUM<%uj&%2{1na<|n}X1el)y^Alix0?bc}lMyIh7(^!dVti&`{ zVj3$kjg^?jN=#!Vrm+&10e*>-vyhs7CW$1kwTN<5$2IKTh@-UGk^b}MYE*{eUTB}3 zZ0Dc+^!PWE$Nrm!RI;a+*}KW!o@C0reJvq*PpMQ|TB5u~*V59;`1mSW`vF0zOS>r%xAv3&q#C~`}*?Nm&>m# zU+`I&nx~${D{C+Gv*>kYtivRG%(g332QYS&UzvU8yfm;ZwA z@I0;vd&>NnfxH2y3|m`hHK=0S5O_p|HZ`>bJbDc$bl9i+&veO9w}R%@Nn+pMML zrTYgc!X@kYhJZ1z>C3%q_LqKX_H5zo?qB+)-De9QnSP&f@O{%?``YyT%3tEhy>E8) z8-HnhZLRP1TtqL~_B=K9-D6eZyuns7zu`-|-Ns#1uDYe)xP`-AZgy8s`~a;QO^!f4jJUk1+zj49dM2 zugNFNpVnU+=f=jngXK?iNw$|UnC(4g{I&7*^S(b;2oAZ8sF4rqGujgWX>n0Ike!t+qk}b&DfanFUwE7 zTZ`X2mxqRy--!fDo3;!#xDHxvdO(A;X>b(WOR7qrVNFnyEa;W#%X)E}j4RnowIp?* zR+w^ZVBU(nqb;)qU(@ejZ^`ZTCrdq1V|+ym?+T@Ieyda}-=v=>i9Y!U=#$DjiX6%F z_!y)RXpW6!#|pMI7sistzZ@GYUt?VV?pdQ){!g>-{zd(I^WW0lpW ztdXqZ9qpycA^`e|^^US6P~K7Ie9Jb6vh-0RfLh+q(gINyZ?36=>reKSPF`Q3Or&3ek{(3svnJQp=r z-dHaGDJKzt_`P=va7_r#D?UFxPUM`4}d6oAIm8 zdsxe%E7RPaWg3!}e=PG~lWTplb$iyzir1~GUJ7?)5@A|ra;`d>)${meHBIz1BrS8^ z$+@JXW$qC9PKh7;@eE`dNk5)JKb}E9oaQYmXJiJECUYY-6gp z?S?zXbJriewl&(C9^Kd7aoubw*pQmr*&S{w|3xAjEsUhY-MMIS*8;Esd;89vGlS-D zAOqJqQBy6*tahxgsT5fed*wzd^%_lCFD!GLe6ku#TN1tC*~=QGC=27Q|C;t0$I9O~ zQsplgUo{S$UH;e0tGCGg7tcMQ*8TfjB}-0a6*G0TkvaGvi?ihxB7gQ&D%k@~ik3+& zcC(?ad{4%_chzrxXSLn@YxPdc=WZ}wXAN|%>#*~kEI(Q;5t3WWD|l(wFOTl586@jZ zW#NYpKe`&((naUw<>U+{fC>+~S?r$;x@F_KH{LKlQ~t^uZ^-KBL&og#a{2elPtxL* z?|pYQb-WGNU$PuwukjD)KfAuTek=8QqEJC1Q@{R_$NUH4|I}i?u5)ig?*^>C*R!De z2ePAM@<}O>#+37@;g7ez_y4;2cW=JCl>N}2;VItzp(({`=cu*3Ya4?4^RjVBdn>?6+DXU7@)h%@#P zCF~>4*hiePk2qr=amGI4jD5rz`-n645ohcp&e%tsu}|G@A1y|RE*cPzh%%wA)|p0G zCn(PIIC<{g$!BF6tW+71?=5h?V4d&hd_OMCd2{^r67jX=7AlVeFslhD_Mj9IG>zcAbpRdYY#Y)w5W2ElOb%TR5C)(PwZ3BbZY+vuf zLT_IzHt93#TbhG^S`(SAX$Vw#o0- z_=(w$M`Pnwizm3%wbO7YDT6xHWc;Ljd*-7_<16Kp@`?BJy;saZ*k$XE2=c0Qhg1TR zg)we=UBXqp97FpkY#%~#8*57ebt;`C@tX0?Sf*K;NRNMdD zq)xyDPbSNpRrv`%WzSTVZ^*KADH9Ol?^32s&RcXVEp{t%!n=tslxn3>S#V4R63SMx z=2(>!*v?ANk(}|48ocjtsOA4&?kajcsnqn(FBi>6s=VRJa@DP|=wr(>#^3Bur<#xa8 z?lok+a zanwPsVV&tOo0#sZwZGM@ok{gQ`MmDKMp;Kq&AJ?vW%C`@#zEOJ<68&c|Dm7x(K*+_ zHg#gS2(j-=KAF5v{SXgjnOY{Vn;}b1=B@Ye!+HH!;$GORiX866`C=o#ixkL<)yUJ} zT8Migr1r%9j3h3~$Ia04@(b#Jg>@JA{zc47jcbP!>ptZpL2e`asc)F zYs3qP#ci{doJfI`8}iaevS+B=y(&zIAB<*4Vm$6DN|dnqOKyXnu+f>0CcKcbaFq*YPg$r0QL4+Y1)O_P$wuE18J!&0FOYH}bs` z>%J$|I;}p?LA%kE9pQT?%5S}jgMKh#ev194TFZABkGn%y7Q64*g;#&i&iWZ@S}&C} zthy`4?^MS-8yZ?8;ccmE^T(c^(Zg@`yTAF~{&7juan~=MJ7G+^Uv;!ut?~v40DH30 zIR1mPunsjjjLArJELrERj(68Lw2n(#2~W?^5%*Wvr{{kdDf2dCJIn|uK1nn3ZdyFe z+g3kfY;SE<_vW(=e0K3tpIw{;z_dW{bBD2==dAeWy4^MXpjkxY?pNP@pz1F9P6OXr z+{AYl=>Yv5w=2u@kE!Pu8~%}X@1SL0EVsyoL^@g*=-*YIN908Gk;sW0*~r%I`hSpH z<-hE*Pku$6&;}n9J{Uw@wNU|zNjsJx`U@g-J0OOx~y8IKP zrhG@`CqivBaKFX1`yE_&KilSL9V{#qy}K^NmHTe`IrLjz=s6H_aJFX+X93quYfmE5 zayg{wzl+N!55x{_c2C)<%}SNBdZH>7y*ltC82aTq46psa1tTq&@@x$}o5-d1WB;c< zn*|lE2R!J3H^9rg)}GDw9<;Jr>xauBM*m$pP!J`XMu@{NGG>#7B5?Gs?|rZQEsOs2 zd*2g%n=YQTls}o4eo9Ns@NX`z<0^ z-OdTipHlM1^Hi@rk8=-jkG3d!e^Eo^M?Dif|5j^YQb4)7Y<@wp-j?|bwx#11-qCLj zMya-j(?B z-%bPQcD5sORqYzIETNWTZD9R^CqF_brqSRiK`gcnZ`magB>LHU#_gh{Q>d< zEB#jfmU*WrICyQxgqGhy#?Zo%y@k)3UC`}leOaaTCFlujjR|@dwbC>nMZMSj)V zq3|x%Z~EQrc>u_t{f+`i=R1bAE(X*`EzM5PIp6W^_S^SG-cq!BmHp;;-%7t(eoCeP z^_&i!r*nzE(S}|5f6AT)kbR>h2?#MZHr_11rSgs%XAW#=y@e3dZ-&AK2}aSV3LD~- zC@U5aaTOb;^BPJQ+8DNZv4&y0O%Y(-reMu#^C_jLL0m&XRG`?6wKm_*`!0&{(RN?_ zE#((pQ9CJg-DUgDo_C_n5B+Z&0Ij6pgML8-HGujQi*1MoffcgrpW_`XywDr7+pMHb zJ*h<}C|$w}yW7G<9-@`Oo7HRb!f&(RtoL2lZz8Agw34H+M{`7+{(tt~1w68=I?%0i zR4tXHl2j$tOQot(Nu_$Js=FkWq|#g6QoE&oxLbC++ilY}22A@A#~8;n#<2}yOk=nV zFb-*Oh9riN#_&0YWQehQaE1`#7{-_MU5^XomvODAbQ&+)j$wJ zoI-jY<=0X8sLwgR`g`qTZrW6oRFj?JBU_+|5Rl{%w3d3G z*8IANzB=Pm&fw$CNE3y^4iN+_+}WkH8+aD1iAR(XgwJ z%1@*S?X}b1uGiF_bm@%tPXB+d)7bCVsQ%em#x{}b`KC+QixQNU=$CmEL+YADVn!`^ zOxJ1FU)La)#Mge&b0}kHq3=gBTx-R_>{yug=U#(ivTahk`tliTAbWj~R@REFtJE4u z+`~rk^y*v9zf^rUr0;6&LS~0WVYbPqn}D+9Ko5~(El`q>oSj!BfisC}Nslp!FpPw( z2pRvFHnGml|1553XiUH38r11}%S#lIXe=u_E%Yn79b$3G8zp;5=vT$glzcZKE-F*m=dz&Iz9$on zm6j!qkWf%-E1@9sFIV5Hr-zw1ZLLS@^P~MwS}!(AX;T)_ zs!bcadfx46w&qBzBT2-Pd1^QX@VmCoq;2_6#N}f~yG)X}u4IlxCaOM5`_B2XtwD?O znHOoznjaerwvJF(0MTQ8UFzp)vHjX-61)wc7z;e}L#tusi@`v%*HJ??N|kkum|2Qa z>p@t1{iN`0t-Fdf=(RE7-WSeHP)3^ zgjaOg9(a+XP*nBDIzd2$sq!2&Xt|``!%i;OvNe`gQlw-oS_6O=$>ft+lT1FFs_a>l zy&oc>T25)>Q!@8N9%@RHtzhD_6`}YoIwA5(_kw{6wasX;Wyu(!IjVqK{m{!~Nh((o zQnaP!OOd-m8*){n6?I|JAMa8NQlUS2lO@C~`9k~72)*Sc)^jQxDs$RI%R+_n2C-Qs zg-EDT%V0%?DbdZRku8wnwYDpI)7@H4xf--A<6W{?bIz;YV^gC>nOEV~^%;`Ftx~s5 zgsUXM$vAaJq@qrlU8U7DgMzikDNgKJpaJpyY(`0eYd;HDlpS?@iKauQGv>%&O#{$|+&2LN_wYjr>t) z%Fg9i8gvW^%L|a&=20D;5%!jMsX4XA;ha-@Bsk-h$QC9LQj{rVJwmT|hhSFb*v71l z(Kam+$Am`xeQ8CjYP3bs8{&ag+BGpFlgYS|jY`f~zNh+p+Hvv=dhf)5vORd(h(f4{ z9#L|o=GUsv=XwKCNP=~xrdYiy*YhcfJhf;6BdJSE9GCCc@)E^s3ryzIp-<{G%3%(Q z_iY??96NKm(gk(RqQVvQ8a3CzBOh`mUh*$N9;`FwX><2u${h0 zyh;xE6S31-_mLv~_&{(hSK%9(EJ+@fs~@?9glhW8XF=lwBJNT@ii7wuo8vP z>;9}R*Uc84+uaIK*g|xfFaV$D3jl9piP^{BNQQ{cQQZ+v;_jb@12Ov8(es zKFBE6P=kZZ98`gNb+DU* zK59&7#hsBe{#wqUI9n}SO&A}EB~|$vt(?KiIH~XEq06@|WU`4uVQKjw&e}|N+rs56 zR6cxY$5L_7mS5;#XS*)s^_q75>KD`4@#@M zw{Od4grqH7M|4@ezqNL?F+MbG9ijD;o@q3Zd(0C!g`VcptmSCoKp4esjrR{Z_yGgZ*c3&!lZ-p(>eAn>OP} zdt%Ltppo|cxPx_tsmj4-I@8Q%k!Chl7&6B18$JA}e&28Os2>dv z^sjz>_3QoSxcXs^^J|?3psn-edLkc{wTxhvX9T~>?mpi$P$DZR_+*R}8U2jX!RLF1 z_h9WmAuBKa9tc+G{(R5q_o&n#d5^?yct-N+A{Gt=Gm2NVlCrD@vHtNgsP~YEqjF8o z_l&$pa#!o)vGZxq`yMr(+Itv}AOeYp)fxqxmjDh)CMrs&VE=;K4xMm7LJRo@Ag2gV%NoKSpT-xDkmGJ1T78r`-)??MK6Rmk z51sBk%XgK>hwuLR=RQ~a-Z#$nJ*&-;>L-os1^2p5*L`RCZrb|peXDY%C9v1T&)l0`_J=T^Zoz$qB~mqE_)%*-G_$Bf2_E_R{!q#=hpIB zsd;ESHy+wn|HjVSR&!>2MSb_&w9n)jU#ZSn59w##zJX`nUVY!1XILFE*7LsyW61f8 z>(9^sjI4_OobOocAk{b5@e4n5obRl8X7`1k+0A$CXRzI%-}67CiM}?T!P7se9x@{h zkD@2^$*TKYuXnx0^-kA^T)*M^oa;Zi{)ANazs3*JVe}d$W8T)XI zTbTdE_&>^A?=(MT{sxkOQmz!Sv}(XKHI^^%`=)hOW5O5s zT`E`qto`~7te>x+&Cl8@MC<40#`f0M)|L)mOG``ZQ|Gv^wl}r3H1SV<{=qr!t6w_D z)6Q0Z)>6|y$9ZbFp|!QWqZJzPb+kYaXTQI~q5w??Xa9NsxgK_Y{t`MI$_9EpVclhJTEa^f8Kt3M1Yeszj(r}9+y)u+$#w6oPq-%`CF zzEJ(%Qtq#+PiH)BDe;RL-C2x`SJfxyjGu9T=~`+iLjwJga5$RONIdKPPNzCM*Zn*H zrMGlmHMhahb%YhN!Vb1sfB()%C{>^3bKLKAs>=lg?qq&!tYLc&8QMahKlf(9G)gbM{A0 zfK#zI9jGO4+9&_zS^YGy=70O|=}#wp4;y!w3GN<^RzDzp`hx3&#;1(?&)X-ecNm{~$+-Wemv|+8ddzjqc+5C zf@R~e)2w%Pa&sQ0R;rbZ!|7GLjuWby*m#g3TW%I*~ z!7F2UKY00uae}sf8e_;d8Q-XW=@aHJ-h1zZyfIPz$qO%NJWRQ6H%H99yw}E=y+zd6 zL?n2VIZ|zrc{Z;5Yva%UTAndExxs$MQ_IiSPw9Er6~Bej?b_e^4jjnr>SR#UD%z%B z)0FnxZyKf$w|bfJoRsCHl`pvNG`^zd*{<=g-$r}V9lJf*e}xQC-6p2_vP`Yf_z`39 zBYf#G*PD&!jL)d~wf7WUY@f41y7)br&*BS4<0p*mpD^zIJNg@Aer`YCoZi0rdDApk z>T@dma0q@_p?|ji()^&tXvO9!pUd9s*k7BV!rWVJp@^QEfcmo@*SM8^phaZuX+aFK zof#5Zx9>dG9kZs|ZtCl@njfg%{G9cQ5X3IDeh0LE|AqT!eEFBnU#4*Iz15cuukPRb zT}PnpV`}a%u79bp@r_TI%yj1O?lm5$e$PmlAt7XgdfK;}2{q^4Yw}6-K3R)2{HgR< zny;y;+bc2zik2G5_Oo@~lQuPBp&Hb*iA1p{(7uy>3-XzoXT2rWWTu_3YicvBHwLOJ z#^;$-dF{Y+&(Uq3J`QbvY7K3#e=}_x;7Mg?r~w$CGG%U!k5`}n2vy)s*9V~O$1K{m z<=^^NG-tks{&#R}!DFUOb>M{;jL(P^`9$X4dujjl51F@_jm`Ot3CXWmgO|SLT-TaI z<{KH9Eq6344-Gdrpnl!eSE{d=l=`c_Vo=k!fx_q4n>q7(vJXofG;@Q6*%{WKQdHaR|c7rB+v1EvypR7Ca1P8M~TxhVslAs%dAark$ahc7|%&8LDY#sHUBvns$b2 z+8L^8XQ-x~p_+C^m(B&hlCzfqzd{Rg6Yl!=H?j@Hts%!K|snH+h(I_?gqtxh+ zQlmdgjs7S#`lHn7kHWW6YV=2`(I2Hof0UP~8vRjf^hfDxow}r2w5G1}P*r14rB|+} zE90*?Z>KymKR+@Oi^t=`!&|owk5o@mF*;uu8!Jdv?e0QhY_z!9cxrfVZg`k4#fL{` zXGh9B7@sdx#tU0Q-QD5M#j#4E&>e}8o8)y(oqpL&7(ZO&m%PLEF4u=$kGMYX`f@`> z;e`C%%MLy1`aP)u`+NFKiC=U5iV})mYa>+*#uFiVpX+VvI>Y&P^7~;9HvQ z>u+=YJ+4pdqo|&Snx5vgil;e>>S-jz(;P+hG)GZA%~4cOa}?Fn97XjsM^Qb^QB+TJ z6xGujW6yK*BA||6Tft(N?)#|DELWFBlS!nN{C)#uW*tYxu{``a1B?SZfP=sh;3#km zI1Zcuo&cT!ECO*sAn>J>fo|kQAs_>c13Q3&z!Bgma11yOoB*Bxo&l^@e6-?|R^({3 z9PE+PO|ruZ_Q=5=IoKlyd*on`9R1G09y!<}2YcjTj~wiggFSN6r1WKlzO2xf75cJ5 zUsmYL3Vm6jFDvwAg}$uNmlgW5LSI(wzO2xf6<&XmTGoeLD`-RNpg2PR> z(#Q>}-L15{tlM2?)XVm$ml^dkqh4mz%Zz%NQ7`kSac~Oy@P@ zzZEi)YX0xwx|QoMab4#6KCY6``|r4lf_Ru~lIth9=D2=_t0eyZ4p&*WJtfOeT@q@}^u&gSXXdsH_rzj7!$ULEBSXd)N2X_n^aoq!W`>7id`aXF zlKvj!N#h|gENi|Ay$e_5o{tWrgXOFMv5>?!A+p@|O{h8{_$I>Z4wy5Ztp3289;i&n z*>V${0%1H<{ecnr0C)DMVoca`)!^!Jm0hoMYMYQ~n~%wtX)8#6pcd5nZyKLG2IJHqJ#W zpECuXSjS?+*oR>lU&`g@=kolUo6qM?yOPPF5iwwghmuL7aXu%1kU!_<%gMgJ-qEq% zWHJT$u`lk2r+>m4T6`<4~-6<@}Ru+tCLR zYK0Mmmt=can6q8Ft-nCYVawKNv}_MQzl-1Mq$_CMG2P>paAT8ShR~Ifb9Bm_Gg1+c zC0l-)%*E{1NFgty`tkU|=;GhO|G9e+!l=vU^j{l6i_sJ61zb* zN}a@Sv7)Xwx^Cn8r0Yh8whW=kLTpr44@o6VL<)*wuS|_39G*l9CXs?kq+k*$m_!OD zk%CF2U=k^qL<%O6f=Q%c(w2frq+n8Xz8Dro41l6-02GHek~zl7NtCMPzh#!cV7k?`PfF*!B0Z zmvwC0cK6%2Ei5j)ZO7kbE2wKYrCk2ayYl&BVc+Ku z9EkNyW-?QyWN)CrGBi{yCF1c!wgDa4*4)z8mQ1$EwYjk|7>zc0TD?t8dt1HnWM}7Q zc&7>a7&5olXlNf)^m^CNo4QJg5QCy>H&QC}d~i-Rkns%xRD zgIuqdJ(^LaQ%|A@WThdPJglywT;=x_j8VBB!ql^LKP(^r+4g_p8S~C8why$CqF;# z{N)+>lux|gJx%$+g@ycJQzU-V%`xM@ZM|aOw#EK*x_^;&x6Sj0%P&U4?u`=5EJlwt{@lvdUe8x87`g z!;;U3Yn(Np2tS7O76w?rTQ71s2It7~dw}aGS2?I~7uN-@R|-e%;1pwdAP1Am)r9Ee z8nHO-pldI`D~gFgy%DH40`*3CFaq^Ppxy}78-aQwP;Uh4jX=E-s5fF$Zv^>`5u_KK zIa9dQQ#PG z95?|y0Xzd({J4X@?2y+>U-r_My>?&r(wDvTWiNf%OJDZVm%a35FMZidU-r_Mz4T=- zec4N2_MV>y)>&5TE$_8@3Ei?rPoY=j^fvKYX5|lN2UIvSb@}B}GeVF0uKI^g(q2R7 zw{L1{+_Y(vyUF8rf6Q_Jk|&>E*jmgB)!!61PR;CM3neEz_s`Vs?v4nBN1`P7`J#^d ze%kXiZ*sfcEzNGn{TVP0h(RTTf2Ws8YxP}yN;tQNXuanLP` z1Ia)ai$x#3nuOA&OhiA|5Z9bOlPkxWT$bm=!%YN`{}aG6a0s{=xEpv7_!#gg@Fego z&~_bsqf6ImcWH>`WhZmw6wcVjCStFdEGHmmvK$1()K|je70$=vq?j=HCUE?%QwQ_rT%6;Q}8Nt`A(F-oyv#>jK}}mLD9jTF9gNP>1=PqvDS{#%)T|WI?(`SCg)sJj= z(vlxO>*Q8KLyGMy%EhOLB}atGu;N_B=g}*B`KLib3%|JJn3g}`DRr95kh2gu8ld=``WWloviHJU+j5p@*i2hT^eAVr*<~ery!)I!ov2haO6v#I3%$Fga1! zf`h%MSeTeBY`!&iUqd2Z9-f^qm&F(=m*;1P%kf0ReX$Q4zge8DlnQc)U!hc)EN+Q( zclXE|;uSf!+k7v=bi(xmi*9=uLcAmg#!FS7lgOMguFT~me<O(E}6$h#EsE`_{HA@5SiyA<*+Wy`x1@-9X9>K^kHt=E>@QriF<*0O%< zI=s@vN#qTw3a11@>Xs;SV8?i8UuS2cw>-LaZfs2Imw=Z&vw!&fX*n%>^(Qq{NYq24 z$9T9mKR66VC}I%@BMLW#ugX#0r(OT$bPX4U8h)Tm{4_q0vuwWLN?UPOOOjX%I8Hi) zvglx#JOf!=rD}4NU&`_aZKU^SjcNa2VX6AyQeluYs^2h?N=3p`#rL@1K0O@@6Z3gP z_V4a04Yx$2EyJa|u1%#D=Ef^KDj$B=($e^BWwAeXt+PMh1P`CKs6r$tDm9(v$uvk#(|8&rr$KTWB&R`g8YHJdavCJ3L2?=- zr)?ysL2?=-%K|m-N~;CZpmley>EXCbOZm(BE5(wI`?(apK6JCIHbr29NanhSPt=d1+6e-|WbEKYB={LxyWVbB}z;As^ZR z51cK|=L{In@O!6h5Y0l0YQdLBk0I}Dp+J+AMjXjO+-pq6)*coXt2U6QYp@H6f zzB}64*`F$OaR#ILgs-c;r?|N=Q4S7$Ggrj8DdS2g9lMc?Va zBJTTwYqM*Qr9I+!5v9+KDh_)R`8H^IFZ%dPT#sT4J(k)@TNptfBk1E^YS)OK7+@r6 zb;_uaOxVBa?1M4^PXSp2c{Y ztx#jRY-T(*QF+}mH0BNzic7DnO!UOFsa-p=g^0U5pIKha7mS6<{x?ii@N?|$OI9Y{ zaOK2gZ>%)fKQKI!N^Hrc#8GIvzH|C!_~)zeV5@7pMU87~jcSju7$M>vq%N7ZK*SSj z*L}1#WsC3=cqlaG0OG^LpO-F6*5(2eg7G!IoRC z!*t0aNj{L+kl}o%n4$6qiAKvGMO%wqBk$fMMVl$gCa2vPnP-ju>I;S^lX-W;Q0SFU zg&Kxw(w$Gc(ErMy6RKUYcW!jz`}ee_)po$ckmpD_$@ zKTuM{Re-n(5LbbI6(Ftx#8rT}3J_NT;wnH~1&FHvaTOr0f=yfnh^s&+!9-noD3a2I zrC3o+hq6@Qt>|;BZ37w}6}e|j1!N0eFetkV9t?WD{+5AaSI`K#lL*27SRycxx-2y` z&==ZuWHggAe%Rsb>5=dD^!PefKhi(g-_sND_UGe?&7DT|M{{$JhP|m+E}5(Jr~JOY zz^*_*WcN>ukfObM&X%^Gnf+P~8){fkg-#u}`FQna<8e77^Ln1sXKmWKUOL5M-E;QQ zn$;u510nT3p>-n!rPZGk!NGHzg#D=BLV*>^x2Ur<-yJe|qdX_{`o=ZS)$-?bNmS+< zsE5pP=*0)_fn>d^A*nefcbZ|v<`=$*V(U})t0_G`YosbCC4)BgMkj z>JN>`uELg{Xm6x1D!lc^)7Kh5Y516Mv+J-mW@^}}F?;Et<}od~=JZK3)^LdCbEX>D;9@fp$F;IWmO=F8|5%Th^)U-PS;L7 zFE`YpJ}_fbxLkQCpWC@Bm)nxdRqx%L63*rxa`l!8S^(AvW39yTfJ@d zHvQex5pvG|43u#npaF>4YdcK@ye_+RfZ_19@N)*a=(%+ycAB zgbY_PRJBc-6&;#M^+P4<_yc6LF09fkwdCqGR^VWv_^kt>y?5T3SXhY1#!|hvcD9gt z;O}6Iw6~A)l3!@Q^Uhdx&iG2iee|+azwfBGwX2H^gm!;dtM?ww52ow7(=Qu;Zq4cH zd1tg&X0+Ea#eMQY-`XiY-_-Uob1Fxyi9v^fEKmWKfUALc8|v}qVBC^5&mnHJ(9=F8R-pM zp_O^bhVT{e0t&Xo7b)%=#jvwNO|!0gTzEu27}4bAMp_H!df|Ox#vr(}6{1+Cwu@$# z`+iO+3O z2nW+WfzIwgM_VWR)Z7sx6yk$G*w@+K8TR=a9=&IJrmt^gxP06G6OX@X=gw^I;K4^u zn9moJiB>N`oMx78X0f-|+Zy(HJ^b>aiOTyAO-w|(PwcOLA(!2~^OoPbN}uK4Kzzex zHZRwCZ|IB)3mbWv{JaAjOY*^z#)h53*lA%S&+yCID_q7aT-GAd!%YOp0TaM7 za0s{=xEpv7_!#gg@FegoATGEiT3gYHi4v|^K}@V5CRTX+3SwdfF|mS}SV2szASPB2 z6Dx>`6~u&rPQOj>J#XJnz1EqM7-yY<* z2l?$metVGL9^|(N`Rze|dywBAO$OhkxgP>nD-l|W$i&e~gjOQ75}}m{twd-gLMstk ziO@=fRwB}hL{TbGVFhxkVDnd?!U|MafeI^7VFfCzK!p{kumTlUpu&nxg%zlr&#xkO()WvRxP&CDz`Si^(Fx7Sid- zkyxVPu#~ux`bN#oEvXxC^xb*q>g~B$ylbStrK_*Elur+YLVc;+LSL+-BN$4id|e&w zQ@vYC(}hen6iFsWr-lQe?kIAUs)lBS?0u=J+v9qpQ)?MQriMdFIg?2u%6QWzvIrts zy0*&tGKLV6HLaL=CLc+^?9FqI-ClZJ3HB&I?>xjk7kE}kZ zei&1=)lmlyRN1?B&*_`Z{)WAbqzQ6~yWZ>^L63}}$JVMk3?oKzJN;@GJ(r+!VIaYW z5?zqa+no;cnT$#n4RS9gs0>P(py8%$8tAeHKoX3|D0G@s1AZxVf4lJzKgf%AuI ztYo~B>y{*`C!y_@0q%AMYFTNQ$p@D?aCV1$aEIfAJLQ8r9Ur_$K6sCwB$GK525-Ud zB-a~|BcdbxjN8@`Cii**pLB_|Dljj1a-Gw1W)3+shn$&%lje{!bI6%FrwqCqX#YAsy$)OyxWA42-CXa} z@4Sz9-p4!d4oDLuGO3$D?D4Jf;LLz}bkloj+q z9HA^NXM}#-@_wstii5n%ihxWT5{-3)K)U)#?sZ0~B#92`cD#9aG!P61+S|K&+S-k1 z^RB2U*Y@_#XnTioou{eCGcrDUyAj>r+A=tJ{dl}R*yITXV*%p>6UBTmxc##Cy=`pV z$|e0i!n>O~{C)YZu15Y<-(nbkpFf(4_&iOa;81^?uSw4CXn^j-#_}m$ zex>nMivz{ZRa}@6hQZP~3)6{JrE@54eJ&RCu-50p=<{Ky0h;n~69IC-1h5Po0&WKG z1|9@H20RKp2|NpkKEI6C_Gy~m2hHz;=J!GK`=EJAaoPvX?}O&|LG$~d`F+s*K4^X) zG{4WL`F+s*K27suE}dgh*XLKj1D8mjizXg}bH0bc`D zPR}|`Q`6^=w)_{PQ7iLavq{L^smyD!BYf@>;*fM6W2F@zk-bX%)jQmGbRLZz{j=EY zo?J2B6YCk793Sq__v4vV^pk zlilxrx9_2cR{wiGHr`?xv>h=H`Zh%pS8CTm9X&zgHh{nlotP56!!o!RBea2gyQ;rE+(y1*pXMWk7LxDsnRZo3Xc;FOns{iqj7e&gv)H9t zSXee)%1DLp#|ip6)13%~OR=aY*fY@X-BkT8(~V{Nt4|rfy?TgIdlN13*t~h<_s}8i zslXTXLt-NlSJr5Bj<`NRYs6L}xf*f1V?00 z;jdo!s~7(2g}-{?uU=cu_rhPjBIkA7R=lV-Ipk>G&W7oc*EC`!hky()4(tF90!M(O zz%k%BZ~}M&cm}Xm?mbBA9>vB&Ov1jBSuWQwzl(7#_kCD0WFn~rd|lSEsAAR51X-qv zU&&qL>jRs&48LJ=Dw!;g&Zf6yvZgy4byvR-9Ui{w(4lS|Yqx=QquFKZqYgEuP z>cCgOe9*7)wSJ{v#DjUhm3}|Np4IrA1)sAZW)@jE3qEJT=PdY~1)sCva~6Egg3np- zISW2#ZG6sx&so9eNo@BFuRDo*VOYIZax&$lCAp4q9n;bKN#vn?brN|vi9DP{9!??; zCy|Gf$iqqG;Uw~K5_vd@Je;)U;Uw~KQprPUasGnq=#5Im51e-uYz%Awx93>Wx-W!l zw}tCZVvG0TR@iB}o!r?b$Zd0YnJsp$2LUGJ8xszCuokk)@{rvhf`17MyY+h2&3ct! zqF%4Mv1HunMK>+DS+BZTuew>Ux>>KfS+BZTuew>Ux>>KfS+BamgdCQ2l4TrTejS#h z?QNQdwn0PNprLKh&^Bmj8#J^H8rlX8ZG(okK||Z1p>5F6HfU&@O+(wDp>3LmX00SI z`;2Tq#--3yg;!efh>PDIFYC{Xj!i<(41MyHglLqtH49zOGB2~x^(-?#3ti7b*R#;| zEOb2!UC%<-v(WV{bZxQFx4HQ~AbMU}Tok%i#wtE(QRd3mv;KpPYLmZXe{oy0+tbqA zfn)ixHNRyxK-l*aE%h`uw`t$B^Ow~xU5L2r_L_Mw_P*F_?M~`GuWcYPc|rXD)^;2D zU}GDVQc`Ixv;mCW8;h-e<3gyhb<8Pz!h^2KwRowg2ApO6)`k=iyvY7aku9?00bf`T z#>FuFKMem5!>z;c|1kVN4F3;0SOOI0hUCP5@5;&j6M^y%|2+toTgA0+KV7 z#SV~bp5OEGX70=IpN$jQ2de{>8|63S_nT4!+qb7uo~Vc5&uDMlvk^9XZmP(?DR-I8 z?3v-!doF}OTW|bG?e(5;Eg8q1oHZ_5Vcek=2IPYQZ6)ZP;Ii9RV{(gYyTarEgP+%! zoClNhU~(Qz&VxzWusaVX=fUJWn4AZb^I&owOwNPJc^i}SU~*nd@@ci;l7c6CTc%4b z_(gR{_(5UvqIZcaPvS+#7r{A0uRsG8Ey<^$uxTi4nmL??!lt3HX(((O3Y&()rlGKD zC~O)En}))sZ3>%)!lo64NsHTrlGghu9lLSFAGu+=dN3Nz<)Um+ki6kqnPUGg;Y7l8ptZ-xN>qcwqJ63sIWHuk@OG3$nPM;q5XWanjnPkk+Jw z*0S zo`Vb_i03(o=Q)VyIf&;ui03(o=Q)VyIf&;ui03&dZx<;PheqPiNSs&1p%IDg#i5Zn zG!lnK;?PJO8i_+AacCrN(?}c|i7OfzXA-8Mx+zU{bF?@|i*vL%M~ic`I7f?fv^YnL zbF?@|i*vL%M~haF?c3aZ9}xRNTHG#FXGN=Rf55yEZgQs3A=5TYc(eV%fTq2+w&2@* zS&Qm~0)J@(l=uHOMxuS1`r=)|)?JCmEYgcZhO7T@Ve-RrQ1qwvLdg1cjSX?$^{YO~ zQ#QF271jNe*02A>YgQ|Z!PZ$9^8cK)!Wu^Z2C(;nj$Pb)Av6h&7Hr=raf+=@32|QU zsE$6IWYf`r!qFx5lAgqiskIMjQA#rC$b-|Dskr$PcuKWLR7seN-Z#8)zp9>M>ps|kXoG;(1_`T# zTZ#|FJ+BM@l(Zfwp$AInff9N^$}pDD110o82|Z9k50uaYCGeUnx*(SUq+j#%2wt_5aIe73{adrR7=_vWE4#6Urh3pJ3gmv98a%m$Di< z=-101H1;JvSkTxnfc*m4FM$05*e`(n0@yEr{Q}r8fc*m4FM$05*tfQHJkQOGfLc3@ zLWHtg#P+|B(!9OnSGg@4c|i!s0OP<8;2>}WI0_sCjsquvCxB-FYweIl$BNrh2R3_Y zS?y2ZnmPr@o4MaCx;NZZB8mM~`4NpjjojFIL+sY}o833JZ)lZQ4czWa9`4+`w{t@} z)Lt*taadf>x&ORy_|w6i9V-)D*;FE?sKgM@hf|5KgkL32Z@#nh?r*#*l-?e=b!vks zwQX{CE5JBIu6bik{nahPe%R}^2y9F~h}kx|oEtBtqjZ7I40V>oCYN||MRQ|?Mk~;J zMg3pm=heVXz}tcM0v`oF4SW&!W5ANd6}YjYWpUZcL)i>_YMZ>^+N^}W1gu-d9r<14 z$qxa`7H>r=w<48Wk;<(|$wIJ^vpOHM6a`8GG-2ZY0=#Yu-1jZ{|o zQcbUt0*t$gu~;CI@QnJq6IZU?Mjbz^qE$xPuc0)_Mp{3o>(r##JH?*`HTH0;X(r##Jx2C0~ zn&F_c)3@PdkS@*hnnhl@sBymp?w7#*61ZOi_e{Svre0{2Tc z?w7#*lES^Tc)4g~l{wTa3#qk<>RXIdhW{+xNjG|RQvNZstv%iv%XfQ1n@CShzkMT3fo!L|3q}wb7v9No#AQbAtpUas<2G zjtwG5mYtUSc~@6n-_L6I)zk z(Qh=+xYn|_?Ytj0gs}61Xph;NohPX{%DOL#{3tr)N8N=l`b00a28d$`qViaLvWpYv zElf40rNJ1|U<_$6hBO$1=f;o*V@QKBq`?@{U<_$6hBO#M8jK+gtmMJxxp@&#cHSJ_ z5pS(%ZOQzZqj|l{$#O+E;!O?#8DJdP0UQL507rpiz;WON@C5J-U}=G2B!Rf1sRcvp zb}F=7H}m^uc{BGFZRcexR!sjj_=yik4t0LvwfKq~LV;U-1Fy|rd=}hFYSnAQZNvY* zxK;MvYsT#>S9Tuy?rTHs?Y>*bUnOd5I)4ZbOkvCIUMs_=glDH5JX;U%$fh5?CqQzP zgl9*H0*i2!WU@ugv5Ro*A{@I2$1cLLi*W2B9J>g|F2b>kaO@%+y9mcF!m*3C{$7M* z7lmU_qOHXNCw^JIE}x{WOSdks(AjCYb6Q&g^NeL4-k*o}=i&W%cz+(=pNIG7;r)4d ze;(eShxg~hCo=9Vx*Dsdgu2)MfbvZ9K;uxjjt}_;#I)9JQ%N_^4)Cov-2CuadCEHyPyWMrI zqMKJ^Bg&?T1DeJTKw}4>u>;V>0ch+1GE{-jGoad zL&LGyrlAdZgwAb3Ch`fYDQDeD*(je}J#j%#k!?#_F=f}zwQ{H~b}WTccMI1GME+Ex z%O{Ec>THs%E3If`ip7x7NIeFuY{^RyVb+mkWlJt#OG=!IrklCx2J*lpuoJijxCM9z z@IK&S;4{Eez}En^)8RE!&U&lzwZ*5`+Lx~~SiRoPlsxLB86c0k=$c*|DX6DSpM^$Z zu(mLEI|jBOO(F&Cs+G)xGV-I0{3s(o%E*s0@}rFWC?h}0$d5Aeqm2A0BR|T>4{Oo? zJU1@_N=KC}^EE;{@){j6P#DaDL2>>m3}(S#77S*=U=|E!!C)2)X2D<<3}(S#77S*= zU=|FD3sJ`#3P_%UmOLdSPYKB*m32x;o)VI$gybn9c}hs05|XEci3_gN*{J24f72_>Cg<>fRXT*E*Xr~pgA)xb@_+ky829|b-Qd=dC# zz;YUH21AmU1BS-IP7tttOA^qw9kq%O8o^}<$N=NO4&Weg1UL#D1C9eHfG2=w0844- zVI3ia`c7A=k`M+%vZS*Ykn&mjl&+vq0z+!&j3s_+q16jxX){UO&6fVXFs5`a_d~B5 zU&?PI`CEP`N1ZK&J8gMmL(H1sO+w+9IBlyNyxH6&ww;ezlv52ZUD{SBEq2mkH?8-8 zx6NGp`Ao`%$!C(rC0CWgbuQ=Jg>lzlXLV0+9D!DH_m4J+LW!5dZ>HEvqpn3G?!=$d zFe+L3uyw;|tHNK1VNPqwI1S%T!*|p0-86hR4c|?}chm6QG<-J=-%Z1J)9~Fid}n1V zJE!AaDdY3LFED11EqdfM)=U z2kl}E^_}_hrZl*eC0Fs3-cTc-mARMetnlE4&7TWn_1QC4T(OO#cb&=ezy{H5%T@C+ z;xr?!ZARINXJ=@9v$c@2w;CtZYDPQVCpDfY!Sf_|mc{cVc%B5$li+z0JWqn>N$@-g zo+rWcBzT^*(f)47Hr~}g*dWqv-hRQt`I3cmDWK5`C~w=@aIbMLHF!AQsg@q` zQ^3CMhOw|Oc8?p5cEizbIGTkVa1b~G90iU6$AJ^T6TmZo#nEoGs%$8bVtWx+N_G3E zL<7DvFP2Tn}RSFuS%|2i6&gRvl8zhAq z|4SX;^Xh3^Ki5;%{*~zIu5+cYU68B4YA3QqHjYUvpH0$UHe?ywye#_{#4cWAXPwP1 zu5VlT*J2-^Z41K%ac4aKlHuJr+GJ0|cU~>dq~6X}*FGcd)VcL6my_5&Iy?BxZF75+ zjU|gU+aNNzZ$AYfz-8OqlGe#f=;S4I@)A0EN&UZ?pEm(-2i^;O6!I(ZSw5{JVu3wtrmOY$l#BDv1XV)Qd>! zMI`kil6nzIy@;e8ZGmlk z1$6$>GDdXc$Un#4y7rCI1LqX8d4E*dVBM~Voz&1JzV0sZb=T{k`9P_lLHsLz&=6#T zLQ?S3t&+>PT_SB6lBzOFTS^GiFNH1Fl_U;Jsc5NyH)iZoMJsjEX5mzI?pr6#$8?=a zU1a1N)wx*AI1yvdlo~4q8k$`MUh7q+tcu{)NSo9M8yKNf%iY8X8)bwBhJZc_y!PfB z)!Qf$YrOzpfi<7XS3m-#^DWL|Vr~*iWFDJ%i_{8h<}DP+mO{32LWN#gH`T|ujqT<` zdTcRn|6T2kKdtw%LjO0``?&p!_3u+_YrkuobA0szD^kEhCQHm7NvFcJW1LcxqK$F3 z>jJ5Av;v%!r8p+(fOUNSX!l)eiJ8)`A6R$~TZ2#99kA(Do6-C!et%UMv zYuD1*q?Q@y$Nv>~BW~y#4>u6U{l@+@*l0Z3_6F8VDa<3iC z2^`D`2A5#bmcYTBz`>lr!JNRsoWQ}Hz`>lr!JNRsoWQ}HpyOb%COsV6Msw`M4 zy(~2b?R}yD(fP|}jm1w=htRp#)A7q!ZB*KFK<({pTgxX-IiSmy%OqY%9#F2Q-bdpr zFTHfV?#&IcU$90>^;kzzN_9;2FSLqJ>cWA*J}mYa}&whDlBQF1 z`B6k?F0=(1sk6orRAJUJ<4)&Bg7#C-F58gW%h(6sgo*lSAT5?O7{_ zf}$^iT3^^gyc6OQwi8FcglEEZJR&UDrF>&Z(z*?7ReP=X|C{!v-l(TvZ}WXjBlMKs zx$D;H7(cSnY0E3||Mj_^c4E?5`^&~)fFX!Y5whr8_|!-LBr{QJ|44eROte(A;h?lj zjagsKCKbQElzftcT0UNuI15(0q&~Ac3{6>!{I&+0Z709;8 z%yk4bE&5p|IZoSkL{?LBwu*6gCLCTP0S}r#F#?`wUn=Pkc{}HgugEd!VylZDVeGxN zs^7{tShv+JEn=NKsI-V(#npv%C}b@04l&|IR!Lc?nAB2BGIl9@D}PAx1zE#LSgldd zMIG@?lwZB5dMCTUcf#Gus{T9vDw z8(Qbyo_X0!AhuMuIw<`DdgR8ls}8m5IJ>$qq@JfaVf>hcGJEHP6pxdd_G%7I;e69k z&$I5VVXo+$ORAo0+r%zHr)|k*A$8EHkNJbDE5@F`H}*XI@ct*CG=5|M4`M(1QN(TG zPu7*-&%f46cdiojVJP(@6=aUYI$yh5(5s{l;pj~?EqL9;7ez&jdD|{sI+NRW)Huv& zaqyJsTB*Kl{678a#)$Z&;wstguSH7|Lrg@BS6TniiWHg`cDI#X#(J@;_pIq92ED|f zml*UCgI;3LOALC6K`$}rB?i63pqCi*5`$i1Hoe54mzbg#TWtzm2;B&xM6d+ai4NID zd#5tRPN=vnCl$%DqG{XuzzboVNDr3YSV{IqyNCDfYJ;=7N`uuu^t1nM|G@U8{_4w< z>6u8Rzc0`=(p@rMiLL%)%`bki79tz&fcW zzLvTNLe9y;`=D(%aj%JGWs{4cw+XegK5B=Kkmk+Y$YF(fU=r8~Tm#$!yaRY2@G$Th z;3?p1fGq6VX*DdPs(Bn_i-+3RBSk(~DH+<=ar`aQn&~o4joiM+kr+F!#z% z6M9}c)j(dN4&=3vZ?u%JJuLk zjMuVD8x|C8$6cRrQk!$dXP+r~!IfW>$*~qUQAA7>5feqkL=iDjL`)PB6Gg;C5iwCj zOcW6lMZ`oAG1h@6-{$7~fW)6#Xf3KdY9d@V$;j8ND9^AxpL^-0n-w`bC!iq{yTc;v zbyFbtzR{xbb?aRxzFQTqt)=lb_&(Fi)`fnwsBgzhFV%_eaobL?{L~ z&ZkeLIivSu$o*d9-y1!#>VJ7d^pO~3_K~KU^Lz9MS;vR&DiUY0{Xyr6q1c6oeK+# z%&Th-{Sg{(WUjANW9%Z|@VT>((h_M_c{XL2*~5;gM4R=))`pUZU2%tsl$ETe4nWL% z5zFfIEPHBV))u^0wf9L)Q2-Gbuti`1ffGOk1`vS(L|_0B7(fID5P<PV}WI0_sCjsquvCxB-F z%l%_ZNNwWDL<=3twS$Qjuan%%vEmNSQPM#U+0v1KY=Gl|r$1-7V%1-dRi8z4Ozhgy5)TDiT9^Z9keE(PP~kG2rvUxwqjN1o!}w^=_?u^DlfC1k+otmQ z{y_gg@7P3XOSETV{Ed@6ac}EPw7@A86+-lmyWOAq{1e!JA7$ikq;T%$2yQ++Vp-=wln^nPGvIU2uHnwTyOr}~G7 zuG^B%hQr6+Fj9{8RDavZWwMid3Wd)v{(Lf7EN05-OgOk@OM5gF9vNOSif^8s=?V;F z#@=$7+cP$P^>}f!v9h^v zdr8C_sP|ZnGU2v>^l9vhW6%e7eLU|2yFRe%1G_%3>jS$!uLpw;<4^%K0ord>8a_d-trsfiiOFE zUmBii@Rmw9%{HXHp-SqSeD3ny|EamRx3p!VlFo#J*S_wl|LfoZ^Or8qq_^)KA8-CN zvaG>1b$XAPFmHwL2ASCx&yE?30iEtXFEJaLLoegA)xjX+m4jq9pL)_Y$aRY>i6)o` zYF&FUIhoBE#=70YC>Pg#TI{EoEMJl)5|O)K0jLSO(f!@ z<8z6*@zF#)ksKb~nwTCeg}S>Vn+sD949-k$E_O#E-7Q9MY;xlAy_Jbr!g%+)jYMo> z;_}NUCS$$EXU%t;J?w8L%*ufdr3EVUF1+wxA>hW~+Z%3>aW z?eHKYF+X1E>x=bNB9%yYJU%ou&S}|~Wi!d%$?DH=E2Ou~h%Q>hp=j*x1(D;b9?V*Wa7h z8+**(furoRhxNIr%)h}GR1h6o5~qsCtIcOBmstgEw%4L(%K1>uIQ zF)&z+n!oF={)6SO`&;8h;}-J~JYhd~7OGHTX1}-vb*MnewTq4->3D*gWUIJSCiOoz z5A=njSx?gM4}}KJM~q-sJ|B+wLhYS_Xv{}>B*Rtj>%GptT3(&{IYrJGx$`W#9`2F+jpLgN?o^RErZrt3fR-j9OgHn4p6+23qQs2b zUz%t^4doWXS!8Gb&upw`yEok4w(z)&^ARHy9GuRF+5=s|*@lQ3wdwi>egBBn_xCyb zenuVedf%#M<-hze^kxWHizPT@p2vGEQ#2pj>90>^;kzzN_9;2FSLHJAt| zImrhS&=R9bpDfX9fU7MF?Hxhy_MTXFmyG6E!)zcBYzyV52ZJHQv~Z|#@hktFadBc` zFtQ#Ojb9KKfWpN?8^VQ?1C_1)zYG`9RmFjeBZHB|I$U^axcI8^EokeLxZ{gPgOj!_ z-%UNq#_E3kgF3N^8K~;7QB3h)Xg_@(+%W- zNnj^%4R8za4&Z&j!@y^Nr+}{k${B)LB90IFPlX>lm`AP*D>!T6)@aw``>N62-qR9j z^R{`KTKkNM!Krnvoo#J_CU2k7PnD-&sMix~XeAw7fx{Qq2S|ZXFJs_exad%y0e|k=g{23%RhEX zshsFHbzHrr@SL0E>YL;ArU^-#xMZ6f*H;bD`GNUU;#^V0f61x^h^n)W-vx8I#GVm(t+Ju#zbrC6Wm#4Ypda0< z+N1pyd+=B6VfD6$RrMbH6?^bk?7?5L2YvP+J~K(K~T{8Nae+Zm6_59R~}g!9yjid z%uF2|E)IqxQ_}|)OIeZE|9W!zf0i?C^NYRLQ1Q#Z$eihVjO?eEgVNx-3V*TN`~Vhn z#FbvV=h*2n_8_-JN{ffDPfOlDByS&*w-3qNhve-;^7bKl`;feSNZvjqZy%Dk56Rny zqNeFBHucZZ=JkH$NIc~D(S!4Q!EF4+CF1hd|awvk?nD=heow9Ovy3Yjv#*1v_q|){ymxI+_t@WFs2V5k1FLu;bk<$0x&2R&V~w z1D)o(RzFTEQsxNd)x|%p>0k$R(2E1p&dOQWzO3nDYJpX>;zBZ&0Zk79=pg_-1fYii z^bmj^0?kk35$+trhyapnB)|K(=C~#$ zQ)As8?@jBDNeQp}yXj`yZ)VmfZn|l5qI$>3$W;eMhC#rA;gP>`p4Mw5Ke)1Y@5%@N z?Vigozx)I5URqk(@o(;5y6m#0yPU`L;&0PC-A6)psg&$n_!G#}O_`!uAT=>C-A6)psg&$n_ZCv=lg&$mKKYu_v+op=CD!%N2 zFXgOPWq0(zQ9bZw4}94JU-rP4J@91@eAxqE_Q01t@MRBt*#lq7iLqc$s^N;W%g!Wd z8lN`UyJma-!^6eR1EI~OEz^fb%U5oHYV)3ds2=>%frI&+IT#M*1Dhk=!@~#n9NP7u zI~I7?wTHJ~RxHRo?Ku6o@e1=)as8!po~*s|__|I~JVRHPjzHw%Bxthy^?h8MF!Pij zL0o&{M-W%O8uLY$ot7+?R#!|#t4!^vHY%Xflr`^Jp@UCi7@Ak0$eI zGLI(nXflr`^C)gN9aK&$vnw@b*wkgexpuHZze8WQQi_D(S(_RAG5=Fpsa4?@+n#pGa z?)Jjwg-R|T3UOjkC>siQ!N{({>62#IoQ4;Cu00AHl5r&-8JP?5%r0ZMx%XFMA%4_{l3#Ea30ab@-Ep?tir@W#2B-rlj{xxFK!#XxeR zu)8=~?9Y!~xn)aBYuIPxVn6Y>G>??uytrj3N?a@xN)ka1DcODi26@?h7^)ti{?A99 z{9!GP>khn#2(gIc7)eX2skK_4OKRaEni*FVf!4$Ej{SVr2XUXIaeYV92!FqXm7e6F zx3cD(GMpnt*c9iK;hZv@Q-*WOa84P{DZ@EsIHwHfl;NB*oKuE#%5aX9a?_I7S##*p z9rqyQDu+3j8NR)I6$RO?rU^?#$pAS-zz*Qaefkt2#BHTkS&DM`_!|my56r-@Asp-tE;QKtDmWKDoH2lgr>=h zkc0>!LVhq7ykYNx4A|l2R7(@n{7@Um5$7EtaM!6VcP_C2lg2QmVKITJ*;p6i% z4&%pgvGV=bKIc?dcju*P)a#}5TRFQ*_V{;>&m@; zS)b7(1iQ4dzJ)(46|Q{WWtVN+`SB~>zy8AaN5BVYZTp3^7xKm;WEOp?nJ!h|)g)ae zh%OV#_M9RTb`mccWXl9OQmhCa-a(QAP6{KV@&Xu@)kKPB6l6dFFbEg}tOaZV>;&ur z>;>!x90nW%5S@&o#!+B1fm&xP_b4R*J|p;tlZRS;YgTBSmR=D_ze3#p;H@MvSu-~!FEb$hTZ(qb@G#CS}`HKUA z|HtL-tsxlHiRlrvt?x*C4F>+8cftHn0)aigL}=k)Dy%me{rEA5lqq=B(VvsK}=c<@wvoa zS7(p1e}mFT(*B&MAg_`10lMhI2zCI-zfVCQ6nZ~vBn+$;?FlapJ9lK+(sku* z&gbtauIlOT>oRxuuB`g|moGm6De=PYH|Zmvsq~T#i32Ic5*JiUiXt3WgzY$l?KqGZhwvGPupNi69fzIzu+Xo5zHy4~ z`!LjkG-QM+z63Qt;^aCssccge8tw51vVb(dLJPDT(+^tkgBJXt1wUxP4_fep7W|+E zKWM=ZTJVDw{GbJO9>;mis z><1hM90R~Liuf#06)lWKaCr)pYgVag;mn*urEV}-;=7opCsC`1u58#e>f4adwJ zu~+XmRH?T@rQQW|M^(_0qEkQ2QjJq9gq@>>M2wau(KkRh0wi;QyVd@ql7HpcoG* z#siA+fMPtL7!N4M1B&rz+`|Kk@d%3X9jE=}LRF`f>q35rgR>KF5u7~^&K?J6kAt(v z!P(>B>~V1RI5>M8oIMWC9tUTSgR{rM*@;7)OjS3U`g@_ekg~3t{1tV*y}MMsxUV;# zUDS17=Qx+@Yj)3Dn#)3Gzm;_M_HeMTa`DLgk+GgnLTkTc-qPCK;q$smG_T2*`VL|z z`uJ_le0hdm(wrT^081XtJM!Jx6r9+A6ZObGBPII#gIHsHhtoY;U98*pL+ zPHe!54LGp@Cv;>VJ;_e8=yLzRX_ zVR?7&fn^;XvG(@S;Y`NsNs>LGy?w0K)&_e*XV-$^LVF;f+7mhue-A-}!Je=iHU!uc zo2kS=6V#w61+=B>R0alhx9m&<1MR3G1A`k^cNgvJHwqd6@nbhbT9_KR$<9r>spf`3 zqgDTNUerrDX?Okn=#e8X{`D=bPGkKad3r; z_yy!>zQ1{7RXfGxO5N;aRwQi2FyT-*h65afB5DN}C+`QHQtAN5aDZbtz%d-)7!Gg@ z2RMcU9K!*Q;Q+^QfMZZxE>VlbEP`W5pdpbJ+{Cg#T^2aV0(DuSE(_FUfx0YEmj&vw zKwTE7%K~*-pf0PSE(_FU33XHDf3oHqXH{BPEK&;L3tx^AmLWeN2j~YZ2CM;G0oVb! z4X_8W4{!)@6rj4LlW62CY$nkkWMUHH1XdE&-XU~=NqvqS z$zLq>k<7pBI-v|~o;OcX2Ex&MN%%MRc3)P`W&^&C4o7(9s!tzTw+_0%iWLVpRQfI( z>MHZako`Y2x={AFw{@*5b%JhSy8!Xn!9$!Z(M&ay@&t+br!;1)@9qxx4P?wH_go;1 z8Mj=u@v<#jcHQ}bt1o`vWfXn}d&V-AHuDIkUN+U9F@rke#Jb`N>++zW&=DEVu=0=v zqX+bM~5WgdkjP9(*6f zbij-<;!8gu2j~YZ2CM;G0oVb!4X_8W4{!)@6rfU60TflBc}eS83F}#jK2V@T3F}!2 z>sblwSqbY|3F}!2>sblwSqbY|3F}!2>sblwSqbY|iK?Ww45KZ>Xv;9#GK{tiqbASEoK$$FP3GlgQJ zC;3@bbQQT;Y$ng;e^QJ@zx)K>5ZWVx-aa{a=G z7G(>OB^S>vv}JQY$>a*vj&#NYP1q$Ae)?$h%uqc{ll-(t+QKw$<9@>4)D zV?M!8Y0E@ZnFmqKgQ!9RW)x&V0Wb&{1FQvX0qg|q0_+9s2OI_*0|*`H|9E@K=Gr`d z=?5<9>x+a-nmwf}SMc~dI#vso+SA(#9=dYn6C3*aHqI%P!l4zb?)}EP_0m0oP_EE@ zVMj^fse;cb_7u{WLx>r8sF`YJ*lnAshU_RxvT9ODp)nJm^8>%6_=j!>GeWrukwR(vjg`vy@X*&SO0|o#~02cwS1Y8fe z9dIAui-4~Hz6DT4s5Iy?jRs*I{%wXQguzXfEp22@0Pz#&ilT>6aIPpgR}`Ep3eFV; z=Zb=JMZvkE;9OB~t|&NH6r79b9i#dp3O@xmtqt8BdiIfb!Yxj;<^rvecU#b!3$*3}t+_yJF3_3_wB`b>VJ`?^7ho@7 zKj1Ln7=YGPGSCU+m@vKpKprpvSOT~Ra3$b+!0mwh0AB=r1@J9^N?}n@7$FBjdH@Ze zwRD~uf{2A1p9Ss%zT8;64D{2Y~wka328f1HgR%xDNpL0pLEM;XVM|2T(nf zTQs(ET(JRBd05$ncFw2XO*=_Q+xM#N7lWlQpr`>&mVcisoj1{Tu|9PnbPNJaQ1C1YXCZGid8c(^@zW5itU_7YzZuT?&ZEi;$+MYDJtL`x)e=@3t)r5b$j$rgc;kWo3 z9lPShJ^Yu_8?aT5KxBGH<<&vxy7RI6Q{Rcc$eKbrj2^_IhdH4ihQ*`;T!U@k=hEuf=pf*QGu#ym`h;hJj0QsY^CBMY6r6VuOBFTj=l~3i# z_9^1Hbi#`v1UG(PN1xvpjzz}jx@*<0MfRi*Uyi4Pv0zemCcS32wZ$3jxWJy4F+Q(Eitd;u$o%U z|C(rlL|5!grz9iiv3PG=Fc_-8q)R6IyQASa>BD>Z-TWC{5o>Xr9aE{AVlWh{reG~# zjdHxnfCR>~NuQK~H3U2;(zOh%$-tTntjWNd46Mn(nhdPTz?uxK$-tTntjWNdEU-qA z0OVsNBZ}BTZiE`;2jl?#fW?3{fGYqy0Jj150QLb60geLHHOmRUMLWnbTLkw|^+li} zJ1xYE-=AN+IFCR2zy8Zad|)7+0PpBeB>0j3{{GFI@L&I?&70vY>h4)}QK_q|2Q%^z ze}Es;C16)Bs3WBWolpWE^1La*C+1T{Xnl7oNLz zjJ_VDugB=?G5UIpK6Y->IH_h>qR}ZqRSr(MIQW93%^8jsW08p4>&&Keh^KbC<-2}Z z|AjO!84Sic!y&gj;*KMzCtgiP;^EsvUW^mb)D!4WnZ2O)hiqe=BIJ*9G|&=70XvFW zGlDDGP0_m$M0nNye9o3gLQ0F841y_K=A5mjm;vPNIGu$PjDQ@I!Y2(jA?7^aoy(uU zIF$;8JKdS6$LmWLV*Q!S+Hxe4&yFCQgJ1S0lG)C9Ok#zAPYwoiFlpKyK{pO?4M$5$ z!^U8+y)PE`d6Vvl*DVJWx_Jn6a~R`{QwmjjL?D`=Lz+(fxRwD3BMSwoi9i)*ZJ{(Q zre5r;S4@@ZJ`dlp_o2w}aOB8-UF^+UZVBz!;p9mBq2lx~Xg@JE6OEJ15Ki)WOrEHx zWTirj&_6X*|(_&ti*jp-w@{rt!`jO{STz%lX#5o9KW?;-r zt7NmjDdDCX_BTu3c1j6FiT7y1p1r65TFuFJr|rSf{P#-q23a~Z%N==vRNGxTH9#Y; z&J$W2zth&;v&4xVb@o&-9l@yUx|hsLr?T1M(FLJ&G@?%?`mhNy9OlpTtlv@THyBz& zevda_7%8PwL;cz8E7sQd!uEVDQp97*JR$yi5VYIch&yT(j0U)eEU82tQBu`| zW=_Nw|AKpU_Dw4`y#E@6RKruEV-EwD2Z75Z&dwO+WfhylV7r7(GmJ$-Uh7xU>l0^c z2|2tU9IqeiS-<#48+XYK*atWSI0{hLvwo~+{b2BvBmfGWFl?xP{Sol~ z5smkcpb;bB{UhN0B&3ai_m6=0kAU}&fcKAp_m6=0kAU}&fcKAx`P7Q5Vk~I|b9Uhk z$|uquQ&^G7joePN_htAX&V384CM>ClZ)Ck8Mk!)C6l>R{cB$FBD6m7dcKtHrbS4sm zbCR$<#fx5VHaBk+vG0LGE*=}oI_$wf90g z+~EcDSM&}c_=IwZ+|8Hsmq8)g_}Rh#1+~Bdq*2Rkq)%wb9tx50#TJ~d;KYCUpC7ll z-BhIi39nq@WoWc7gGy(#%MHw`OYAJknKOyk#3i5G&Up)tf3R8{-qu#HC(V~xd|sKG z%}y8YIU>nK%q8aFiDmp5_Ua#&o?`gT^^}`WrKVs$96GUvA7ej&PLdJx6@E*w_~rol zDLM*lDvezv4ISlBLq*~2VbpgBV;^HzsPq3_)=La$r+Wa%uVVfqf=Fqh|3j@bj?sn{21SdKqrSWzUQEiWKX4!yxgpgsJJ`~ z8c(nrRT__i#2yfYZ5D!OOP5%fK9ep{Y*r-#o`xzBDVDl*KG;xtW>>i&Veu@L zi?>g%TkJ;+@#B1ubPK+)x%JHfTC_7}9rIhbZ5*w{>PN)iT2<9*~ z;T#+cSi(XuzJ*|X3&Hpng7GZ`<68*Ew-AhPAsF96FusLgd<((&77~sZoG0GA(VE;X z>B*@0bn2I|NqNJOcqJH)7KTHA`9Bu4C!8*iCllr_o8K9=IfKFOOuDm-GsXupQ8^^1 zJRUJdPk@&9qHo2i*_)%(x2V!LEzc}dvpD;3Wep)Yr-WnWVA;wcI3b7NgdBnsatKby zAvht2;Dj846LJVn$RRi(hv0-9f)jFz)$;o&{0tz16FjqMM0g5<0%Us8%ywYCh=4&K z?iH0gKoe~i%L-%Rba&Rtd@_BNG&gq# zWpL9nxM>;Ov<5e|kT91KM`80bt1h#D`V@Kb=|S_-Q?RCO&?Fk>s)jIChC zRxo2Ln6VYi*a~KB1v9pS8C$`OtzgDhFk>s2u@%hN3SK}_QD~uHEQ)Ih?G~TNwG>lb zOBuzrl)>9F+S@XCTLy1KmI=U8z&gNHfExgJ0PY7o26z(i9Dw-4Nz_5j0}m~6Y7n)l zNfPNI=il2wyCa@xD<)zNM__&`)zgzsj>_RkXOGvLNqN2YP$-j#h6Da+4ku$g;y@Pd z6ud?LRM_pd>qC{S91Mk0mBe{i3T)9rz=6L*2x51+f>FD}Y_)j>Jxc5#XoqBu)AAP| zSRSDmJEL(`_=`!QAQLFa1PU^Nf=r+w6DY_83NnF$OrRhWD98i~GJ%3jpdgb*K_*a; zi6}_nFL8wfob&cR*o#9k0V z(2}7!#&~NKGLzsdab0&W_)0v|or;9LuC_vtJ{)XIdwm{vKIV~IO)VDUE6eiWEA0`a zM^6fNbF35&Vxt+Di^U4wQa|xEzLW3xo#?PT%O3c{%AN}7PVVEIIy(mj2Kq~#-cldj zPSu_=+V^9$Z~c_^{nm8YfA5n+pRvUsm1-xYB6-;6MOi2^wZCtZC)!T*C17Sd6zo=J5#flpG?gL9`?iI__(Cxh|_Xd zoFdln4@)7Q9*=nT3NbI8QX_^w%y+xR6jsz<@Q0`HyN7^>c*CFP zQa8UUYVYrnx+}#`r|zOJPk^TOqAx}EpcsRvAPy-JR;N;P(YxQiVyfh#nN`J~{-oVm%HgEb)lcAKb`?gE zRv!kIb5j`k!>7?0p)Z2&_F+uPj{mAkc@!-}J)Np1PK)RJ-&wJvL;Kzp#g0p7r`bWT z11*0J^flA})*Q<>RgIH!O==wcZ-;=pM}Rv;k1MO#BOQ&fr>JkB%J&qmlPg@>{#7lA z6aKe%OX1>=|7IE&^ey0+*0|>Rsm1I~RkQwp6eBS|m7wLR9+7iZjJR|*nh~uvb@n{p zDc$;epc_fsPO2NBee0*R?{}mdNw=O{Hv-)*=M(R~e-EcZO-Xb-HCAx6S(721O=6Ei zr<}lAKEv-gyLv|U?A>`^JB6Nc55E^@_x%uU&9Vb3cSqV`!QESta8&GRqP@A~D$BvZ zpsmRP(mIV;2mAo?fb;_v1J(ep0PFzV2G|4G2RH;c3Q)~zlRGChR~bX?lq80(Ubrj} z%gDUK7)E!Lv@y!JWH_DF6O{e7%J~~z>N%8ss$GNMS(!60m-Biu>3q!Zi%3!=tbZAY z9aj4N5u`Uxr}#Izi^YkaT*mKh!49zck+zSF z#xjMmOkpfj7|RsKGKH~BVJuVHSf*g2OhJQJ#?Og2rBHhaZ>7yq)B+kY?I@opPv_GO zThKrSl6Ih{u~qcH__OKsFe1Qpyf&vJ8td=hzN&9{qIdq4#ZJFR`dhoBHJKdzc3XEK z&{m0rTQAT4ThCRNfIk;^U_%-7rF;1cy6w~G3CB}GT9Aav4r>U}Qyf5Jpc$+J7%PSr ztNV{;!RSUQIf%CCYDDd1V}r#ZqV+sio4` z!OGd+VSJtwX98>Sp%(Bj8l9k+b&#bH?u;8^cH!hWe9Qs*0gC}^09OEZ0B!^90qg@D z0vrXXv&#sIFoGg5Pnzv}fl0Ir7ej!5Iommq$snk`x5HVPv#40~`!cz z&&6EG_}e>+=U1{hq_l0tmTo3^hKl1y8uQ_I$}@=OH7=p>kJE69JPsjGpPo|$B2wZs zjKb;}{?mGHDyN{m5BKtK0=E(HiK-YEB?iC*f(symjj&7}G%{C{ecwvXa=4Sl4QjQP zp4g*o@}X@VVsl*c)@~E26+$^a#M|12=M-DR;X<&K%Lee5$>kU1a#C0qi&VO@S(8+m zm&t_vPP5tT$>(}{ayZ^4*S1g&&5cCj@d41|_psmR-*q>_U!r9XHw*H63bMb_QrZ}- z*{pRa`;RL_E7RUhN))A8-znE=5D(C7;_1EtHSP>see>QFoo*8)*S#yvv#VCcW7SHs zMGEUB?(%K8`|h~()u5D{n@T!^+-TssO>N=&k7zH^BUs~qf-&u73)vdJUZu8inEh!q zk%mlM6KqsmE8x*$uRu*~GNlcagW`)UTZyZfQ)FYTVNTUBr)ro}HO#3R=2Q)Hs)jjL z!q`<^<-5?AXrnJt z^u>9SzIYUgUD5FR(5+rR-rTQ9WN~|;HI=RSe1G=T)GpRH*5XNj&=Zuqc$K;eKKTOh z^(JViRpsp3HeuS`CW$@SWVKUDO5mFaRnd-MkU&EQ;){Y{T)hLmra+octj8w$Z|45^ z(^+ZBg=_iC^?gr&^jYP7e~-1~8+e~x^~sa;1kQ|+wF}lJRt_=}qE0U*0ikKBbo0C+ zdz(4v^RM1;BmcLB){x0~Io}*cAJ*~*aQ@H_;Ujuf#g?`gpO~1&*E4j&Ih_C(!A&G{ z(Xk(4i4rHA&(jK@5(N^g#MegcYqjtx$R2clIYsVBsL4pZ$82)pO2^Ak%@hQo@IjRE zIdPnFCy+_n#f;#T=oc6c=9DUV(Nt?Hx_Se>(0(AZ_47x20s;6Rkq$RZ$22Qfzg`~* zh9YCms`Nu_gna&JPcUeAMSrecd9@sN*lRV6i{J|w7e5vZAxROV3akGLDWVGnTsEZXCPwD64DA{j%ftau5UPl^m3QPz+MxPL_B{tD9|8A|fcr>juQ!+L=_P%x*zrU@pPM^3k3Vc|#2@J% zR{jFU%?nv;pE_SNZ*2V>g`IGqHvVvNppp=kssd9 zzmxB3@A#WSL)RcV-P+LYA5zCd4@HsaD2!>=pd&L{Xr-2;Rn%g-YC#1=6-qc1R>xV> z`8BPN-qfs{Dq4J>W|5=Q2f*AkZT?Y3jZfPpT{s~P4MMX|HtX`zJ*34uY|(n(N9UGF zt?!wt%X5s#1N;rph*RC~M~f_pu;8aUtSKbRl8|dW4xocVQi5S?yatDQ9hm4PMEEo5 zLOy7UOV4b6mRBe8a=;d80WbM$%-yF^f4On;Kquyp!nb|Oh*P4U*~_xgjn--sOHjd2 z5VJOjw+1n5gP65J%-SGkZ4k3Ih*=xNtPNt;1~F@cn6*J|)&?hL4{Cz%*V(v*O!*j+*{ zMSrQQfI!1^bz;O7jQP3|s>WXnIUF`fBM21A$TGwbKXQEp{C1n&Zu9!0`CzCR@K`Nw zr#k-M(a|9ut5u5EvPWpD3MJ>#rYm)YgmzL}=-3V~`Gh4gHI&s#qkLMw146G+BV{z* zY%Uh4dC+Tm`PV<^@$^JG&CXE3R||$#t=5M>Cw=umFZw83^u}a*&XRwOEffT*yH->w~zJ&NDBRz`jG)ezMy<4UqKtJETxW<6}w)=JkUd=A%eBy zN?AmJU5NU;PO>lFO@gFWa;PP=r&noD+QHE%5B~9;`cQxD zlH77h9?V^mSgv25;s@7!2S-&!FMgKKssD&4m3}{sHhvxb_OQijzbR9qrox)g7K+%n zP+L5zz-6JUXp5s!5=n-Xu>skqk>4*pkncs_4k6j`2Z#JFsnxzPx%K_&ij>f;O1*z8 zf8fH5-xauW-<}CW*Dd#dL21u!ZsAAJ9$y0wcH#TQ`w^-j@Sw+)&P+qUF_lEuA?qP& z8UncIaa}KOH8|b6e-D>|^ zSVir>=IK$=-r&8I8kF!a;o2hlO^;9sT4J9E**|=`r0?$k{2{mhC+GYD7e^}9`WqxD z>l7P^?jDp95JIZr9avC6zE%i#ZGy$p>Z2TPB^Da8g6J^-nps3S!x_Cr7u9qp+H6zQ z6*a-)q$Px}xkBD_@XY2AoqenKy4Ox`5}Exz=+4TT!xP?3FsYo;q@%Mtuk+RqpS}}h zi!usZR2K{&2URT4eiX7rIly@c!R-`#itC_2xMGuv!B|0AreZ)h_|Xu4LN??fObU(X zi=!D{hNa~kw|1ar)Jk%Pc2W|t~5q)EYdlVZJ2#SH0^qh(R-pA?ZnLtJB~@ zEPEgp^t#LLw$oe55}|0&)6!zlTbv=gJyz;4o!n@K(URC__-FVt(vL9;X;xNe63&1S z7z?bFu3@2+1F+#pB8N2f&zeFTEl6*$)Jf5jC=%T(#*^_My&;e5C$12Y?|~ol3wF@Do&D7u1li8(T44# zSvga(kMj*CV{0Jq^{BQG%{CGa_q*D47jlDM3I)41l2zn8-%`vZ|H?PP2uP`9FqZH6 zrq1k}x`*q9jGsmx@}mtpBr7Oc3cAF#Ag~T>_X4|VKris9X(_X%=}pQhbTXCZ*`l%{ zSmDhm`$0u?)n+`=UGzABT845v$ylnsNRnieZ*Ph{M~w>96a#*zFPaI~aB3B_$b3W& z`MExl$Qc*DtFN6|3oG&dP4n;M79*EN7{zAnHm|^ zn6@kgOnFL)E6JgQ?t9$_Lee(>G3Ue1@6yqtXyd9A>!n5f7Z3+OEwH7AM4M>8qN(XD zWEb6VHQkupm!a)Qi^2!}Wu!R+-H%YweS(R&m4u?yY~b6!z-${PXq8s9zFtO*1Kt z^CAo&bR}D;+A2;R;mS@-gOJ7*Rly2-Zx$2}$sfWu4vdk~b@!{W1<6kR@>}>mx7#h< zQh)Q}PyDRDo5l@WHl?EUFze>4n#WBYgvpH74tkw~UZ=i4M>lfH4F@`HLuW}7#DV6N zsWFU9jp`B2W)x&V0Wb&{1FQvX0qg|q0_+9s2OI_*1CZT~l+nU*9D{`QwjJ-5NbI6B zk(FZ~vXGdGliP$!O3Dz4$$0t8D7*$xT|Pc=Cm){m!RY6M(a#5?pASYqAB=uJ82x-O z`uSk=^TFungVE1NT!y5Pb3TlO!V1V{rTT^_J*IZPwCbi59wC^w`g)056I1`o!KLR7 z48)>HLU`wq1?}x}u&bxGpsOqg+uIi$xpOF$jzy#$?d=`QR;<}{;radgh}^ky#rD+; z7vytra8@tgzG7vk9MSimf8nM}mX3jj^yrFRS}E;8qQ2#96Lviu5tx38y&pLUX#|(B z%f&U1>k;3G;vix~@>K>xhppLDlr*b;ub`*ECxpuFHX6`n-v)7gs%= zQ_hE?Dzue#1$6~=vP;wd?m@iv;t~!Zco1& zt%j>Q+S9MiVljRephQO`qXo2ebYR9E14)~6qLH!%*&$n<-0yp=(NxEHywZSCl0=?ClRw) zTHUP?r^D1@)Nyl*$>oVf+N@ri#Tt$D2cv#_Yc$e5oJh3dFA<;99f`Ku{k}krcDmQ3 zti$Q_CZs8!)@!hn~Xg6y<3QK4E%E6{JU3 z8S|K~>)}_W>sIgpb;_P|Zb&g@OIV>x;7TP(6eUO$B}fz{NE9VV6eUO$B}fz{NE9VV z6eUO$;%FAK$(LZ=69>Ey`#9&Un4&FP(Uz@f%T}~yE84OZZP|*pY(-nPqAgp|maS;Z zRpWTtDxd?;`Bn-(^P*|BGgD9vSJox zN|`_&=XA>F?VaY;)YPkhw%N@zIUf`S4CWj0n|WNBSiHD)bYUXya9FJY@7R)Su3omp zD^rTSczkrU{`ZNYfu&?-mykE1{p__=QudN=!b-x zq946@363nTAB}9cU#Gv$t{c0~xp_0$_ISM_eec9}T@8z!W^Zl93P_WalEo4yAc;mZ zxl&|~5+;T=k=0PtuW?X~)&i=MLS_gNjZ?4ifG-L5)`d)_p#uWrzdtm3Q1J4U7IiOtgUkS84K3aQwhQMJ}1T3*C_ zdQhKCE;}!t(#LotJUkqZaOvCqogEz=gHmrQolfsD;zTF7IYzTNv=!Z9>rg~!1XcU`t0ial0Ft&urLzW$0S+q>5*kgH`<-gWU{?d zk?Jd=z9*y?>c7;5AX;fQX(&_57%A$T;A0GYjDe3a@G%BH#=yrI_!t8pW8h;9e2jsQ zG4L@4K1Rnv3O^?UAA@vBadwJ++R$U_zqDBFc2ZXCHmgN?OUW$iupqPoKd5h!UOG|J z$wK#fUd4pcGQ|!@N^>hHF+u`(0-RAhQ$Uk0V1*+A?wngLgvnV*r$Y&CL>$JnahQIV zZGC@>5?W$yZMC-SZT=v=h4>Porkc$n);tobc;5V7!zVFmjJzeyTZjXDMf` z=BE+~XJF5&KiZW};8-3rnE4vr2JUvJQ`pPyx7zI9E{cznRQkUO?ZO#;DjuV%;r_Qo zX$@A?n7C7EZ_~_ax1os|+on!I^yy~l=@SJV**xOx4bdkxj00pyrxUb_C_Ba8q5pwO zpMJ%s?uS~U9x>H|0=XjF07HPKfOUYY05<^c0Nf9F4Dcl2Ie-?6Krzc&^hg3_jp}0x z)svMXhWQ%9e2rng#xP%zaTTxwa2sF`U?1QR;3z6}>{1sA{x1%ZIP%IR`OBQE?! zqJmDgV-4G?+XP-cxr%{)6i#HoZEZD%&m&*|c4&k5#{k_X=UtY20jT3->mcHdlo}MD z9k>u2PytgqU*AZJ;P%LN-56HE>#n2UbGww`Mdap2%(_ac9QVa~p#*#4_c}zgaig_S38r+6vl`)1EbkaFS&(jCDd?Fr;Hh zDjA7(=oMNOa>@dJ3HGN3^25n$SXFjO3TBaXFLbqE}9)OVSZt zmlwRg zdR-djH;Q&C>ma5SX0x@4htg_t&elp$5X1;_*ueiUMvdP%ABnfTIZMCaf2VZB&}t6^ ztX5s9#p#SMC08S_-+H2O!i%@N6rVkDb%90V>Q?X*t7hh~f}dEyPpsf4R`3%m_=y$# z#0q|51wXNZpIE_9tl%eB@DnShJyKvOqebLJJ__u0t$!D6{7&OSf6(nMBq9q;$kV&D zC1hx|$^O<><9f8|wiAgHWi-UskQa8 z_iRO5u(w0fdj5#6M2wE5d0AamL_Rt@S=sDOXThrnHw(j@9jqBqhGcFt!rVrC8Zh!^ z6l6dFFbEg}tOaZV>;&ur>;>!x90nW%(9(jF+ZCTzNLj6B!H21-4dlt7wS@NhxRq7P zjaABxRmzQ3%8gaZjaABxRmzQ3%8gaZjaABxRmzQ3%8gaZt*ugStWs{VN|A>&MbWKT z6($3(m7OXLZ)m^rnO`DraEDv$wib(}h2mwt&{Woyyc?ZYs9!+nYCJ?`VC8`H5t>DC&7gJKX>A6rn?dVl(7GA4ZU(KJLF;DFx*4=?2CbVx>t;dg2qsWy z{eL?=AsxXP@pSc=JiN9ACK)1j2h^$~%^#$9o;M!9%LZ-2WEU!zqp1wO`FqHZ6zZRS z4x;cUq2z?KSQV)+SCfl^tz%Zaj1zdb#9CAI zJMkkihG*bMKfA=aInvC5rm>^V_Ddc-H9HdHSmp z)rKNGW=jr+{0qDHbevBA%pd*5oKql?4CarV5?dq>DDp4GWq?mklYb|%atb*Z(*(Vp zZOK*4o>^RP#2B7oz2T3(F?j0bW?Lk(^OUR2q)bKmH}g|~3x$^w360l%_)Epzzu*q0QUnP13U?M4nT~RtdKb& z4=A>~>2y8)^LIDsH#j#&Hg=tg*h|ZoJ2z~2>MZDeKdh0!RfFEWSW}3-YZz%o(aWJQEnt1jaLg@l0Sm6By3~#xsHOOkg|{VmuRQ zo7_B}XQ26Kx9GRnuZ~=O8p7W{G2z~@;h|H~KKAoUBm7ON4*p?f5tZ9B*vgf#C9Iqr zU#K?TF0Ohj`48mH?v0VF?bHIP?%nE4Y;faM82_(HNBJAN>qLE;wf6LNy@a|pL^j$l z(O+WMUFW~VEw6Nrj#5sJ3sY)qC}@(lpd}Bii=B zsoHk3x^^#fFMp)bv@7hJ29>6vE*iI&bQ0{i$QmQYjkv9n5yK_?C>XohwFfqNd4xUC zq+w$_PC@m`F>?hwk5;Q)heO@zOhsHCi{F`$t*v}S9R?Lg|ERkec1!H8YP6m9)zRh= zim?}Mr~Jf%SIokxicM`-t(lG1f4Dss@kN4`u+8rFWLIdzeKSX9-l)rMmd!0zXTW0N zBN|;Cz}O$qS+L%9H0_p(i}qVpA66E8m*f%R0Gg@Gh_e1T0E|G}CP+zElt5neIW43_ zO>Pt0zov?9C60U$ir+k-j{X73W{+AUZ9%i&;53_bvShQxe6D1R&ZRd>ext)|)~(m* z@tLU4YjC$p2CF3~#XLTv)yP})2CG$eYv=)f9s!*m((T2L$9ASjU_?r^ADv=@^5UvQ z!YeKyt$%v-Mag)9zNBXMYjt|8Es!>G$1MjPL6g_)ZL#P! zYv>tI`28lAUa}dCcDpRZORLqXGeEpdX?S`-8s=Zq`7uc$@2k9pgSRLroh!z{ff?R= zzCm8=K4?m3Mn}_WbI2TvRVwkQS?Bj&?aT}#Rcdiq-+^lOrzZ!ii@KY-;q85=3)zR{v>BTs zy@9qPGl#3p-L6#J0~TPkwYrFjEt@uY8vghFHv2Y{m(A=O*G=JS|JO`h?VG{hmQBSR zvawhsRWYSZQSYp_3MfA8LH@Ecqc812D-B z^toedpHImfXLR{2xa2R<Keg6{h^<^E!_DssAzpHjnsL^Eb>02y&wBU~BIG(f`=pnH$ z!w0oX*nG8}*x0uLdUuhxUvXm#jm-!d^<1;dk*|jQ{ia5Y#IBm$vuu3Oob&i%se<(C zVdGOpU00jS-|F!u)i?>_$%p}+)_ zM&8G`Di>ozwBVb)kR(Yyo|JzlWn+?=-C&WFZm9)j@}MH65KO7K(FLgBW)L^&U?5qs zmUcn^?b7t$E-;EN=)Yaif4iXnc0vE`g8thD{kIGHZx{66F6h5q(0{w2|8@!emkt@p zfs1vc=5BCj%F;E^SX!GzZzbSHELBruHu?wnb*GZSA(v}%x@*kQ8Z@U}enjR-KbRqk zOPY{A#Oe=)Y$pHw{Cr_xa=Ih{Zm3 z8i~ox?Y5ZTf3Z@a(K+dq9gW<(`S>;LcF z_tWq-V?3v|e|mox8;mVxBmT@SMnliJekh_EgVAW9Dp5vGoGE3!MY(^{XX>rZZ|6U6 z`uS{pb(Vd5c4iMw-|9!2TiyKYW4||FoVx4N@YTSJw82C~At=LaJcqJRtA*(6*DSX+ zy|=lZbGd&ueQAEz*|hmw+SB~5ccY$D^|$%G&3%Ng7wceNS}J{D*4M*NN`EH(rSw(l zIq4sze}NNx8t0n9S%1y%r*rl*exAk|r$0ZPlTQEsOrNL!{!E|GRG*=-#w*srfnW2p z=?u?LuRl9&{HK3E{r%Zrv;OJ7pI-m;`lf$B{rCB4@1Ne^lh>!6`jD+C@{=;QXL^@X zxAxnCDZicG4qn4Tpsa)@vtd|W70gEL#@>!)7vEr4r_pRY(EQjd%^&KoHvfo^H~*@B zhw>eE3Sa@}4>qrwKWVPy!R8P3XYfhk_GlBQw>3W1_coqHjmy;!dJ8^`pX?h$3%;q} z-du%bY&?kS>z6_Zz%$Ad%Z;Kc<$>|3-!wgT1mEDd%3}{Uf2cp!{3D45v@yEBdcOI) z4>y0{iRKqi`Ko?T^Mfxpf2jX?^RM_TQ@(ovSOXS;J}j2o6)XZb*qb}tcn(eb2Rx>9 z1&{GZ@D*;5z-2Jj@4(fI9sqrO+)KBW`>!@Cz*Da@x8cC#FSbwqVDg1{Ub;v@CXKnd z)nqoAfLJ_eINnREyf!eABy3O=W9Sx%qygsU8fARuc`; zNy~*()Dy&*8O>&OD9>E>sv{u8)8?{;#$2h#qO?JLyX4obzi^7@b*H#*Y;J=>9-^nb z>G>ILR;OtOZT1sY8BKnp(Si#;H}%8ZRJNSt+2%Hp4$Rrc6R-2Pr9^NE z*&=1q^RY2H-qscjxv{~j!{c_Fq(e@RJzVVUs1D3C7t1}xc5i*OGmZ>m9(Oz>%MOPQ zNyV^_m^n|pE+O+X>uh*Bu+v+#*{CLHu;5CGrYWe=!{8yOUX;)S+QCsm50bD?BvGcU zFBHv|K~HF%&{}Iac_*@yxA|D3<@MKQbcV0jZe61_o!?omubI&wfAv@2S8u+DkKa3d zNGD!9@k{Ah_?axoCh!T>$3gaX%9$j5Oltn6$>=u&Me`b(!h6*mPDnkZBumwaj2Pr& z^MU$IxCW4zF$gCNeFGC|07g=pfwT{buH>s%sM)8MFztld*JVn&29PHS{^DNH?m*I& zbP;oq7UpYmM_c=^_VxUte@*D(ygpf*yXsf}A>~%g8_7uzcFzkHx_zb{V;+%^@o#|xI{>0xt`O(mgfAcp_8Nc|)p3z0MajMUG;!SBx`W{jvDG^WV=&9+f zFgCCeQmUa|)tp5v#FU2PLD`=9U?r9D>T_F0^4Nk~9r>_+^Z49=KcAJp7m3c>i7Xwf z=TBU8>Bp|Q?tNqPS0iJGm`lhIA>P@~zNq#=U`$1jz=t23(`bl;xcUXdXje;0*hmq4 zg2Hu0fOJ-5ln|!tEZi$uu*fU|;<5;c%OW5yi-5Q+0^+g=h|3}%E{lM;ECS-P2#CvS z0dZLb#ARV$rB_D9u_2TkN-2Q_5i3RVD;nm>W1JwMYMFvGSrutcv?3C!@j0&cjwSV7 zOFG(Jrp)?kE?3CTkL2_nc1L^L+_~*-c4xaj7nxVcXLEDcXWrW0)olrdEZtq(*A$D3 z7i63V^?aqwHsOQ_;*^0;Q;t`FIReLUUD zV|pH||FfQN(evx-ckAo4MVRm}aPdR*eG2|3adE27Vuz28}o0lx<$N5gF^m=N~d%CWwGfme`&!~$|(+#Kp z{!E|GSpPI#Zn~~_rq9!Lzv<6U*YDobbrru9CRT3xSiPs~PHMsL>ALUfx{8qky7Z(b z`JS$;=+Di1C6WL(^y=RyU6(Qnj7nu`x!4skfCP{ivG?=u3d!&(wn3G-y&~p=@}yrb z1j7Yv7*}PZISGTb8K{EmFs{pRU4ZLK+C8^e%@J6@P6d)EC6S1bat10A<{CDE`$Bk> zAw0@LzwXC%F<=eg3cwD)ZGb(1eSkxNqX2a`g$&_QR-E(H8$Tci=m#tYtN~mB*a5f= zum`XYa0qY|pw?1AEd{ED_GFad?=Nfq{xXEpGW`8z`1{N7_m|=CFT>wohQGfIe}5VN z{xbaiW%&Ec@b{OgO4=W?7;RaMwk$?l7K?vt@c$Kn9e~>adjR_YhX6+bYFiegEsM3b zOrR|jT3aU2mI<_F0&STbtA;R4A^q}NOCKJ`+^UoiyCK{#sH(FaM zExq#pgUuhd8ceXl8T2NzUca}g{D!%$Z9%QrW|E^O8xB^r`L zPW*+M%E;eofFV(@hf!7ERNgip+1t`J^BNM}89(vm6LslJ(u0t@|EO69ijtBVrR=vL zT@Q#=B~1~s2t;~-NF1X9SOd5Mumf-#U=Ls);1J*_Kt-enh(tZmYmm`JJyHXEr$h;f zml~$yDHaK+Fv`3x;wq><0jf`6uo4)o z1O_XC!AfAT5*Vxm1}lNVN?@=O7_0;ai?%Rf(!GenPXX!-EQ0Eb8r2s;^+iy95ma9U z)fYkaMNoYaR9^(u7eVz!P<;_pUj)?`Q6=rz#a1Apy^c3Z>l>wO)Kb&zXtK}I>@cab zqH!1lqRMEd5Ozp1)Ur1j@VW5}G;@)F7BefGn#`mXKz+hL(l0r0lmqSE#l-ON;#-&D z@79|ZFLKPEzw`R}3l=W;$l^+080j#9%iv)7$}7tQA;-K$7hbq{r2ed<)$FuI3ejwP zdp24KIhf3YhTjmGjiY}Q746q_xPu+bI1X7dvE>~+LP8(E0|5OWUeSoOqHQbe~F z@%>RCev7)Ic4B`6EsT1sw{(rDQPY$Pr3EV}U0oz07qAY|16Hhkbj3CkR#0r=*fik5 zU90%y!IDd!CbE!+DRxw~3E_z801V!F*FbMpU9fOV*O1{T3LIMj?rke9T~;WR3WfSM zT(~!usLqYW-@IfPrx2$@!Qn{BTaxy#>ME5=s~;}Zr9y$%3lA^J=5pC357%GzS1ARg zoT>W1?De9N5@YZIEaD_I&tY#hOHl!mq5@5a3Iyp!kZw>icqNSnoj=z~+o(+tJpzC{ zU;wZLa1r22!1aLJ0rvsE2>1%%TL2+2d>@6M0VuWQAZi;v4xm``Dn_aYK>J#%_(D0q zhhnVb;)FHqT2l6JcS2I3vkE&QsdPe8>4c=x2}z|Bl1e8el}<=1osd*IA*pmiQUOjC zNu?8Wyb~{0{d^AWn$aX-*kvIOr~-nLLs3ivx(8IKTUdpR!1vU0dA{_%jhDug$$ThV z%oW0$ivt4}Sd+_^ENw4FBRxYETgQ^Gz~9*N+-Kx)p`#Mz1^!<{Z5@?RPb}Q)^|iEm zJjo@+4!b^IsE!QJ>FG`;TJ)EA;<02PFx1W+9-rO8k*)s^_biQ`6DOpMv={qzZ0y=* zI-KEynsIDMD-mX^La8*bB-QgoM|@Bs01Jt@En+6ax{n)LSUBx%;KwluarlGJlpg4H z%|kr&f38}!YWu$J7u@lck4Sr0?OV04e%VUCk4^++C;m!WB=S}pQC}-!$#$xI!X~r~ zn)V_`Gwy0KJ@V$>j(Gk_l+-@{mI6@cH^aB79^O~&j6z-GXM zU}d|pi4>i3FK5h)``-PG`S7fajaV|i3*XFGtOD4}SbQ7c8O9PaU<+f(M{&NyE&$$> z-N;xT_2f}!0lzK4z0&qLg4wG83`_@}>v)T?B0hH}0LS3$0F(i}ql{H{A`Ld`9>_2@ zhNAF>5QH`-N zz|sWZ0AuIj`SbQOHolgz2`6J0;JXzO##X+<*y?K-yKpsQ7v0L(+FgvT!*lBg8M_$2 zz4%qe-e(3p%-AI;Us_;nyq7cKthy?Hpz727LbLe#UNmoUxnG z{+sS%>|fd!MV|O|M4>IOcV96xcKBw-o_ve3uRhM$-`xdxld-RD0KCN5*Y9EM zzhDf$f#<)8_I(TWJcrNU!Ee9&Dr0}Yi?M%rkg@OK9slhe#$Fs{?EA+V!%x{i?Z#2j zRdHN2`{72$exzsY$1gDUlQ$Uqzu!bQbiC_d3XHvqd#|DH*HO>U4>R`0X2yQu1)!2Q zQT`9S^Zy$K;PZd3X6)?6gzd9KpqjO(k68+I{nKESx8m2ulz z#_g{&?%s|tQ9Kja&$zsW@gN|yn(^>*#v>aUk5&N37{{F9u{Rix?_xZ0obl8sgA)>f z=hN>no_&<@+=Gng_cC4pv^~vudjW7W;~h1|i+HvZ*UpC-FX1;`V~m$~0r1=I7Z~r^ z&3G@amA4r0M_mKk7$1Cw@u43xKId`9tM@QIvY+wV0mc`g-HT+#m!M5c%Zx8WUE_m{ zpMN9c6WbYIo?v_ho?XQlUu|Z5%@)Sj;`7B$9H_pT@k@^|z6tj>A7>o&&96j#SKZ3^ z)o9}f3V>sbf6&bMR@Ao*b$uwq_;uGX{$cd%Be=ig4aRT4vmZtIkH#1WJ@cC)0JQaE zKVbak4UGTEgN)zuJmY`5jqzL2){pA}Z!&({e#UR#2tXZo;Mq^04|n0&Pohnq+RgZ< zQO9m4BF%IWD z|B}r3U%t-xmp6cgMi_tMM#leUobkWKvxkl|{v_J)G~V;I7Z`s=&-gd+{5Np_n|S9@ zT%W5l{%!Q@`9a3N`#4r4Jon$WG5&qr`~K66zl8q&Q-blApU3jCf${%=_WlU>eiC8) zpI0;f3fk~9wD;8vYNluu4oHxT@b0d@7`0PE-B;N)m`Efr$faN)eYv=_gMaBV- zGAW8{^i3wkaE<+#N%7a2lz5d%Nk9sa!SmUhnUpg#DUWL&?JB&)r1n-Obu4F6=Mg4# zp}y{ofW1uWd74SRWdQ1_>}OKnF2FG+_2c*b6~Gq2g8;Ox{~aa`tOeW!_%V|PcQR=R z&kk>663#i2=8OV%F{z6B=Nz8zOaL|l z(8tjm0namO5uRBr1MtqpdjYt&1n(TH0Px+|K_)Hr0`T6Yr~_+`G`<<|CX>!T%%lm_ zH-Y*mUSiS(G5~$K0QZ-V0d_KJg&A;!Nh|TYmDez7)i_`;lU5f2_r z8Q?A^UHLo!-)|WQ9AMH__b}<|LBL@qT?6>wD3i7l9AVP61;8#QZF_}DAHuWO;Wr<~ z`#zFj(hj_P=Yvf8Xog9D6al=+q?_>FpWwHDT4mC$W+r|74JO_GI+O0Whe>zhou7Dt zNq3>1Pd?40yYc%^q5e<3%A|YsO!^F-`z${1!RNgPnRGwu`?LK_`rLNF5hi`U$fQ5t z&7?0JW7598Oxl0*|KHfRz*#wNaj)n0&b^Y5BhDa%jD30cTojtio{A`%6heDub~C&7 z%pQC1=_XDHA%q-p;&jMyixWZ!A>@b?LI@#*5JCvw|9zjeXT~|-_xpYITYIhbT-N2e z?e#qGv)>_kS09qgCXu|SndH4F`!D#o9ChzQo%AKgy! zvDG9WN4X~;>&X!$SA%|P2Fa)A0FeL81d?lz{_IMU&q2m>>qtI7isTE$Bwy?xxehvC znosg&==~35tzSy=l>o`tJOI4`@CN8N7Lt6kmE^{WB;OhcSU~da0f1E`->E|=j56;6 z-s?m1{Upf`K!1qzk2aG0cmv5#UBGOTTT4lPww~nYNZ-~#@=M733Oc?XN^*NYlHcS5 z7Lfc7GQLNhA7ImuNZ$$iXXyQT8_8eb(=GxmA^Gbd{5=Nf{Jj{k6=CIOQWAgJP&JU! z>qzA-BUR7`kR(+&iB$0dQY9{_-kMY&r1d3IyZ0xBU8?H0n$#W}N$rLFy_S;dzk<}> zkg*Ty4A?N;L#>@Lj2n zh7HHSo?)P?lcZ|R;Bz5jeQh!06^HCOa zP0dBx#hTP5;ODI+h54o~gZ%lsNL^J-YQYLp*OZdF7J1jXr2e{{)S?bjH$cvf$h&D2 zslP+VEi*{nhVr+e-t7xX-2wVe$h;H$-H?Ab$}WQqhzr%dux)uhsr%=UdSDQ#6&pxB zIG@x*$a@%hD+d5plX|3;)T0wgt#U~{0a;J>C-oHao<`j@kiTXrspp{gdC+UulX_tb zsdfEGy)>89f55K?{~F4^wu;p2OGv%ZKx$(pskb2aofV|s9YN}SLcjgGL+OO z!$^G!_zXHfpGa!k7*bynsjpD~YtY+K_FLGo17*LTN9xBlq<%vFFM|L(N$pxh>er>D ze(z6OEF`VAk#;7L&REnlyKB0s3=tZPYT1I;8I?@e8NsmjCK4k*w3D7YCGEPI8(;<7}F4AYfjx#3$ zc91@6E@_NO-H39Hu&Fs806Fn7q$dNnjv$@bPI?M-O+ng}trn&ZAUzfQRMelk9I%OW zvH^fPZR<(ndqTH^PBoK0J4reXoPn*G6{NHCNp}tA7??^!7jR!sWxU8Lu4Abka7UA2<*)u?~< zTGH3Lq!+FteLe7^rKE4@LmIK6{@Vc3izfh}>t@JZg7UY_C4HMFeLK?cfZV$XfV#^d z^WOE?TBH60`J`7s*Tb_(KMHykWIX}DR&OW$4D>&bGA}~b%dlxZ>aO2O`jruYxqywN zUj_Z@8qyfI`Zdt6%>#hnFbDvducO}UlK`;m4cM>|`ETU|CIYsQejEDUZU8_YVoLok z%D-C&Ks&v+i1hoYvw0Wk55Rvo6tIl+M}q;Y06R!;fvsC0a|`mfz_yP^0Tu(+0+9d7 zAizY@pY{W+BfS+ew!*&8CINPl#{Now0eN3E15oda4Wzdf17O3p*?<**ZKS^(2$%?1 z2!NcgG+-!T1^{Ic+v=}T=j#Q4O{BM@{PqsOD$?Je>^GwTsQb-!(%*vq7WB8EzXSap z=mPQI{&6S(<*@J5I|l=@0M!2p=|3$d{WHq{ z4BbB??Uy>hD$=`Lz%tUm4g@R!>>~Z!L;&)BM;c;m{rfhYD-8wA2dpC_T);d4(&Yre zGBT>CG$P@$sE6Ei01I!^)ln;Q+ z;)!HR1_My91m$}{Z?E|P(7ln~do!6nsMBWzU^ZYmU<;Yu`U6G*AZxc3fbC@Z4g^dB zpnhMZ^+ny?ivg8@X21f#D!^7U_&zuNAhRE2_JhoRkhuqB?g5#5K<0pXWDZm=9mBg{IPeC z83sG5N0F(iBU1+-hHoKr9BdthvL`GcGa5D_PB$k({z;H|5@^KdX6y*SA~Fpcu#*hd zTr+MPneiwyei;CACJX>HkU14)PetBoNW;F`oDTl<4!|8`uqK-`TP>V5iOeL(YFbXF z8Ts*HWLnpdnL3+H+fXv?{mG>A$#l#olP)EbSxu%B<)%+0GYk3WY#?*)LNarR%ms7E z%pFMP;$p&42ANAC=W^s-0o$(HMCR(PWUfWpbxX)xzn;vEgUBo%L+0i>GJoGf1~Ij{ zwSmlSkb8T8%$>k@?IN>mIhlJ`lld3qEbjxD1K3XHz78_?k0P@IHm;aY=0W5=1iv0e z`by|{1oiQKZyuXK277ArILbY~oXitz$vin2u$av1fn=USI${9xj0cdt26)XjGS3Yn z^L#OxwWG+q0C_JW?ufyLr8^~;g ze{Umw6Xd4Q3bK=)lp&hCJIE?@&WdnC!(vp+d|)d7&+ zAF}pdMb4ifcOU#hW55ys^zOHroIe9f=a931DPScz2V}_^H~`R0&OyV-IT&e&RFX4j z204d~0Kk^P8_6kKO3smxeg@Yoz*MIsezta_+5wex-H}khpgeSeZ(AcM#8U=&~qHpj)$z{ zVaEx5$T_i;oY5`-GEV9TSV_*wLjjO87U^TxkkbI%fc$Y2$T?*IIpb%OGXeD`Y$oS4 zP0s1fq>P z4f-13Yk?PPa;`&}94yX~v$U0*+Xeu3lJigKxP2Zucgz8-C+E&Oa_*WB0DU*=KhQu9 z;(zDiq2xS*GLNDD6Y%j#l=0vxq(9vcu#z0idBU^Y_i!R>|3!f;jlauIQKr2=!#bX6 zg@>1CGObbOBBl)<#az#{_uOYG(*=}I_b^>d{pmsMa)mB{p3CSZrUeZYRlW9YL62CC*P+z$R(*@K=9>R1nZh%$PraES(6D_UT(oi5680>w9N~@bw zjq%d4Gc(zETc&hmds8aikxIw1@#eD9>SVI?WUoS|^yGLZo}Pxpv8m2Lu~N6o1<~ zr@^anGU{dkX*?>9Lvk5prl=Eq6ZldZgVHlVc{RsEOU#$z)oMYhB=|HQHhc9mD4Bx( zQrKJutG)k2ptX)_FxZCls9o=u%OcHsSOuz{M&YTs$N&H6|9@-jwlSyWMA+GeA?~%x zR9NY?Z5Hj5!PVa*@qfDizYLap<)(L)EAv~j&6_*Y`yl%LK^XXd`R7z&Vo9MeI`RF6 z`?7hM7X|oDVG-dGKQ0LOrat(kU0+;+=!bVF?ulyx{c(-pPx!Um0Nl0M50CRp@k`bN z@NU|HxXF1iX5k=Q5q!^JOs78wer13(Tbmp7B>uB`y#ig=QN{N8@7hu`~<~ zU4ubXho-K_)q@cj3dhm$Xw(yMb#^qm@g#KhSZbhg=&tcJ0T&NXqtj_3oq-vD7EPj< z&~z)!#hiZ}w*|K1*1(l?Io*WIh)adRe14lQq-*I4nn%xIO}UxwqVH)3{Xn;11$Y6M z8Scf9Y@!7iLN8)CtfhbB{aWj=3cQRz2QZmlqxJL(-n!L7+v#e0gErurWGj6^U*gi^ z6bziHX!<0&U)6gNGV*t*=KtG4hrP;KS{(*ORokw%%eA-4|(WAl< zc_LpFh(b|BztV4_Sd@rfqBq{`MWT<`P4pE)>@ND@_iKBKy+nVpHy#G>BL;|l#eTGt ze!|`@{+2M7(2cj5tZ0 zEbup-#W-;a{X)BF3w?Xvdq%I&f>`Q*pLPi;T$9KSigQCZ>xSVy2iS&JnZ4x#B#$&1{Z1UtAzA6m!Kz z;$m@$m?th3mx;^8d~t=i67M=&Ag&hIh-<|{ah>?9xLzz0H;5a>-^5~ZleiggLR%vK zA#M@3ilyQr)(^Db@1AI^DS{2+c5JH=1pXYq^JC4LpZ;cadtg~VT4ms%R>$UK=Z3uK`z zlEtz__QJc~`pDg6U%9*NC-;zh%DrTNxwrh2+(!9i>$IA)wRC$^_T~3r|$TRTCG z>*mPwmcgQ<&2mNljOx`2!mH(2<<$dyg`G8y@ACwQt zhviE7h+~!_^2i zQXQv`SEJMk>O?hKjZr75lhs(&pvI|F)Oa;PovKb#r>lwT40WbDOHEQS)u@_Ovx=+9 zsztS`gqotJs-$XD?JA`@)Y&SnGAgS&)igC-%}_JdEOm~Wt>U?#9x=_tk z7paTYC2F3!R9&VnSM${s>PmH$TA;30*QjgNLUo<`tGZq-Qa7j@)!)=&b(6YT{ar0l z|4_H6Th&r^oBF4^UEQJXRClSn)iQODx>x;6Em!xc`_%(#g?dmuq#jl))g$Ur^_W_v z9#>DOC)H~8lzLh{qt>Wr)pP23wN|~L{;gib+0Om+fO<*2tp0<0Y7f&IwVqZWrhJwz zpr>(t?r!x8J*Qq(uc;061U;!I3zm`bce2 zAFEH)r)sPEOnt7tP}|g(>MQlN+OEE#`RZHso!X(kS3js9)lT)3`dR&=cBx<0Z|Zj( zWeF{{!tXDPc66T3*9E#z7wKYMqI>Dyx{uyX_oYR8ce;^ops#g5y$9V!*XTX=^$a~z&(i1U+4@|4p8kuTqtDkD=nM5+eUZLcU!v#fOZ8>? zay?&Pp|8|e=>_^~eT}|WFVxrRzv}DtB7K9tQU6UZ);H;!_22ap{SSSMzEv;90r5Ze z?fMRVr@l+yt(WP0^u79Ddbz$&->)CgEA)fc{jd{kVQYKdD#ir}WeM z8NEh7tDn=)>$Um?{cruEUZ-EuFYEv4_4*b4s(wvx(68$^^qYF4eoMbikLY*wCjG8{ zPrt7>>kssY`XjwXf2=>zpX#mpGyS>#LT}Sw;&AnAy&cD<-|Fx54*fk2K!4Ob^-nn1 z{6+85zv|!g@3fkpGGv62Mj4IYz&-~exn*GfI=0G#h9ApkQhnPX;P;;0$+zd8Hm@;#u378<>*cdjh ziI{RzVJb}(UN1Go3^hlaW6ZH;n5i~3rq1Lui!<=c(GLuZqG@2&UY~p6JX)&!PVWya=CTZGCyGfZ2 zbGAvFjLDi#GtEplGt5jg%ba6on{&;1<}YTBIp17hE;Mt^Mdo62iJ50EHJ6#o&3to( zxzb!^7MQEeHRf8g&|GK!YOXhn%njy7^Eb2D++=Pxe>Y3aKg=!WRH?Npi&1+_ZdELBW-ZUG{Tjp)^j@e}1HSd}C&1Um~ z`Oti1wwRC2C+1VL)qG|?H(!`-=1cRH`PytZ-tA-uz&GG&{{t=4bPZ*=2q; zznR}1a)cutFHPL*?%GsGF{9PJ$A9P12os-2p=&h`X$rUAxv7FRQ0U6)tg7HdkU+Vf%-JJpTp z__Vka^HE-PswLGPpPCo5xTv-%k#6d2o1Bc#C~E4a3u~KG*;rFkygggk)I~aVO)-de znk}k>o>(?7$_nGGFlrUXeN-6js$CfGBJ-kbM%-d2Y9+>fR5ZNXkfN4ux^Q@xfWnq8 z(rNLLx)mSJ$ZR6n9Cs2v${We{C)oax*8YTTu#s$O!eTk{ zIGLDIbX>RGqAA_9wIN*2xT1J!OFABJPsZAt6HU&jSW{;0G~#<<6MJD7_)tKJQC|_ZF-dDM#Ii<+e%dnjnNhr-skkZao@6tQg-ir6*^MXWEO zNG{)68;V$KLp-WO5tb8SeG%3dVSN$S7qM*=irBshMQq=M%2_Xu@K8DHD`$P>tgqbm zNvNFlm9xHb)>F=U%2`jj9Sfmy)>qE@Dp+3y`&q$$R#Jb@Dp+p? z>#bmY6|9fPa;TE^RI;8*)>FxPD%qb(mS4&8c@BgsS$-w^RmplPb9&gXD)y_2^;NOH zD%MlQ{#3D^D%MlQa;sQw70az+xz#MMn&nlqylQTjYPP?I<<+pgH7vJ=<<+pfT9#MK zcGPmcT5iu;u3yXbdG3aI?uP1EUX=Zfx&C{xYm3=l$IM}U`ZQ!I=rawi zptPsZ93SiGh#@{{Yiy3m6FTLIopM3~k%Mo69FtHdx2DY4L`z#tHN-me*cvsaHKA$& zV=@V=rn;)wlb5roHy6zDg|V)ND315=C?AH{=JEXTseYX{KUdXHG=6>Eh~lQjbIvr! zli64v%hI!up}fqj@0usfO!Zw$`mR|cYiebCr<{?AnOm>N$Upfq;-RK(z?7-xV%!hyi&NlQnIiQN$U&mq1W zV*h#3aCy;idC_or(QtXuaCy;idC_n~cGA1Nc(@__3P7E=MD7nA@Kh9hVm!mlqwE7af-u9XHJF@3P-6`^}4y%h8SNa=Y{5<8oBv^5X0A;_Gs>;BvI!a%%kuZG7>4cD(> zdurI88kWb=hFi<^Il6FpN8r}7e2yYqjw0MT?vFYihaB;{yw13FJRa(JJaA<1M%kVy z+Y@DbqFg?jE6@H#S${qIQ*Y}>qQ3rMon3c=QM(QS`|^PO@doVoH?S`c*q0ZK+I0rl zZ!ciq9$>C-*MVTvt^>eUU)0Wf@Lb-`d+@&fz`p&!e)|IZ{S}Pbc?|5^56trHJO^ug~^6WeY&+_a%2G9P4*&jQ9gHbzwf&KOZW_@=4g7^CinDyED3!e4a`5TPd`3KDP z?fe4I{@M8jp8d1)3p}^ConPSDKD$c^Mr|+y%=X#&2A=!N?mED;e|FaqjM`lRaJ@6$ z4_BS(7LE5qR%g183dZNYDhsCPC~y4?Ms2hY%=Xy%8;sid3(WS|`3s)=$433&xxK0? z3WvwjZ8%$LOlGWTJ9UFmJ9U9sl%2ZZSyVMQy$$@4&O@!5o4%Tx)=qWgbJJFH)8LrR zccq$}#!mfU)J}b19?Ev=gAd#CHLSme_1mQY`K-T&_1g^y(s`)ar2#zauVMXmLxOZ} zO1pFfqju>4X8&the=X~;W&O3>l(nqiZe&o8{kIz#@a(_c$be`4_HY6`>$gi&Flv`3 zVAgLBC&06QyHtT^{dTDW&;Hw`3OxI7LnZL+zYUqdbNk!SDHydIDPV4YyVQYa|9Pp3 zM%jP6^dX=9w@V**_CLz{?J)<^S$~xMkFtKdGzO#f{?ZK`bWXK%Vy2I5@7qHXwT|60lR>Em#hjWCt5nw@n#&+c&;GHcLBJ(q_e#lSEg{^Dvo(ra&{;7H{OAB zIumcUWkdB&TcX`N`OIKv+s>)sNI`r?6XJ5zvgK?X6bjh&6xbhBp@3Z%f&IY&TvgJP z!p^A;2ie(JdZtb$(lINdy4>l&4KHj7d@HM~bTdxmbhI;_;uLnxPS8-mPEcTfU;?vW zb|QlJy8_rB2*7?5g@QKb2Da%z8xx0u_PYz%@6u2(6ex&ivN&1BMKs@nppC9VK^t8G zhw?o?S`+E!g6#B^Pxz}DvaB&d8(@KFq4o;^yfrQusV;zsxOc;?wK&^~6K6|jiY6zf z<>=xJI;EX6tSOaM1+jEGH9Z-hoXzv8&W=JK``7DiW^-zKyQLaooTWNDn*GyC9LV`Y z7?@47u@(5LViwrk-k6D720iVmvMRuIc9i&qK>7;1O8Gj`gWf2LC5z%Su#v`-h(yee z6nO4C05yfFbhgzy(2XUF6YUsz7-Csm-?RDQilX@0or!6&WW2p8Zdy~FnRp2XXEN22 zXo@AV75B^WnMEjUcOt-cl;F&w&=ZO&oXmD~k&U)i#O}{<*yt}d!2V(f>@POJc1%Th ztVC>Fgml|h5xYMFZ@V$VV<}?yXGr%K8(=%;A~tRUZ|6nC#!WcnWc_x32A=iX{TX=M zS`i+b5gwZnyFWud>$m$e@cv>C%=)=iBHStwyFbIBs=wF)vwpij1JC+xoCcot+c*t8 z`)}hk@T}j)Y2aDE-JgMH|Ly(^Jo|6=XE@Yl{Wh)x&-!g#2cGrYxDGt)w{abK)^FoF z@T}kN&%m>OUY8=gE=6pd7mC<8519Mk#(Chm|81NH-p&>LX-9w5MR+}n*fh>asrp8dCR zBzX4U#*yGzzl|etc+Tx_<4EwV-=2DdXZ`ln8$A1OPrbo&``goR@T}j)q2O7+jYGk+ zetY_jLwnY5_le+HzuhN-XZ`l{8$9c`r{LgOzdghQ&-yF4{q1Qu(z*RBpueLrnQEG9 z`_Z0?ha&b=95|eZMXyIb>#yYgudFaVb6LARBS-C;yyWDb`Ru)onvq|hm(JS!WEM|M zn&Za*%xlIaV{q0c8#9L@Hf9E9-Mmgmc%6>eJuvcl?Abjqc=p|%q=RSQtLh6TC$Ne) zH>PG-O*YgHMQo@HT!Uc2E0M-ZoR>`}VlAB=wy2GQLlOS6jqsOk#74nL=Z@ho-w3{Z z{rYK^!(TcP{t}MZxCr%lK-jn_6tQs;Fi%KCzrLL0WNU|wdXZn{c+dBe+-5eoMRI*X zqJ3IpC!EN7lr2-3>WH`7L|;ldFTdry{FVnJ1(~)4zTjd_aeNm|>ta-* z(E6L9@^X!5*x9_1$Ju>gOV7LtjR)J=yu=hg&*0&7Sxm*sV)-#YK=uefrz}wf6! zCHqp!Vntm?uVt|UzT4ns`wxN3Vu)Yxn6WH|7$Wz$5#?Z408daYg>780iwbv9kuIvD zi>m6PYPzVpE~-99)pW_L>5^B|C9lSl$M-DYER3`E)T2a6k82I^qJ_OE?!MNLD;4`dsJvH%vEi+9(&o%gX>ks5n<1Wr2n9}EarF@B|L;7)@2g+9CSjEH)R9i+K}TtI{@oHAjl=x zMGOx6y^)uDI8)XX>nQM#>^-W;8{XJ`b<_3HZaUgCy{|l3CY-uI(YG z&Zqt1f@5fQ2?u`YHqQ|pNtb2PjW$10p39GLI|Xo5h}M`DPp8TP23rFZ@R<~TdE)Ih z%6tO54WB6Vw+ud6Fd09KVG7Z=Etg5mu;u(6geCobge};fV1ap7Vao9}rt*CjuI72X zhdqVB1y&!Zd|#bUc-p)&o-(hD)#XuEl~>5ueN#QES4S+33qaWR_{DqstiRjw`JxV3oNBhB@I16F=99L&GM$Y%%RpGm8oMoP z?6S(KG1F}s>gOy&eZTFS+f#NR`GjYgM{t{Y zq-`@xd6xO*Jj?uY)-s>8miYyJ%RH(_n|W+c%RD}3nMbo_9%(JZ2v7UH=@Xu19>JD* zq_xaao@IVH&oaN9wah22Wqv{5GLPzEnaB3D%;R&Gc{E$*ktL~gGrs>5zL=O}yVz;; zd)jHT$PNL=jxJ}iO>D8K)uMz&Q!JWlQPQF|i`p%+L)huC+}ReTEy`GAhojSJxoH+n zx5$ocXQt(5`KZX-1@qu9vUhg;^kTljXBmHuk5u(w%FBJmIvsO-k7ADRN(@beV;n!f zh$pN!Ws2hM-a(U1kGFNvg?6={jQeQC{&HSZTZV&`R?AemJ(%h$pV1wuREJ5YaB@-N zoA2E?@RpbSX$TWyO>K<`HgGW2mFzG1NcLA*T)FVdmULaa@HozKd(e>{ba`3R%rut) z9qZ`jTN?1wyQ^39s8Z?CyZzy&Mt3gOtL|FWJ?NSqbgf6{@s}qn@A@(c;3kKcpC8L$ zt$}a(I1EW-K=@xD;Sjz)Y@JXKt3tk2Hr@4`p-(Pcur1cs*zdm+JS|@kN-*eg9Uzjpjelkxdg9N-|m!imwXQc-JnW_I<~McMwMeh8 z{wKw^!XJ}44@-P5=Yi#NHkJ5;wyUJib(QqFT**T0uA1UyU8@8(IXSs!kBqW-ubx?Dac{bWu-jt_o3XNZp1*%9i#y(C4jH_WLx#V{D~lI( z?E)dM=cX_xrQ9ux=QpJ$PmaeS##^sY* Date: Fri, 1 Jul 2022 21:11:45 +0200 Subject: [PATCH 306/355] [kandinsky | code] Coding style improvement in the new font (part 1) --- apps/code/python_text_area.cpp | 96 ++---------------------------- escher/include/escher/text_area.h | 4 +- escher/src/text_area.cpp | 16 ++--- kandinsky/Makefile | 5 +- kandinsky/fonts/rasterizer.c | 11 +++- kandinsky/include/kandinsky/font.h | 1 + kandinsky/src/font.cpp | 10 ++++ 7 files changed, 36 insertions(+), 107 deletions(-) diff --git a/apps/code/python_text_area.cpp b/apps/code/python_text_area.cpp index a0e7a5a407d..da2274cb758 100644 --- a/apps/code/python_text_area.cpp +++ b/apps/code/python_text_area.cpp @@ -31,95 +31,6 @@ bool isItalic(mp_token_kind_t tokenKind) { if (tokenKind == MP_TOKEN_STRING) { return true; } - - static_assert(MP_TOKEN_ELLIPSIS + 1 == MP_TOKEN_KW_FALSE - && MP_TOKEN_KW_FALSE + 1 == MP_TOKEN_KW_NONE - && MP_TOKEN_KW_NONE + 1 == MP_TOKEN_KW_TRUE - && MP_TOKEN_KW_TRUE + 1 == MP_TOKEN_KW___DEBUG__ - && MP_TOKEN_KW___DEBUG__ + 1 == MP_TOKEN_KW_AND - && MP_TOKEN_KW_AND + 1 == MP_TOKEN_KW_AS - && MP_TOKEN_KW_AS + 1 == MP_TOKEN_KW_ASSERT - /* Here there are keywords that depend on MICROPY_PY_ASYNC_AWAIT, we do - * not test them */ - && MP_TOKEN_KW_BREAK + 1 == MP_TOKEN_KW_CLASS - && MP_TOKEN_KW_CLASS + 1 == MP_TOKEN_KW_CONTINUE - && MP_TOKEN_KW_CONTINUE + 1 == MP_TOKEN_KW_DEF - && MP_TOKEN_KW_DEF + 1 == MP_TOKEN_KW_DEL - && MP_TOKEN_KW_DEL + 1 == MP_TOKEN_KW_ELIF - && MP_TOKEN_KW_ELIF + 1 == MP_TOKEN_KW_ELSE - && MP_TOKEN_KW_ELSE + 1 == MP_TOKEN_KW_EXCEPT - && MP_TOKEN_KW_EXCEPT + 1 == MP_TOKEN_KW_FINALLY - && MP_TOKEN_KW_FINALLY + 1 == MP_TOKEN_KW_FOR - && MP_TOKEN_KW_FOR + 1 == MP_TOKEN_KW_FROM - && MP_TOKEN_KW_FROM + 1 == MP_TOKEN_KW_GLOBAL - && MP_TOKEN_KW_GLOBAL + 1 == MP_TOKEN_KW_IF - && MP_TOKEN_KW_IF + 1 == MP_TOKEN_KW_IMPORT - && MP_TOKEN_KW_IMPORT + 1 == MP_TOKEN_KW_IN - && MP_TOKEN_KW_IN + 1 == MP_TOKEN_KW_IS - && MP_TOKEN_KW_IS + 1 == MP_TOKEN_KW_LAMBDA - && MP_TOKEN_KW_LAMBDA + 1 == MP_TOKEN_KW_NONLOCAL - && MP_TOKEN_KW_NONLOCAL + 1 == MP_TOKEN_KW_NOT - && MP_TOKEN_KW_NOT + 1 == MP_TOKEN_KW_OR - && MP_TOKEN_KW_OR + 1 == MP_TOKEN_KW_PASS - && MP_TOKEN_KW_PASS + 1 == MP_TOKEN_KW_RAISE - && MP_TOKEN_KW_RAISE + 1 == MP_TOKEN_KW_RETURN - && MP_TOKEN_KW_RETURN + 1 == MP_TOKEN_KW_TRY - && MP_TOKEN_KW_TRY + 1 == MP_TOKEN_KW_WHILE - && MP_TOKEN_KW_WHILE + 1 == MP_TOKEN_KW_WITH - && MP_TOKEN_KW_WITH + 1 == MP_TOKEN_KW_YIELD - && MP_TOKEN_KW_YIELD + 1 == MP_TOKEN_OP_ASSIGN - && MP_TOKEN_OP_ASSIGN + 1 == MP_TOKEN_OP_TILDE, - "MP_TOKEN order changed, so Code::PythonTextArea::TokenColor might need to change too."); - if (tokenKind >= MP_TOKEN_KW_FALSE && tokenKind <= MP_TOKEN_KW_YIELD) { - return true; - } - static_assert(MP_TOKEN_OP_TILDE + 1 == MP_TOKEN_OP_LESS - && MP_TOKEN_OP_LESS + 1 == MP_TOKEN_OP_MORE - && MP_TOKEN_OP_MORE + 1 == MP_TOKEN_OP_DBL_EQUAL - && MP_TOKEN_OP_DBL_EQUAL + 1 == MP_TOKEN_OP_LESS_EQUAL - && MP_TOKEN_OP_LESS_EQUAL + 1 == MP_TOKEN_OP_MORE_EQUAL - && MP_TOKEN_OP_MORE_EQUAL + 1 == MP_TOKEN_OP_NOT_EQUAL - && MP_TOKEN_OP_NOT_EQUAL + 1 == MP_TOKEN_OP_PIPE - && MP_TOKEN_OP_PIPE + 1 == MP_TOKEN_OP_CARET - && MP_TOKEN_OP_CARET + 1 == MP_TOKEN_OP_AMPERSAND - && MP_TOKEN_OP_AMPERSAND + 1 == MP_TOKEN_OP_DBL_LESS - && MP_TOKEN_OP_DBL_LESS + 1 == MP_TOKEN_OP_DBL_MORE - && MP_TOKEN_OP_DBL_MORE + 1 == MP_TOKEN_OP_PLUS - && MP_TOKEN_OP_PLUS + 1 == MP_TOKEN_OP_MINUS - && MP_TOKEN_OP_MINUS + 1 == MP_TOKEN_OP_STAR - && MP_TOKEN_OP_STAR + 1 == MP_TOKEN_OP_AT - && MP_TOKEN_OP_AT + 1 == MP_TOKEN_OP_DBL_SLASH - && MP_TOKEN_OP_DBL_SLASH + 1 == MP_TOKEN_OP_SLASH - && MP_TOKEN_OP_SLASH + 1 == MP_TOKEN_OP_PERCENT - && MP_TOKEN_OP_PERCENT + 1 == MP_TOKEN_OP_DBL_STAR - && MP_TOKEN_OP_DBL_STAR + 1 == MP_TOKEN_DEL_PIPE_EQUAL - && MP_TOKEN_DEL_PIPE_EQUAL + 1 == MP_TOKEN_DEL_CARET_EQUAL - && MP_TOKEN_DEL_CARET_EQUAL + 1 == MP_TOKEN_DEL_AMPERSAND_EQUAL - && MP_TOKEN_DEL_AMPERSAND_EQUAL + 1 == MP_TOKEN_DEL_DBL_LESS_EQUAL - && MP_TOKEN_DEL_DBL_LESS_EQUAL + 1 == MP_TOKEN_DEL_DBL_MORE_EQUAL - && MP_TOKEN_DEL_DBL_MORE_EQUAL + 1 == MP_TOKEN_DEL_PLUS_EQUAL - && MP_TOKEN_DEL_PLUS_EQUAL + 1 == MP_TOKEN_DEL_MINUS_EQUAL - && MP_TOKEN_DEL_MINUS_EQUAL + 1 == MP_TOKEN_DEL_STAR_EQUAL - && MP_TOKEN_DEL_STAR_EQUAL + 1 == MP_TOKEN_DEL_AT_EQUAL - && MP_TOKEN_DEL_AT_EQUAL + 1 == MP_TOKEN_DEL_DBL_SLASH_EQUAL - && MP_TOKEN_DEL_DBL_SLASH_EQUAL + 1 == MP_TOKEN_DEL_SLASH_EQUAL - && MP_TOKEN_DEL_SLASH_EQUAL + 1 == MP_TOKEN_DEL_PERCENT_EQUAL - && MP_TOKEN_DEL_PERCENT_EQUAL + 1 == MP_TOKEN_DEL_DBL_STAR_EQUAL - && MP_TOKEN_DEL_DBL_STAR_EQUAL + 1 == MP_TOKEN_DEL_PAREN_OPEN - && MP_TOKEN_DEL_PAREN_OPEN + 1 == MP_TOKEN_DEL_PAREN_CLOSE - && MP_TOKEN_DEL_PAREN_CLOSE + 1 == MP_TOKEN_DEL_BRACKET_OPEN - && MP_TOKEN_DEL_BRACKET_OPEN + 1 == MP_TOKEN_DEL_BRACKET_CLOSE - && MP_TOKEN_DEL_BRACKET_CLOSE + 1 == MP_TOKEN_DEL_BRACE_OPEN - && MP_TOKEN_DEL_BRACE_OPEN + 1 == MP_TOKEN_DEL_BRACE_CLOSE - && MP_TOKEN_DEL_BRACE_CLOSE + 1 == MP_TOKEN_DEL_COMMA - && MP_TOKEN_DEL_COMMA + 1 == MP_TOKEN_DEL_COLON - && MP_TOKEN_DEL_COLON + 1 == MP_TOKEN_DEL_PERIOD - && MP_TOKEN_DEL_PERIOD + 1 == MP_TOKEN_DEL_SEMICOLON - && MP_TOKEN_DEL_SEMICOLON + 1 == MP_TOKEN_DEL_EQUAL - && MP_TOKEN_DEL_EQUAL + 1 == MP_TOKEN_DEL_MINUS_MORE, - "MP_TOKEN order changed, so Code::PythonTextArea::TokenColor might need to change too."); - - return false; } @@ -393,8 +304,8 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char // If the token is being autocompleted, use DefaultColor/Font KDColor color = (tokenFrom <= autocompleteStart && autocompleteStart < tokenEnd) ? Palette::CodeText : TokenColor(lex->tok_kind); - bool font = (tokenFrom <= autocompleteStart && autocompleteStart < tokenEnd) ? false:isItalic(lex->tok_kind); - + bool italic = (tokenFrom <= autocompleteStart && autocompleteStart < tokenEnd) ? false : isItalic(lex->tok_kind); + LOG_DRAW("Draw \"%.*s\" for token %d\n", tokenLength, tokenFrom, lex->tok_kind); drawStringAt(ctx, line, UTF8Helper::GlyphOffsetAtCodePoint(text, tokenFrom), @@ -405,7 +316,8 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char selectionStart, selectionEnd, HighlightColor, - font); + italic + ); mp_lexer_to_next(lex); LOG_DRAW("Pop token %d\n", lex->tok_kind); diff --git a/escher/include/escher/text_area.h b/escher/include/escher/text_area.h index 827ea73141a..efd31363303 100644 --- a/escher/include/escher/text_area.h +++ b/escher/include/escher/text_area.h @@ -111,12 +111,10 @@ class TextArea : public TextInput, public InputEventHandler { m_cursorLocation = m_text.text(); } void drawRect(KDContext * ctx, KDRect rect) const override; - void drawStringAt(KDContext * ctx, int line, int column, const char * text, int length, KDColor textColor, KDColor backgroundColor, const char * selectionStart, const char * selectionEnd, KDColor backgroundHighlightColor,bool isItalic = false) const; + void drawStringAt(KDContext * ctx, int line, int column, const char * text, int length, KDColor textColor, KDColor backgroundColor, const char * selectionStart, const char * selectionEnd, KDColor backgroundHighlightColor, bool isItalic = false) const; virtual void drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn, const char * selectionStart, const char * selectionEnd) const = 0; virtual void clearRect(KDContext * ctx, KDRect rect) const = 0; KDSize minimalSizeForOptimalDisplay() const override; - KDFont * usedFont; - KDFont * ItalicFont; void setText(char * textBuffer, size_t textBufferSize); const char * text() const override { return m_text.text(); } const char * editedText() const override { return m_text.text(); } diff --git a/escher/src/text_area.cpp b/escher/src/text_area.cpp index ddbe3170171..c3867ef73db 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -516,13 +516,15 @@ void TextArea::ContentView::drawRect(KDContext * ctx, KDRect rect) const { void TextArea::ContentView::drawStringAt(KDContext * ctx, int line, int column, const char * text, int length, KDColor textColor, KDColor backgroundColor, const char * selectionStart, const char * selectionEnd, KDColor backgroundHighlightColor, bool isItalic) const { if (length < 0) { return; - } - const KDFont * ItalicFont = (m_font == KDFont::LargeFont) ? KDFont::ItalicLargeFont : KDFont::ItalicSmallFont; - const KDFont * usedFont = isItalic ? ItalicFont : m_font; - - + } + + const KDFont * usedFont = m_font; + if (isItalic) { + usedFont = m_font->toItalic(); + } + KDSize glyphSize = usedFont->glyphSize(); - + bool drawSelection = selectionStart != nullptr && selectionEnd > text && selectionStart < text + length; KDPoint nextPoint = ctx->drawString( @@ -563,7 +565,7 @@ KDSize TextArea::ContentView::minimalSizeForOptimalDisplay() const { return KDSize( /* We take into account the space required to draw a cursor at the end of * line by adding glyphSize.width() to the width. */ - span.width() + m_font->glyphSize().width() + 4, + span.width() + m_font->glyphSize().width(), span.height() ); } diff --git a/kandinsky/Makefile b/kandinsky/Makefile index c67fc3e0107..3b8fabb7213 100644 --- a/kandinsky/Makefile +++ b/kandinsky/Makefile @@ -87,12 +87,13 @@ simple_kandinsky_src += $(addprefix kandinsky/fonts/, \ $(eval $(call raster_font,SmallFont,SmallFontExtended,1,12,7,14)) -$(eval $(call raster_font,ItalicSmallFont,ItalicSmallFontExtended,1,12,7,14)) $(eval $(call raster_font,LargeFont,LargeFontExtended,1,16,10,18)) +$(eval $(call raster_font,ItalicSmallFont,ItalicSmallFontExtended,1,12,7,14)) $(eval $(call raster_font,ItalicLargeFont,ItalicLargeFontExtended,1,16,10,18)) $(eval $(call raster_font,SmallFont,SmallFontSimple,0,12,7,14)) $(eval $(call raster_font,LargeFont,LargeFontSimple,0,16,10,18)) + else kandinsky_src += $(addprefix kandinsky/fonts/, \ @@ -106,7 +107,7 @@ default_kandinsky_src = $(kandinsky_src) simple_kandinsky_src = $(kandinsky_src) $(eval $(call raster_font,SmallFont,SmallFontSimple,0,12,7,14)) -$(eval $(call raster_font,ItalicSmallFont,ItalicSmallFontSimple,0,12,7,14)) $(eval $(call raster_font,LargeFont,LargeFontSimple,0,16,10,18)) +$(eval $(call raster_font,ItalicSmallFont,ItalicSmallFontSimple,0,12,7,14)) $(eval $(call raster_font,ItalicLargeFont,ItalicLargeFontSimple,0,16,10,18)) endif diff --git a/kandinsky/fonts/rasterizer.c b/kandinsky/fonts/rasterizer.c index b08f95e21b2..3a51da86d64 100644 --- a/kandinsky/fonts/rasterizer.c +++ b/kandinsky/fonts/rasterizer.c @@ -123,15 +123,20 @@ int main(int argc, char * argv[]) { } int glyph_width = maxWidth-1; - if (glyph_width == 9) { glyph_width = 10; } /* This was made to avoid a problem, the ratio of the width by the height was not */ - if (glyph_width == 8) { glyph_width = 7; } /* adequate and the fonts couldn't be compiled, this is useless with other fonts */ + if (glyph_width == 9) { // FIXME: This is a TEMPORARY FIX, we should instead fix the ttf file + glyph_width += 1; + } else if (glyph_width == 8) { + glyph_width -= 1; + } if (packed_glyph_width != 0) { ENSURE(glyph_width == packed_glyph_width, "Expecting a packed glyph width of %d but got %d instead", packed_glyph_width, glyph_width); } else { printf("Computed packed_glyph_width = %d\n", glyph_width); } int glyph_height = maxAboveBaseline+maxBelowBaseline; - if (glyph_height == 13) { glyph_height = 14;} /* Same problem */ + if (glyph_height == 13) { // FIXME: Same here + glyph_height += 1; + } if (packed_glyph_height != 0) { ENSURE(glyph_height == packed_glyph_height, "Expecting a packed glyph height of %d but got %d instead", packed_glyph_height, glyph_height); } else { diff --git a/kandinsky/include/kandinsky/font.h b/kandinsky/include/kandinsky/font.h index 20eeb8a5119..68cae667d70 100644 --- a/kandinsky/include/kandinsky/font.h +++ b/kandinsky/include/kandinsky/font.h @@ -78,6 +78,7 @@ class KDFont { return RenderPalette::Gradient(textColor, backgroundColor); } KDSize glyphSize() const { return m_glyphSize; } + const KDFont * toItalic() const; // Return the font in italic style, or the font itself if it is already italic constexpr KDFont(size_t tableLength, const CodePointIndexPair * table, KDCoordinate glyphWidth, KDCoordinate glyphHeight, const uint16_t * glyphDataOffset, const uint8_t * data) : m_tableLength(tableLength), m_table(table), m_glyphSize(glyphWidth, glyphHeight), m_glyphDataOffset(glyphDataOffset), m_data(data) { } diff --git a/kandinsky/src/font.cpp b/kandinsky/src/font.cpp index 7889fde815a..fe7c4f12f04 100644 --- a/kandinsky/src/font.cpp +++ b/kandinsky/src/font.cpp @@ -41,6 +41,16 @@ KDSize KDFont::stringSizeUntil(const char * text, const char * limit) const { return stringSize; } +const KDFont * KDFont::toItalic() const { + if (this == KDFont::LargeFont) { + return KDFont::ItalicLargeFont; + } else if (this == KDFont::SmallFont) { + return KDFont::ItalicSmallFont; + } + + return this; +} + void KDFont::setGlyphGrayscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const { fetchGrayscaleGlyphAtIndex(indexForCodePoint(codePoint), glyphBuffer->grayscaleBuffer()); } From a1e213ac91466c29a635c73c697a0c83f7eb6582 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Mon, 4 Jul 2022 11:09:58 +0200 Subject: [PATCH 307/355] [apps] Add Shift + Ans shortcut to go to the last application (#196) --- apps/apps_container.cpp | 22 ++++++++++++++++++++++ apps/apps_container.h | 3 +++ apps/apps_container_storage.cpp | 17 +++++++++++++++++ apps/apps_container_storage.h | 1 + ion/include/ion/events.h | 1 + 5 files changed, 44 insertions(+) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index 336b9e98916..7e63f750315 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -225,6 +225,7 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { return didProcessEvent || alphaLockWantsRedraw; } +// List of keys that are used to switch between apps, in order of app to go (eg. 0 : First App, 1 : Second App, 2 : Third App, ...) static constexpr Ion::Events::Event switch_events[] = { Ion::Events::ShiftSeven, Ion::Events::ShiftEight, Ion::Events::ShiftNine, Ion::Events::ShiftFour, Ion::Events::ShiftFive, Ion::Events::ShiftSix, @@ -236,14 +237,17 @@ bool AppsContainer::processEvent(Ion::Events::Event event) { // Warning: if the window is dirtied, you need to call window()->redraw() if (event == Ion::Events::USBPlug) { if (Ion::USB::isPlugged()) { + // If the exam mode is enabled, we ask to disable it, else, we enable USB if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { displayExamModePopUp(GlobalPreferences::ExamMode::Off); window()->redraw(); } else { Ion::USB::enable(); } + // Update brightness when USB is plugged Ion::Backlight::setBrightness(GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()); } else { + // If the USB isn't plugged in USBPlug event, we disable USB Ion::USB::disable(); } return true; @@ -274,20 +278,38 @@ bool AppsContainer::processEvent(Ion::Events::Event event) { return true; } + // Add Shift + Ans shortcut to go to the previous app + if (event == Ion::Events::ShiftAns) { + switchTo(appSnapshotAtIndex(m_lastAppIndex)); + return true; + } + + // If the event is the OnOff key, we suspend the calculator. if (event == Ion::Events::OnOff) { suspend(true); return true; } + // If the event is a brightness event, we update the brightness according to the event. if (event == Ion::Events::BrightnessPlus || event == Ion::Events::BrightnessMinus) { int delta = Ion::Backlight::MaxBrightness/GlobalPreferences::NumberOfBrightnessStates; int NumberOfStepsPerShortcut = GlobalPreferences::sharedGlobalPreferences()->brightnessShortcut(); int direction = (event == Ion::Events::BrightnessPlus) ? NumberOfStepsPerShortcut*delta : -delta*NumberOfStepsPerShortcut; GlobalPreferences::sharedGlobalPreferences()->setBrightnessLevel(GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()+direction); } + // Else, the event was not processed. return false; } bool AppsContainer::switchTo(App::Snapshot * snapshot) { + // Get app index of the snapshot + int m_appIndexToSwitch = appIndexFromSnapshot(snapshot); + // If the app is home, skip app index saving + if (m_appIndexToSwitch != 0) { + // Save last app index + m_lastAppIndex = m_currentAppIndex; + // Save current app index + m_currentAppIndex = m_appIndexToSwitch; + } if (s_activeApp && snapshot != s_activeApp->snapshot()) { resetShiftAlphaStatus(); } diff --git a/apps/apps_container.h b/apps/apps_container.h index a8fca78ddcf..c6c53b0fe32 100644 --- a/apps/apps_container.h +++ b/apps/apps_container.h @@ -28,6 +28,7 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag static bool poincareCircuitBreaker(); virtual int numberOfApps() = 0; virtual App::Snapshot * appSnapshotAtIndex(int index) = 0; + virtual int appIndexFromSnapshot(App::Snapshot * snapshot) = 0; App::Snapshot * initialAppSnapshot(); App::Snapshot * hardwareTestAppSnapshot(); App::Snapshot * onBoardingAppSnapshot(); @@ -67,6 +68,8 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag static KDColor k_promptFGColors[]; static KDColor k_promptBGColors[]; static int k_promptNumberOfMessages; + int m_currentAppIndex; // Home isn't included after the second app switching + int m_lastAppIndex; AppsWindow m_window; EmptyBatteryWindow m_emptyBatteryWindow; Shared::GlobalContext m_globalContext; diff --git a/apps/apps_container_storage.cpp b/apps/apps_container_storage.cpp index 2ef60aafa5c..ad2dec6197c 100644 --- a/apps/apps_container_storage.cpp +++ b/apps/apps_container_storage.cpp @@ -36,3 +36,20 @@ App::Snapshot * AppsContainerStorage::appSnapshotAtIndex(int index) { assert(index >= 0 && index < k_numberOfCommonApps); return snapshots[index]; } + +// Get the index of the app from its snapshot +int AppsContainerStorage::appIndexFromSnapshot(App::Snapshot * snapshot) { + App::Snapshot * snapshots[] = { + homeAppSnapshot() + APPS_CONTAINER_SNAPSHOT_LIST + }; + assert(sizeof(snapshots)/sizeof(snapshots[0]) == k_numberOfCommonApps); + for (int i = 0; i < k_numberOfCommonApps; i++) { + if (snapshots[i] == snapshot) { + return i; + } + } + // Achievement unlock : how did you get here ? + assert(false); + return NULL; +} diff --git a/apps/apps_container_storage.h b/apps/apps_container_storage.h index 9abd3c27ad4..c3191a751da 100644 --- a/apps/apps_container_storage.h +++ b/apps/apps_container_storage.h @@ -12,6 +12,7 @@ class AppsContainerStorage : public AppsContainer { AppsContainerStorage(); int numberOfApps() override; App::Snapshot * appSnapshotAtIndex(int index) override; + int appIndexFromSnapshot(App::Snapshot * snapshot) override; void * currentAppBuffer() override { return &m_apps; }; private: union Apps { diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index 51c8da397b7..e7ae2feb2b1 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -185,6 +185,7 @@ constexpr Event ShiftSeven = Event::ShiftKey(Keyboard::Key::Seven); constexpr Event ShiftEight = Event::ShiftKey(Keyboard::Key::Eight); constexpr Event ShiftNine = Event::ShiftKey(Keyboard::Key::Nine); +constexpr Event ShiftAns = Event::ShiftKey(Keyboard::Key::Ans); constexpr Event ShiftEXE = Event::ShiftKey(Keyboard::Key::EXE); // Alpha From 77167d17068fd260bb8994f781d4ee72a033d1cd Mon Sep 17 00:00:00 2001 From: Laury Date: Sun, 3 Jul 2022 21:07:07 +0200 Subject: [PATCH 308/355] [statistics] Added 3 new measurements --- apps/statistics/base.de.i18n | 3 ++ apps/statistics/base.en.i18n | 3 ++ apps/statistics/base.es.i18n | 3 ++ apps/statistics/base.fr.i18n | 3 ++ apps/statistics/base.hu.i18n | 3 ++ apps/statistics/base.it.i18n | 3 ++ apps/statistics/base.nl.i18n | 3 ++ apps/statistics/base.pt.i18n | 3 ++ apps/statistics/calculation_controller.cpp | 7 +++- apps/statistics/calculation_controller.h | 2 +- apps/statistics/store.cpp | 44 ++++++++++++++++++++++ apps/statistics/store.h | 3 ++ 12 files changed, 77 insertions(+), 3 deletions(-) diff --git a/apps/statistics/base.de.i18n b/apps/statistics/base.de.i18n index 256fae301a4..bb7260e8450 100644 --- a/apps/statistics/base.de.i18n +++ b/apps/statistics/base.de.i18n @@ -24,3 +24,6 @@ SampleStandardDeviationS = "Standardabweichung s" SumValues = "Summe" SumSquareValues = "Quadratsumme" InterquartileRange = "Interquartilsabstand" +GeometricMean = "Geometrisches Mittel" +HarmonicMean = "Harmonische Mittel" +StatisticsMode = "Modus " diff --git a/apps/statistics/base.en.i18n b/apps/statistics/base.en.i18n index 645eeb47461..70a86dda6b9 100644 --- a/apps/statistics/base.en.i18n +++ b/apps/statistics/base.en.i18n @@ -24,3 +24,6 @@ SampleStandardDeviationS = "Sample std deviation s" SumValues = "Sum of values" SumSquareValues = "Sum of squared values" InterquartileRange = "Interquartile range" +GeometricMean = "Geometric mean" +HarmonicMean = "Harmonic Mean" +StatisticsMode = "Mode" diff --git a/apps/statistics/base.es.i18n b/apps/statistics/base.es.i18n index 2eb0c5f5b94..c38da099c00 100644 --- a/apps/statistics/base.es.i18n +++ b/apps/statistics/base.es.i18n @@ -24,3 +24,6 @@ SampleStandardDeviationS = "Desviación típica s" SumValues = "Suma" SumSquareValues = "Suma cuadrados" InterquartileRange = "Rango intercuartilo" +GeometricMean = "Significado geometrico" +HarmonicMean = "Significado armonico" +StatisticsMode = "Modo" diff --git a/apps/statistics/base.fr.i18n b/apps/statistics/base.fr.i18n index c3459bbdc71..dd97cb98593 100644 --- a/apps/statistics/base.fr.i18n +++ b/apps/statistics/base.fr.i18n @@ -16,6 +16,9 @@ RectangleWidth = "Largeur des rectangles" BarStart = "Début de la série" FirstQuartile = "Premier quartile" Median = "Médiane" +GeometricMean = "Moyenne géométrique" +HarmonicMean = "Moyenne harmonique" +StatisticsMode = "Mode" ThirdQuartile = "Troisième quartile" TotalFrequency = "Effectif total" Range = "Étendue" diff --git a/apps/statistics/base.hu.i18n b/apps/statistics/base.hu.i18n index 82da786c60c..544a5fe11d0 100644 --- a/apps/statistics/base.hu.i18n +++ b/apps/statistics/base.hu.i18n @@ -24,3 +24,6 @@ SampleStandardDeviationS = "Minta std eltérés σ" SumValues = "Értékek összege" SumSquareValues = "Négyzetértékek összege" InterquartileRange = "Interkvartilis tartomány" +GeometricMean = "Geometriai átlag" +HarmonicMean = "Harmonikus átlag" +StatisticsMode = "Mód" diff --git a/apps/statistics/base.it.i18n b/apps/statistics/base.it.i18n index 043fd75dfca..5cbd3650fe9 100644 --- a/apps/statistics/base.it.i18n +++ b/apps/statistics/base.it.i18n @@ -24,3 +24,6 @@ SampleStandardDeviationS = "Dev. std campionaria s" SumValues = "Somma" SumSquareValues = "Somma dei quadrati" InterquartileRange = "Scarto interquartile" +GeometricMean = "Media geometrica" +HarmonicMean = "Media armonica" +StatisticsMode = "Modalità" diff --git a/apps/statistics/base.nl.i18n b/apps/statistics/base.nl.i18n index beee307b850..4caad6efc2a 100644 --- a/apps/statistics/base.nl.i18n +++ b/apps/statistics/base.nl.i18n @@ -24,3 +24,6 @@ SampleStandardDeviationS = "Standaardafwijking s" SumValues = "Som" SumSquareValues = "Som van kwadraten" InterquartileRange = "Interkwartielafstand" +GeometricMean = "Geometrisch gemiddelde" +HarmonicMean = "Harmonisch gemiddelde" +StatisticsMode = "Modus" diff --git a/apps/statistics/base.pt.i18n b/apps/statistics/base.pt.i18n index 11f5df9f15c..73e76f58c45 100644 --- a/apps/statistics/base.pt.i18n +++ b/apps/statistics/base.pt.i18n @@ -24,3 +24,6 @@ SampleStandardDeviationS = "Desvio padrão amostral s" SumValues = "Somatório" SumSquareValues = "Soma dos quadrados" InterquartileRange = "Amplitude interquartil" +GeometricMean = "Média geométrica" +HarmonicMean = "Média Harmônica" +StatisticsMode = "Modo" diff --git a/apps/statistics/calculation_controller.cpp b/apps/statistics/calculation_controller.cpp index fe21be45812..4dce5526640 100644 --- a/apps/statistics/calculation_controller.cpp +++ b/apps/statistics/calculation_controller.cpp @@ -79,12 +79,15 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int I18n::Message::Maximum, I18n::Message::Range, I18n::Message::Mean, + I18n::Message::GeometricMean, + I18n::Message::HarmonicMean, I18n::Message::StandardDeviationSigma, I18n::Message::Deviation, I18n::Message::FirstQuartile, I18n::Message::ThirdQuartile, I18n::Message::Median, I18n::Message::InterquartileRange, + I18n::Message::StatisticsMode, I18n::Message::SumValues, I18n::Message::SumSquareValues, I18n::Message::SampleStandardDeviationS}; @@ -94,8 +97,8 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int } // Display a calculation cell CalculPointer calculationMethods[k_totalNumberOfRows] = {&Store::sumOfOccurrences, &Store::minValue, - &Store::maxValue, &Store::range, &Store::mean, &Store::standardDeviation, &Store::variance, &Store::firstQuartile, - &Store::thirdQuartile, &Store::median, &Store::quartileRange, &Store::sum, &Store::squaredValueSum, &Store::sampleStandardDeviation}; + &Store::maxValue, &Store::range, &Store::mean, &Store::geometricMean, &Store::harmonicMean, &Store::standardDeviation, &Store::variance, &Store::firstQuartile, + &Store::thirdQuartile, &Store::median, &Store::quartileRange, &Store::mode, &Store::sum, &Store::squaredValueSum, &Store::sampleStandardDeviation}; int seriesIndex = m_store->indexOfKthNonEmptySeries(i-1); double calculation = (m_store->*calculationMethods[j-1])(seriesIndex); EvenOddBufferTextCell * calculationCell = static_cast(cell); diff --git a/apps/statistics/calculation_controller.h b/apps/statistics/calculation_controller.h index ae001ccaa5e..7d7df8faa15 100644 --- a/apps/statistics/calculation_controller.h +++ b/apps/statistics/calculation_controller.h @@ -43,7 +43,7 @@ class CalculationController : public Shared::TabTableController, public ButtonRo bool handleEvent(Ion::Events::Event event) override; void didBecomeFirstResponder() override; private: - static constexpr int k_totalNumberOfRows = 15; + static constexpr int k_totalNumberOfRows = 18; static constexpr int k_maxNumberOfDisplayableRows = 11; static constexpr int k_numberOfCalculationCells = 3 * k_maxNumberOfDisplayableRows; static constexpr int k_numberOfSeriesTitleCells = 3; diff --git a/apps/statistics/store.cpp b/apps/statistics/store.cpp index 219f4c8dc1c..ecfade0d144 100644 --- a/apps/statistics/store.cpp +++ b/apps/statistics/store.cpp @@ -162,6 +162,34 @@ double Store::mean(int series) const { return sum(series)/sumOfOccurrences(series); } +double Store::geometricMean(int series) const { + double geometricMean = 1; + int numberOfCoefficients = 0; + int numberOfPairs = numberOfPairsOfSeries(series); + for (int k = 0; k < numberOfPairs; k++) { + if (m_data[series][0][k] <= 0) { + return NAN; + } + geometricMean *= std::pow(m_data[series][0][k], m_data[series][1][k]); + numberOfCoefficients += m_data[series][1][k]; + } + return std::pow(geometricMean, 1.0/numberOfCoefficients); +} + +double Store::harmonicMean(int series) const { + double harmonicMean = 0; + int numberOfCoefficients = 0; + int numberOfPairs = numberOfPairsOfSeries(series); + for (int k = 0; k < numberOfPairs; k++) { + if (m_data[series][0][k] <= 0) { + return NAN; + } + harmonicMean += m_data[series][1][k]/m_data[series][0][k]; + numberOfCoefficients += m_data[series][1][k]; + } + return numberOfCoefficients/harmonicMean; +} + double Store::variance(int series) const { /* We use the Var(X) = E[(X-E[X])^2] definition instead of Var(X) = E[X^2] - E[X]^2 * to ensure a positive result and to minimize rounding errors */ @@ -217,6 +245,22 @@ double Store::quartileRange(int series) const { return thirdQuartile(series)-firstQuartile(series); } +double Store::mode(int series) const { + double modeValue = NAN; + double numberOfRepeats = 0; + int numberOfPairs = numberOfPairsOfSeries(series); + for (int k = 0; k < numberOfPairs; k++) { + if (m_data[series][1][k] > numberOfRepeats) { + modeValue = m_data[series][0][k]; + numberOfRepeats = m_data[series][1][k]; + } + else if (m_data[series][1][k] == numberOfRepeats) { + modeValue = NAN; + } + } + return modeValue; +} + double Store::median(int series) const { return sortedElementAtCumulatedFrequency(series, 1.0/2.0, true); } diff --git a/apps/statistics/store.h b/apps/statistics/store.h index 136791a237f..aa19c799888 100644 --- a/apps/statistics/store.h +++ b/apps/statistics/store.h @@ -35,12 +35,15 @@ class Store : public Shared::MemoizedCurveViewRange, public Shared::DoublePairSt double minValue(int series) const; double range(int series) const; double mean(int series) const; + double geometricMean(int series) const; + double harmonicMean(int series) const; double variance(int series) const; double standardDeviation(int series) const; double sampleStandardDeviation(int series) const; double firstQuartile(int series) const; double thirdQuartile(int series) const; double quartileRange(int series) const; + double mode(int series) const; double median(int series) const; double sum(int series) const; double squaredValueSum(int series) const; From 51a5f699c3dbe75e0644b005f2047bc2bd316860 Mon Sep 17 00:00:00 2001 From: Laury Date: Wed, 6 Jul 2022 22:52:49 +0200 Subject: [PATCH 309/355] [escher] XNT button is now cyclic --- apps/Makefile | 1 + apps/apps_container.h | 4 +++ apps/code/app.cpp | 7 ++-- apps/code/editor_controller.cpp | 6 +++- apps/code/editor_controller.h | 1 + apps/code/helpers.cpp | 12 ++++--- apps/code/helpers.h | 2 +- apps/code/python_text_area.cpp | 10 +++--- apps/code/python_text_area.h | 2 +- apps/shared/expression_field_delegate_app.h | 1 + apps/shared/layout_field_delegate.h | 2 ++ apps/shared/text_field_delegate.h | 2 ++ apps/shared/text_field_delegate_app.cpp | 4 ++- apps/shared/text_field_delegate_app.h | 2 ++ apps/xnt_loop.cpp | 22 ++++++++++++ apps/xnt_loop.h | 15 ++++++++ escher/include/escher/input_event_handler.h | 2 +- escher/include/escher/input_view_controller.h | 2 ++ escher/include/escher/layout_field.h | 2 +- escher/include/escher/layout_field_delegate.h | 1 + escher/include/escher/text_area.h | 5 +-- escher/include/escher/text_area_delegate.h | 1 + escher/include/escher/text_field.h | 5 +-- escher/include/escher/text_field_delegate.h | 1 + escher/include/escher/text_input.h | 5 +-- escher/src/input_view_controller.cpp | 8 +++++ escher/src/layout_field.cpp | 8 ++++- escher/src/text_area.cpp | 35 ++++++++++++++----- escher/src/text_field.cpp | 31 ++++++++++++---- escher/src/text_input.cpp | 16 ++++----- 30 files changed, 167 insertions(+), 48 deletions(-) create mode 100644 apps/xnt_loop.cpp create mode 100644 apps/xnt_loop.h diff --git a/apps/Makefile b/apps/Makefile index b4d2c701635..3aed3073532 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -60,6 +60,7 @@ apps_src += $(addprefix apps/,\ suspend_timer.cpp \ timer_manager.cpp \ title_bar_view.cpp \ + xnt_loop.cpp \ ) tests_src += $(addprefix apps/,\ diff --git a/apps/apps_container.h b/apps/apps_container.h index c6c53b0fe32..1366b01e76d 100644 --- a/apps/apps_container.h +++ b/apps/apps_container.h @@ -18,6 +18,7 @@ #include "shared/global_context.h" #include "clock_timer.h" #include "on_boarding/prompt_controller.h" +#include "xnt_loop.h" #include @@ -48,6 +49,8 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag void displayExamModePopUp(GlobalPreferences::ExamMode mode); void shutdownDueToLowBattery(); void setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus newStatus); + CodePoint XNT(CodePoint defaultXNT, bool * shouldRemoveLastCharacter) { m_XNTLoop.XNT(defaultXNT, shouldRemoveLastCharacter); } + void resetXNT() { m_XNTLoop.reset(); } OnBoarding::PromptController * promptController(); void redrawWindow(bool force = false); void activateExamMode(GlobalPreferences::ExamMode examMode); @@ -85,6 +88,7 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag OnBoarding::App::Snapshot m_onBoardingSnapshot; HardwareTest::App::Snapshot m_hardwareTestSnapshot; USB::App::Snapshot m_usbConnectedSnapshot; + XNTLoop m_XNTLoop; }; #endif diff --git a/apps/code/app.cpp b/apps/code/app.cpp index 4db851b222b..d865e7a4612 100644 --- a/apps/code/app.cpp +++ b/apps/code/app.cpp @@ -129,9 +129,10 @@ VariableBoxController * App::variableBoxForInputEventHandler(InputEventHandler * } bool App::textInputDidReceiveEvent(InputEventHandler * textInput, Ion::Events::Event event) { - const char * pythonText = Helpers::PythonTextForEvent(event); - if (pythonText != nullptr) { - textInput->handleEventWithText(pythonText); + bool shouldRemoveLastCharacter = false; + char buffer[CodePoint::MaxCodePointCharLength + 1]; + if (Helpers::PythonTextForEvent(event, buffer, &shouldRemoveLastCharacter)) { + textInput->handleEventWithText(buffer, false, false, shouldRemoveLastCharacter); return true; } return false; diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index f314f3d7294..257398ae580 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -5,6 +5,7 @@ #include #include #include "../global_preferences.h" +#include using namespace Shared; @@ -72,6 +73,10 @@ void EditorController::viewDidDisappear() { m_menuController->scriptContentEditionDidFinish(); } +void EditorController::textAreaDidReceiveNoneXNTEvent() { + AppsContainer::sharedAppsContainer()->resetXNT(); +} + bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) { if (App::app()->textInputDidReceiveEvent(textArea, event)) { return true; @@ -81,7 +86,6 @@ bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events: return true; } - if (event == Ion::Events::Backspace && textArea->selectionIsEmpty()) { /* If the cursor is on the left of the text of a line, backspace one * indentation space at a time. */ diff --git a/apps/code/editor_controller.h b/apps/code/editor_controller.h index 4cd32c1edc8..42b35bfa7af 100644 --- a/apps/code/editor_controller.h +++ b/apps/code/editor_controller.h @@ -30,6 +30,7 @@ class EditorController : public ViewController, public TextAreaDelegate, public TELEMETRY_ID("Editor"); /* TextAreaDelegate */ + void textAreaDidReceiveNoneXNTEvent() override; bool textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) override; /* InputEventHandlerDelegate */ diff --git a/apps/code/helpers.cpp b/apps/code/helpers.cpp index 18250e77f47..37ec974058b 100644 --- a/apps/code/helpers.cpp +++ b/apps/code/helpers.cpp @@ -1,20 +1,24 @@ #include "helpers.h" #include +#include namespace Code { namespace Helpers { -const char * PythonTextForEvent(Ion::Events::Event event) { +bool PythonTextForEvent(Ion::Events::Event event, char * buffer, bool * shouldRemoveLastCharacter) { for (size_t i=0; iXNT('x', shouldRemoveLastCharacter); + buffer[UTF8Decoder::CodePointToChars(XNT, buffer, CodePoint::MaxCodePointCharLength + 1)] = 0; + return true; } + return false; } - return nullptr; } } } diff --git a/apps/code/helpers.h b/apps/code/helpers.h index 1273ca045e7..69b6b384288 100644 --- a/apps/code/helpers.h +++ b/apps/code/helpers.h @@ -6,7 +6,7 @@ namespace Code { namespace Helpers { -const char * PythonTextForEvent(Ion::Events::Event event); +bool PythonTextForEvent(Ion::Events::Event event, char * buffer, bool * shouldRemoveLastCharacter); } } diff --git a/apps/code/python_text_area.cpp b/apps/code/python_text_area.cpp index da2274cb758..3edf9eeae8f 100644 --- a/apps/code/python_text_area.cpp +++ b/apps/code/python_text_area.cpp @@ -426,14 +426,14 @@ bool PythonTextArea::handleEvent(Ion::Events::Event event) { return result; } -bool PythonTextArea::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) { +bool PythonTextArea::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText, bool shouldRemoveLastCharacter) { if (*text == 0) { return false; } if (m_contentView.isAutocompleting()) { removeAutocompletion(); } - bool result = TextArea::handleEventWithText(text, indentation, forceCursorRightOfText); + bool result = TextArea::handleEventWithText(text, indentation, forceCursorRightOfText, shouldRemoveLastCharacter); addAutocompletion(); return result; } @@ -493,9 +493,10 @@ bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIn if (textToInsertLength > 0) { // Try to insert the text (this might fail if the buffer is full) - if (!m_contentView.insertTextAtLocation(textToInsert, const_cast(autocompletionLocation), textToInsertLength)) { + if (!m_contentView.isAbleToInsertTextAt(textToInsertLength, autocompletionLocation, false)) { return false; } + m_contentView.insertTextAtLocation(textToInsert, const_cast(autocompletionLocation), textToInsertLength); autocompletionLocation += textToInsertLength; m_contentView.setAutocompleting(true); m_contentView.setAutocompletionEnd(autocompletionLocation); @@ -507,7 +508,8 @@ bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIn assert(strlen(parentheses) == parenthesesLength); /* If couldInsertText is false, we should not try to add the parentheses as * there was already not enough space to add the autocompletion. */ - if (addParentheses && m_contentView.insertTextAtLocation(parentheses, const_cast(autocompletionLocation), parenthesesLength)) { + if (addParentheses && m_contentView.isAbleToInsertTextAt(parenthesesLength, autocompletionLocation, false)) { + m_contentView.insertTextAtLocation(parentheses, const_cast(autocompletionLocation), parenthesesLength); m_contentView.setAutocompleting(true); m_contentView.setAutocompletionEnd(autocompletionLocation + parenthesesLength); return true; diff --git a/apps/code/python_text_area.h b/apps/code/python_text_area.h index fb4bba2dafe..57a46a5f85c 100644 --- a/apps/code/python_text_area.h +++ b/apps/code/python_text_area.h @@ -23,7 +23,7 @@ class PythonTextArea : public TextArea { void loadSyntaxHighlighter() { m_contentView.loadSyntaxHighlighter(); } void unloadSyntaxHighlighter() { m_contentView.unloadSyntaxHighlighter(); } bool handleEvent(Ion::Events::Event event) override; - bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override; + bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false, bool shouldRemoveLastCharacter = false) override; /* autocompletionType returns: * - EndOfIdentifier if there is currently autocompletion, or if the cursor is * at the end of an identifier, diff --git a/apps/shared/expression_field_delegate_app.h b/apps/shared/expression_field_delegate_app.h index 0e175281355..466e5eb2153 100644 --- a/apps/shared/expression_field_delegate_app.h +++ b/apps/shared/expression_field_delegate_app.h @@ -9,6 +9,7 @@ namespace Shared { class ExpressionFieldDelegateApp : public TextFieldDelegateApp, public LayoutFieldDelegate { public: virtual ~ExpressionFieldDelegateApp() = default; + void layoutFieldDidReceiveNoneXNTEvent() override { AppsContainer::sharedAppsContainer()->resetXNT(); } bool layoutFieldShouldFinishEditing(LayoutField * layoutField, Ion::Events::Event event) override; bool layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) override; protected: diff --git a/apps/shared/layout_field_delegate.h b/apps/shared/layout_field_delegate.h index 79f25607d6a..5b2bb1c3c5e 100644 --- a/apps/shared/layout_field_delegate.h +++ b/apps/shared/layout_field_delegate.h @@ -2,11 +2,13 @@ #define SHARED_LAYOUT_FIELD_DELEGATE_H #include "expression_field_delegate_app.h" +#include "../apps_container.h" namespace Shared { class LayoutFieldDelegate : public ::LayoutFieldDelegate { public: + void layoutFieldDidReceiveNoneXNTEvent() override { AppsContainer::sharedAppsContainer()->resetXNT(); } bool layoutFieldShouldFinishEditing(LayoutField * layoutField, Ion::Events::Event event) override; bool layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) override; bool layoutFieldDidFinishEditing(LayoutField * layoutField, Poincare::Layout layoutR, Ion::Events::Event event) override; diff --git a/apps/shared/text_field_delegate.h b/apps/shared/text_field_delegate.h index bc148e95a6c..cd04bcffb6c 100644 --- a/apps/shared/text_field_delegate.h +++ b/apps/shared/text_field_delegate.h @@ -2,11 +2,13 @@ #define SHARED_TEXT_FIELD_DELEGATE_H #include "text_field_delegate_app.h" +#include "../apps_container.h" namespace Shared { class TextFieldDelegate : public ::TextFieldDelegate { public: + void textFieldDidReceiveNoneXNTEvent() override { AppsContainer::sharedAppsContainer()->resetXNT(); } bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; protected: diff --git a/apps/shared/text_field_delegate_app.cpp b/apps/shared/text_field_delegate_app.cpp index ece157f9bd9..68ae84d0b3e 100644 --- a/apps/shared/text_field_delegate_app.cpp +++ b/apps/shared/text_field_delegate_app.cpp @@ -72,10 +72,12 @@ bool TextFieldDelegateApp::fieldDidReceiveEvent(EditableField * field, Responder if (XNTCanBeOverriden()) { xnt = field->XNTCodePoint(xnt); } + bool shouldRemoveLastCharacter = false; + xnt = AppsContainer::sharedAppsContainer()->XNT(xnt, &shouldRemoveLastCharacter); size_t length = UTF8Decoder::CodePointToChars(xnt, buffer, bufferSize); assert(length < bufferSize - 1); buffer[length] = 0; - return field->handleEventWithText(buffer); + return field->handleEventWithText(buffer, false, false, shouldRemoveLastCharacter); } return false; } diff --git a/apps/shared/text_field_delegate_app.h b/apps/shared/text_field_delegate_app.h index 0dd472e78e4..a33adffde77 100644 --- a/apps/shared/text_field_delegate_app.h +++ b/apps/shared/text_field_delegate_app.h @@ -5,6 +5,7 @@ #include "input_event_handler_delegate_app.h" #include #include +#include "../apps_container.h" class EditableField; @@ -16,6 +17,7 @@ class TextFieldDelegateApp : public InputEventHandlerDelegateApp, public TextFie Poincare::Context * localContext() override; virtual bool XNTCanBeOverriden() const { return true; } virtual CodePoint XNT() { return 'x'; } + virtual void textFieldDidReceiveNoneXNTEvent() override { AppsContainer::sharedAppsContainer()->resetXNT(); } bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; bool isAcceptableText(const char * text); diff --git a/apps/xnt_loop.cpp b/apps/xnt_loop.cpp new file mode 100644 index 00000000000..e81b6d1aa59 --- /dev/null +++ b/apps/xnt_loop.cpp @@ -0,0 +1,22 @@ +#include "xnt_loop.h" +#include + +CodePoint XNTLoop::XNT(CodePoint defaultCodePoint, bool * shouldRemoveLastCharacter) { + assert(shouldRemoveLastCharacter != nullptr); + static constexpr CodePoint XNTCodePoints[] = {'x', 'n', 't', UCodePointGreekSmallLetterTheta}; + int XNTCodePointSize = sizeof(XNTCodePoints) / sizeof(CodePoint); + + if (m_loopIndex == -1) { + for (int i = 0; i < XNTCodePointSize; i++) { + if (XNTCodePoints[i] == defaultCodePoint) { + m_loopIndex = i; + break; + } + } + } else { + *shouldRemoveLastCharacter = true; + m_loopIndex = (m_loopIndex + 1) % XNTCodePointSize; + } + + return XNTCodePoints[m_loopIndex]; +} diff --git a/apps/xnt_loop.h b/apps/xnt_loop.h new file mode 100644 index 00000000000..e9ed8a0c915 --- /dev/null +++ b/apps/xnt_loop.h @@ -0,0 +1,15 @@ +#ifndef APPS_XNT_LOOP +#define APPS_XNT_LOOP + +#include + +class XNTLoop { +public: + XNTLoop(): m_loopIndex(-1) {} + void reset() { m_loopIndex = -1; } + CodePoint XNT(CodePoint defaultCodePoint, bool * shouldRemoveLastCharacter); +private: + int m_loopIndex; +}; + +#endif diff --git a/escher/include/escher/input_event_handler.h b/escher/include/escher/input_event_handler.h index b5fed665fc5..72f444cad5b 100644 --- a/escher/include/escher/input_event_handler.h +++ b/escher/include/escher/input_event_handler.h @@ -10,7 +10,7 @@ class InputEventHandlerDelegate; class InputEventHandler { public: InputEventHandler(InputEventHandlerDelegate * inputEventHandlerdelegate) : m_inputEventHandlerDelegate(inputEventHandlerdelegate) {} - virtual bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) { return false; } + virtual bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false, bool shouldRemoveLastCharacter = false) { return false; } protected: bool handleBoxEvent(Ion::Events::Event event); InputEventHandlerDelegate * m_inputEventHandlerDelegate; diff --git a/escher/include/escher/input_view_controller.h b/escher/include/escher/input_view_controller.h index 5d0bd919706..2b89f6901e6 100644 --- a/escher/include/escher/input_view_controller.h +++ b/escher/include/escher/input_view_controller.h @@ -27,12 +27,14 @@ class InputViewController : public ModalViewController, InputEventHandlerDelegat void abortEditionAndDismiss(); /* TextFieldDelegate */ + void textFieldDidReceiveNoneXNTEvent() override; bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; bool textFieldDidAbortEditing(TextField * textField) override; /* LayoutFieldDelegate */ + void layoutFieldDidReceiveNoneXNTEvent() override; bool layoutFieldShouldFinishEditing(LayoutField * layoutField, Ion::Events::Event event) override; bool layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) override; bool layoutFieldDidFinishEditing(LayoutField * layoutField, Poincare::Layout layoutR, Ion::Events::Event event) override; diff --git a/escher/include/escher/layout_field.h b/escher/include/escher/layout_field.h index bd36e8b14a3..3d6a4c76a65 100644 --- a/escher/include/escher/layout_field.h +++ b/escher/include/escher/layout_field.h @@ -45,7 +45,7 @@ class LayoutField : public ScrollableView, public ScrollViewDataSource, public E } /* Responder */ - bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override; + bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false, bool shouldRemoveLastCharacter = false) override; bool handleEvent(Ion::Events::Event event) override; // TODO: factorize with TextField (see TODO of EditableField) bool shouldFinishEditing(Ion::Events::Event event) override; diff --git a/escher/include/escher/layout_field_delegate.h b/escher/include/escher/layout_field_delegate.h index ce9dfaf02ff..080cfc47c0b 100644 --- a/escher/include/escher/layout_field_delegate.h +++ b/escher/include/escher/layout_field_delegate.h @@ -9,6 +9,7 @@ class LayoutField; class LayoutFieldDelegate : public ContextProvider{ public: + virtual void layoutFieldDidReceiveNoneXNTEvent() {}; virtual bool layoutFieldShouldFinishEditing(LayoutField * layoutField, Ion::Events::Event event) = 0; virtual bool layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) = 0; virtual bool layoutFieldDidFinishEditing(LayoutField * layoutField, Poincare::Layout layoutR, Ion::Events::Event event) { return false; } diff --git a/escher/include/escher/text_area.h b/escher/include/escher/text_area.h index efd31363303..110268b3f94 100644 --- a/escher/include/escher/text_area.h +++ b/escher/include/escher/text_area.h @@ -16,7 +16,7 @@ class TextArea : public TextInput, public InputEventHandler { TextArea(Responder * parentResponder, View * contentView, const KDFont * font = KDFont::LargeFont); void setDelegates(InputEventHandlerDelegate * inputEventHandlerDelegate, TextAreaDelegate * delegate) { m_inputEventHandlerDelegate = inputEventHandlerDelegate; m_delegate = delegate; } bool handleEvent(Ion::Events::Event event) override; - bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override; + bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false, bool shouldRemoveLastCharacter = false) override; void setText(char * textBuffer, size_t textBufferSize); protected: @@ -120,7 +120,8 @@ class TextArea : public TextInput, public InputEventHandler { const char * editedText() const override { return m_text.text(); } size_t editedTextLength() const override { return m_text.textLength(); } const Text * getText() const { return &m_text; } - bool insertTextAtLocation(const char * text, char * location, int textLength = -1) override; + bool isAbleToInsertTextAt(int textLength, const char * location, bool shouldRemoveLastCharacter) const override; + void insertTextAtLocation(const char * text, char * location, int textLength = -1) override; void moveCursorGeo(int deltaX, int deltaY); bool removePreviousGlyph() override; bool removeEndOfLine() override; diff --git a/escher/include/escher/text_area_delegate.h b/escher/include/escher/text_area_delegate.h index c16c5ffa051..9c907a98746 100644 --- a/escher/include/escher/text_area_delegate.h +++ b/escher/include/escher/text_area_delegate.h @@ -5,6 +5,7 @@ class TextArea; class TextAreaDelegate { public: + virtual void textAreaDidReceiveNoneXNTEvent() {}; virtual bool textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) = 0; }; diff --git a/escher/include/escher/text_field.h b/escher/include/escher/text_field.h index f7901b7f8af..055c7ed3be1 100644 --- a/escher/include/escher/text_field.h +++ b/escher/include/escher/text_field.h @@ -34,7 +34,7 @@ class TextField : public TextInput, public EditableField { void setText(const char * text); void setEditing(bool isEditing) override { m_contentView.setEditing(isEditing); } CodePoint XNTCodePoint(CodePoint defaultXNTCodePoint) override; - bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override; + bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false, bool shouldRemoveLastCharacter = false) override; bool handleEvent(Ion::Events::Event event) override; constexpr static int maxBufferSize() { return ContentView::k_maxBufferSize; @@ -71,10 +71,11 @@ class TextField : public TextInput, public EditableField { void reinitDraftTextBuffer(); void setDraftTextBufferSize(size_t size) { assert(size <= k_maxBufferSize); m_draftTextBufferSize = size; } size_t draftTextBufferSize() const { return m_draftTextBufferSize; } + bool isAbleToInsertTextAt(int textLength, const char * location, bool shouldRemoveLastCharacter) const override; /* If the text to be appended is too long to be added without overflowing the * buffer, nothing is done (not even adding few letters from the text to reach * the maximum buffer capacity) and false is returned. */ - bool insertTextAtLocation(const char * text, char * location, int textLength = -1) override; + void insertTextAtLocation(const char * text, char * location, int textLength = -1) override; KDSize minimalSizeForOptimalDisplay() const override; bool removePreviousGlyph() override; bool removeEndOfLine() override; diff --git a/escher/include/escher/text_field_delegate.h b/escher/include/escher/text_field_delegate.h index 4be1741e68a..790cc75e9f8 100644 --- a/escher/include/escher/text_field_delegate.h +++ b/escher/include/escher/text_field_delegate.h @@ -7,6 +7,7 @@ class TextField; class TextFieldDelegate { public: + virtual void textFieldDidReceiveNoneXNTEvent() {}; virtual bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) = 0; virtual bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) = 0; virtual bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { return false; } diff --git a/escher/include/escher/text_input.h b/escher/include/escher/text_input.h index 0de69538cc3..96648be9630 100644 --- a/escher/include/escher/text_input.h +++ b/escher/include/escher/text_input.h @@ -49,7 +49,8 @@ class TextInput : public ScrollableView, public ScrollViewDataSource { // Virtual text get/add/remove virtual const char * text() const = 0; - virtual bool insertTextAtLocation(const char * text, char * location, int textLength = -1) = 0; + virtual bool isAbleToInsertTextAt(int textLength, const char * location, bool shouldRemoveLastCharacter) const = 0; + virtual void insertTextAtLocation(const char * text, char * location, int textLength) = 0; virtual bool removePreviousGlyph() = 0; virtual bool removeEndOfLine() = 0; @@ -92,7 +93,7 @@ class TextInput : public ScrollableView, public ScrollViewDataSource { /* If the text to be appended is too long to be added without overflowing the * buffer, nothing is done (not even adding few letters from the text to reach * the maximum buffer capacity) and false is returned. */ - bool insertTextAtLocation(const char * textBuffer, char * location); + void insertTextAtLocation(const char * textBuffer, char * location, int textLength); bool removeEndOfLine(); ContentView * contentView() { return const_cast(nonEditableContentView()); diff --git a/escher/src/input_view_controller.cpp b/escher/src/input_view_controller.cpp index 2947054a7df..ada1a0c4498 100644 --- a/escher/src/input_view_controller.cpp +++ b/escher/src/input_view_controller.cpp @@ -58,6 +58,14 @@ bool InputViewController::textFieldDidAbortEditing(TextField * textField) { return true; } +void InputViewController::textFieldDidReceiveNoneXNTEvent() { + m_textFieldDelegate->textFieldDidReceiveNoneXNTEvent(); +} + +void InputViewController::layoutFieldDidReceiveNoneXNTEvent() { + m_layoutFieldDelegate->layoutFieldDidReceiveNoneXNTEvent(); +} + bool InputViewController::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) { return m_textFieldDelegate->textFieldDidReceiveEvent(textField, event); } diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index cfdbea00cae..8fd02835805 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -355,7 +355,7 @@ void LayoutField::reload(KDSize previousSize) { markRectAsDirty(bounds()); } -bool LayoutField::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) { +bool LayoutField::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText, bool shouldRemoveLastCharacter) { /* The text here can be: * - the result of a key pressed, such as "," or "cos(•)" * - the text added after a toolbox selection @@ -417,6 +417,9 @@ bool LayoutField::handleEventWithText(const char * text, bool indentation, bool if (currentNumberOfLayouts + resultLayout.numberOfDescendants(true) >= k_maxNumberOfLayouts) { return true; } + if (shouldRemoveLastCharacter) { + cursor->performBackspace(); + } insertLayoutAtCursor(resultLayout, resultExpression, forceCursorRightOfText); } return true; @@ -431,6 +434,9 @@ bool LayoutField::shouldFinishEditing(Ion::Events::Event event) { } bool LayoutField::handleEvent(Ion::Events::Event event) { + if (m_delegate && event != Ion::Events::XNT) { + m_delegate->layoutFieldDidReceiveNoneXNTEvent(); + } bool didHandleEvent = false; KDSize previousSize = minimalSizeForOptimalDisplay(); bool shouldRecomputeLayout = m_contentView.cursor()->showEmptyLayoutIfNeeded(); diff --git a/escher/src/text_area.cpp b/escher/src/text_area.cpp index c3867ef73db..5427ed5b652 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -33,7 +33,7 @@ static inline void InsertSpacesAtLocation(int spacesCount, char * buffer, int bu } } -bool TextArea::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) { +bool TextArea::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText, bool shouldRemoveLastCharacter) { if (*text == 0) { return false; } @@ -123,11 +123,19 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for return false; } - // Insert the text - if (!insertTextAtLocation(text, insertionPosition)) { + int textLength = strlen(text); + if (!contentView()->isAbleToInsertTextAt(textLength, insertionPosition, shouldRemoveLastCharacter)) { return true; } + if (shouldRemoveLastCharacter) { + removePreviousGlyph(); + insertionPosition = const_cast(cursorLocation()); + } + + // Insert the text + insertTextAtLocation(text, insertionPosition, textLength); + // Insert the indentation if (indentation) { UTF8Helper::PerformAtCodePoints( @@ -160,6 +168,9 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for } bool TextArea::handleEvent(Ion::Events::Event event) { + if (m_delegate != nullptr && event != Ion::Events::XNT) { + m_delegate->textAreaDidReceiveNoneXNTEvent(); + } if (m_delegate != nullptr && m_delegate->textAreaDidReceiveEvent(this, event)) { return true; } @@ -575,12 +586,21 @@ void TextArea::ContentView::setText(char * textBuffer, size_t textBufferSize) { m_cursorLocation = text(); } -bool TextArea::ContentView::insertTextAtLocation(const char * text, char * location, int textLength) { +bool TextArea::ContentView::isAbleToInsertTextAt(int textLength, const char * location, bool shouldRemoveLastCharacter) const { + int removedCharacters = 0; + if (shouldRemoveLastCharacter) { + UTF8Decoder decoder(m_text.text(), location); + const char * previousGlyphPos = decoder.previousGlyphPosition(); + assert(previousGlyphPos != nullptr); + removedCharacters = location - previousGlyphPos; + } + return m_text.textLength() + textLength - removedCharacters < m_text.bufferSize() && textLength != 0; +} + +void TextArea::ContentView::insertTextAtLocation(const char * text, char * location, int textLength) { int textLen = textLength < 0 ? strlen(text) : textLength; assert(textLen < 0 || textLen <= strlen(text)); - if (m_text.textLength() + textLen >= m_text.bufferSize() || textLen == 0) { - return false; - } + assert(isAbleToInsertTextAt(textLen, location, false)); // Scan for \n bool lineBreak = UTF8Helper::HasCodePoint(text, '\n', text + textLen); @@ -589,7 +609,6 @@ bool TextArea::ContentView::insertTextAtLocation(const char * text, char * locat // Replace System parentheses (used to keep layout tree structure) by normal parentheses Poincare::SerializationHelper::ReplaceSystemParenthesesByUserParentheses(location, textLen); reloadRectFromPosition(location, lineBreak); - return true; } bool TextArea::ContentView::removePreviousGlyph() { diff --git a/escher/src/text_field.cpp b/escher/src/text_field.cpp index 2587bf024b3..4389658acc2 100644 --- a/escher/src/text_field.cpp +++ b/escher/src/text_field.cpp @@ -119,14 +119,23 @@ void TextField::ContentView::reinitDraftTextBuffer() { setCursorLocation(s_draftTextBuffer); } -bool TextField::ContentView::insertTextAtLocation(const char * text, char * location, int textLen) { +bool TextField::ContentView::isAbleToInsertTextAt(int textLength, const char * location, bool shouldRemoveLastCharacter) const { + int removedCharacters = 0; + if (shouldRemoveLastCharacter) { + UTF8Decoder decoder(s_draftTextBuffer, location); + const char * previousGlyphPos = decoder.previousGlyphPosition(); + assert(previousGlyphPos != nullptr); + removedCharacters = location - previousGlyphPos; + } + return m_currentDraftTextLength + textLength - removedCharacters < m_draftTextBufferSize && textLength != 0; +} + +void TextField::ContentView::insertTextAtLocation(const char * text, char * location, int textLen) { assert(m_isEditing); size_t textLength = textLen < 0 ? strlen(text) : (size_t)textLen; // TODO when paste fails because of a too big message, create a pop-up - if (m_currentDraftTextLength + textLength >= m_draftTextBufferSize || textLength == 0) { - return false; - } + assert(isAbleToInsertTextAt(textLength, location, false)); memmove(location + textLength, location, (s_draftTextBuffer + m_currentDraftTextLength + 1) - location); @@ -139,7 +148,6 @@ bool TextField::ContentView::insertTextAtLocation(const char * text, char * loca m_currentDraftTextLength += copySize-1; // Do no count the null-termination reloadRectFromPosition(m_horizontalAlignment == 0.0f ? location : s_draftTextBuffer); - return true; } KDSize TextField::ContentView::minimalSizeForOptimalDisplay() const { @@ -416,6 +424,9 @@ CodePoint TextField::XNTCodePoint(CodePoint defaultXNTCodePoint) { bool TextField::handleEvent(Ion::Events::Event event) { assert(m_delegate != nullptr); + if (event != Ion::Events::XNT) { + m_delegate->textFieldDidReceiveNoneXNTEvent(); + } size_t previousTextLength = strlen(text()); bool didHandleEvent = false; if (privateHandleMoveEvent(event)) { @@ -486,7 +497,7 @@ bool TextField::privateHandleSelectEvent(Ion::Events::Event event) { return false; } -bool TextField::handleEventWithText(const char * eventText, bool indentation, bool forceCursorRightOfText) { +bool TextField::handleEventWithText(const char * eventText, bool indentation, bool forceCursorRightOfText, bool shouldRemoveLastCharacter) { size_t previousTextLength = strlen(text()); if (!isEditing()) { @@ -518,7 +529,13 @@ bool TextField::handleEventWithText(const char * eventText, bool indentation, bo // Replace System parentheses (used to keep layout tree structure) by normal parentheses Poincare::SerializationHelper::ReplaceSystemParenthesesByUserParentheses(buffer); - if (insertTextAtLocation(buffer, const_cast(cursorLocation()))) { + int textLength = strlen(buffer); + if (contentView()->isAbleToInsertTextAt(textLength, cursorLocation(), shouldRemoveLastCharacter)) { + if (shouldRemoveLastCharacter) { + removePreviousGlyph(); + } + + insertTextAtLocation(buffer, const_cast(cursorLocation()), textLength); /* The cursor position depends on the text as we sometimes want to position * the cursor at the end of the text and sometimes after the first * parenthesis. */ diff --git a/escher/src/text_input.cpp b/escher/src/text_input.cpp index da741f62c9d..608a289b809 100644 --- a/escher/src/text_input.cpp +++ b/escher/src/text_input.cpp @@ -170,15 +170,13 @@ void TextInput::setAlignment(float horizontalAlignment, float verticalAlignment) contentView()->setAlignment(horizontalAlignment, verticalAlignment); } -bool TextInput::insertTextAtLocation(const char * text, char * location) { - if (contentView()->insertTextAtLocation(text, location)) { - /* We layout the scrollable view before scrolling to cursor because the - * content size might have changed. */ - layoutSubviews(); - scrollToCursor(); - return true; - } - return false; +void TextInput::insertTextAtLocation(const char * text, char * location, int textLength) { + assert(contentView()->isAbleToInsertTextAt(textLength, location, false)); + contentView()->insertTextAtLocation(text, location, textLength); + /* We layout the scrollable view before scrolling to cursor because the + * content size might have changed. */ + layoutSubviews(); + scrollToCursor(); } bool TextInput::removeEndOfLine() { From 1fbd5281a9c07905af130f8060a1e5121f1d1104 Mon Sep 17 00:00:00 2001 From: Laury Date: Thu, 7 Jul 2022 12:37:55 +0200 Subject: [PATCH 310/355] [escher] Fixed bugs in previous commit (XNT) --- apps/apps_container.h | 2 +- apps/code/app.cpp | 18 ++++++++++++++---- apps/code/helpers.cpp | 13 +++---------- apps/code/helpers.h | 2 +- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/apps/apps_container.h b/apps/apps_container.h index 1366b01e76d..0d49cdedb16 100644 --- a/apps/apps_container.h +++ b/apps/apps_container.h @@ -49,7 +49,7 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag void displayExamModePopUp(GlobalPreferences::ExamMode mode); void shutdownDueToLowBattery(); void setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus newStatus); - CodePoint XNT(CodePoint defaultXNT, bool * shouldRemoveLastCharacter) { m_XNTLoop.XNT(defaultXNT, shouldRemoveLastCharacter); } + CodePoint XNT(CodePoint defaultXNT, bool * shouldRemoveLastCharacter) { return m_XNTLoop.XNT(defaultXNT, shouldRemoveLastCharacter); } void resetXNT() { m_XNTLoop.reset(); } OnBoarding::PromptController * promptController(); void redrawWindow(bool force = false); diff --git a/apps/code/app.cpp b/apps/code/app.cpp index d865e7a4612..12833f6f835 100644 --- a/apps/code/app.cpp +++ b/apps/code/app.cpp @@ -3,6 +3,7 @@ #include #include "helpers.h" #include +#include namespace Code { @@ -129,10 +130,19 @@ VariableBoxController * App::variableBoxForInputEventHandler(InputEventHandler * } bool App::textInputDidReceiveEvent(InputEventHandler * textInput, Ion::Events::Event event) { - bool shouldRemoveLastCharacter = false; - char buffer[CodePoint::MaxCodePointCharLength + 1]; - if (Helpers::PythonTextForEvent(event, buffer, &shouldRemoveLastCharacter)) { - textInput->handleEventWithText(buffer, false, false, shouldRemoveLastCharacter); + if (event == Ion::Events::XNT) { + int bufferSize = CodePoint::MaxCodePointCharLength + 1; + char buffer[bufferSize]; + bool shouldRemoveLastCharacter = false; + CodePoint codePoint = AppsContainer::sharedAppsContainer()->XNT('x', &shouldRemoveLastCharacter); + UTF8Decoder::CodePointToChars(codePoint, buffer, bufferSize); + buffer[UTF8Decoder::CharSizeOfCodePoint(codePoint)] = 0; + textInput->handleEventWithText(const_cast(buffer), false, false, shouldRemoveLastCharacter); + return true; + } + const char * pythonText = Helpers::PythonTextForEvent(event); + if (pythonText != nullptr) { + textInput->handleEventWithText(pythonText); return true; } return false; diff --git a/apps/code/helpers.cpp b/apps/code/helpers.cpp index 37ec974058b..7a74f724bb5 100644 --- a/apps/code/helpers.cpp +++ b/apps/code/helpers.cpp @@ -1,24 +1,17 @@ #include "helpers.h" #include -#include namespace Code { namespace Helpers { -bool PythonTextForEvent(Ion::Events::Event event, char * buffer, bool * shouldRemoveLastCharacter) { +const char * PythonTextForEvent(Ion::Events::Event event) { for (size_t i=0; iXNT('x', shouldRemoveLastCharacter); - buffer[UTF8Decoder::CodePointToChars(XNT, buffer, CodePoint::MaxCodePointCharLength + 1)] = 0; - return true; - } - return false; } + return nullptr; } } } diff --git a/apps/code/helpers.h b/apps/code/helpers.h index 69b6b384288..1273ca045e7 100644 --- a/apps/code/helpers.h +++ b/apps/code/helpers.h @@ -6,7 +6,7 @@ namespace Code { namespace Helpers { -bool PythonTextForEvent(Ion::Events::Event event, char * buffer, bool * shouldRemoveLastCharacter); +const char * PythonTextForEvent(Ion::Events::Event event); } } From 3a220c0507ef28885f5db3cb67a5f0fbe6683b31 Mon Sep 17 00:00:00 2001 From: Hug0 Date: Sat, 8 Oct 2022 19:25:03 +0200 Subject: [PATCH 311/355] Upsilon readme nix (#287) --- README.fr.md | 15 +++++++++++++++ README.md | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/README.fr.md b/README.fr.md index 9c6cbacd3c4..8513ebe692c 100644 --- a/README.fr.md +++ b/README.fr.md @@ -80,6 +80,21 @@ dnf install make automake gcc gcc-c++ kernel-devel git ImageMagick libX11-devel

    +
    + +Nix/Nixos + +
    + +Installez toutes les dépendances grâce à cette commande: +```bash +nix-env -p gcc libpng libjpeg xorg.libX11 pkg-config freetype xorg.libXext python3 imagemagick python310Packages.lz4 python310Packages.pypng python310Packages.pypng gcc-arm-embedded +``` + +
    + +
    +
    diff --git a/README.md b/README.md index e2187c19964..40c7fc52d96 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,22 @@ dnf install make automake gcc gcc-c++ kernel-devel git ImageMagick libX11-devel
    +
    + +Nix or Nixos + +
    + +To install all dependencies: + +```bash +nix-shell -p gcc libpng libjpeg xorg.libX11 pkg-config freetype xorg.libXext python3 imagemagick python310Packages.lz4 python310Packages.pypng python310Packages.pypng gcc-arm-embedded +``` + +
    + +
    +
    From 1d00d7d9d3bda00405ef713523959635dccf993c Mon Sep 17 00:00:00 2001 From: Rathmox <55508107+Rathmox@users.noreply.github.com> Date: Sat, 8 Oct 2022 19:25:28 +0200 Subject: [PATCH 312/355] Fix README powershell commands (#278) --- README.fr.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.fr.md b/README.fr.md index 8513ebe692c..806a8379ea0 100644 --- a/README.fr.md +++ b/README.fr.md @@ -161,7 +161,7 @@ Votre version de windows doit être >= 1903. 1. Apuyez simulatanément sur les touches "windows" et "x" puis cliquez sur "Powershell administrateur". Entrez ensuite ceci dans la nouvelle fenêtre: ```powershell -dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux all /norestart +dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart ``` Cette commande active WSL diff --git a/README.md b/README.md index 40c7fc52d96..4135856a154 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ You need a windows version >= 1903. 1. Use simultaneously win + X keys and then click on "admin powershell". ```powershell -dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux all /norestart +dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart ``` This command activate WSL functionalities. From 78b606e8254b0a5760f583b86395f83c4996c749 Mon Sep 17 00:00:00 2001 From: devdl11 <54149885+devdl11@users.noreply.github.com> Date: Sat, 19 Nov 2022 11:13:17 +0100 Subject: [PATCH 313/355] [bootloader] Fix e19 boot (#263) --- bootloader/interface/static/messages.h | 2 +- ion/src/device/bootloader/internal_flash.ld | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bootloader/interface/static/messages.h b/bootloader/interface/static/messages.h index e47764ac87e..3623915ce9d 100644 --- a/bootloader/interface/static/messages.h +++ b/bootloader/interface/static/messages.h @@ -72,7 +72,7 @@ class Messages { constexpr static const char * aboutMessage4 = "and select the OS"; constexpr static const char * aboutMessage5 = "to boot."; - constexpr static const char * bootloaderVersion = "Version 1.0.0 - FREED0M"; + constexpr static const char * bootloaderVersion = "Version 1.0.1 - FREED0M.19"; //USB NAMES constexpr static const char * usbUpsilonBootloader = "Upsilon Bootloader"; diff --git a/ion/src/device/bootloader/internal_flash.ld b/ion/src/device/bootloader/internal_flash.ld index e441c6527eb..6a2555c3567 100644 --- a/ion/src/device/bootloader/internal_flash.ld +++ b/ion/src/device/bootloader/internal_flash.ld @@ -1,7 +1,7 @@ /* Same as flash.ld but everything is linked in internal flash */ MEMORY { - INTERNAL_FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K + INTERNAL_FLASH (rx) : ORIGIN = 0x00200000, LENGTH = 64K SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K } From 08eb1aec99c5a4524ecc6d37d38ad379c2c62703 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Sat, 3 Dec 2022 17:21:26 +0100 Subject: [PATCH 314/355] [CI] Fix Web CI (#303) --- .github/workflows/ci-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 2d0a52687e1..11fc24cb312 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -220,7 +220,7 @@ jobs: web: runs-on: ubuntu-latest steps: - - uses: numworks/setup-emscripten@v1 + - uses: numworks/setup-emscripten@master with: sdk: 1.40.1 - uses: actions/checkout@v2 From 851ec2ab69b04e6a028fbb1c8cedf3e3709f6966 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Sat, 3 Dec 2022 18:14:43 +0100 Subject: [PATCH 315/355] [bootloader] Fix exam mode with Epsilon 19 and add support for Epsilon 20 (#302) * [bootloader] Fix exam mode on Epsilon 19 * [bootloader] Fix comment indentation * [bootloader] Restore comment indentation (even if GitHub tell that it isn't indented properly) * [bootloader] Turn tabulations into spaces * [bootloader] Mark Epsilon 19 as "safe to boot" * [bootloader] Fix Epsilon 20 boot * [bootloader] Chang version to 1.0.2 --- .gitignore | 2 + bootloader/boot.cpp | 2 +- bootloader/interface/static/messages.h | 4 +- bootloader/main.cpp | 11 +- bootloader/slots/slot.cpp | 27 ++- bootloader/slots/slot.h | 4 +- bootloader/slots/slot_exam_mode.cpp | 239 +++++++++++++------------ bootloader/slots/slot_exam_mode.h | 22 ++- bootloader/slots/userland_header.cpp | 4 +- bootloader/utility.cpp | 20 ++- 10 files changed, 195 insertions(+), 140 deletions(-) diff --git a/.gitignore b/.gitignore index 2ff966b9b60..56cb4179582 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ epsilon.map .gradle .idea/ .vs +.cache/ +compile_commands.json diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index 931b27afc39..bb9ba5aed26 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -107,7 +107,7 @@ void Boot::bootSlot(Bootloader::Slot s) { if (!s.userlandHeader()->isOmega() && !s.userlandHeader()->isUpsilon()) { // We are trying to boot epsilon, so we check the version and show an advertisement if needed const char * version = s.userlandHeader()->version(); - const char * min = "18.2.4"; + const char * min = "20.0.0"; int versionSum = Utility::versionSum(version, strlen(version)); int minimalVersionTrigger = Utility::versionSum(min, strlen(min)); if (versionSum >= minimalVersionTrigger) { diff --git a/bootloader/interface/static/messages.h b/bootloader/interface/static/messages.h index 3623915ce9d..6d97ee991cd 100644 --- a/bootloader/interface/static/messages.h +++ b/bootloader/interface/static/messages.h @@ -57,7 +57,7 @@ class Messages { constexpr static const char * epsilonWarningTitle = "Epsilon Slot"; constexpr static const char * epsilonWarningMessage1 = "!! WARNING !! "; - constexpr static const char * epsilonWarningMessage2 = "This version of epsilon"; + constexpr static const char * epsilonWarningMessage2 = "This version of Epsilon"; constexpr static const char * epsilonWarningMessage3 = "can lock the calculator."; constexpr static const char * epsilonWarningMessage4 = "Proceed the boot ?"; constexpr static const char * epsilonWarningMessage5 = "EXE - Yes"; @@ -72,7 +72,7 @@ class Messages { constexpr static const char * aboutMessage4 = "and select the OS"; constexpr static const char * aboutMessage5 = "to boot."; - constexpr static const char * bootloaderVersion = "Version 1.0.1 - FREED0M.19"; + constexpr static const char * bootloaderVersion = "Version 1.0.2 - FREED0M.20"; //USB NAMES constexpr static const char * usbUpsilonBootloader = "Upsilon Bootloader"; diff --git a/bootloader/main.cpp b/bootloader/main.cpp index c0f0995db11..9452fc15829 100644 --- a/bootloader/main.cpp +++ b/bootloader/main.cpp @@ -20,30 +20,29 @@ __attribute__ ((noreturn)) void ion_main(int argc, const char * const argv[]) { bool isSlotA = Bootloader::Slot::isFullyValid(Bootloader::Slot::A()); if (isSlotA) { - Bootloader::ExamMode::ExamMode SlotAExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotAExamMode(!Bootloader::Slot::A().userlandHeader()->isOmega()); + Bootloader::ExamMode::ExamMode SlotAExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotAExamMode(Bootloader::Slot::A().userlandHeader()->version()); if (SlotAExamMode != Bootloader::ExamMode::ExamMode::Off && SlotAExamMode != Bootloader::ExamMode::ExamMode::Unknown) { // We boot the slot in exam_mode Bootloader::Slot::A().boot(); - } + } } bool isSlotB = Bootloader::Slot::isFullyValid(Bootloader::Slot::B()); if (isSlotB) { - Bootloader::ExamMode::ExamMode SlotBExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotBExamMode(!Bootloader::Slot::B().userlandHeader()->isOmega()); + Bootloader::ExamMode::ExamMode SlotBExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotBExamMode(Bootloader::Slot::B().userlandHeader()->version()); if (SlotBExamMode != Bootloader::ExamMode::ExamMode::Off && SlotBExamMode != Bootloader::ExamMode::ExamMode::Unknown && isSlotB) { // We boot the slot in exam_mode Bootloader::Slot::B().boot(); } - } - // I have no idea if this will work, but if Pariss did a good job, it should + // I have no idea if this will work, but if Parisse did a good job, it should bool isKhiSlot = Bootloader::Slot::isFullyValid(Bootloader::Slot::Khi()); if (isKhiSlot) { - Bootloader::ExamMode::ExamMode KhiExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotKhiExamMode(); + Bootloader::ExamMode::ExamMode KhiExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotKhiExamMode(Bootloader::Slot::Khi().userlandHeader()->version()); if (KhiExamMode != Bootloader::ExamMode::ExamMode::Off && KhiExamMode != Bootloader::ExamMode::ExamMode::Unknown && isKhiSlot) { // We boot the slot in exam_mode Bootloader::Slot::Khi().boot(); diff --git a/bootloader/slots/slot.cpp b/bootloader/slots/slot.cpp index d8ce933985f..2a3cebee21f 100644 --- a/bootloader/slots/slot.cpp +++ b/bootloader/slots/slot.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include extern "C" void jump_to_firmware(const uint32_t* stackPtr, const void(*startPtr)(void)); @@ -24,7 +25,13 @@ const KernelHeader* Slot::kernelHeader() const { } const UserlandHeader* Slot::userlandHeader() const { - return m_userlandHeader; + if (m_userlandHeader->isValid()) { + return m_userlandHeader; + } else if (m_userland2Header->isValid()) { + return m_userland2Header; + } else { + return m_userlandHeader; + } } [[ noreturn ]] void Slot::boot() const { @@ -37,6 +44,24 @@ const UserlandHeader* Slot::userlandHeader() const { Ion::Device::Flash::LockSlotA(); } + // Erase the bootloader integrated in slots in Epsilon 20 + if (m_userland2Header->isValid()) { + if (m_address == 0x90000000) { + // Check if bootloader is present in slot A + if (*(uint32_t*)0x90010000 != 0xFFFFFFFF) { + // Erase bootloader in slot A + Ion::Device::ExternalFlash::EraseSector(9); + } + } + else if (m_address == 0x90400000) { + // Check if bootloader is present in slot B + if (*(uint32_t*)0x90410000 != 0xFFFFFFFF) { + // Erase bootloader in slot B + Ion::Device::ExternalFlash::EraseSector(73); + } + } + } + // Configure the MPU for the booted firmware Ion::Device::Board::bootloaderMPU(); diff --git a/bootloader/slots/slot.h b/bootloader/slots/slot.h index 8aa97b4d388..1d004660590 100644 --- a/bootloader/slots/slot.h +++ b/bootloader/slots/slot.h @@ -14,7 +14,8 @@ class Slot { Slot(uint32_t address) : m_kernelHeader(reinterpret_cast(address)), m_userlandHeader(reinterpret_cast(address + 64 * 1024)), - m_address(address) { } + m_userland2Header(reinterpret_cast(address + 128 * 1024)), + m_address(address) {} const KernelHeader* kernelHeader() const; const UserlandHeader* userlandHeader() const; @@ -32,6 +33,7 @@ class Slot { private: const KernelHeader* m_kernelHeader; const UserlandHeader* m_userlandHeader; + const UserlandHeader* m_userland2Header; const uint32_t m_address; }; diff --git a/bootloader/slots/slot_exam_mode.cpp b/bootloader/slots/slot_exam_mode.cpp index 45b3123239e..47c22995e0a 100644 --- a/bootloader/slots/slot_exam_mode.cpp +++ b/bootloader/slots/slot_exam_mode.cpp @@ -40,88 +40,70 @@ size_t numberOfBitsAfterLeadingZeroes(int i) { return maxShift; } -uint8_t * SignificantSlotAExamModeAddress(bool newVersion) { - uint32_t * persitence_start_32 = (uint32_t *)SlotsExamMode::getSlotAStartExamAddress(newVersion); - uint32_t * persitence_end_32 = (uint32_t *)SlotsExamMode::getSlotAEndExamAddress(newVersion); - if (!newVersion) { - assert((persitence_end_32 - persitence_start_32) % 4 == 0); - while (persitence_start_32 < persitence_end_32 && *persitence_start_32 == 0x0) { - // Scan by groups of 32 bits to reach first non-zero bit - persitence_start_32++; - } - uint8_t * persitence_start_8 = (uint8_t *)persitence_start_32; - uint8_t * persitence_end_8 = (uint8_t *)persitence_end_32; - while (persitence_start_8 < persitence_end_8 && *persitence_start_8 == 0x0) { - // Scan by groups of 8 bits to reach first non-zero bit - persitence_start_8++; - } - if (persitence_start_8 == persitence_end_8 - // we can't toggle from 0[3] to 2[3] when there is only one 1 bit in the whole sector - || (persitence_start_8 + 1 == persitence_end_8 && *persitence_start_8 == 1)) { - assert(Ion::Device::Flash::SectorAtAddress(SlotsExamMode::getSlotAStartExamAddress(newVersion)) >= 0); - Ion::Device::Flash::EraseSector(Ion::Device::Flash::SectorAtAddress(SlotsExamMode::getSlotAStartExamAddress(newVersion))); - return (uint8_t *)SlotsExamMode::getSlotAStartExamAddress(newVersion); - } +uint8_t SlotsExamMode::FetchSlotExamMode(const char * version, const char * Slot) { + // Get start and end from version and slot + uint32_t start = 0; + uint32_t end = 0; + if (Slot == "A") { + // If version under 16 get old addresses + if (version[0] < '1' || (version[0] == '1' && version[1] < '6')) { + start = getSlotAStartExamAddress(0); + end = getSlotAEndExamAddress(0); + } + // Else get new addresses + else { + start = getSlotAStartExamAddress(1); + end = getSlotAEndExamAddress(1); + } + } + else if (Slot == "B") { + // If version under 16 get old + if (version[0] < '1' || (version[0] == '1' && version[1] < '6')) { + start = getSlotBStartExamAddress(0); + end = getSlotBEndExamAddress(0); + } + // Else get new + else { + start = getSlotBStartExamAddress(1); + end = getSlotBEndExamAddress(1); + } + } else if (Slot == "Khi") { + // We directly get the address of the Khi exam mode without checking the + // version, because on Khi, version is KhiCAS version, not the OS version + start = getSlotKhiStartExamAddress(); + end = getSlotKhiEndExamAddress(); + } - return persitence_start_8; - } else { - persitence_end_32 = persitence_end_32 - 1; - while (persitence_end_32 - (uint32_t)(10 / 8) >= persitence_end_32 && *persitence_end_32 == 0xFFFFFFFF) { - persitence_end_32 -= 1; - } - uint8_t * start = reinterpret_cast(persitence_start_32); - uint8_t * end = reinterpret_cast(persitence_end_32 + 1) - 1; - while (end >= start + 2 && *end == 0xFF) { - end -= 1; - } - return end - 1; + if (strcmp("15.9.0", version) >= 0) { + return examFetch15(start, end); + } else if (strcmp("16.9.0", version) > 0) { + return examFetch16(start, end); + } + else if (strcmp("19.0.0", version) > 0) { + return examFetch1718(start, end); + } + else { + return examFetch19(start, end); } - } -uint8_t * SignificantSlotBExamModeAddress(bool newVersion) { - uint32_t * persitence_start_32 = (uint32_t *)SlotsExamMode::getSlotBStartExamAddress(newVersion); - uint32_t * persitence_end_32 = (uint32_t *)SlotsExamMode::getSlotBEndExamAddress(newVersion); - if (!newVersion) { - assert((persitence_end_32 - persitence_start_32) % 4 == 0); - while (persitence_start_32 < persitence_end_32 && *persitence_start_32 == 0x0) { - // Scan by groups of 32 bits to reach first non-zero bit - persitence_start_32++; - } - uint8_t * persitence_start_8 = (uint8_t *)persitence_start_32; - uint8_t * persitence_end_8 = (uint8_t *)persitence_end_32; - while (persitence_start_8 < persitence_end_8 && *persitence_start_8 == 0x0) { - // Scan by groups of 8 bits to reach first non-zero bit - persitence_start_8++; - } - if (persitence_start_8 == persitence_end_8 - // we can't toggle from 0[3] to 2[3] when there is only one 1 bit in the whole sector - || (persitence_start_8 + 1 == persitence_end_8 && *persitence_start_8 == 1)) { - assert(Ion::Device::Flash::SectorAtAddress(SlotsExamMode::getSlotBStartExamAddress(newVersion)) >= 0); - Ion::Device::Flash::EraseSector(Ion::Device::Flash::SectorAtAddress(SlotsExamMode::getSlotBStartExamAddress(newVersion))); - return (uint8_t *)SlotsExamMode::getSlotBStartExamAddress(newVersion); - } - return persitence_start_8; - } else { - persitence_end_32 = persitence_end_32 - 1; - while (persitence_end_32 - (uint32_t)(10 / 8) >= persitence_end_32 && *persitence_end_32 == 0xFFFFFFFF) { - persitence_end_32 -= 1; - } - uint8_t * start = reinterpret_cast(persitence_start_32); - uint8_t * end = reinterpret_cast(persitence_end_32 + 1) - 1; - while (end >= start + 2 && *end == 0xFF) { - end -= 1; - } - return end - 1; - } - + +uint8_t SlotsExamMode::FetchSlotAExamMode(const char* version) { + return FetchSlotExamMode(version, "A"); } -uint8_t * SignificantSlotKhiExamModeAddress() { - uint32_t * persitence_start_32 = (uint32_t *)SlotsExamMode::getSlotKhiStartExamAddress(); - uint32_t * persitence_end_32 = (uint32_t *)SlotsExamMode::getSlotKhiEndExamAddress(); - +uint8_t SlotsExamMode::FetchSlotBExamMode(const char* version) { + return FetchSlotExamMode(version, "B"); +} + +uint8_t SlotsExamMode::FetchSlotKhiExamMode(const char* version) { + return FetchSlotExamMode(version, "Khi"); +} + +uint8_t SlotsExamMode::examFetch15(uint32_t start, uint32_t end) { + uint32_t * persitence_start_32 = (uint32_t *)start; + uint32_t * persitence_end_32 = (uint32_t *)end; assert((persitence_end_32 - persitence_start_32) % 4 == 0); while (persitence_start_32 < persitence_end_32 && *persitence_start_32 == 0x0) { // Scan by groups of 32 bits to reach first non-zero bit @@ -136,65 +118,86 @@ uint8_t * SignificantSlotKhiExamModeAddress() { if (persitence_start_8 == persitence_end_8 // we can't toggle from 0[3] to 2[3] when there is only one 1 bit in the whole sector || (persitence_start_8 + 1 == persitence_end_8 && *persitence_start_8 == 1)) { - assert(Ion::Device::Flash::SectorAtAddress(SlotsExamMode::getSlotKhiStartExamAddress()) >= 0); - Ion::Device::Flash::EraseSector(Ion::Device::Flash::SectorAtAddress(SlotsExamMode::getSlotKhiStartExamAddress())); - return (uint8_t *)SlotsExamMode::getSlotKhiStartExamAddress(); - } - - return persitence_start_8; -} - -uint8_t SlotsExamMode::FetchSlotAExamMode(bool newVersion) { - uint8_t * readingAddress = SignificantSlotAExamModeAddress(newVersion); - if (!newVersion) { - // Count the number of 0[3] before reading address - uint32_t nbOfZerosBefore = ((readingAddress - (uint8_t *)getSlotAStartExamAddress(newVersion)) * numberOfBitsInByte) % 4; + assert(Ion::Device::Flash::SectorAtAddress(start) >= 0); + Ion::Device::Flash::EraseSector(start); + uint32_t nbOfZerosBefore = (((uint8_t*)start - (uint8_t*)start) * numberOfBitsInByte) % 4; // Count the number of 0[3] at reading address - size_t numberOfLeading0 = (numberOfBitsInByte - numberOfBitsAfterLeadingZeroes(*readingAddress)) % 4; + size_t numberOfLeading0 = (numberOfBitsInByte - numberOfBitsAfterLeadingZeroes(*(uint8_t*)start)) % 4; return (nbOfZerosBefore + numberOfLeading0) % 4; - } else { - return *((uint8_t *)readingAddress); } - -} - -uint8_t SlotsExamMode::FetchSlotBExamMode(bool newVersion) { - uint8_t * readingAddress = SignificantSlotBExamModeAddress(newVersion); - if (!newVersion) { - // Count the number of 0[3] before reading address - uint32_t nbOfZerosBefore = ((readingAddress - (uint8_t *)getSlotBStartExamAddress(newVersion)) * numberOfBitsInByte) % 4; + uint32_t nbOfZerosBefore = ((persitence_start_8 - (uint8_t*)start) * numberOfBitsInByte) % 4; // Count the number of 0[3] at reading address - size_t numberOfLeading0 = (numberOfBitsInByte - numberOfBitsAfterLeadingZeroes(*readingAddress)) % 4; + size_t numberOfLeading0 = (numberOfBitsInByte - numberOfBitsAfterLeadingZeroes(*persitence_start_8)) % 4; return (nbOfZerosBefore + numberOfLeading0) % 4; - } else { - return *((uint8_t *)readingAddress); +} + +uint8_t SlotsExamMode::examFetch16(uint32_t start, uint32_t end) { + uint8_t* persitence_start_8 = (uint8_t*)start; + uint8_t* persitence_end_8 = (uint8_t*)end; + while (persitence_start_8 + 1 <= persitence_end_8 && (*persitence_start_8 != 0xFF)) { + // Scan by groups of 8 bits to reach first non-zero bit + persitence_start_8++; } - + + return *(persitence_start_8 - 1); } -uint8_t SlotsExamMode::FetchSlotKhiExamMode() { - uint8_t * readingAddress = SignificantSlotKhiExamModeAddress(); - // Count the number of 0[3] before reading address - uint32_t nbOfZerosBefore = ((readingAddress - (uint8_t *)getSlotKhiStartExamAddress()) * numberOfBitsInByte) % 4; - // Count the number of 0[3] at reading address - size_t numberOfLeading0 = (numberOfBitsInByte - numberOfBitsAfterLeadingZeroes(*readingAddress)) % 4; - return (nbOfZerosBefore + numberOfLeading0) % 4; +uint8_t SlotsExamMode::examFetch1718(uint32_t start, uint32_t end) { + uint8_t* persitence_start_8 = (uint8_t*)start; + uint8_t* persitence_end_8 = (uint8_t*)end; + while (persitence_start_8 + 1 <= persitence_end_8 && (*persitence_start_8 != 0xFF)) { + // Scan by groups of 8 bits to reach first non-zero bit + persitence_start_8++; +} + + return *(persitence_start_8 - 2); +} + +uint8_t SlotsExamMode::examFetch19(uint32_t start, uint32_t end) { + uint16_t* start16 = (uint16_t*)start; + uint16_t* end16 = (uint16_t*)end; + + while (start16 + 1 <= end16 && *start16 != 0xFFFF) { + start16++; + } + + return *(start16 - 1) >> 8; } -uint32_t SlotsExamMode::getSlotAStartExamAddress(bool newVersion) { - return newVersion ? SlotAExamModeBufferStartNewVersions : SlotAExamModeBufferStartOldVersions; +uint32_t SlotsExamMode::getSlotAStartExamAddress(int ExamVersion) { + if (ExamVersion == 0) { + return SlotAExamModeBufferStartOldVersions; + } + else { + return SlotAExamModeBufferStartNewVersions; + } } -uint32_t SlotsExamMode::getSlotAEndExamAddress(bool newVersion) { - return newVersion ? SlotAExamModeBufferEndNewVersions : SlotAExamModeBufferEndOldVersions; +uint32_t SlotsExamMode::getSlotAEndExamAddress(int ExamVersion) { + if (ExamVersion == 0) { + return SlotAExamModeBufferEndOldVersions; + } + else { + return SlotAExamModeBufferEndNewVersions;; + } } -uint32_t SlotsExamMode::getSlotBStartExamAddress(bool newVersion) { - return newVersion ? SlotBExamModeBufferStartNewVersions : SlotBExamModeBufferStartOldVersions; +uint32_t SlotsExamMode::getSlotBStartExamAddress(int ExamVersion) { + if (ExamVersion == 0) { + return SlotBExamModeBufferStartOldVersions; + } + else { + return SlotBExamModeBufferStartNewVersions; + } } -uint32_t SlotsExamMode::getSlotBEndExamAddress(bool newVersion) { - return newVersion ? SlotBExamModeBufferEndNewVersions : SlotBExamModeBufferEndOldVersions; +uint32_t SlotsExamMode::getSlotBEndExamAddress(int ExamVersion) { + if (ExamVersion == 0) { + return SlotBExamModeBufferEndOldVersions; + } + else { + return SlotBExamModeBufferEndNewVersions; + } } uint32_t SlotsExamMode::getSlotKhiStartExamAddress() { diff --git a/bootloader/slots/slot_exam_mode.h b/bootloader/slots/slot_exam_mode.h index d9e380e9a31..3c19ac7a90f 100644 --- a/bootloader/slots/slot_exam_mode.h +++ b/bootloader/slots/slot_exam_mode.h @@ -25,17 +25,23 @@ static const uint32_t SlotKhiExamModeBufferEnd = 0x90183000; class SlotsExamMode{ public: - static uint8_t FetchSlotAExamMode(bool newVersion); - static uint8_t FetchSlotBExamMode(bool newVerion); - static uint8_t FetchSlotKhiExamMode(); - - static uint32_t getSlotAStartExamAddress(bool newVersion); - static uint32_t getSlotAEndExamAddress(bool newVersion); - static uint32_t getSlotBStartExamAddress(bool newVersion); - static uint32_t getSlotBEndExamAddress(bool newVersion); + static uint8_t FetchSlotExamMode(const char* version, const char* Slot); + static uint8_t FetchSlotAExamMode(const char* version); + static uint8_t FetchSlotBExamMode(const char* version); + static uint8_t FetchSlotKhiExamMode(const char* version); + + static uint32_t getSlotAStartExamAddress(int ExamVersion); + static uint32_t getSlotAEndExamAddress(int ExamVersion); + static uint32_t getSlotBStartExamAddress(int ExamVersion); + static uint32_t getSlotBEndExamAddress(int ExamVersion); static uint32_t getSlotKhiStartExamAddress(); static uint32_t getSlotKhiEndExamAddress(); + static uint8_t examFetch15(uint32_t start, uint32_t end); + static uint8_t examFetch1718(uint32_t start, uint32_t end); + static uint8_t examFetch16(uint32_t start, uint32_t end); + static uint8_t examFetch19(uint32_t start, uint32_t end); + }; enum class ExamMode : int8_t { diff --git a/bootloader/slots/userland_header.cpp b/bootloader/slots/userland_header.cpp index 3c8a9190872..a3e57afec7a 100644 --- a/bootloader/slots/userland_header.cpp +++ b/bootloader/slots/userland_header.cpp @@ -10,7 +10,9 @@ const char * UserlandHeader::version() const { } const bool UserlandHeader::isValid() const { - return m_header == Magic && m_footer == Magic; + // We only verify only the first Magic Number, to display version such as + // Epsilon 16 with older UserlandHeader layout + return m_header == Magic; } const bool UserlandHeader::isOmega() const { diff --git a/bootloader/utility.cpp b/bootloader/utility.cpp index fb6d676ac5b..786e8e0fc78 100644 --- a/bootloader/utility.cpp +++ b/bootloader/utility.cpp @@ -1,10 +1,26 @@ #include #include +// This function takes a pointer to a string (version) and the size of the +// string (versionSize) and returns an integer representing the version. +// Example: "1.2.3" will return 10203. +// "1.0.1-dev" will return 10001. int Utility::versionSum(const char * version, int length) { int sum = 0; - for (int i = 0; i < length; i++) { - sum += version[i] * (strlen(version) * 100 - i * 10); + int currentNumber = 0; + // List of numbers that are allowed in a version + const char * allowedNumbers = "0123456789"; + for (int i = 0; i < length; i++) { + if (version[i] == '.') { + sum = sum * 100 + currentNumber; + currentNumber = 0; + } else if (strchr(allowedNumbers, version[i]) != nullptr) { + currentNumber = currentNumber * 10 + (version[i] - '0'); + } else { + // We found a character that is not a number or a dot, so we stop + break; + } } + sum = sum * 100 + currentNumber; return sum; } From 1bf164e1e38bd198301b4c85b67beb96eb0e785c Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Sat, 3 Dec 2022 18:53:22 +0100 Subject: [PATCH 316/355] [CI] Add bootloader in bootloader binpack (#304) * [CI] Add bootloader in bootloader binpack * [CI] Second try to add bootloader in bootloader binpack --- .github/workflows/ci-workflow.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 11fc24cb312..36de0e30a07 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -161,7 +161,7 @@ jobs: - uses: actions/checkout@v2 with: submodules: 'recursive' - - run: make -j2 MODEL=n0110 bootloader.dfu + - run: make -j2 MODEL=n0110 bootloader - run: make -j2 epsilon.A.dfu epsilon.B.dfu - run: make -j2 epsilon.onboarding.A.dfu - run: make -j2 epsilon.onboarding.B.dfu @@ -171,6 +171,11 @@ jobs: - run: make -j2 epsilon.onboarding.beta.B.dfu - run: make -j2 binpack - run: cp output/release/device/bootloader/binpack-bootloader-`git rev-parse HEAD | head -c 7`.tgz output/release/device/bootloader/binpack-bootloader.tgz + - run: | + gzip -d output/release/device/bootloader/binpack-bootloader.tgz + tar rvf output/release/device/bootloader/binpack-bootloader.tar output/release/device/n0110/bootloader.bin --transform 's,^output/release/device/n0110/bootloader.bin,binpack/bootloader.bin,' + gzip output/release/device/bootloader/binpack-bootloader.tar + mv output/release/device/bootloader/binpack-bootloader.tar.gz output/release/device/bootloader/binpack-bootloader.tgz - id: 'auth' if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} uses: 'google-github-actions/auth@v0' From 62d4d59d6b0718e0962b1d1ba15764ef7042761e Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Mon, 5 Dec 2022 20:44:54 +0100 Subject: [PATCH 317/355] [CI] Fix bootloader upload --- .github/workflows/ci-workflow.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 36de0e30a07..631f7002483 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -171,11 +171,8 @@ jobs: - run: make -j2 epsilon.onboarding.beta.B.dfu - run: make -j2 binpack - run: cp output/release/device/bootloader/binpack-bootloader-`git rev-parse HEAD | head -c 7`.tgz output/release/device/bootloader/binpack-bootloader.tgz - - run: | - gzip -d output/release/device/bootloader/binpack-bootloader.tgz - tar rvf output/release/device/bootloader/binpack-bootloader.tar output/release/device/n0110/bootloader.bin --transform 's,^output/release/device/n0110/bootloader.bin,binpack/bootloader.bin,' - gzip output/release/device/bootloader/binpack-bootloader.tar - mv output/release/device/bootloader/binpack-bootloader.tar.gz output/release/device/bootloader/binpack-bootloader.tgz + - run: cp output/release/device/n0110/bootloader.bin output/release/device/bootloader/binpack/ + - run: cd output/release/device/bootloader && tar cvfz binpack-bootloader.tgz binpack/* - id: 'auth' if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} uses: 'google-github-actions/auth@v0' From 2a89c23e0778704b664baec1cd69bd5c2152e01c Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Mon, 5 Dec 2022 20:57:51 +0100 Subject: [PATCH 318/355] [CI] Rebuild checksums before uploading bootloader --- .github/workflows/ci-workflow.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 631f7002483..ccf2a8010f9 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -172,6 +172,7 @@ jobs: - run: make -j2 binpack - run: cp output/release/device/bootloader/binpack-bootloader-`git rev-parse HEAD | head -c 7`.tgz output/release/device/bootloader/binpack-bootloader.tgz - run: cp output/release/device/n0110/bootloader.bin output/release/device/bootloader/binpack/ + - run: cd output/release/device/bootloader && for binary in epsilon.onboarding*.bin; do shasum -a 256 -b binpack/${binary} > binpack/${binary}.sha256;done - run: cd output/release/device/bootloader && tar cvfz binpack-bootloader.tgz binpack/* - id: 'auth' if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} From 8d6c7d1bff3a9750b59af69e82e8c3c05658320d Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Tue, 6 Dec 2022 16:37:41 +0100 Subject: [PATCH 319/355] [CI] Build all bin checksums --- .github/workflows/ci-workflow.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index ccf2a8010f9..8c3bcbf4099 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -172,7 +172,8 @@ jobs: - run: make -j2 binpack - run: cp output/release/device/bootloader/binpack-bootloader-`git rev-parse HEAD | head -c 7`.tgz output/release/device/bootloader/binpack-bootloader.tgz - run: cp output/release/device/n0110/bootloader.bin output/release/device/bootloader/binpack/ - - run: cd output/release/device/bootloader && for binary in epsilon.onboarding*.bin; do shasum -a 256 -b binpack/${binary} > binpack/${binary}.sha256;done + - run: cp output/release/device/n0110/bootloader.bin output/release/device/bootloader/ + - run: cd output/release/device/bootloader && for binary in *.bin; do shasum -a 256 -b binpack/${binary} > binpack/${binary}.sha256;done - run: cd output/release/device/bootloader && tar cvfz binpack-bootloader.tgz binpack/* - id: 'auth' if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} From c9c868c32c56babb44f9821736544fe89e09fa78 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Tue, 6 Dec 2022 17:14:27 +0100 Subject: [PATCH 320/355] [CI] Use latest version of ARM toolchain for bootloader --- .github/workflows/ci-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 8c3bcbf4099..63b3308f5a1 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -157,7 +157,7 @@ jobs: runs-on: ubuntu-latest steps: - run: sudo apt-get install build-essential imagemagick libfreetype6-dev libjpeg-dev libpng-dev pkg-config - - uses: numworks/setup-arm-toolchain@2020-q2 + - uses: numworks/setup-arm-toolchain@2022-08 - uses: actions/checkout@v2 with: submodules: 'recursive' From 1de090cea4a4e3d91d6092bfcbe9be10fcf2223e Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Fri, 9 Dec 2022 17:42:44 +0100 Subject: [PATCH 321/355] [apps/home] Quick select for apps using numbers (#306) * [apps/home] Quick select for apps using numbers * [apps/home] Fix build error --- apps/home/controller.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/apps/home/controller.cpp b/apps/home/controller.cpp index a79700b3f0d..da3a147d43d 100644 --- a/apps/home/controller.cpp +++ b/apps/home/controller.cpp @@ -75,7 +75,7 @@ Controller::Controller(Responder * parentResponder, SelectableTableViewDataSourc m_view.backgroundView()->setDefaultColor(Palette::HomeBackground); - + #ifdef HOME_DISPLAY_EXTERNALS int index = External::Archive::indexFromName("wallpaper.obm"); if(index > -1) { @@ -86,6 +86,13 @@ Controller::Controller(Responder * parentResponder, SelectableTableViewDataSourc #endif } +static constexpr Ion::Events::Event home_fast_navigation_events[] = { + Ion::Events::Seven, Ion::Events::Eight, Ion::Events::Nine, + Ion::Events::Four, Ion::Events::Five, Ion::Events::Six, + Ion::Events::One, Ion::Events::Two, Ion::Events::Three, + Ion::Events::Zero, Ion::Events::Dot, Ion::Events::EE +}; + bool Controller::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::OK || event == Ion::Events::EXE) { AppsContainer * container = AppsContainer::sharedAppsContainer(); @@ -143,6 +150,21 @@ bool Controller::handleEvent(Ion::Events::Event event) { return m_view.selectableTableView()->selectCellAtLocation(numberOfColumns() - 1, selectionDataSource()->selectedRow() - 1); } + // Handle fast home navigation + for(int i = 0; i < std::min((int) (sizeof(home_fast_navigation_events) / sizeof(Ion::Events::Event)), this->numberOfIcons()); i++) { + if (event == home_fast_navigation_events[i]) { + int row = i / k_numberOfColumns; + int column = i % k_numberOfColumns; + // Get if app is already selected + if (selectionDataSource()->selectedRow() == row && selectionDataSource()->selectedColumn() == column) { + // If app is already selected, launch it + return handleEvent(Ion::Events::OK); + } + // Else, select the app + return m_view.selectableTableView()->selectCellAtLocation(column, row); + } + } + return false; } From 23121479576071476431f689eecf3a0344f27e1d Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Thu, 29 Dec 2022 12:38:44 +0100 Subject: [PATCH 322/355] [apps/atomic] Use latest commit (redesign) --- .gitmodules | 4 ++-- apps/atomic | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 35f8c77ce5b..a535fa5dadf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "apps/rpn"] path = apps/rpn - url = https://github.com/Lauryy06/Upsilon-RPN.git + url = https://github.com/UpsilonNumworks/Upsilon-RPN.git [submodule "apps/atomic"] path = apps/atomic - url = https://github.com/Lauryy06/atomic + url = https://github.com/UpsilonNumworks/atomic diff --git a/apps/atomic b/apps/atomic index acefa4fa3c0..c03cdc4ca11 160000 --- a/apps/atomic +++ b/apps/atomic @@ -1 +1 @@ -Subproject commit acefa4fa3c0b562cbc0cbd64c35d718e17a913e2 +Subproject commit c03cdc4ca1196a1b6bed65224057defef95f4b39 From ed002ed14b6fcc839845a04ba893a0fb99eaf844 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Sun, 1 Jan 2023 11:59:36 +0100 Subject: [PATCH 323/355] [apps/atomic] Fix last commit (std::max error) --- apps/atomic | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/atomic b/apps/atomic index c03cdc4ca11..5f7063d4474 160000 --- a/apps/atomic +++ b/apps/atomic @@ -1 +1 @@ -Subproject commit c03cdc4ca1196a1b6bed65224057defef95f4b39 +Subproject commit 5f7063d447414028a3c4b750cd6dbe83bd6296e6 From 1a681c7c7566d1f70fb005ba139a5d9ce7148950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc?= <49951010+lolocomotive@users.noreply.github.com> Date: Sun, 1 Jan 2023 14:16:12 +0100 Subject: [PATCH 324/355] Fix android simulator (#277) * Fix android CI * Set NDK path * Reduced log spam * Fixed NDK path * Fixed NDK path * Android NDK v21e * Use debug signin config if SIGNING_STORE_FILE is not defined This allows the apk to be installed * Fix OmegaActivity class name * Fix formatting Co-authored-by: Yaya-Cout Co-authored-by: Yaya-Cout --- .github/workflows/ci-workflow.yml | 4 +++- ion/src/simulator/android/build.gradle | 2 ++ ion/src/simulator/android/src/cpp/platform_language.cpp | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 63b3308f5a1..2bfbd0f126a 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -48,7 +48,9 @@ jobs: - uses: actions/checkout@v2 with: submodules: 'recursive' - - run: make -j2 PLATFORM=simulator TARGET=android + - run: wget -nv https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip + - run: unzip -q android-ndk-r21e-linux-x86_64.zip + - run: make -j2 PLATFORM=simulator TARGET=android NDK_PATH=./android-ndk-r21e - id: 'auth' if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} uses: 'google-github-actions/auth@v0' diff --git a/ion/src/simulator/android/build.gradle b/ion/src/simulator/android/build.gradle index bb48650fd6c..c2e36530e41 100644 --- a/ion/src/simulator/android/build.gradle +++ b/ion/src/simulator/android/build.gradle @@ -55,6 +55,8 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt') if (projectVariable('SIGNING_STORE_FILE')) { signingConfig signingConfigs.environment + } else { + signingConfig signingConfigs.debug } } } diff --git a/ion/src/simulator/android/src/cpp/platform_language.cpp b/ion/src/simulator/android/src/cpp/platform_language.cpp index b41b1ae4695..7aa29c2eea8 100644 --- a/ion/src/simulator/android/src/cpp/platform_language.cpp +++ b/ion/src/simulator/android/src/cpp/platform_language.cpp @@ -12,7 +12,7 @@ const char * languageCode() { JNIEnv * env = static_cast(SDL_AndroidGetJNIEnv()); jobject activity = static_cast(SDL_AndroidGetActivity()); - jclass j_class = env->FindClass("io/github/omega/OmegaActivity"); + jclass j_class = env->FindClass("io/github/omega/simulator/OmegaActivity"); jmethodID j_methodId = env->GetMethodID( j_class, "retrieveLanguage", From 7030c58c51978f301a250a964f463cb0148d31f2 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Thu, 5 Jan 2023 17:18:11 +0100 Subject: [PATCH 325/355] [storage] Fix broken script saving/loading --- apps/code/script_template.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/code/script_template.h b/apps/code/script_template.h index c3f0f5b99b4..6eac56d34b1 100644 --- a/apps/code/script_template.h +++ b/apps/code/script_template.h @@ -10,7 +10,7 @@ class ScriptTemplate { constexpr ScriptTemplate(const char * name, const char * value) : m_name(name), m_value(value) {} static const ScriptTemplate * Empty(); const char * name() const { return m_name; } - const char * content() const { return m_value;} + const char * content() const { return m_value + Script::StatusSize(); } const char * value() const { return m_value; } private: const char * m_name; From 72d10f8396f6f716a1243a8b0ff3ba0801c14130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc?= <49951010+lolocomotive@users.noreply.github.com> Date: Thu, 5 Jan 2023 17:33:53 +0100 Subject: [PATCH 326/355] [Simulator] Don't resize canvas to 0x0 (#310) * [Simulator] Don't resize canvas to 0x0 * [storage] Fix broken script saving/loading * [Simulator] Don't resize canvas to 0x0 Co-authored-by: Yaya-Cout --- .../external/sdl/src/video/emscripten/SDL_emscriptenvideo.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/ion/src/simulator/external/sdl/src/video/emscripten/SDL_emscriptenvideo.c b/ion/src/simulator/external/sdl/src/video/emscripten/SDL_emscriptenvideo.c index ad9b33cbcb4..36de89e3d7d 100644 --- a/ion/src/simulator/external/sdl/src/video/emscripten/SDL_emscriptenvideo.c +++ b/ion/src/simulator/external/sdl/src/video/emscripten/SDL_emscriptenvideo.c @@ -268,9 +268,6 @@ Emscripten_DestroyWindow(_THIS, SDL_Window * window) data->egl_surface = EGL_NO_SURFACE; } #endif - - /* We can't destroy the canvas, so resize it to zero instead */ - emscripten_set_canvas_element_size(data->canvas_id, 0, 0); SDL_free(data->canvas_id); SDL_free(window->driverdata); From 0e65c97774f983ec0a47fde14bfedc79a2a03edb Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Sat, 14 Jan 2023 19:52:30 +0000 Subject: [PATCH 327/355] [Simulator] Fix MicroPython in Web simulator and update to Emscripten 3.1.30 --- .github/workflows/ci-workflow.yml | 2 +- build/toolchain.emscripten.mak | 4 +++- ion/src/simulator/external/config.web.mak | 3 +++ .../src/joystick/emscripten/SDL_sysjoystick.c | 17 +++++++++++++++++ python/port/genhdr/qstrdefs.in.h | 2 ++ python/port/mpconfigport.h | 4 ++++ python/port/port.cpp | 10 +++++++++- 7 files changed, 39 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 2bfbd0f126a..fe8ec12b43a 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -228,7 +228,7 @@ jobs: steps: - uses: numworks/setup-emscripten@master with: - sdk: 1.40.1 + sdk: latest - uses: actions/checkout@v2 with: submodules: 'recursive' diff --git a/build/toolchain.emscripten.mak b/build/toolchain.emscripten.mak index 14baf3a8f6b..24a125dbf2f 100644 --- a/build/toolchain.emscripten.mak +++ b/build/toolchain.emscripten.mak @@ -12,6 +12,8 @@ EMFLAGS += -s SAFE_HEAP=1 EMFLAGS += -s STACK_OVERFLOW_CHECK=1 EMFLAGS += -s DEMANGLE_SUPPORT=1 EMFLAGS += -s MAIN_MODULE=1 +EMFLAGS += -g +EMFLAGS += -O3 else EMFLAGS += -s MAIN_MODULE=2 endif @@ -26,4 +28,4 @@ EMSCRIPTEN_MODULARIZE ?= 1 LDFLAGS += -s MODULARIZE=$(EMSCRIPTEN_MODULARIZE) -s 'EXPORT_NAME="Epsilon"' --memory-init-file 0 SFLAGS += $(EMFLAGS) -LDFLAGS += $(EMFLAGS) -Oz -s EXPORTED_FUNCTIONS='["_main", "_IonSimulatorKeyboardKeyDown", "_IonSimulatorKeyboardKeyUp", "_IonSimulatorEventsPushEvent", "_IonSoftwareVersion", "_IonPatchLevel", "_IonDisplayForceRefresh"]' -s EXPORTED_RUNTIME_METHODS='["UTF8ToString"]' +LDFLAGS += $(EMFLAGS) -Oz -s EXPORTED_RUNTIME_METHODS='["UTF8ToString"]' diff --git a/ion/src/simulator/external/config.web.mak b/ion/src/simulator/external/config.web.mak index 003fcf83971..0821a80a37e 100644 --- a/ion/src/simulator/external/config.web.mak +++ b/ion/src/simulator/external/config.web.mak @@ -36,3 +36,6 @@ sdl_src += $(addprefix ion/src/simulator/external/sdl/src/, \ video/emscripten/SDL_emscriptenopengles.c \ video/emscripten/SDL_emscriptenvideo.c \ ) + +# Add SDL_JOYSTICK_EMSCRIPTEN flag +SDL_SFLAGS += -DSDL_JOYSTICK_EMSCRIPTEN diff --git a/ion/src/simulator/external/sdl/src/joystick/emscripten/SDL_sysjoystick.c b/ion/src/simulator/external/sdl/src/joystick/emscripten/SDL_sysjoystick.c index 92b831a05b6..83eb3c65d95 100644 --- a/ion/src/simulator/external/sdl/src/joystick/emscripten/SDL_sysjoystick.c +++ b/ion/src/simulator/external/sdl/src/joystick/emscripten/SDL_sysjoystick.c @@ -415,6 +415,23 @@ SDL_JoystickDriver SDL_EMSCRIPTEN_JoystickDriver = EMSCRIPTEN_JoystickQuit, }; +// Alias SDL_DUMMY_JoystickDriver to SDL_EMSCRIPTEN_JoystickDriver +SDL_JoystickDriver SDL_DUMMY_JoystickDriver = +{ + EMSCRIPTEN_JoystickInit, + EMSCRIPTEN_JoystickGetCount, + EMSCRIPTEN_JoystickDetect, + EMSCRIPTEN_JoystickGetDeviceName, + EMSCRIPTEN_JoystickGetDevicePlayerIndex, + EMSCRIPTEN_JoystickGetDeviceGUID, + EMSCRIPTEN_JoystickGetDeviceInstanceID, + EMSCRIPTEN_JoystickOpen, + EMSCRIPTEN_JoystickRumble, + EMSCRIPTEN_JoystickUpdate, + EMSCRIPTEN_JoystickClose, + EMSCRIPTEN_JoystickQuit, +}; + #endif /* SDL_JOYSTICK_EMSCRIPTEN */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index 3fae92b55b0..78cfdb182c8 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -279,6 +279,8 @@ Q(popitem) Q(pow) Q(print) Q(property) +Q(pystack_space_exhausted) +Q(pystack_use) Q(radians) Q(randint) Q(random) diff --git a/python/port/mpconfigport.h b/python/port/mpconfigport.h index 9040132fe8d..0e6716d087c 100644 --- a/python/port/mpconfigport.h +++ b/python/port/mpconfigport.h @@ -8,6 +8,7 @@ /* MicroPython configuration options * We're not listing the default options as defined in mpconfig.h */ +#if __EMSCRIPTEN__ // Enable a PyStack where most objects are allocated instead of always using the heap /* This enables to allocate and free memory in a scope (thus, Python can call * Python) but also has the collateral effect of removing bugs regarding @@ -17,7 +18,10 @@ * collecting roots the transpiled C code is denied access to Javascript * variables that can store pointers to the Python heap. The pointed objects * are therefore erased prematurely. */ +#define MICROPY_ENABLE_PYSTACK (1) +#else #define MICROPY_ENABLE_PYSTACK (0) +#endif // Whether to encode None/False/True as immediate objects instead of pointers to // real objects. Reduces code size by a decent amount without hurting diff --git a/python/port/port.cpp b/python/port/port.cpp index e86a6ef2245..67c57ef3f9d 100644 --- a/python/port/port.cpp +++ b/python/port/port.cpp @@ -10,6 +10,7 @@ #ifdef __EMSCRIPTEN__ #include +__attribute__((noinline)) void python_error_start(const char* type) { EM_ASM({ Module.___temp_python_error = new Object(); @@ -18,6 +19,7 @@ void python_error_start(const char* type) { }, type); } +__attribute__((noinline)) void python_error_add_trace(const char* file, int line, const char* block) { EM_ASM({ var temp_obj = new Object(); @@ -28,10 +30,11 @@ void python_error_add_trace(const char* file, int line, const char* block) { }, file, line, block); } +__attribute__((noinline)) void python_error_end() { EM_ASM({ if (typeof Module.onPythonError === "function") { - Module.onPythonError(Module.___temp_python_error); + Module.onPythonError(Module.___temp_python_error); } delete Module.___temp_python_error; }); @@ -55,6 +58,7 @@ extern "C" { #include "py/mphal.h" #include "py/nlr.h" #include "py/parsenum.h" +#include "py/pystack.h" #include "py/repl.h" #include "py/runtime.h" #include "py/stackctrl.h" @@ -163,6 +167,10 @@ extern "C" { } void MicroPython::init(void * heapStart, void * heapEnd) { +#if __EMSCRIPTEN__ + static mp_obj_t pystack[1024]; + mp_pystack_init(pystack, &pystack[MP_ARRAY_SIZE(pystack)]); +#endif /* We delimit the stack part that will be used by Python. The stackTop is the * address of the first object that can be allocated on Python stack. This * boundaries are used: From 7af7b895f57ae758f6b0b056717b9bdf4bac0072 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Mon, 16 Jan 2023 18:39:31 +0100 Subject: [PATCH 328/355] [i18n] Clean translations --- apps/reader/base.de.i18n | 2 -- apps/reader/base.en.i18n | 2 -- apps/reader/base.es.i18n | 2 -- apps/reader/base.fr.i18n | 2 -- apps/reader/base.hu.i18n | 2 -- apps/reader/base.it.i18n | 2 -- apps/reader/base.nl.i18n | 2 -- apps/reader/base.pt.i18n | 2 -- apps/settings/base.de.i18n | 8 -------- apps/settings/base.en.i18n | 8 -------- apps/settings/base.es.i18n | 8 -------- apps/settings/base.fr.i18n | 8 -------- apps/settings/base.hu.i18n | 8 -------- apps/settings/base.it.i18n | 8 -------- apps/settings/base.nl.i18n | 8 -------- apps/settings/base.pt.i18n | 8 -------- apps/settings/base.universal.i18n | 3 --- apps/settings/main_controller.cpp | 2 -- apps/settings/sub_menu/preferences_controller.cpp | 9 --------- build/utilities/translations_clean.py | 2 +- 20 files changed, 1 insertion(+), 95 deletions(-) diff --git a/apps/reader/base.de.i18n b/apps/reader/base.de.i18n index 9cbb398970f..873f577b188 100644 --- a/apps/reader/base.de.i18n +++ b/apps/reader/base.de.i18n @@ -1,5 +1,3 @@ ReaderApp = "Leser" ReaderAppCapital = "LESER" NoFileToDisplay = "Keine Dateien zum Anzeigen" -FileError1 = "Fehler beim Lesen der Datei" -FileError2 = "Bitte überprüfen Sie die Syntax" diff --git a/apps/reader/base.en.i18n b/apps/reader/base.en.i18n index 2cb016526a4..812b213b83f 100644 --- a/apps/reader/base.en.i18n +++ b/apps/reader/base.en.i18n @@ -1,5 +1,3 @@ ReaderApp = "Reader" ReaderAppCapital = "READER" NoFileToDisplay = "No file to display" -FileError1 = "Error while reading file" -FileError2 = "Please check its syntax" diff --git a/apps/reader/base.es.i18n b/apps/reader/base.es.i18n index 76962dca3c4..674bec03e8b 100644 --- a/apps/reader/base.es.i18n +++ b/apps/reader/base.es.i18n @@ -1,5 +1,3 @@ ReaderApp = "Lector" ReaderAppCapital = "LECTOR" NoFileToDisplay ="No hay archivos para mostrar" -FileError1 = "Error al leer el archivo" -FileError2 = "Por favor revise su sintaxis" diff --git a/apps/reader/base.fr.i18n b/apps/reader/base.fr.i18n index b74e6add0f5..e017f6bda44 100644 --- a/apps/reader/base.fr.i18n +++ b/apps/reader/base.fr.i18n @@ -1,5 +1,3 @@ ReaderApp = "Liseuse" ReaderAppCapital = "LISEUSE" NoFileToDisplay = "Aucun fichier à afficher" -FileError1 = "Erreur durant la lecture du fichier" -FileError2 = "Veuillez vérifier sa syntaxe" \ No newline at end of file diff --git a/apps/reader/base.hu.i18n b/apps/reader/base.hu.i18n index 9580dc40add..d0595b0c639 100644 --- a/apps/reader/base.hu.i18n +++ b/apps/reader/base.hu.i18n @@ -1,5 +1,3 @@ ReaderApp = "Olvasó" ReaderAppCapital = "OLVASÓ" NoFileToDisplay = "Nincs megjeleníthető fájl" -FileError1 = "Hiba a fájl olvasása közben" -FileError2 = "Kérjük, ellenőrizze a szintaxisát" diff --git a/apps/reader/base.it.i18n b/apps/reader/base.it.i18n index abfa80eda75..98caf87b7e1 100644 --- a/apps/reader/base.it.i18n +++ b/apps/reader/base.it.i18n @@ -1,5 +1,3 @@ ReaderApp = "Lettore" ReaderAppCapital = "LETTORE" NoFileToDisplay = "essun file da visualizzare" -FileError1 = "Errore durante la lettura del file" -FileError2 = "Si prega di controllare la sua sintassi" diff --git a/apps/reader/base.nl.i18n b/apps/reader/base.nl.i18n index 9f8c2304f40..0e744c24cc1 100644 --- a/apps/reader/base.nl.i18n +++ b/apps/reader/base.nl.i18n @@ -1,5 +1,3 @@ ReaderApp = "Lezer" ReaderAppCapital = "LEZER" NoFileToDisplay = "Geen bestanden om weer te geven" -FileError1 = "Fout tijdens het lezen van bestand" -FileError2 = "Controleer de syntaxis ervan" diff --git a/apps/reader/base.pt.i18n b/apps/reader/base.pt.i18n index 0c2281287ca..0be9365227b 100644 --- a/apps/reader/base.pt.i18n +++ b/apps/reader/base.pt.i18n @@ -1,5 +1,3 @@ ReaderApp = "Leitor" ReaderAppCapital = "LEITOR" NoFileToDisplay = "Nenhum arquivo para exibir" -FileError1 = "Erro ao ler o arquivo" -FileError2 = "Verifique sua sintaxe" diff --git a/apps/settings/base.de.i18n b/apps/settings/base.de.i18n index 3eb0e59fbb7..8f8cd5898b2 100644 --- a/apps/settings/base.de.i18n +++ b/apps/settings/base.de.i18n @@ -72,14 +72,6 @@ Time = "Uhrzeit" RTCWarning1 = "Das Aktivieren der Uhr verkürzt die" RTCWarning2 = "Akkulaufzeit im Bereitschaftsmodus." SyntaxHighlighting = "Syntaxhervorhebung" -USBExplanation1 = "USB-Schutz schützt Ihren" -USBExplanation2 = "Taschenrechner vor" -USBExplanation3 = "unbeabsichtigter Verriegelung" -USBProtection = "USB-Schutz" -USBProtectionLevel = "Akzeptierte Updates" -USBDefaultLevel = "Basierend auf Upsilon" -USBLowLevel = "Basierend auf Omega" -USBParanoidLevel = "Nichts" Normal = "Normal" IdleTimeBeforeDimming = "Abdunkeln nach (s)" IdleTimeBeforeSuspend = "Anhalten nach (s)" diff --git a/apps/settings/base.en.i18n b/apps/settings/base.en.i18n index c6a048e2a7c..56f15205480 100644 --- a/apps/settings/base.en.i18n +++ b/apps/settings/base.en.i18n @@ -72,14 +72,6 @@ Time = "Time" RTCWarning1 = "Enabling the clock drains the battery faster" RTCWarning2 = "when the calculator is powered off." SyntaxHighlighting = "Syntax Highlighting" -USBExplanation1 = "The USB protection protects" -USBExplanation2 = "the calculator from" -USBExplanation3 = "unintentional locking" -USBProtection = "USB Protection" -USBProtectionLevel = "Updates accepted" -USBDefaultLevel = "Based on Upsilon" -USBLowLevel = "Based on Omega" -USBParanoidLevel = "None" Normal = "Normal" IdleTimeBeforeDimming = "Dim after (s)" IdleTimeBeforeSuspend = "Suspend after (s)" diff --git a/apps/settings/base.es.i18n b/apps/settings/base.es.i18n index 839f25ba8a3..42ce777f24e 100644 --- a/apps/settings/base.es.i18n +++ b/apps/settings/base.es.i18n @@ -72,14 +72,6 @@ Time = "Hora" RTCWarning1 = "Activar el reloj gasta la batería más rápido" RTCWarning2 = "cuando la calculadora está apagada." SyntaxHighlighting = "Resaltado de sintaxis" -USBExplanation1 = "La protección USB protege" -USBExplanation2 = "su calculadora del" -USBExplanation3 = "bloqueo involuntario" -USBProtection = "Protección USB" -USBProtectionLevel = "Actualizaciones aceptadas" -USBDefaultLevel = "Basado en Upsilon" -USBLowLevel = "Basado en Omega" -USBParanoidLevel = "Ninguno" Normal = "Normal" IdleTimeBeforeDimming = "Oscurecer después de (s)" IdleTimeBeforeSuspend = "Suspender después de (s)" diff --git a/apps/settings/base.fr.i18n b/apps/settings/base.fr.i18n index 87a4596ce1c..676e64687ca 100644 --- a/apps/settings/base.fr.i18n +++ b/apps/settings/base.fr.i18n @@ -72,14 +72,6 @@ Time = "Heure" RTCWarning1 = "Activer l'horloge décharge la batterie plus" RTCWarning2 = "vite quand la calculatrice est éteinte." SyntaxHighlighting = "Coloration syntaxique" -USBExplanation1 = "La protection USB protège votre" -USBExplanation2 = "calculatrice contre un verrouillage" -USBExplanation3 = "non-intentionnel" -USBProtection = "Protection USB" -USBProtectionLevel = "Mise à jour acceptées" -USBDefaultLevel = "Basées sur Upsilon" -USBLowLevel = "Basées sur Omega" -USBParanoidLevel = "Aucune" Normal = "Normale" IdleTimeBeforeDimming = "Assombrir après (s)" IdleTimeBeforeSuspend = "Éteindre après (s)" diff --git a/apps/settings/base.hu.i18n b/apps/settings/base.hu.i18n index 99dc79fc3df..a46e8cddaa0 100644 --- a/apps/settings/base.hu.i18n +++ b/apps/settings/base.hu.i18n @@ -72,14 +72,6 @@ Time = "Óra" RTCWarning1 = "Amikor a számológép alvómódban van, az óra" RTCWarning2 = "használása az elemet gyorsabban meríti ki." SyntaxHighlighting = "Szintaxis kiemelés" -USBExplanation1 = "Az USB-védelem megvédi" -USBExplanation2 = "a számológépet a nem" -USBExplanation3 = "szándékos reteszeléstől" -USBProtection = "USB védelem" -USBProtectionLevel = "Elfogadott frissítések" -USBDefaultLevel = "Upsilon alapján" -USBLowLevel = "Omega alapján" -USBParanoidLevel = "Egyik sem" Normal = "Normale" IdleTimeBeforeDimming = "Assombrir après (s)" IdleTimeBeforeSuspend = "Éteindre après (s)" diff --git a/apps/settings/base.it.i18n b/apps/settings/base.it.i18n index 2603da889ce..e7dfe4d7fbf 100644 --- a/apps/settings/base.it.i18n +++ b/apps/settings/base.it.i18n @@ -72,14 +72,6 @@ Time = "Time" RTCWarning1 = "Enabling the clock drains the battery faster" RTCWarning2 = "when the calculator is powered off." SyntaxHighlighting = "Evidenziazione della sintassi" -USBExplanation1 = "La protezione USB protegge" -USBExplanation2 = "la calcolatrice dal" -USBExplanation3 = "blocco involontario" -USBProtection = "Protezione USB" -USBProtectionLevel = "Aggiornamenti accettati" -USBDefaultLevel = "Basato su Upsilon" -USBLowLevel = "A base di Omega" -USBParanoidLevel = "Nessuno" Normal = "Normale" IdleTimeBeforeDimming = "Scurisci dopo (s)" IdleTimeBeforeSuspend = "Sospendi dopo (s)" diff --git a/apps/settings/base.nl.i18n b/apps/settings/base.nl.i18n index d71a7682472..f26e546c402 100644 --- a/apps/settings/base.nl.i18n +++ b/apps/settings/base.nl.i18n @@ -72,14 +72,6 @@ Time = "Time" RTCWarning1 = "Enabling the clock drains the battery faster" RTCWarning2 = "when the calculator is powered off." SyntaxHighlighting = "Syntax Highlighting" -USBExplanation1 = "USB-beveiliging beschermt uw" -USBExplanation2 = "rekenmachine tegen" -USBExplanation3 = "onbedoelde vergrendeling" -USBProtection = "USB-beveiliging" -USBProtectionLevel = "Updates geaccepteerd" -USBDefaultLevel = "Gebaseerd op Upsilon" -USBLowLevel = "Op basis van Omega" -USBParanoidLevel = "Geen" Normal = "Normaal" IdleTimeBeforeDimming = "Donkerder maken na (s)" IdleTimeBeforeSuspend = "Suspend after (s)" diff --git a/apps/settings/base.pt.i18n b/apps/settings/base.pt.i18n index b7639cb6e7b..950bc992718 100644 --- a/apps/settings/base.pt.i18n +++ b/apps/settings/base.pt.i18n @@ -72,14 +72,6 @@ Time = "Time" RTCWarning1 = "Enabling the clock drains the battery faster" RTCWarning2 = "when the calculator is powered off." SyntaxHighlighting = "Destaque da sintaxe" -USBExplanation1 = "A proteção USB protege" -USBExplanation2 = "sua calculadora contra" -USBExplanation3 = "bloqueios não intencionais" -USBProtection = "Proteção USB" -USBProtectionLevel = "Atualizações aceitas" -USBDefaultLevel = "Baseado em Upsilon" -USBLowLevel = "Baseado em Ômega" -USBParanoidLevel = "Nenhum" Normal = "Normal" IdleTimeBeforeDimming = "Diminuir depois (s)" IdleTimeBeforeSuspend = "Suspender depois (s)" diff --git a/apps/settings/base.universal.i18n b/apps/settings/base.universal.i18n index 124dc211236..210e673fbaf 100644 --- a/apps/settings/base.universal.i18n +++ b/apps/settings/base.universal.i18n @@ -1,4 +1 @@ UsbSetting = "USB" -USBDefaultLevelDesc = "L0" -USBLowLevelDesc = "L1" -USBParanoidLevelDesc = "L2" diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index 4b590b45f11..7e71771f93a 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -15,8 +15,6 @@ constexpr SettingsMessageTree s_modelFloatDisplayModeChildren[4] = {SettingsMess constexpr SettingsMessageTree s_modelComplexFormatChildren[3] = {SettingsMessageTree(I18n::Message::Real), SettingsMessageTree(I18n::Message::Cartesian), SettingsMessageTree(I18n::Message::Polar)}; constexpr SettingsMessageTree s_modelDateTimeChildren[3] = {SettingsMessageTree(I18n::Message::ActivateClock), SettingsMessageTree(I18n::Message::Date), SettingsMessageTree(I18n::Message::Time)}; constexpr SettingsMessageTree s_symbolChildren[4] = {SettingsMessageTree(I18n::Message::SymbolMultiplicationCross),SettingsMessageTree(I18n::Message::SymbolMultiplicationMiddleDot),SettingsMessageTree(I18n::Message::SymbolMultiplicationStar),SettingsMessageTree(I18n::Message::SymbolMultiplicationAutoSymbol)}; -constexpr SettingsMessageTree s_usbProtectionChildren[2] = {SettingsMessageTree(I18n::Message::USBProtection), SettingsMessageTree(I18n::Message::USBProtectionLevel, s_usbProtectionLevelChildren)}; -constexpr SettingsMessageTree s_usbProtectionLevelChildren[3] = {SettingsMessageTree(I18n::Message::USBDefaultLevel), SettingsMessageTree(I18n::Message::USBLowLevel), SettingsMessageTree(I18n::Message::USBParanoidLevel)}; constexpr SettingsMessageTree s_externalChildren[2] = {SettingsMessageTree(I18n::Message::ExtAppWrite), SettingsMessageTree(I18n::Message::ExtAppEnabled)}; constexpr SettingsMessageTree s_symbolFunctionChildren[3] = {SettingsMessageTree(I18n::Message::SymbolDefaultFunction), SettingsMessageTree(I18n::Message::SymbolArgDefaultFunction), SettingsMessageTree(I18n::Message::SymbolArgFunction)}; constexpr SettingsMessageTree s_modelMathOptionsChildren[6] = {SettingsMessageTree(I18n::Message::AngleUnit, s_modelAngleChildren), SettingsMessageTree(I18n::Message::DisplayMode, s_modelFloatDisplayModeChildren), SettingsMessageTree(I18n::Message::EditionMode, s_modelEditionModeChildren), SettingsMessageTree(I18n::Message::SymbolFunction, s_symbolFunctionChildren), SettingsMessageTree(I18n::Message::ComplexFormat, s_modelComplexFormatChildren), SettingsMessageTree(I18n::Message::SymbolMultiplication, s_symbolChildren)}; diff --git a/apps/settings/sub_menu/preferences_controller.cpp b/apps/settings/sub_menu/preferences_controller.cpp index 06650e842e9..dbf254966cb 100644 --- a/apps/settings/sub_menu/preferences_controller.cpp +++ b/apps/settings/sub_menu/preferences_controller.cpp @@ -170,15 +170,6 @@ Layout PreferencesController::layoutForPreferences(I18n::Message message) { return LayoutHelper::String(text, strlen(text), font); } - // DFU Protection level - case I18n::Message::USBDefaultLevel: - case I18n::Message::USBLowLevel: - case I18n::Message::USBParanoidLevel: - { - const char * text = " "; - return LayoutHelper::String(text, strlen(text), k_layoutFont); - } - default: assert(false); return Layout(); diff --git a/build/utilities/translations_clean.py b/build/utilities/translations_clean.py index bc955991a06..afee202faaa 100644 --- a/build/utilities/translations_clean.py +++ b/build/utilities/translations_clean.py @@ -32,7 +32,7 @@ ] IGNORE_PATHS_CONTENTS = [ "__pycache__", ".png", ".esc", ".ttf", ".ico", ".jpg", ".jar", ".icn", - ".bnr", ".i18n" + ".bnr", ".i18n", ".cache" ] # If the key contain something that's in this list, keep it to prevent code breaking From 224cec5efce36db6796b099e81c4da4848bc385f Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Mon, 16 Jan 2023 18:49:52 +0100 Subject: [PATCH 329/355] [apps/settings] Remove rests of USB protection --- apps/settings/main_controller_prompt_beta.cpp | 1 - apps/settings/main_controller_prompt_update.cpp | 2 -- 2 files changed, 3 deletions(-) diff --git a/apps/settings/main_controller_prompt_beta.cpp b/apps/settings/main_controller_prompt_beta.cpp index 3b169700c10..0f48e7940ed 100644 --- a/apps/settings/main_controller_prompt_beta.cpp +++ b/apps/settings/main_controller_prompt_beta.cpp @@ -17,7 +17,6 @@ constexpr SettingsMessageTree s_modelMenu[] = SettingsMessageTree(I18n::Message::CodeApp, s_codeChildren), #endif SettingsMessageTree(I18n::Message::BetaPopUp), - //SettingsMessageTree(I18n::Message::UsbSetting, s_usbProtectionChildren), SettingsMessageTree(I18n::Message::ExternalApps, s_externalChildren), SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; diff --git a/apps/settings/main_controller_prompt_update.cpp b/apps/settings/main_controller_prompt_update.cpp index b566124578a..2deed3c3e8c 100644 --- a/apps/settings/main_controller_prompt_update.cpp +++ b/apps/settings/main_controller_prompt_update.cpp @@ -18,8 +18,6 @@ constexpr SettingsMessageTree s_modelMenu[] = #endif SettingsMessageTree(I18n::Message::UpdatePopUp), SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), - // SettingsMessageTree(I18n::Message::UsbSetting, s_usbProtectionChildren), - SettingsMessageTree(I18n::Message::UsbSetting, s_usbProtectionChildren), SettingsMessageTree(I18n::Message::ExternalApps, s_externalChildren), SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; From 12d8eeeb2c181e72b24aab7d00d51bd5a7661c1a Mon Sep 17 00:00:00 2001 From: lolocomotive Date: Sat, 21 Jan 2023 14:39:57 +0100 Subject: [PATCH 330/355] [Simulator] Fix keys not working and reduce log spam --- build/toolchain.emscripten.mak | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/toolchain.emscripten.mak b/build/toolchain.emscripten.mak index 24a125dbf2f..34aa5715d5b 100644 --- a/build/toolchain.emscripten.mak +++ b/build/toolchain.emscripten.mak @@ -22,10 +22,11 @@ endif # Configure EMFLAGS EMFLAGS += -s WASM=1 -s SINGLE_FILE=1 +EMFLAGS += -Wno-unused-command-line-argument # Configure LDFLAGS EMSCRIPTEN_MODULARIZE ?= 1 LDFLAGS += -s MODULARIZE=$(EMSCRIPTEN_MODULARIZE) -s 'EXPORT_NAME="Epsilon"' --memory-init-file 0 SFLAGS += $(EMFLAGS) -LDFLAGS += $(EMFLAGS) -Oz -s EXPORTED_RUNTIME_METHODS='["UTF8ToString"]' +LDFLAGS += $(EMFLAGS) -Oz -s EXPORTED_RUNTIME_METHODS='["UTF8ToString"]' -s EXPORTED_FUNCTIONS='["_main", "_IonSimulatorKeyboardKeyDown", "_IonSimulatorKeyboardKeyUp", "_IonSimulatorEventsPushEvent", "_IonSoftwareVersion", "_IonPatchLevel", "_IonDisplayForceRefresh"]' From 58a373e683bed7c6876f9688cc085c4da6a47bb1 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Sun, 29 Jan 2023 19:36:13 +0100 Subject: [PATCH 331/355] [apps/reader] Fix reader crash when opening bookmark I just disabled color restoration, but I am still not understanding why it isn't working Fix #307 --- apps/reader/word_wrap_view.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp index b02c4ed03c2..3585e966951 100644 --- a/apps/reader/word_wrap_view.cpp +++ b/apps/reader/word_wrap_view.cpp @@ -17,6 +17,7 @@ WordWrapTextView::WordWrapTextView(ReadBookController * readBookController) : m_length(0), m_isRichTextFile(false), // Value isn't important, it will change when the file is loaded m_lastPagesOffsetsIndex(0), + m_textColor(Palette::PrimaryText), m_readBookController(readBookController) { for (int i = 0; i < k_lastOffsetsBufferSize; i++) { @@ -618,7 +619,8 @@ BookSave WordWrapTextView::getBookSave() const { void WordWrapTextView::setBookSave(BookSave save) { m_pageOffset = save.offset; - m_textColor = save.color; + // TODO: Understand why the color save crash the calculator and fix it + // m_textColor = save.color; m_lastPagesOffsetsIndex = 0; for (int i = 0; i < k_lastOffsetsBufferSize; i++) { From 4acc79f1c7b609563d7ec63054cef659ccfea715 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Fri, 3 Feb 2023 08:25:54 +0100 Subject: [PATCH 332/355] [apps/on_boarding] Fix logo view crash when interrupted --- apps/on_boarding/logo_controller.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/on_boarding/logo_controller.cpp b/apps/on_boarding/logo_controller.cpp index 0597bfcdf09..fb870c3829d 100644 --- a/apps/on_boarding/logo_controller.cpp +++ b/apps/on_boarding/logo_controller.cpp @@ -16,8 +16,7 @@ LogoController::LogoController() : } bool LogoController::fire() { - Container::activeApp()->dismissModalViewController(); - AppsContainer::sharedAppsContainer()->removeTimer(this); + Container::activeApp()->dismissModalViewController(); return true; } @@ -53,6 +52,7 @@ void LogoController::viewDidDisappear() { AppsContainer::sharedAppsContainer()->activateExamMode(GlobalPreferences::sharedGlobalPreferences()->examMode()); } } + AppsContainer::sharedAppsContainer()->removeTimer(this); ViewController::viewDidDisappear(); } From b43d77305174df97c4dc8aeca9325cc73b671b86 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Wed, 8 Feb 2023 17:09:48 +0100 Subject: [PATCH 333/355] [calculation/UnitList] Fix crash when pressing OK --- .../additional_outputs/unit_list_controller.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/calculation/additional_outputs/unit_list_controller.cpp b/apps/calculation/additional_outputs/unit_list_controller.cpp index 41ac6d8f21b..37fdcf7763e 100644 --- a/apps/calculation/additional_outputs/unit_list_controller.cpp +++ b/apps/calculation/additional_outputs/unit_list_controller.cpp @@ -24,7 +24,12 @@ bool UnitListController::handleEvent(Ion::Events::Event event) { if (selectedRow() == 0 && (event == Ion::Events::OK || event == Ion::Events::EXE)) { return true; } - return ListController::handleEvent(event); + + // HACK: Change the selected row (prevent some bugs when OK is pressed) + selectRow(selectedRow() - 1); + bool value = ListController::handleEvent(event); + selectRow(selectedRow() + 1); + return value; } void UnitListController::setExpression(Poincare::Expression e) { From a729ef2a4cdde797ac71f9997a2c757caf9c1903 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Fri, 17 Feb 2023 19:32:16 +0100 Subject: [PATCH 334/355] [apps] Move Dimensions i18n out of calculation This allow Escher to build successfully without calculation app --- apps/calculation/base.de.i18n | 24 -------------- apps/calculation/base.en.i18n | 24 -------------- apps/calculation/base.es.i18n | 24 -------------- apps/calculation/base.fr.i18n | 24 -------------- apps/calculation/base.hu.i18n | 62 +++++++++++------------------------ apps/calculation/base.it.i18n | 24 -------------- apps/calculation/base.nl.i18n | 24 -------------- apps/calculation/base.pt.i18n | 24 -------------- apps/shared.de.i18n | 24 ++++++++++++++ apps/shared.en.i18n | 24 ++++++++++++++ apps/shared.es.i18n | 24 ++++++++++++++ apps/shared.fr.i18n | 24 ++++++++++++++ apps/shared.hu.i18n | 24 ++++++++++++++ apps/shared.it.i18n | 24 ++++++++++++++ apps/shared.nl.i18n | 24 ++++++++++++++ apps/shared.pt.i18n | 24 ++++++++++++++ 16 files changed, 211 insertions(+), 211 deletions(-) diff --git a/apps/calculation/base.de.i18n b/apps/calculation/base.de.i18n index c2c11d837ab..419d3b96574 100644 --- a/apps/calculation/base.de.i18n +++ b/apps/calculation/base.de.i18n @@ -18,27 +18,3 @@ Discriminant = "Diskriminante" OnlyRoot = "Wurzel" FirstRoot = "Erste Wurzel" SecondRoot = "Zweite Wurzel" -TimeDimension = "Zeit" -DistanceDimension = "Distanz" -MassDimension = "Masse" -CurrentDimension = "Betrieb" -TemperatureDimension = "Temperatur" -AmountOfSubstanceDimension = "Quantität der Materie" -LuminousIntensityDimension = "Lichtintensität" -FrequencyDimension = "Frequenz" -ForceDimension = "Stärke" -PressureDimension = "Druck" -EnergyDimension = "Energie" -PowerDimension = "Mächtig" -ElectricChargeDimension = "Elektrische Ladung" -ElectricPotentialDimension = "Elektrisches Potenzial" -ElectricCapacitanceDimension = "Elektrische Kapazität" -ElectricResistanceDimension = "Elektrischer Wiederstand" -ElectricConductanceDimension = "elektrische Leitfähigkeit" -MagneticFluxDimension = "magnetischer Fluss" -MagneticFieldDimension = "Magnetfeld" -InductanceDimension = "Induktivität" -CatalyticActivityDimension = "Katalytische Aktivität" -SurfaceDimension = "Auftauchen" -VolumeDimension = "Volumen" -SpeedDimension = "Geschwindigkeit" diff --git a/apps/calculation/base.en.i18n b/apps/calculation/base.en.i18n index b636b47a51e..23df23e3a55 100644 --- a/apps/calculation/base.en.i18n +++ b/apps/calculation/base.en.i18n @@ -18,27 +18,3 @@ Discriminant = "Discriminant" OnlyRoot = "Root" FirstRoot = "First root" SecondRoot = "Second root" -TimeDimension = "Time" -DistanceDimension = "Distance" -MassDimension = "Mass" -CurrentDimension = "Running" -TemperatureDimension = "Temperature" -AmountOfSubstanceDimension = "Quantity of matter" -LuminousIntensityDimension = "Light intensity" -FrequencyDimension = "Frequency" -ForceDimension = "Strength" -PressureDimension = "Pressure" -EnergyDimension = "Energy" -PowerDimension = "Powerful" -ElectricChargeDimension = "Electrical charge" -ElectricPotentialDimension = "Electric potential" -ElectricCapacitanceDimension = "Electrical capacity" -ElectricResistanceDimension = "Electrical resistance" -ElectricConductanceDimension = "electrical conductance" -MagneticFluxDimension = "magnetic flux" -MagneticFieldDimension = "Magnetic field" -InductanceDimension = "Inductance" -CatalyticActivityDimension = "Catalytic activity" -SurfaceDimension = "Surface" -VolumeDimension = "Volume" -SpeedDimension = "Speed" diff --git a/apps/calculation/base.es.i18n b/apps/calculation/base.es.i18n index b23115eab28..1df949c380c 100644 --- a/apps/calculation/base.es.i18n +++ b/apps/calculation/base.es.i18n @@ -18,27 +18,3 @@ Discriminant = "Discriminante" OnlyRoot = "Raíz" FirstRoot = "Primera raíz" SecondRoot = "Segunda raíz" -TimeDimension = "Tiempo" -DistanceDimension = "Distancia" -MassDimension = "Masa" -CurrentDimension = "Correr" -TemperatureDimension = "La temperatura" -AmountOfSubstanceDimension = "cantidad de materia" -LuminousIntensityDimension = "Intensidad de luz" -FrequencyDimension = "Frecuencia" -ForceDimension = "Fuerza" -PressureDimension = "Presión" -EnergyDimension = "Energía" -PowerDimension = "Potencia" -ElectricChargeDimension = "Carga eléctrica" -ElectricPotentialDimension = "Potencial eléctrico" -ElectricCapacitanceDimension = "Capacidad eléctrica" -ElectricResistanceDimension = "Resistencia eléctrica" -ElectricConductanceDimension = "conductancia eléctrica" -MagneticFluxDimension = "flujo magnético" -MagneticFieldDimension = "Campo magnético" -InductanceDimension = "Inductancia" -CatalyticActivityDimension = "Actividad catalítica" -SurfaceDimension = "Superficie" -VolumeDimension = "Volumen" -SpeedDimension = "Velocidad" diff --git a/apps/calculation/base.fr.i18n b/apps/calculation/base.fr.i18n index 74416f32c0a..2b9e7bdac8b 100644 --- a/apps/calculation/base.fr.i18n +++ b/apps/calculation/base.fr.i18n @@ -18,27 +18,3 @@ Discriminant = "Discriminant" OnlyRoot = "Racine" FirstRoot = "Première racine" SecondRoot = "Seconde racine" -TimeDimension = "Temps" -DistanceDimension = "Distance" -MassDimension = "Masse" -CurrentDimension = "Courant" -TemperatureDimension = "Température" -AmountOfSubstanceDimension = "Quantité de matière" -LuminousIntensityDimension = "Intensité lumineuse" -FrequencyDimension = "Fréquence" -ForceDimension = "Force" -PressureDimension = "Pression" -EnergyDimension = "Énergie" -PowerDimension = "Puissance" -ElectricChargeDimension = "Charge électrique" -ElectricPotentialDimension = "Potentiel électrique" -ElectricCapacitanceDimension = "Capacité électrique" -ElectricResistanceDimension = "Résistance électrique" -ElectricConductanceDimension = "Conductance électrique" -MagneticFluxDimension = "Flux magnétique" -MagneticFieldDimension = "Champ magnétique" -InductanceDimension = "Inductance" -CatalyticActivityDimension = "Activité catalytique" -SurfaceDimension = "Surface" -VolumeDimension = "Volume" -SpeedDimension = "Vitesse" \ No newline at end of file diff --git a/apps/calculation/base.hu.i18n b/apps/calculation/base.hu.i18n index 7879cdf4280..4c6cfc4a020 100644 --- a/apps/calculation/base.hu.i18n +++ b/apps/calculation/base.hu.i18n @@ -1,44 +1,20 @@ -CalculApp = "Számolás" -CalculAppCapital = "SZÁMOLÁS" -AdditionalResults = "További eredmények" -DecimalBase = "Decimális" -HexadecimalBase = "Hexadecimális" -BinaryBase = "Bináris" -PrimeFactors = "Alapvetö tényezök" -MixedFraction = "Vegyes frakció" -EuclideanDivision = "Euklideszi osztás" -AdditionalDeterminant = "Meghatározó" -AdditionalInverse = "inverz" -AdditionalRowEchelonForm = "Sor echelon forma" -AdditionalReducedRowEchelonForm = "Csökkentett sorú Echelon forma" -AdditionalTrace = "Nyomkövetés" -CanonicalForm = "Kanonikus forma" -FactorizedForm = "Factorizált forma" -Discriminant = "Discriminant" -OnlyRoot = "Gyökér" -FirstRoot = "Első gyökér" +CalculApp = "Számolás" +CalculAppCapital = "SZÁMOLÁS" +AdditionalResults = "További eredmények" +DecimalBase = "Decimális" +HexadecimalBase = "Hexadecimális" +BinaryBase = "Bináris" +PrimeFactors = "Alapvetö tényezök" +MixedFraction = "Vegyes frakció" +EuclideanDivision = "Euklideszi osztás" +AdditionalDeterminant = "Meghatározó" +AdditionalInverse = "inverz" +AdditionalRowEchelonForm = "Sor echelon forma" +AdditionalReducedRowEchelonForm = "Csökkentett sorú Echelon forma" +AdditionalTrace = "Nyomkövetés" +CanonicalForm = "Kanonikus forma" +FactorizedForm = "Factorizált forma" +Discriminant = "Discriminant" +OnlyRoot = "Gyökér" +FirstRoot = "Első gyökér" SecondRoot = "Második gyökér" -TimeDimension = "Idő" -DistanceDimension = "Távolság" -MassDimension = "Tömeg" -CurrentDimension = "Futó" -TemperatureDimension = "Hőfok" -AmountOfSubstanceDimension = "Az anyag mennyisége" -LuminousIntensityDimension = "Fény intenzitása" -FrequencyDimension = "Frekvencia" -ForceDimension = "Erő" -PressureDimension = "Nyomás" -EnergyDimension = "Energia" -PowerDimension = "Erős" -ElectricChargeDimension = "Elektromos töltő" -ElectricPotentialDimension = "Elektromos potenciál" -ElectricCapacitanceDimension = "Elektromos kapacitás" -ElectricResistanceDimension = "Elektromos ellenállás" -ElectricConductanceDimension = "elektromos vezetőképesség" -MagneticFluxDimension = "mágneses fluxus" -MagneticFieldDimension = "Mágneses mező" -InductanceDimension = "Induktivitás" -CatalyticActivityDimension = "Katalitikus aktivitás" -SurfaceDimension = "Felület" -VolumeDimension = "Hangerő" -SpeedDimension = "Sebesség" diff --git a/apps/calculation/base.it.i18n b/apps/calculation/base.it.i18n index 8e00cd884e2..62620d05c9c 100644 --- a/apps/calculation/base.it.i18n +++ b/apps/calculation/base.it.i18n @@ -18,27 +18,3 @@ Discriminant = "Discriminante" OnlyRoot = "Radice" FirstRoot = "Prima radice" SecondRoot = "Seconda radice" -TimeDimension = "Volta" -DistanceDimension = "Distanza" -MassDimension = "Messa" -CurrentDimension = "In esecuzione" -TemperatureDimension = "Temperatura" -AmountOfSubstanceDimension = "Quantità di materia" -LuminousIntensityDimension = "Intensità luminosa" -FrequencyDimension = "Frequenza" -ForceDimension = "Forza" -PressureDimension = "Pressione" -EnergyDimension = "Energia" -PowerDimension = "Potere" -ElectricChargeDimension = "Carica elettrica" -ElectricPotentialDimension = "Potenziale elettrico" -ElectricCapacitanceDimension = "Capacità elettrica" -ElectricResistanceDimension = "Resistenza elettrica" -ElectricConductanceDimension = "conduttanza elettrica" -MagneticFluxDimension = "flusso magnetico" -MagneticFieldDimension = "Campo magnetico" -InductanceDimension = "Induttanza" -CatalyticActivityDimension = "Attività catalitica" -SurfaceDimension = "Superficie" -VolumeDimension = "Volume" -SpeedDimension = "Velocità" diff --git a/apps/calculation/base.nl.i18n b/apps/calculation/base.nl.i18n index c2385f0186b..1b286d49b9b 100644 --- a/apps/calculation/base.nl.i18n +++ b/apps/calculation/base.nl.i18n @@ -18,27 +18,3 @@ Discriminant = "Discriminant" OnlyRoot = "Wortel" FirstRoot = "Eerste wortel" SecondRoot = "Tweede wortel" -TimeDimension = "Tijd" -DistanceDimension = "Afstand" -MassDimension = "Massa" -CurrentDimension = "Rennen" -TemperatureDimension = "Temperatuur" -AmountOfSubstanceDimension = "Hoeveelheid materie" -LuminousIntensityDimension = "Lichtsterkte" -FrequencyDimension = "Frequentie" -ForceDimension = "Kracht" -PressureDimension = "Druk" -EnergyDimension = "Energie" -PowerDimension = "Kracht" -ElectricChargeDimension = "Elektrische lading" -ElectricPotentialDimension = "elektrische potentiaal" -ElectricCapacitanceDimension = "elektrische capaciteit:" -ElectricResistanceDimension = "Elektrische weerstand" -ElectricConductanceDimension = "elektrische geleiding:" -MagneticFluxDimension = "magnetische flux" -MagneticFieldDimension = "Magnetisch veld" -InductanceDimension = "Inductie" -CatalyticActivityDimension = "Katalytische activiteit" -SurfaceDimension = "Oppervlak" -VolumeDimension = "Volume" -SpeedDimension = "Snelheid" diff --git a/apps/calculation/base.pt.i18n b/apps/calculation/base.pt.i18n index c6a1aa7ae01..15abb51a7e1 100644 --- a/apps/calculation/base.pt.i18n +++ b/apps/calculation/base.pt.i18n @@ -18,27 +18,3 @@ Discriminant = "Discriminante" OnlyRoot = "Raiz" FirstRoot = "Primeira raiz" SecondRoot = "Segunda raiz" -TimeDimension = "Tempo" -DistanceDimension = "Distância" -MassDimension = "Massa" -CurrentDimension = "Corrida" -TemperatureDimension = "Temperatura" -AmountOfSubstanceDimension = "Quantidade de matéria" -LuminousIntensityDimension = "Intensidade da luz" -FrequencyDimension = "Frequência" -ForceDimension = "Força" -PressureDimension = "Pressão" -EnergyDimension = "Energia" -PowerDimension = "Poderoso" -ElectricChargeDimension = "Carga elétrica" -ElectricPotentialDimension = "Potencial elétrico" -ElectricCapacitanceDimension = "Capacidade elétrica" -ElectricResistanceDimension = "Resistência elétrica" -ElectricConductanceDimension = "condutância elétrica" -MagneticFluxDimension = "fluxo magnético" -MagneticFieldDimension = "Campo magnético" -InductanceDimension = "Indutância" -CatalyticActivityDimension = "Atividade catalítica" -SurfaceDimension = "Superfície" -VolumeDimension = "Volume" -SpeedDimension = "Velocidade" diff --git a/apps/shared.de.i18n b/apps/shared.de.i18n index b72d2803a15..f4fb2d05f80 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -97,3 +97,27 @@ ColorPurple = "Violett " ColorCyan = "Cyan " ColorPink = "Rosa " ColorOrange = "Orange " +TimeDimension = "Zeit" +DistanceDimension = "Distanz" +MassDimension = "Masse" +CurrentDimension = "Betrieb" +TemperatureDimension = "Temperatur" +AmountOfSubstanceDimension = "Quantität der Materie" +LuminousIntensityDimension = "Lichtintensität" +FrequencyDimension = "Frequenz" +ForceDimension = "Stärke" +PressureDimension = "Druck" +EnergyDimension = "Energie" +PowerDimension = "Mächtig" +ElectricChargeDimension = "Elektrische Ladung" +ElectricPotentialDimension = "Elektrisches Potenzial" +ElectricCapacitanceDimension = "Elektrische Kapazität" +ElectricResistanceDimension = "Elektrischer Wiederstand" +ElectricConductanceDimension = "elektrische Leitfähigkeit" +MagneticFluxDimension = "magnetischer Fluss" +MagneticFieldDimension = "Magnetfeld" +InductanceDimension = "Induktivität" +CatalyticActivityDimension = "Katalytische Aktivität" +SurfaceDimension = "Auftauchen" +VolumeDimension = "Volumen" +SpeedDimension = "Geschwindigkeit" diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index 257e39c9429..a5f65b59495 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -97,3 +97,27 @@ ColorPurple = "Purple " ColorCyan = "Cyan " ColorPink = "Pink " ColorOrange = "Orange " +TimeDimension = "Time" +DistanceDimension = "Distance" +MassDimension = "Mass" +CurrentDimension = "Running" +TemperatureDimension = "Temperature" +AmountOfSubstanceDimension = "Quantity of matter" +LuminousIntensityDimension = "Light intensity" +FrequencyDimension = "Frequency" +ForceDimension = "Strength" +PressureDimension = "Pressure" +EnergyDimension = "Energy" +PowerDimension = "Powerful" +ElectricChargeDimension = "Electrical charge" +ElectricPotentialDimension = "Electric potential" +ElectricCapacitanceDimension = "Electrical capacity" +ElectricResistanceDimension = "Electrical resistance" +ElectricConductanceDimension = "electrical conductance" +MagneticFluxDimension = "magnetic flux" +MagneticFieldDimension = "Magnetic field" +InductanceDimension = "Inductance" +CatalyticActivityDimension = "Catalytic activity" +SurfaceDimension = "Surface" +VolumeDimension = "Volume" +SpeedDimension = "Speed" diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index f1f6ca4ade0..cc76ce7e0c1 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -97,3 +97,27 @@ ColorPurple = "Púrpura " ColorCyan = "Cian " ColorPink = "Rosa " ColorOrange = "Naranja " +TimeDimension = "Tiempo" +DistanceDimension = "Distancia" +MassDimension = "Masa" +CurrentDimension = "Correr" +TemperatureDimension = "La temperatura" +AmountOfSubstanceDimension = "cantidad de materia" +LuminousIntensityDimension = "Intensidad de luz" +FrequencyDimension = "Frecuencia" +ForceDimension = "Fuerza" +PressureDimension = "Presión" +EnergyDimension = "Energía" +PowerDimension = "Potencia" +ElectricChargeDimension = "Carga eléctrica" +ElectricPotentialDimension = "Potencial eléctrico" +ElectricCapacitanceDimension = "Capacidad eléctrica" +ElectricResistanceDimension = "Resistencia eléctrica" +ElectricConductanceDimension = "conductancia eléctrica" +MagneticFluxDimension = "flujo magnético" +MagneticFieldDimension = "Campo magnético" +InductanceDimension = "Inductancia" +CatalyticActivityDimension = "Actividad catalítica" +SurfaceDimension = "Superficie" +VolumeDimension = "Volumen" +SpeedDimension = "Velocidad" \ No newline at end of file diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index e7c89c7a00e..2428b309b4e 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -97,3 +97,27 @@ ColorPurple = "Violet " ColorCyan = "Cyan " ColorPink = "Rose " ColorOrange = "Orange " +TimeDimension = "Temps" +DistanceDimension = "Distance" +MassDimension = "Masse" +CurrentDimension = "Courant" +TemperatureDimension = "Température" +AmountOfSubstanceDimension = "Quantité de matière" +LuminousIntensityDimension = "Intensité lumineuse" +FrequencyDimension = "Fréquence" +ForceDimension = "Force" +PressureDimension = "Pression" +EnergyDimension = "Énergie" +PowerDimension = "Puissance" +ElectricChargeDimension = "Charge électrique" +ElectricPotentialDimension = "Potentiel électrique" +ElectricCapacitanceDimension = "Capacité électrique" +ElectricResistanceDimension = "Résistance électrique" +ElectricConductanceDimension = "Conductance électrique" +MagneticFluxDimension = "Flux magnétique" +MagneticFieldDimension = "Champ magnétique" +InductanceDimension = "Inductance" +CatalyticActivityDimension = "Activité catalytique" +SurfaceDimension = "Surface" +VolumeDimension = "Volume" +SpeedDimension = "Vitesse" diff --git a/apps/shared.hu.i18n b/apps/shared.hu.i18n index 7931f0c7585..a916a2e7629 100644 --- a/apps/shared.hu.i18n +++ b/apps/shared.hu.i18n @@ -97,3 +97,27 @@ ColorPurple = "Lila " ColorCyan = "Cián " ColorPink = "Rózsaszín " ColorOrange = "Narancssárga " +TimeDimension = "Idő" +DistanceDimension = "Távolság" +MassDimension = "Tömeg" +CurrentDimension = "Futó" +TemperatureDimension = "Hőfok" +AmountOfSubstanceDimension = "Az anyag mennyisége" +LuminousIntensityDimension = "Fény intenzitása" +FrequencyDimension = "Frekvencia" +ForceDimension = "Erő" +PressureDimension = "Nyomás" +EnergyDimension = "Energia" +PowerDimension = "Erős" +ElectricChargeDimension = "Elektromos töltő" +ElectricPotentialDimension = "Elektromos potenciál" +ElectricCapacitanceDimension = "Elektromos kapacitás" +ElectricResistanceDimension = "Elektromos ellenállás" +ElectricConductanceDimension = "elektromos vezetőképesség" +MagneticFluxDimension = "mágneses fluxus" +MagneticFieldDimension = "Mágneses mező" +InductanceDimension = "Induktivitás" +CatalyticActivityDimension = "Katalitikus aktivitás" +SurfaceDimension = "Felület" +VolumeDimension = "Hangerő" +SpeedDimension = "Sebesség" diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n index 91bef493b21..0e078c3011f 100644 --- a/apps/shared.it.i18n +++ b/apps/shared.it.i18n @@ -97,3 +97,27 @@ ColorPurple = "Viola " ColorCyan = "Ciano" ColorPink = "Rosa " ColorOrange = "Arancia " +TimeDimension = "Volta" +DistanceDimension = "Distanza" +MassDimension = "Messa" +CurrentDimension = "In esecuzione" +TemperatureDimension = "Temperatura" +AmountOfSubstanceDimension = "Quantità di materia" +LuminousIntensityDimension = "Intensità luminosa" +FrequencyDimension = "Frequenza" +ForceDimension = "Forza" +PressureDimension = "Pressione" +EnergyDimension = "Energia" +PowerDimension = "Potere" +ElectricChargeDimension = "Carica elettrica" +ElectricPotentialDimension = "Potenziale elettrico" +ElectricCapacitanceDimension = "Capacità elettrica" +ElectricResistanceDimension = "Resistenza elettrica" +ElectricConductanceDimension = "conduttanza elettrica" +MagneticFluxDimension = "flusso magnetico" +MagneticFieldDimension = "Campo magnetico" +InductanceDimension = "Induttanza" +CatalyticActivityDimension = "Attività catalitica" +SurfaceDimension = "Superficie" +VolumeDimension = "Volume" +SpeedDimension = "Velocità" diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index 62060e011c9..d8a4b076f31 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -97,3 +97,27 @@ ColorPurple = "Purper" ColorCyan = "Cyaan " ColorPink = "Roze" ColorOrange = "Oranje" +TimeDimension = "Tijd" +DistanceDimension = "Afstand" +MassDimension = "Massa" +CurrentDimension = "Rennen" +TemperatureDimension = "Temperatuur" +AmountOfSubstanceDimension = "Hoeveelheid materie" +LuminousIntensityDimension = "Lichtsterkte" +FrequencyDimension = "Frequentie" +ForceDimension = "Kracht" +PressureDimension = "Druk" +EnergyDimension = "Energie" +PowerDimension = "Kracht" +ElectricChargeDimension = "Elektrische lading" +ElectricPotentialDimension = "elektrische potentiaal" +ElectricCapacitanceDimension = "elektrische capaciteit:" +ElectricResistanceDimension = "Elektrische weerstand" +ElectricConductanceDimension = "elektrische geleiding:" +MagneticFluxDimension = "magnetische flux" +MagneticFieldDimension = "Magnetisch veld" +InductanceDimension = "Inductie" +CatalyticActivityDimension = "Katalytische activiteit" +SurfaceDimension = "Oppervlak" +VolumeDimension = "Volume" +SpeedDimension = "Snelheid" diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index 4edea43ba44..de0852119a3 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -97,3 +97,27 @@ ColorPurple = "Roxa " ColorCyan = "Ciano" ColorPink = "Cor de rosa " ColorOrange = "Laranja " +TimeDimension = "Tempo" +DistanceDimension = "Distância" +MassDimension = "Massa" +CurrentDimension = "Corrida" +TemperatureDimension = "Temperatura" +AmountOfSubstanceDimension = "Quantidade de matéria" +LuminousIntensityDimension = "Intensidade da luz" +FrequencyDimension = "Frequência" +ForceDimension = "Força" +PressureDimension = "Pressão" +EnergyDimension = "Energia" +PowerDimension = "Poderoso" +ElectricChargeDimension = "Carga elétrica" +ElectricPotentialDimension = "Potencial elétrico" +ElectricCapacitanceDimension = "Capacidade elétrica" +ElectricResistanceDimension = "Resistência elétrica" +ElectricConductanceDimension = "condutância elétrica" +MagneticFluxDimension = "fluxo magnético" +MagneticFieldDimension = "Campo magnético" +InductanceDimension = "Indutância" +CatalyticActivityDimension = "Atividade catalítica" +SurfaceDimension = "Superfície" +VolumeDimension = "Volume" +SpeedDimension = "Velocidade" From c0d998017d027a82ccf5c5f0602cb57ab18bf40c Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Fri, 17 Feb 2023 19:32:50 +0100 Subject: [PATCH 335/355] [apps/main] Add open app option --- apps/apps_container.cpp | 11 +++++++++-- apps/apps_container.h | 6 ++++++ apps/main.cpp | 19 ++++++++++++++++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index 7e63f750315..ef2753f2085 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -37,7 +37,8 @@ AppsContainer::AppsContainer() : m_homeSnapshot(), m_onBoardingSnapshot(), m_hardwareTestSnapshot(), - m_usbConnectedSnapshot() + m_usbConnectedSnapshot(), + m_startAppSnapshot() { m_emptyBatteryWindow.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height), false); // #if __EMSCRIPTEN__ @@ -346,7 +347,13 @@ void AppsContainer::run() { /* Normal execution. The exception checkpoint must be created before * switching to the first app, because the first app might create nodes on * the pool. */ - bool switched = switchTo(initialAppSnapshot()); + bool switched; + if (m_startAppSnapshot != nullptr) { + switched = switchTo(m_startAppSnapshot); + } else { + switched = switchTo(initialAppSnapshot()); + } + assert(switched); (void) switched; // Silence compilation warning about unused variable. } else { diff --git a/apps/apps_container.h b/apps/apps_container.h index 0d49cdedb16..485c5ce3b2d 100644 --- a/apps/apps_container.h +++ b/apps/apps_container.h @@ -59,6 +59,9 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag // Ion::StorageDelegate void storageDidChangeForRecord(const Ion::Storage::Record record) override; void storageIsFull() override; + #ifdef EPSILON_GETOPT + void setStartApp(App::Snapshot * snapshot) { m_startAppSnapshot = snapshot; } + #endif protected: Home::App::Snapshot * homeAppSnapshot() { return &m_homeSnapshot; } private: @@ -89,6 +92,9 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag HardwareTest::App::Snapshot m_hardwareTestSnapshot; USB::App::Snapshot m_usbConnectedSnapshot; XNTLoop m_XNTLoop; + #ifdef EPSILON_GETOPT + App::Snapshot * m_startAppSnapshot; + #endif }; #endif diff --git a/apps/main.cpp b/apps/main.cpp index b73b73e77be..ec1db8b8073 100644 --- a/apps/main.cpp +++ b/apps/main.cpp @@ -1,6 +1,7 @@ #include "apps_container.h" #include "global_preferences.h" #include +#include #define DUMMY_MAIN 0 #if DUMMY_MAIN @@ -50,13 +51,29 @@ void ion_main(int argc, const char * const argv[]) { } continue; } + + /* Option should be given at run-time: + * $ ./epsilon.elf --open-app code + */ + const char * appNames[] = {"home", EPSILON_APPS_NAMES}; + if (strcmp(argv[i], "--open-app") == 0 && argc > i+1) { + const char * requestedAppName = argv[i+1]; + for (int j = 0; j < AppsContainer::sharedAppsContainer()->numberOfApps(); j++) { + App::Snapshot * snapshot = AppsContainer::sharedAppsContainer()->appSnapshotAtIndex(j); + if (strcmp(requestedAppName, appNames[j]) == 0) { + AppsContainer::sharedAppsContainer()->setStartApp(snapshot); + break; + } + } + continue; + } + /* Option should be given at run-time: * $ ./epsilon.elf --[app_name]-[option] [arguments] * For example: * $ make -j8 PLATFORM=emscripten EPSILON_APPS=code * $ ./epsilon.elf --code-script hello_world.py:print("hello") --code-lock-on-console */ - const char * appNames[] = {"home", EPSILON_APPS_NAMES}; for (int j = 0; j < AppsContainer::sharedAppsContainer()->numberOfApps(); j++) { App::Snapshot * snapshot = AppsContainer::sharedAppsContainer()->appSnapshotAtIndex(j); // Compare name in order to find if the firsts chars which are different are NULL and '-' From 1067216d4f866bf1258b7a24faafc0f0920085c3 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Fri, 17 Feb 2023 21:36:34 +0100 Subject: [PATCH 336/355] [bootloader] Mark Epsilon 20.3 as safe --- apps/on_boarding/app.cpp | 2 +- bootloader/boot.cpp | 4 +- bootloader/drivers/board.cpp | 133 +++++++++++++------------ bootloader/interface/static/messages.h | 4 +- ion/src/device/regs/rcc.h | 1 + ion/src/device/shared/regs/rcc.h | 1 + 6 files changed, 74 insertions(+), 71 deletions(-) diff --git a/apps/on_boarding/app.cpp b/apps/on_boarding/app.cpp index 59f83fc2ff1..1b789338b92 100644 --- a/apps/on_boarding/app.cpp +++ b/apps/on_boarding/app.cpp @@ -25,7 +25,7 @@ bool App::processEvent(Ion::Events::Event e) { if (e == Ion::Events::Home) { return true; } - if (e == Ion::Events::OnOff) { + if (e == Ion::Events::OnOff && !GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { Ion::Power::standby(); // Force a core reset to exit } return ::App::processEvent(e); diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index bb9ba5aed26..8a90481b34e 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -82,7 +82,7 @@ void Boot::patchKernel(const Slot & s) { data[origin_isr + sizeof(uint32_t) * 6 + 1] = ptr[1]; data[origin_isr + sizeof(uint32_t) * 6 + 2] = ptr[2]; data[origin_isr + sizeof(uint32_t) * 6 + 3] = ptr[3]; - + // data[origin_isr + sizeof(uint32_t) * 5] = ptr[0]; // MemManage // data[origin_isr + sizeof(uint32_t) * 5 + 1] = ptr[1]; // data[origin_isr + sizeof(uint32_t) * 5 + 2] = ptr[2]; @@ -107,7 +107,7 @@ void Boot::bootSlot(Bootloader::Slot s) { if (!s.userlandHeader()->isOmega() && !s.userlandHeader()->isUpsilon()) { // We are trying to boot epsilon, so we check the version and show an advertisement if needed const char * version = s.userlandHeader()->version(); - const char * min = "20.0.0"; + const char * min = "20.3.1"; int versionSum = Utility::versionSum(version, strlen(version)); int minimalVersionTrigger = Utility::versionSum(min, strlen(min)); if (versionSum >= minimalVersionTrigger) { diff --git a/bootloader/drivers/board.cpp b/bootloader/drivers/board.cpp index 0a80a89ec98..0958a942f43 100644 --- a/bootloader/drivers/board.cpp +++ b/bootloader/drivers/board.cpp @@ -272,7 +272,7 @@ void initClocks() { ahb1enr.setGPIOCEN(true); ahb1enr.setGPIODEN(true); ahb1enr.setGPIOEEN(true); - ahb1enr.setDMA2EN(true); + ahb1enr.setDMA2EN(false); RCC.AHB1ENR()->set(ahb1enr); // AHB2 bus @@ -282,8 +282,8 @@ void initClocks() { RCC.AHB3ENR()->setFSMCEN(true); // APB1 bus - // We're using TIM3 for the LEDs - RCC.APB1ENR()->setTIM3EN(true); + // We're using TIM3 for the LEDs (disabled to stick with E16+ Bootloader) + /*RCC.APB1ENR()->setTIM3EN(true); */ RCC.APB1ENR()->setPWREN(true); RCC.APB1ENR()->setRTCAPB(true); @@ -291,7 +291,7 @@ void initClocks() { class RCC::APB2ENR apb2enr(0); // Reset value apb2enr.setADC1EN(true); apb2enr.setSYSCFGEN(true); - apb2enr.setUSART6EN(true); // TODO required if building bench target only? + apb2enr.setUSART6EN(false); // TODO required if building bench target only? (disabled to stick with E16+ Bootloader) RCC.APB2ENR()->set(apb2enr); // Configure clocks in sleep mode @@ -300,86 +300,86 @@ void initClocks() { ahb1lpenr.setGPIOALPEN(true); // Enable IO port A for Charging/USB plug/Keyboard pins ahb1lpenr.setGPIOBLPEN(true); // Enable IO port B for LED pins ahb1lpenr.setGPIOCLPEN(true); // Enable IO port C for LED/Keyboard pins - ahb1lpenr.setGPIODLPEN(false); // Disable IO port D (LCD...) + ahb1lpenr.setGPIODLPEN(true); // Enable IO port D (LCD...) ahb1lpenr.setGPIOELPEN(true); // Enable IO port E for Keyboard/Battery pins - ahb1lpenr.setGPIOFLPEN(false); // Disable IO port F - ahb1lpenr.setGPIOGLPEN(false); // Disable IO port G - ahb1lpenr.setGPIOHLPEN(false); // Disable IO port H - ahb1lpenr.setGPIOILPEN(false); // Disable IO port I - ahb1lpenr.setCRCLPEN(false); - ahb1lpenr.setFLITFLPEN(false); - ahb1lpenr.setSRAM1LPEN(false); - ahb1lpenr.setDMA1LPEN(false); - ahb1lpenr.setDMA2LPEN(false); - ahb1lpenr.setAXILPEN(false); - ahb1lpenr.setSRAM2LPEN(false); - ahb1lpenr.setBKPSRAMLPEN(false); - ahb1lpenr.setDTCMLPEN(false); - ahb1lpenr.setOTGHSLPEN(false); - ahb1lpenr.setOTGHSULPILPEN(false); + ahb1lpenr.setGPIOFLPEN(true); // Enable IO port F + ahb1lpenr.setGPIOGLPEN(true); // Enable IO port G + ahb1lpenr.setGPIOHLPEN(true); // Enable IO port H + ahb1lpenr.setGPIOILPEN(true); // Enable IO port I + ahb1lpenr.setCRCLPEN(true); + ahb1lpenr.setFLITFLPEN(true); + ahb1lpenr.setSRAM1LPEN(true); + ahb1lpenr.setDMA1LPEN(true); + ahb1lpenr.setDMA2LPEN(true); + ahb1lpenr.setAXILPEN(true); + ahb1lpenr.setSRAM2LPEN(true); + ahb1lpenr.setBKPSRAMLPEN(true); + ahb1lpenr.setDTCMLPEN(true); + ahb1lpenr.setOTGHSLPEN(true); + ahb1lpenr.setOTGHSULPILPEN(true); RCC.AHB1LPENR()->set(ahb1lpenr); // AHB2 peripheral clock enable in low-power mode register class RCC::AHB2LPENR ahb2lpenr(0x000000F1); // Reset value - ahb2lpenr.setOTGFSLPEN(false); - ahb2lpenr.setRNGLPEN(false); - ahb2lpenr.setAESLPEN(false); + ahb2lpenr.setOTGFSLPEN(true); + ahb2lpenr.setRNGLPEN(true); + ahb2lpenr.setAESLPEN(true); RCC.AHB2LPENR()->set(ahb2lpenr); // AHB3 peripheral clock enable in low-power mode register class RCC::AHB3LPENR ahb3lpenr(0x00000003); // Reset value - ahb3lpenr.setFMCLPEN(false); - ahb3lpenr.setQSPILPEN(false); + ahb3lpenr.setFMCLPEN(true); + ahb3lpenr.setQSPILPEN(true); RCC.AHB3LPENR()->set(ahb3lpenr); // APB1 peripheral clock enable in low-power mode register class RCC::APB1LPENR apb1lpenr(0xFFFFCBFF); // Reset value - apb1lpenr.setTIM2LPEN(false); + apb1lpenr.setTIM2LPEN(true); apb1lpenr.setTIM3LPEN(true); // Enable TIM3 in sleep mode for LEDs - apb1lpenr.setTIM4LPEN(false); - apb1lpenr.setTIM5LPEN(false); - apb1lpenr.setTIM6LPEN(false); - apb1lpenr.setTIM7LPEN(false); - apb1lpenr.setTIM12LPEN(false); - apb1lpenr.setTIM13LPEN(false); - apb1lpenr.setTIM14LPEN(false); - apb1lpenr.setRTCAPBLPEN(false); - apb1lpenr.setWWDGLPEN(false); - apb1lpenr.setSPI2LPEN(false); - apb1lpenr.setSPI3LPEN(false); - apb1lpenr.setUSART2LPEN(false); - apb1lpenr.setUSART3LPEN(false); - apb1lpenr.setI2C1LPEN(false); - apb1lpenr.setI2C2LPEN(false); - apb1lpenr.setI2C3LPEN(false); - apb1lpenr.setCAN1LPEN(false); - apb1lpenr.setPWRLPEN(false); - apb1lpenr.setLPTIM1LPEN(false); - apb1lpenr.setUSART4LPEN(false); - apb1lpenr.setUSART5LPEN(false); - apb1lpenr.setOTGHSLPEN(false); - apb1lpenr.setOTGHSULPILPEN(false); + apb1lpenr.setTIM4LPEN(true); + apb1lpenr.setTIM5LPEN(true); + apb1lpenr.setTIM6LPEN(true); + apb1lpenr.setTIM7LPEN(true); + apb1lpenr.setTIM12LPEN(true); + apb1lpenr.setTIM13LPEN(true); + apb1lpenr.setTIM14LPEN(true); + apb1lpenr.setRTCAPBLPEN(true); + apb1lpenr.setWWDGLPEN(true); + apb1lpenr.setSPI2LPEN(true); + apb1lpenr.setSPI3LPEN(true); + apb1lpenr.setUSART2LPEN(true); + apb1lpenr.setUSART3LPEN(true); + apb1lpenr.setI2C1LPEN(true); + apb1lpenr.setI2C2LPEN(true); + apb1lpenr.setI2C3LPEN(true); + apb1lpenr.setCAN1LPEN(true); + apb1lpenr.setPWRLPEN(true); + apb1lpenr.setLPTIM1LPEN(true); + apb1lpenr.setUSART4LPEN(true); + apb1lpenr.setUSART5LPEN(true); + apb1lpenr.setOTGHSLPEN(true); + apb1lpenr.setOTGHSULPILPEN(true); RCC.APB1LPENR()->set(apb1lpenr); // APB2 peripheral clock enable in low-power mode register class RCC::APB2LPENR apb2lpenr(0x04F77F33); // Reset value - apb2lpenr.setTIM1LPEN(false); - apb2lpenr.setTIM8LPEN(false); - apb2lpenr.setUSART1LPEN(false); - apb2lpenr.setUSART6LPEN(false); - apb2lpenr.setADC1LPEN(false); - apb2lpenr.setSPI1LPEN(false); - apb2lpenr.setSPI4LPEN(false); - apb2lpenr.setSYSCFGLPEN(false); - apb2lpenr.setTIM9LPEN(false); - apb2lpenr.setTIM10LPEN(false); - apb2lpenr.setTIM11LPEN(false); - apb2lpenr.setSPI5LPEN(false); - apb2lpenr.setSDMMC2LPEN(false); - apb2lpenr.setADC2LPEN(false); - apb2lpenr.setADC3LPEN(false); - apb2lpenr.setSAI1LPEN(false); - apb2lpenr.setSAI2LPEN(false); + apb2lpenr.setTIM1LPEN(true); + apb2lpenr.setTIM8LPEN(true); + apb2lpenr.setUSART1LPEN(true); + apb2lpenr.setUSART6LPEN(true); + apb2lpenr.setADC1LPEN(true); + apb2lpenr.setSPI1LPEN(true); + apb2lpenr.setSPI4LPEN(true); + apb2lpenr.setSYSCFGLPEN(true); + apb2lpenr.setTIM9LPEN(true); + apb2lpenr.setTIM10LPEN(true); + apb2lpenr.setTIM11LPEN(true); + apb2lpenr.setSPI5LPEN(true); + apb2lpenr.setSDMMC2LPEN(true); + apb2lpenr.setADC2LPEN(true); + apb2lpenr.setADC3LPEN(true); + apb2lpenr.setSAI1LPEN(true); + apb2lpenr.setSAI2LPEN(true); RCC.APB2LPENR()->set(apb2lpenr); } @@ -399,6 +399,7 @@ void shutdownClocks(bool keepLEDAwake) { class RCC::AHB1ENR ahb1enr(0x00100000); // Reset value if (keepLEDAwake) { apb1enr.setTIM3EN(true); + apb1enr.setTIM5EN(true); ahb1enr.setGPIOBEN(true); } RCC.APB1ENR()->set(apb1enr); diff --git a/bootloader/interface/static/messages.h b/bootloader/interface/static/messages.h index 6d97ee991cd..d695916c152 100644 --- a/bootloader/interface/static/messages.h +++ b/bootloader/interface/static/messages.h @@ -6,7 +6,7 @@ namespace Bootloader { class Messages { public: constexpr static const char * mainTitle = "Upsilon Calculator"; - + // Home menu constexpr static const char * homeTitle = "Select a slot"; @@ -72,7 +72,7 @@ class Messages { constexpr static const char * aboutMessage4 = "and select the OS"; constexpr static const char * aboutMessage5 = "to boot."; - constexpr static const char * bootloaderVersion = "Version 1.0.2 - FREED0M.20"; + constexpr static const char * bootloaderVersion = "Version 1.0.3 - FREED0M.20.3"; //USB NAMES constexpr static const char * usbUpsilonBootloader = "Upsilon Bootloader"; diff --git a/ion/src/device/regs/rcc.h b/ion/src/device/regs/rcc.h index 017e15aeb69..b6b95193080 100644 --- a/ion/src/device/regs/rcc.h +++ b/ion/src/device/regs/rcc.h @@ -75,6 +75,7 @@ class RCC { public: using Register32::Register32; REGS_BOOL_FIELD(TIM3EN, 1); + REGS_BOOL_FIELD(TIM5EN, 3); REGS_BOOL_FIELD(RTCAPB, 10); REGS_BOOL_FIELD(SPI3EN, 15); REGS_BOOL_FIELD(USART3EN, 18); diff --git a/ion/src/device/shared/regs/rcc.h b/ion/src/device/shared/regs/rcc.h index 0505126328c..07b8854bed8 100644 --- a/ion/src/device/shared/regs/rcc.h +++ b/ion/src/device/shared/regs/rcc.h @@ -118,6 +118,7 @@ class RCC { public: using Register32::Register32; REGS_BOOL_FIELD(TIM3EN, 1); + REGS_BOOL_FIELD(TIM5EN, 3); REGS_BOOL_FIELD(RTCAPB, 10); REGS_BOOL_FIELD(SPI3EN, 15); REGS_BOOL_FIELD(USART3EN, 18); From 83d6d33edf6b81b06c69e9b46882b725f6e8c803 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Sun, 12 Mar 2023 11:19:14 +0100 Subject: [PATCH 337/355] [apps/math_toolbox] Move Matrix and Vectors to a submenu --- apps/math_toolbox.cpp | 18 +++++++++++------- apps/toolbox.de.i18n | 1 + apps/toolbox.en.i18n | 1 + apps/toolbox.es.i18n | 1 + apps/toolbox.fr.i18n | 1 + apps/toolbox.hu.i18n | 1 + apps/toolbox.it.i18n | 1 + apps/toolbox.nl.i18n | 1 + apps/toolbox.pt.i18n | 1 + 9 files changed, 19 insertions(+), 7 deletions(-) diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index 0cb24ab13c6..30af1e1bd32 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -67,13 +67,10 @@ const ToolboxMessageTree arithmeticChildren[] = { }; const ToolboxMessageTree matricesChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::MatrixCommandWithArg, I18n::Message::NewMatrix, false, I18n::Message::MatrixCommand), - ToolboxMessageTree::Leaf(I18n::Message::IndentityCommandWithArg, I18n::Message::Identity), - ToolboxMessageTree::Leaf(I18n::Message::InverseCommandWithArg, I18n::Message::Inverse), ToolboxMessageTree::Leaf(I18n::Message::DeterminantCommandWithArg, I18n::Message::Determinant), - ToolboxMessageTree::Leaf(I18n::Message::TransposeCommandWithArg, I18n::Message::Transpose), + ToolboxMessageTree::Leaf(I18n::Message::InverseCommandWithArg, I18n::Message::Inverse), + ToolboxMessageTree::Leaf(I18n::Message::IndentityCommandWithArg, I18n::Message::Identity), ToolboxMessageTree::Leaf(I18n::Message::TraceCommandWithArg, I18n::Message::Trace), - ToolboxMessageTree::Leaf(I18n::Message::DimensionCommandWithArg, I18n::Message::Dimension), ToolboxMessageTree::Leaf(I18n::Message::RowEchelonFormCommandWithArg, I18n::Message::RowEchelonForm), ToolboxMessageTree::Leaf(I18n::Message::ReducedRowEchelonFormCommandWithArg, I18n::Message::ReducedRowEchelonForm) }; @@ -84,6 +81,14 @@ const ToolboxMessageTree vectorsChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::NormVectorCommandWithArg, I18n::Message::NormVector), }; +const ToolboxMessageTree matricesAndVectorsChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::MatrixCommandWithArg, I18n::Message::NewMatrix, false, I18n::Message::MatrixCommand), + ToolboxMessageTree::Leaf(I18n::Message::TransposeCommandWithArg, I18n::Message::Transpose), + ToolboxMessageTree::Leaf(I18n::Message::DimensionCommandWithArg, I18n::Message::Dimension), + ToolboxMessageTree::Node(I18n::Message::Matrices, matricesChildren), + ToolboxMessageTree::Node(I18n::Message::Vectors, vectorsChildren) +}; + #if LIST_ARE_DEFINED const ToolboxMessageTree listsChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::SortCommandWithArg, I18n::Message::Sort), @@ -856,9 +861,8 @@ const ToolboxMessageTree menu[] = { ToolboxMessageTree::Node(I18n::Message::ComplexNumber, complexChildren), ToolboxMessageTree::Node(I18n::Message::Unit, unitChildren), ToolboxMessageTree::Node(I18n::Message::Arithmetic, arithmeticChildren), - ToolboxMessageTree::Node(I18n::Message::Matrices, matricesChildren), + ToolboxMessageTree::Node(I18n::Message::MatricesAndVectors, matricesAndVectorsChildren), ToolboxMessageTree::Node(I18n::Message::Probability, probabilityChildren), - ToolboxMessageTree::Node(I18n::Message::Vectors, vectorsChildren), #if LIST_ARE_DEFINED ToolboxMessageTree::Node(I18n::Message::Lists,listsChildren), #endif diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index 40c021a7065..02cc172e35c 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -510,3 +510,4 @@ HartreeConstantTag = "Hartbaum-Konstante" MagneticFluxQuantumTag = "Magnetisches Fluss-Quantum" ConductanceQuantumTag = "Leitwertquantum" CirculationQuantumTag = "Auflage-Quantum" +MatricesAndVectors = "Matrizen und Vektoren" diff --git a/apps/toolbox.en.i18n b/apps/toolbox.en.i18n index b3ff61e2961..fd5aaafc814 100644 --- a/apps/toolbox.en.i18n +++ b/apps/toolbox.en.i18n @@ -99,6 +99,7 @@ Calculation = "Calculation" ComplexNumber = "Complex numbers" Combinatorics = "Combinatorics" Arithmetic = "Arithmetic" +MatricesAndVectors = "Matrices and vectors" Matrices = "Matrix" NewMatrix = "New matrix" Identity = "Identity matrix of size n" diff --git a/apps/toolbox.es.i18n b/apps/toolbox.es.i18n index f4f9a212c56..841d37d7e7c 100644 --- a/apps/toolbox.es.i18n +++ b/apps/toolbox.es.i18n @@ -510,3 +510,4 @@ HartreeConstantTag = "Constante de Hartree" MagneticFluxQuantumTag = "Flujo Magnético Cuántico" ConductanceQuantumTag = "Conductancia Quantum" CirculationQuantumTag = "Circulación Quantum" +MatricesAndVectors = "Matrices y vectores" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index 41164cd0863..283f2bc28ef 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -514,3 +514,4 @@ HartreeConstantTag = "Constante de Hartree" MagneticFluxQuantumTag = "Quantum de Flux Magnétique" ConductanceQuantumTag = "Quantum de Conductance" CirculationQuantumTag = "Quantum de Circulation" +MatricesAndVectors = "Matrices et vecteurs" diff --git a/apps/toolbox.hu.i18n b/apps/toolbox.hu.i18n index 79d22308ab3..76fe34d08a4 100644 --- a/apps/toolbox.hu.i18n +++ b/apps/toolbox.hu.i18n @@ -510,3 +510,4 @@ HartreeConstantTag = "Hartree Állandó" MagneticFluxQuantumTag = "Mágneses Fluxuskvantum" ConductanceQuantumTag = "Vezetőképesség Kvantum" CirculationQuantumTag = "Keringési Kvantum" +MatricesAndVectors = "Mátrixok és vektorok" diff --git a/apps/toolbox.it.i18n b/apps/toolbox.it.i18n index 440a87f5662..cf3b94f74fd 100644 --- a/apps/toolbox.it.i18n +++ b/apps/toolbox.it.i18n @@ -510,3 +510,4 @@ HartreeConstantTag = "Costante di Hartree" MagneticFluxQuantumTag = "Flusso magnetico quantico" ConductanceQuantumTag = "Conduttanza quantistica" CirculationQuantumTag = "Circolazione quantistica" +MatricesAndVectors = "Matrici e vettori" diff --git a/apps/toolbox.nl.i18n b/apps/toolbox.nl.i18n index e7a40ed2529..d6ee4cdfab0 100644 --- a/apps/toolbox.nl.i18n +++ b/apps/toolbox.nl.i18n @@ -510,3 +510,4 @@ HartreeConstantTag = "Hartreeconstante" MagneticFluxQuantumTag = "Magnetische flux kwantum" ConductanceQuantumTag = "Kwantumgeleiding" CirculationQuantumTag = "Kwantumcirculatie" +MatricesAndVectors = "Matrices en vectoren" diff --git a/apps/toolbox.pt.i18n b/apps/toolbox.pt.i18n index 4891b4e1b15..219680ae90e 100644 --- a/apps/toolbox.pt.i18n +++ b/apps/toolbox.pt.i18n @@ -510,3 +510,4 @@ HartreeConstantTag = "Constante de Hartree" MagneticFluxQuantumTag = "Fluxo Magnético Quântico" ConductanceQuantumTag = "Quantum de Conduta" CirculationQuantumTag = "Quantum de Circulação" +MatricesAndVectors = "Matrizes e vetores" From a87a99af7a72d681007fba205309c4b3dc0e7f98 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Fri, 17 Mar 2023 17:20:23 +0100 Subject: [PATCH 338/355] [bootloader] Mark Epsilon 20.4 as safe --- bootloader/boot.cpp | 2 +- bootloader/interface/static/messages.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index 8a90481b34e..8f0a68d827e 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -107,7 +107,7 @@ void Boot::bootSlot(Bootloader::Slot s) { if (!s.userlandHeader()->isOmega() && !s.userlandHeader()->isUpsilon()) { // We are trying to boot epsilon, so we check the version and show an advertisement if needed const char * version = s.userlandHeader()->version(); - const char * min = "20.3.1"; + const char * min = "20.4.1"; int versionSum = Utility::versionSum(version, strlen(version)); int minimalVersionTrigger = Utility::versionSum(min, strlen(min)); if (versionSum >= minimalVersionTrigger) { diff --git a/bootloader/interface/static/messages.h b/bootloader/interface/static/messages.h index d695916c152..40600fd8aae 100644 --- a/bootloader/interface/static/messages.h +++ b/bootloader/interface/static/messages.h @@ -72,7 +72,7 @@ class Messages { constexpr static const char * aboutMessage4 = "and select the OS"; constexpr static const char * aboutMessage5 = "to boot."; - constexpr static const char * bootloaderVersion = "Version 1.0.3 - FREED0M.20.3"; + constexpr static const char * bootloaderVersion = "Version 1.0.4 - FREED0M.20.4"; //USB NAMES constexpr static const char * usbUpsilonBootloader = "Upsilon Bootloader"; From b4820d52e41bee4f73eb92ab3be1f69a73b1f601 Mon Sep 17 00:00:00 2001 From: Wiwok <84347864+Wiwok@users.noreply.github.com> Date: Sun, 19 Mar 2023 10:31:27 +0100 Subject: [PATCH 339/355] [README] Fix a bad string in French README (#317) --- README.fr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.fr.md b/README.fr.md index 806a8379ea0..5a734af7a63 100644 --- a/README.fr.md +++ b/README.fr.md @@ -193,7 +193,7 @@ sudo apt-get install build-essential git imagemagick libx11-dev libxext-dev libf ### Installation d'usbipd pour connecter la calculatrice à WSL (facultatif) -Pour connecter la calculatrice, il faut installer cet [outil](https://github.com/dorssel/usbipd-win/releases/download/v1.3.0/usbipd-win_1.3.0.msi). Il permet de connecter deperiphériques USpar internet.Suivez les instructions pour installer. +Pour connecter la calculatrice, il faut installer cet [outil](https://github.com/dorssel/usbipd-win/releases/download/v1.3.0/usbipd-win_1.3.0.msi). Il permet de connecter des périphériques par internet. Suivez les instructions pour installer. #### Ubuntu From 09169961214909b4cda9e92a13b7d92d625dec31 Mon Sep 17 00:00:00 2001 From: Lisra-git <89012417+Lisra-git@users.noreply.github.com> Date: Fri, 7 Apr 2023 23:58:28 +0200 Subject: [PATCH 340/355] [Bootloader] Enhance Global Stability --- apps/apps_container.cpp | 5 +- bootloader/interface/static/messages.h | 2 +- bootloader/slots/slot_exam_mode.cpp | 107 +++++++++++++------------ 3 files changed, 61 insertions(+), 53 deletions(-) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index ef2753f2085..b01c388e204 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -152,7 +152,10 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { * We do it before switching to USB application to redraw the battery * pictogram. */ updateBatteryState(); - if (switchTo(usbConnectedAppSnapshot())) { + if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { + // If we are in exam mode, we don't switch to usb connected app + didProcessEvent = true; + } else if (switchTo(usbConnectedAppSnapshot())) { Ion::USB::DFU(true); // Update LED when exiting DFU mode Ion::LED::updateColorWithPlugAndCharge(); diff --git a/bootloader/interface/static/messages.h b/bootloader/interface/static/messages.h index 40600fd8aae..3e443cf20fe 100644 --- a/bootloader/interface/static/messages.h +++ b/bootloader/interface/static/messages.h @@ -72,7 +72,7 @@ class Messages { constexpr static const char * aboutMessage4 = "and select the OS"; constexpr static const char * aboutMessage5 = "to boot."; - constexpr static const char * bootloaderVersion = "Version 1.0.4 - FREED0M.20.4"; + constexpr static const char * bootloaderVersion = "Version 1.0.5 - FREED0M.20.4"; //USB NAMES constexpr static const char * usbUpsilonBootloader = "Upsilon Bootloader"; diff --git a/bootloader/slots/slot_exam_mode.cpp b/bootloader/slots/slot_exam_mode.cpp index 47c22995e0a..e532828afd0 100644 --- a/bootloader/slots/slot_exam_mode.cpp +++ b/bootloader/slots/slot_exam_mode.cpp @@ -41,48 +41,46 @@ size_t numberOfBitsAfterLeadingZeroes(int i) { } uint8_t SlotsExamMode::FetchSlotExamMode(const char * version, const char * Slot) { - // Get start and end from version and slot - uint32_t start = 0; - uint32_t end = 0; + // Get start and end from version and slot + uint32_t start = 0; + uint32_t end = 0; if (Slot == "A") { - // If version under 16 get old addresses - if (version[0] < '1' || (version[0] == '1' && version[1] < '6')) { - start = getSlotAStartExamAddress(0); + // If version under 16 get old addresses + if (version[0] < '1' || (version[0] == '1' && version[1] < '6')) { + start = getSlotAStartExamAddress(0); end = getSlotAEndExamAddress(0); - } - // Else get new addresses - else { + } + // Else get new addresses + else { start = getSlotAStartExamAddress(1); end = getSlotAEndExamAddress(1); - } - } - else if (Slot == "B") { - // If version under 16 get old - if (version[0] < '1' || (version[0] == '1' && version[1] < '6')) { + } + } + else if (Slot == "B") { + // If version under 16 get old + if (version[0] < '1' || (version[0] == '1' && version[1] < '6')) { start = getSlotBStartExamAddress(0); end = getSlotBEndExamAddress(0); - } - // Else get new - else { + } + // Else get new + else { start = getSlotBStartExamAddress(1); - end = getSlotBEndExamAddress(1); - } - } else if (Slot == "Khi") { + end = getSlotBEndExamAddress(1); + } + } else if (Slot == "Khi") { // We directly get the address of the Khi exam mode without checking the // version, because on Khi, version is KhiCAS version, not the OS version start = getSlotKhiStartExamAddress(); end = getSlotKhiEndExamAddress(); } - if (strcmp("15.9.0", version) >= 0) { + if (strcmp("15.9.0", version) >= 0) { return examFetch15(start, end); } else if (strcmp("16.9.0", version) > 0) { return examFetch16(start, end); - } - else if (strcmp("19.0.0", version) > 0) { + } else if (strcmp("19.0.0", version) > 0) { return examFetch1718(start, end); - } - else { + } else { return examFetch19(start, end); } } @@ -157,47 +155,54 @@ uint8_t SlotsExamMode::examFetch19(uint32_t start, uint32_t end) { uint16_t* start16 = (uint16_t*)start; uint16_t* end16 = (uint16_t*)end; - while (start16 + 1 <= end16 && *start16 != 0xFFFF) { - start16++; + for (uint16_t* i = end16 - 2; i > start16; i--) { + if (*i != 0xFFFF) { + uint8_t highByte = *i >> 8; + uint8_t lowByte = *i & 0xFF; + if (highByte > lowByte) { + return highByte; + } + else { + return lowByte; + } + } } - - return *(start16 - 1) >> 8; } uint32_t SlotsExamMode::getSlotAStartExamAddress(int ExamVersion) { - if (ExamVersion == 0) { + if (ExamVersion == 0) { return SlotAExamModeBufferStartOldVersions; - } - else { - return SlotAExamModeBufferStartNewVersions; - } + } + else { + return SlotAExamModeBufferStartNewVersions; + } } uint32_t SlotsExamMode::getSlotAEndExamAddress(int ExamVersion) { - if (ExamVersion == 0) { - return SlotAExamModeBufferEndOldVersions; - } - else { + if (ExamVersion == 0) { + return SlotAExamModeBufferEndOldVersions; + } + else { return SlotAExamModeBufferEndNewVersions;; - } + } } uint32_t SlotsExamMode::getSlotBStartExamAddress(int ExamVersion) { - if (ExamVersion == 0) { - return SlotBExamModeBufferStartOldVersions; - } - else { - return SlotBExamModeBufferStartNewVersions; - } + if (ExamVersion == 0) { + return SlotBExamModeBufferStartOldVersions; + } + else { + return SlotBExamModeBufferStartNewVersions; + } } uint32_t SlotsExamMode::getSlotBEndExamAddress(int ExamVersion) { - if (ExamVersion == 0) { - return SlotBExamModeBufferEndOldVersions; - } - else { - return SlotBExamModeBufferEndNewVersions; - } + if (ExamVersion == 0) { + return SlotBExamModeBufferEndOldVersions; + } + else { + return SlotBExamModeBufferEndNewVersions; + } } uint32_t SlotsExamMode::getSlotKhiStartExamAddress() { From aadcd37f31bf8122ee8f34e677d650b43b9c1990 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Thu, 13 Apr 2023 16:24:38 +0200 Subject: [PATCH 341/355] Fix 3DS simulator (#243) * [CI] Fix 3DS try 1 * [CI] Fix 3DS try 2 * [CI] Fix 3DS try 3 * [CI] Fix 3DS try 4 * [CI] Fix 3DS try 5 * [CI] Fix 3DS try 6 * [CI] Fix 3DS try 7 * [CI] Fix 3DS try 8 * [CI] Fix 3DS try 9 * [CI] Fix 3DS try 10 * [CI] Fix 3DS try 11 * [CI] Fix 3DS try 12 * [CI] Fix 3DS try 13 * [CI] Fix 3DS try 14 * [CI] Fix 3DS try 15 * [CI] Fix 3DS try 16 * [CI] Fix 3DS try 17 * [CI] Fix 3DS try 18 * [CI] Fix 3DS try 19 * [CI] Make other simulators than 3DS working * [CI] Fix 3DS without breaking others simulators try 1 * Apply suggestions from code review * Improve SDL assert * Fix SDL assert * Fix SDL assert 2 * [CI] Enable iOS, macOS and 3DS tests by default * [CI] Change epsilon-sdk tap url * Revert "[CI] Change epsilon-sdk tap url" This reverts commit 9516607aba10b7500881d96fe7ae5ae68f0db72e. --- .github/workflows/ci-workflow.yml | 28 ++++++++++--------- build/platform.simulator.3ds.mak | 2 ++ build/targets.simulator.3ds.mak | 2 +- build/toolchain.devkitarm.mak | 2 +- ion/src/simulator/3ds/Makefile | 13 +++++++-- ion/src/simulator/3ds/main.cpp | 4 +++ .../external/sdl/include/SDL_stdinc.h | 7 ++++- 7 files changed, 39 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index fe8ec12b43a..d4e42c2a02e 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -8,30 +8,32 @@ on: triggerIos: description: 'Run iOS tests' required: true - default: 'no' + default: 'yes' triggerMacos: description: 'Run macOS tests' required: true - default: 'no' + default: 'yes' trigger3DS: description: 'Run 3DS tests' required: true - default: 'no' + default: 'yes' jobs: nintendo_3ds: - if: github.event.inputs.trigger3DS == 'yes' + if: github.event.inputs.trigger3DS == 'yes' || github.event.inputs.trigger3DS == '' runs-on: ubuntu-latest + container: devkitpro/devkitarm:latest steps: - - run: wget https://github.com/devkitPro/pacman/releases/download/v1.0.2/devkitpro-pacman.amd64.deb -O /tmp/devkitpro-pacman.deb - - run: yes | sudo dpkg -i /tmp/devkitpro-pacman.deb - - run: yes | sudo dkp-pacman -Syu --needed devkitARM 3dstools libctru - - run: echo ::set-env name=DEVKITPRO::/opt/devkitpro - - run: echo ::set-env name=DEVKITARM::/opt/devkitpro/devkitARM - - run: echo ::set-env name=PATH::$DEVKITPRO/tools/bin:$DEVKITARM/bin:$PATH - - uses: actions/checkout@v1 with: submodules: true + - run: sudo apt-get update + - run: sudo apt-get install build-essential imagemagick libfreetype6-dev libjpeg-dev libpng-dev pkg-config -y + - run: yes | sudo dkp-pacman -S --needed devkitARM 3dstools libctru + - run: wget https://github.com/3DSGuy/Project_CTR/releases/download/makerom-v0.18.3/makerom-v0.18.3-ubuntu_x86_64.zip + - run: unzip makerom-v0.18.3-ubuntu_x86_64.zip + - run: rm makerom-v0.18.3-ubuntu_x86_64.zip + - run: chmod +x ./makerom + - run: echo "PATH=.:$PATH" >> $GITHUB_ENV - run: make -j2 PLATFORM=simulator TARGET=3ds - run: make -j2 PLATFORM=simulator TARGET=3ds epsilon.cia - uses: actions/upload-artifact@master @@ -278,7 +280,7 @@ jobs: name: epsilon-linux.bin path: output/release/simulator/linux/epsilon.bin macos: - if: github.event.inputs.triggerMacos == 'yes' + if: github.event.inputs.triggerMacos == 'yes' || github.event.inputs.triggerMacos == '' runs-on: macOS-latest steps: - run: brew install numworks/tap/epsilon-sdk @@ -293,7 +295,7 @@ jobs: name: epsilon-macos.zip path: output/release/simulator/macos/epsilon.app ios: - if: github.event.inputs.triggerIos == 'yes' + if: github.event.inputs.triggerIos == 'yes' || github.event.inputs.triggerIos == '' runs-on: macOS-latest steps: - run: brew install numworks/tap/epsilon-sdk diff --git a/build/platform.simulator.3ds.mak b/build/platform.simulator.3ds.mak index d829e5e0a4a..dc35a659da1 100644 --- a/build/platform.simulator.3ds.mak +++ b/build/platform.simulator.3ds.mak @@ -1,5 +1,7 @@ TOOLCHAIN = devkitarm EXE = elf +EPSILON_TELEMETRY ?= 0 + HANDY_TARGETS_EXTENSIONS = 3dsx cia diff --git a/build/targets.simulator.3ds.mak b/build/targets.simulator.3ds.mak index d06cf52ad5d..23cbe0a3a77 100644 --- a/build/targets.simulator.3ds.mak +++ b/build/targets.simulator.3ds.mak @@ -1,7 +1,7 @@ %.smdh: ion/src/simulator/3ds/assets/logo.png $(Q) echo "SMDH $(notdir $@)" - $(Q) smdhtool --create "Epsilon" "A Numworks in your 3DS!" "Numworks" $< $@ + $(Q) smdhtool --create "Upsilon" "A Numworks in your 3DS!" "Numworks" $< $@ $(BUILD_DIR)/%.3dsx: $(BUILD_DIR)/%.elf $(BUILD_DIR)/%.smdh $(Q) echo "3DSX $(notdir $@)" diff --git a/build/toolchain.devkitarm.mak b/build/toolchain.devkitarm.mak index 52fd7de7560..ce7b31e846f 100644 --- a/build/toolchain.devkitarm.mak +++ b/build/toolchain.devkitarm.mak @@ -31,7 +31,7 @@ LIBDIRS := $(DEVKITPRO)/libctru INCLUDE = $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ -I$(CURDIR)/$(BUILD) -CFLAGS += $(INCLUDE) -DARM11 -D_3DS +CFLAGS += $(INCLUDE) -DARM11 -D_3DS -D__3DS__ CXXFLAGS = $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 diff --git a/ion/src/simulator/3ds/Makefile b/ion/src/simulator/3ds/Makefile index a7de79eddc8..5ca58d89160 100644 --- a/ion/src/simulator/3ds/Makefile +++ b/ion/src/simulator/3ds/Makefile @@ -19,22 +19,29 @@ ion_src += $(addprefix ion/src/simulator/3ds/, \ ion_src += ion/src/shared/collect_registers.cpp -sdl_simu_needs_to_be_removed += $(addprefix ion/src/simulator/shared/, \ +sdl_simu_needs_to_be_removed += $(addprefix ion/src/shared/, \ dummy/display.cpp \ dummy/led.cpp \ dummy/usb.cpp \ dummy/battery.cpp \ + dummy/store_script.cpp \ +) + +sdl_simu_needs_to_be_removed += $(addprefix ion/src/simulator/shared/, \ clipboard.cpp \ display.cpp:-headless \ events_keyboard.cpp:-headless \ events.cpp \ + events_platform.cpp \ framebuffer_base.cpp \ + framebuffer.cpp \ keyboard_sdl.cpp:-headless \ + keyboard.cpp \ main_sdl.cpp:-headless \ + main.cpp \ layout.cpp:-headless \ - dummy/store_script.cpp \ + timing.cpp \ ) # Remove the dummy display (re-implemented) and the SDL simulator stuff. ion_src := $(filter-out $(sdl_simu_needs_to_be_removed),$(ion_src)) - diff --git a/ion/src/simulator/3ds/main.cpp b/ion/src/simulator/3ds/main.cpp index c0b38e9c49a..d95a248c82d 100644 --- a/ion/src/simulator/3ds/main.cpp +++ b/ion/src/simulator/3ds/main.cpp @@ -19,6 +19,10 @@ void Ion::Timing::msleep(uint32_t ms) { svcSleepThread((s64) ms * 1000); } +uint64_t Ion::Timing::millis() { + return svcGetSystemTick() / (1000 * 1000); +} + int main(int argc, char * argv[]) { Ion::Simulator::Main::init(); diff --git a/ion/src/simulator/external/sdl/include/SDL_stdinc.h b/ion/src/simulator/external/sdl/include/SDL_stdinc.h index a95700af2fd..8845cd771a8 100644 --- a/ion/src/simulator/external/sdl/include/SDL_stdinc.h +++ b/ion/src/simulator/external/sdl/include/SDL_stdinc.h @@ -309,8 +309,13 @@ typedef uint64_t Uint64; #endif #endif /* SDL_DISABLE_ANALYZE_MACROS */ +#ifndef __3DS__ #define SDL_COMPILE_TIME_ASSERT(name, x) \ - typedef int SDL_compile_time_assert_ ## name[(x) * 2 - 1] + typedef int SDL_dummy_ ## name[(x) * 2 - 1] +#else +#define SDL_COMPILE_TIME_ASSERT(name, x) \ + typedef int SDL_dummy_ ## name[0] +#endif /** \cond */ #ifndef DOXYGEN_SHOULD_IGNORE_THIS SDL_COMPILE_TIME_ASSERT(uint8, sizeof(Uint8) == 1); From b44a95a9b310cd0a6d0cde3e0db03fcdb9d2dad0 Mon Sep 17 00:00:00 2001 From: circuit10 Date: Wed, 10 May 2023 17:28:18 +0100 Subject: [PATCH 342/355] Casio fx-CG series port (#324) * Initial test - working on Linux * Try to make it work with liba * Stop using liba and the filesystem * IT WORKS * Key input, full res, fix some of the crashes * Fix the hang when doing calculations * Add some more key mappings * Fix the square root issue * Icons * Better key mappings, brightness control, better gamma correction, more effficient framebuffer * Cleanup stage 1 * Cleanup stage 2 * Make the build system build a g3a * Make it not exit when you press the menu button * Add Casio port to README * Use omega-master instead of omega-dev * Fix mistake with cherry-picking in the README * Fix internal storage crash * Fix compile error on Numworks calculators * Upsilon branding * Sharper icon * Make the CI work * Add power off and improve menu * Map Alpha + up/down to the brightness shortcut * Add missing file * Fix web CI build * Revert "Fix web CI build" This reverts commit f19657d9fcd3f2dae9b9512118082e58a73c2523. * Change "prizm" to "fxcg" * Add FASTLOAD option for Add-in Push * Add some charatcers to the catalog on Casio and improve key mappings * Build with -Os -flto * Disable LTO for now as it's causing crashes * Put back the fonts I accidently changed I'd like to add an option for this though as I prefer the ones from Epsilon --- .github/workflows/ci-workflow.yml | 58 ++++ Makefile | 2 + README.md | 37 ++- apps/code/catalog.de.i18n | 6 + apps/code/catalog.en.i18n | 6 + apps/code/catalog.es.i18n | 6 + apps/code/catalog.fr.i18n | 6 + apps/code/catalog.hu.i18n | 6 + apps/code/catalog.it.i18n | 6 + apps/code/catalog.nl.i18n | 6 + apps/code/catalog.pt.i18n | 6 + apps/code/catalog.universal.i18n | 6 + apps/code/python_toolbox.cpp | 9 + apps/graph/graph/graph_view.h | 6 + apps/home/controller.cpp | 2 +- apps/home/controller.h | 11 +- apps/math_toolbox.cpp | 4 + apps/probability/app.h | 2 +- apps/settings/sub_menu/about_controller.cpp | 1 + .../settings/sub_menu/datetime_controller.cpp | 3 + apps/shared.hu.i18n | 1 + apps/shared.it.i18n | 1 + apps/shared.nl.i18n | 1 + apps/shared.universal.i18n | 2 + apps/shared/continuous_function.cpp | 2 +- apps/shared/continuous_function_cache.cpp | 4 + apps/shared/store_controller.h | 5 + apps/solver/solutions_controller.h | 2 +- apps/toolbox.de.i18n | 1 + apps/toolbox.en.i18n | 1 + apps/toolbox.es.i18n | 1 + apps/toolbox.fr.i18n | 1 + apps/toolbox.pt.i18n | 1 + build/platform.simulator.fxcg.mak | 11 + build/targets.simulator.fxcg.mak | 5 + build/toolchain.sh-elf-gcc.mak | 10 + escher/src/icon_view.cpp | 8 + escher/src/image_view.cpp | 8 + ion/include/ion/display.h | 7 + ion/include/ion/internal_storage.h | 4 +- ion/include/ion/keyboard.h | 23 ++ ion/src/shared/events_keyboard.cpp | 11 + ion/src/simulator/external/config.fxcg.mak | 2 + ion/src/simulator/fxcg/Makefile | 66 +++++ ion/src/simulator/fxcg/assets/icon-sel.png | Bin 0 -> 3588 bytes ion/src/simulator/fxcg/assets/icon-uns.png | Bin 0 -> 1750 bytes ion/src/simulator/fxcg/backlight.cpp | 67 +++++ ion/src/simulator/fxcg/clipboard.cpp | 18 ++ ion/src/simulator/fxcg/console.cpp | 24 ++ ion/src/simulator/fxcg/display.cpp | 30 ++ ion/src/simulator/fxcg/display.h | 19 ++ ion/src/simulator/fxcg/events.cpp | 23 ++ ion/src/simulator/fxcg/events.h | 24 ++ ion/src/simulator/fxcg/events_keyboard.cpp | 15 + ion/src/simulator/fxcg/framebuffer.cpp | 56 ++++ ion/src/simulator/fxcg/framebuffer.h | 17 ++ ion/src/simulator/fxcg/keyboard.cpp | 262 ++++++++++++++++++ ion/src/simulator/fxcg/keyboard.h | 15 + ion/src/simulator/fxcg/main.cpp | 114 ++++++++ ion/src/simulator/fxcg/main.h | 20 ++ ion/src/simulator/fxcg/menuHandler.cpp | 97 +++++++ ion/src/simulator/fxcg/menuHandler.h | 14 + ion/src/simulator/fxcg/platform.h | 23 ++ ion/src/simulator/fxcg/power.cpp | 42 +++ ion/src/simulator/fxcg/telemetry_init.cpp | 15 + ion/src/simulator/fxcg/timing.cpp | 27 ++ .../kandinsky/postprocess_gamma_context.h | 10 + kandinsky/src/postprocess_gamma_context.cpp | 53 ++-- liba/include/bridge/math.h | 20 ++ liba/include/bridge/string.h | 2 +- liba/include/bridge/strings.h | 22 ++ libaxx/Makefile.bridge | 3 + libaxx/include/bridge/cmath | 165 +++++++++++ poincare/include/poincare/ieee754.h | 34 ++- poincare/include/poincare/integer.h | 22 +- poincare/include/poincare/tree_pool.h | 2 +- poincare/src/integer.cpp | 44 ++- 77 files changed, 1617 insertions(+), 49 deletions(-) create mode 100644 build/platform.simulator.fxcg.mak create mode 100644 build/targets.simulator.fxcg.mak create mode 100644 build/toolchain.sh-elf-gcc.mak create mode 100644 ion/src/simulator/external/config.fxcg.mak create mode 100644 ion/src/simulator/fxcg/Makefile create mode 100644 ion/src/simulator/fxcg/assets/icon-sel.png create mode 100644 ion/src/simulator/fxcg/assets/icon-uns.png create mode 100644 ion/src/simulator/fxcg/backlight.cpp create mode 100644 ion/src/simulator/fxcg/clipboard.cpp create mode 100644 ion/src/simulator/fxcg/console.cpp create mode 100644 ion/src/simulator/fxcg/display.cpp create mode 100644 ion/src/simulator/fxcg/display.h create mode 100644 ion/src/simulator/fxcg/events.cpp create mode 100644 ion/src/simulator/fxcg/events.h create mode 100644 ion/src/simulator/fxcg/events_keyboard.cpp create mode 100644 ion/src/simulator/fxcg/framebuffer.cpp create mode 100644 ion/src/simulator/fxcg/framebuffer.h create mode 100644 ion/src/simulator/fxcg/keyboard.cpp create mode 100644 ion/src/simulator/fxcg/keyboard.h create mode 100644 ion/src/simulator/fxcg/main.cpp create mode 100644 ion/src/simulator/fxcg/main.h create mode 100644 ion/src/simulator/fxcg/menuHandler.cpp create mode 100644 ion/src/simulator/fxcg/menuHandler.h create mode 100644 ion/src/simulator/fxcg/platform.h create mode 100644 ion/src/simulator/fxcg/power.cpp create mode 100644 ion/src/simulator/fxcg/telemetry_init.cpp create mode 100644 ion/src/simulator/fxcg/timing.cpp create mode 100644 liba/include/bridge/math.h create mode 100644 liba/include/bridge/strings.h create mode 100644 libaxx/Makefile.bridge create mode 100644 libaxx/include/bridge/cmath diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index d4e42c2a02e..e2fd55ddd37 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -18,6 +18,64 @@ on: required: true default: 'yes' jobs: + fxcg: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install curl git python3 build-essential cmake pkg-config -y + - name: Get latest gint commit hash + run: | + LATEST_COMMIT_HASH=$(curl --silent https://gitea.planet-casio.com/api/v1/repos/Lephenixnoir/gint/branches/master | jq -r .commit.id) + echo "Latest commit hash is: $LATEST_COMMIT_HASH" + echo "LATEST_COMMIT_HASH=$LATEST_COMMIT_HASH" >> $GITHUB_OUTPUT + id: get-latest-commit-hash + - name: Cache gint/fxsdk installation + id: cache-gint + uses: actions/cache@v3 + with: + path: | + ~/.local/*/* + !~/.local/share/containers + key: ${{ runner.os }}-gint-${{ steps.get-latest-commit-hash.outputs.LATEST_COMMIT_HASH }} + - name: Install gint/fxsdk + if: steps.cache-gint.outputs.cache-hit != 'true' + env: + URL: "https://gitea.planet-casio.com/Lephenixnoir/GiteaPC/archive/master.tar.gz" + run: | + export PATH="~/.local/bin:$PATH" + cd "$(mktemp -d)" + curl "$URL" -o giteapc-master.tar.gz + tar -xzf giteapc-master.tar.gz + cd giteapc + python3 giteapc.py install Lephenixnoir/GiteaPC -y + sudo apt-get install python3-pil libusb-1.0-0-dev libudev-dev libsdl2-dev libpng-dev libudisks2-dev libglib2.0-dev libmpfr-dev libmpc-dev libppl-dev -y + giteapc install Lephenixnoir/fxsdk:noudisks2 Lephenixnoir/sh-elf-binutils Lephenixnoir/sh-elf-gcc -y + giteapc install Lephenixnoir/OpenLibm Vhex-Kernel-Core/fxlibc Lephenixnoir/sh-elf-gcc -y + giteapc install Lephenixnoir/gint -y + - name: Add fxsdk to PATH + run: echo "~/.local/bin" >> $GITHUB_PATH + - run: make -j2 PLATFORM=simulator TARGET=fxcg + - id: 'auth' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' + - id: 'upload-directory' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/upload-cloud-storage@v0' + with: + path: 'output/release/simulator/fxcg/epsilon.g3a' + destination: 'upsilon-binfiles.appspot.com/dev/simulator/' + parent: false + - uses: actions/upload-artifact@master + with: + name: epsilon.g3a + path: output/release/simulator/fxcg/epsilon.g3a nintendo_3ds: if: github.event.inputs.trigger3DS == 'yes' || github.event.inputs.trigger3DS == '' runs-on: ubuntu-latest diff --git a/Makefile b/Makefile index c42ed081ab2..b3705e7f476 100644 --- a/Makefile +++ b/Makefile @@ -93,6 +93,7 @@ help: @echo " make PLATFORM=simulator TARGET=web" @echo " make PLATFORM=simulator TARGET=windows" @echo " make PLATFORM=simulator TARGET=3ds" + @echo " make PLATFORM=simulator TARGET=fxcg" .PHONY: doc doc: @@ -127,6 +128,7 @@ ifndef USE_LIBA endif ifeq ($(USE_LIBA),0) include liba/Makefile.bridge +include libaxx/Makefile.bridge else SFLAGS += -ffreestanding -nostdinc -nostdlib include liba/Makefile diff --git a/README.md b/README.md index 4135856a154..f9fbad9f88b 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,7 @@ git checkout upsilon-dev ```bash make MODEL=n0100 clean -make MODEL=n0100 EPSILON_I18N=en OMEGA_USERNAME="{Your name, max 15 characters}" -j4 +make MODEL=n0100 EPSILON_I18N=en OMEGA_USERNAME="{Your name, max 15 characters}" -j(nproc) ``` Now, run either: @@ -280,7 +280,7 @@ to directly flash the calculator after pressing simultaneously `reset` and `6` b or: ```bash -make MODEL=n0100 OMEGA_USERNAME="" binpack -j4 +make MODEL=n0100 OMEGA_USERNAME="" binpack -j(nproc) ``` to make binpack which you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). Binpacks are a great way to share a custom build of Upsilonto friends. @@ -301,7 +301,7 @@ Then, build with: ```bash make clean -make OMEGA_USERNAME="{Your name, max 15 characters}" -j4 +make OMEGA_USERNAME="{Your name, max 15 characters}" -j(nproc) ``` Now, run either: @@ -317,7 +317,7 @@ to directly flash the calculator into the current slot, or thought bootloader's or: ```bash -make OMEGA_USERNAME="" binpack -j4 +make OMEGA_USERNAME="" binpack -j(nproc) ``` to make binpack which you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0110/). You'll find them at `output/release/device/bootloader/`. Binpacks are a great way to share a custom build of Upsilon to friends. @@ -330,7 +330,7 @@ to make binpack which you can flash to the calculator from [Ti-planet's webDFU]( ```bash make MODEL=n0110 clean -make MODEL=n0110 OMEGA_USERNAME="{Your name, max 15 characters}" -j4 +make MODEL=n0110 OMEGA_USERNAME="{Your name, max 15 characters}" -j(nproc) ``` Now, run either: @@ -346,7 +346,7 @@ to directly flash the calculator after pressing simultaneously `reset` and `6` b or: ```bash -make MODEL=n0110 OMEGA_USERNAME="" binpack -j4 +make MODEL=n0110 OMEGA_USERNAME="" binpack -j(nproc) ``` to make binpack which you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0110/). You'll find them at `output/release/device/bootloader/`. Binpacks are a great way to share a custom build of Upsilon to friends. @@ -400,7 +400,7 @@ Then, compile Upsilon : ```bash make clean -make PLATFORM=simulator TARGET=web OMEGA_USERNAME="{Your name, max 15 characters}" -j4 +make PLATFORM=simulator TARGET=web OMEGA_USERNAME="{Your name, max 15 characters}" -j$(nproc) ``` The simulator is now in `output/release/simulator/web/simulator.zip` @@ -416,8 +416,8 @@ You need devkitPro and devkitARM installed and in your path (instructions [here] ```bash git clone --recursive https://github.com/UpsilonNumworks/Upsilon.git cd Upsilon -git checkout --recursive upsilon-dev -make PLATFORM=simulator TARGET=3ds -j +git checkout upsilon-dev +make PLATFORM=simulator TARGET=3ds -j(nproc) ``` You can then put epsilon.3dsx on a SD card to run it from the HBC or use 3dslink to launch it over the network: @@ -430,6 +430,22 @@ You can then put epsilon.3dsx on a SD card to run it from the HBC or use 3dslink
    +
    + Casio fx-CG-series Port + +First, install gint and fxsdk along with a cross compiler for the calculator. There are instructions for this (in French, but Google Translate works well enough) [here](https://www.planet-casio.com/Fr/forums/topic16614-last-giteapc-installer-et-mettre-a-jour-automatiquement-des-projets-gitea.html). + +Next: +```bash +git clone --recursive https://github.com/UpsilonNumworks/Upsilon.git +cd Omega +git checkout upsilon-dev +make PLATFORM=simulator TARGET=fxcg -j$(nproc) +``` +Then copy the file at `./output/release/simulator/fxcg/epsilon.g3a` to the calculator over USB. + +
    + Important: Don't forget the `--recursive` tag, because Upsilon relies on submodules. Also, you can change the number of processes that run in parallel during the build by changing the value of the `-j` flag. Don't forget to put your pseudo instead of `{your pseudo, max 15 char}`. If you don't want one, just remove the `OMEGA_USERNAME=""` argument. @@ -476,7 +492,8 @@ You can try Epsilon straight from your browser in the [online simulator](https:/ NumWorks is a registered trademark of NumWorks SAS, 24 Rue Godot de Mauroy, 75009 Paris, France. Nintendo and Nintendo 3DS are registered trademarks of Nintendo of America Inc, 4600 150th Ave NE, Redmond, WA 98052, USA. -NumWorks SAS and Nintendo of America Inc aren't associated in any shape or form with this project. +Casio is a registered trademark of Casio Computer Co., Ltd. CORPORATION JAPAN 6-2, Hon-machi 1-chome Shibuya-ku, Tokyo JAPAN 151-8543. +NumWorks SAS, Nintendo of America Inc and Casio aren't associated in any shape or form with this project. - NumWorks Epsilon is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). - Omega is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index 5ead158750a..b5a22857c9e 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -1,5 +1,11 @@ PythonPound = "Kommentar" PythonPercent = "Modulo" +PythonColon = "Doppelpunkt" +PythonSemicon = "Semikolon" +PythonExclamationMark = "Ausrufezeichen" +PythonLessThan = "Kleiner als" +PythonGreaterThan = "Größer als" +PythonQuestionMark = "Fragezeichen" Python1J = "Imaginäres i" PythonLF = "Zeilenvorschub" PythonTab = "Tabulator" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index 62a9400577f..dd3936409d8 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -1,5 +1,11 @@ PythonPound = "Comment" PythonPercent = "Modulo" +PythonColon = "Colon" +PythonSemicon = "Semicolon" +PythonExclamationMark = "Exclamation mark" +PythonLessThan = "Less than" +PythonGreaterThan = "Greater than" +PythonQuestionMark = "Question mark" Python1J = "Imaginary i" PythonLF = "Line feed" PythonTab = "Tabulation" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index 883b47cf481..62d5bf2c991 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -1,5 +1,11 @@ PythonPound = "Comment" PythonPercent = "Modulo" +PythonColon = "Colon" +PythonSemicon = "Semicolon" +PythonExclamationMark = "Exclamation mark" +PythonLessThan = "Less than" +PythonGreaterThan = "Greater than" +PythonQuestionMark = "Question mark" Python1J = "Imaginary i" PythonLF = "Line feed" PythonTab = "Tabulation" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index 99377015773..3185c5ce5a8 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -1,5 +1,11 @@ PythonPound = "Commentaire" PythonPercent = "Modulo" +PythonColon = "Deux-points" +PythonSemicon = "Point-virgule" +PythonExclamationMark = "Point d'exclamation" +PythonLessThan = "Inférieur à" +PythonGreaterThan = "Supérieur à" +PythonQuestionMark = "Point d'interrogation" Python1J = "i complexe" PythonLF = "Saut à la ligne" PythonTab = "Tabulation" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index 6242e4dc35a..7dd162659e9 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -1,5 +1,11 @@ PythonPound = "Megjegyzés" PythonPercent = "Modulo" +PythonColon = "Kettőspont" +PythonSemicon = "Pontosvessző" +PythonExclamationMark = "Felkiáltójel" +PythonLessThan = "Kisebb mint" +PythonGreaterThan = "Nagyobb mint" +PythonQuestionMark = "Kérdőjel" Python1J = "Képzeletbeli i" PythonLF = "Enter" PythonTab = "Táblázat" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index d8a24f355b9..cb17c6bb21b 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -1,5 +1,11 @@ PythonPound = "Commento" PythonPercent = "Modulo" +PythonColon = "Due punti" +PythonSemicon = "Punto e virgola" +PythonExclamationMark = "Punto esclamativo" +PythonLessThan = "Minore di" +PythonGreaterThan = "Maggiore di" +PythonQuestionMark = "Punto interrogativo" Python1J = "Unità immaginaria" PythonLF = "Nuova riga" PythonTab = "Tabulazione" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index 876073d95b9..4ba8d9906c5 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -1,5 +1,11 @@ PythonPound = "Opmerkingen" PythonPercent = "Modulo" +PythonColon = "Dubbele punt" +PythonSemicon = "Puntkomma" +PythonExclamationMark = "Uitroepteken" +PythonLessThan = "Kleiner dan" +PythonGreaterThan = "Groter dan" +PythonQuestionMark = "Vraagteken" Python1J = "Imaginaire i" PythonLF = "Nieuwe regel" PythonTab = "Tabulatie" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index 155aa785f56..bc740acdf7f 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -1,5 +1,11 @@ PythonPound = "Comentário" PythonPercent = "Módulo" +PythonColon = "Dois pontos" +PythonSemicon = "Ponto e vírgula" +PythonExclamationMark = "Ponto de exclamação" +PythonLessThan = "Menor que" +PythonGreaterThan = "Maior que" +PythonQuestionMark = "Ponto de interrogação" Python1J = "i Complexo" PythonLF = "Nova linha" PythonTab = "Tabulação" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index ca6e19c4576..09d292f7c25 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -1,6 +1,12 @@ PythonCommandAmpersand = "&" PythonCommandLF = "\\n" PythonCommandPercent = "%" +PythonCommandColon = ":" +PythonCommandSemicon = ";" +PythonCommandExclamationMark = "!" +PythonCommandLessThan = "<" +PythonCommandGreaterThan = ">" +PythonCommandQuestionMark = "?" PythonCommandPound = "#" PythonCommandSingleQuote = "'x'" PythonCommandSymbolExp = "^" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 1f75378d332..f8a74902223 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -503,6 +503,15 @@ const ToolboxMessageTree modulesChildren[] = { const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPound, I18n::Message::PythonPound, false), + #ifdef _FXCG + // There is no question mark button on the fx-CG calculators + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColon, I18n::Message::PythonColon, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSemicon, I18n::Message::PythonSemicon, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandExclamationMark, I18n::Message::PythonExclamationMark, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandLessThan, I18n::Message::PythonLessThan, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGreaterThan, I18n::Message::PythonGreaterThan, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandQuestionMark, I18n::Message::PythonQuestionMark, false), + #endif ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPercent, I18n::Message::PythonPercent, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommand1J, I18n::Message::Python1J, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandLF, I18n::Message::PythonLF, false), diff --git a/apps/graph/graph/graph_view.h b/apps/graph/graph/graph_view.h index ded212f6db3..87688114037 100644 --- a/apps/graph/graph/graph_view.h +++ b/apps/graph/graph/graph_view.h @@ -18,7 +18,13 @@ class GraphView : public Shared::FunctionGraphView { * 10.0938275501223 which are hopefully rare enough. * TODO: The drawCurve algorithm should use the derivative function to know * how fast the function moves... */ + #ifndef _FXCG static constexpr float k_graphStepDenominator = 10.0938275501223f; + #else + // This value rounded down has to be a factor of the horizontal resolution / 2 + // On the Casio calculator the resolution is 396 pixels, so 11 is close but works + static constexpr float k_graphStepDenominator = 11.0938275501223f; + #endif GraphView(Shared::InteractiveCurveViewRange * graphRange, Shared::CurveViewCursor * cursor, Shared::BannerView * bannerView, Shared::CursorView * cursorView); diff --git a/apps/home/controller.cpp b/apps/home/controller.cpp index da3a147d43d..1936b47eff7 100644 --- a/apps/home/controller.cpp +++ b/apps/home/controller.cpp @@ -276,7 +276,7 @@ void Controller::tableViewDidChangeSelection(SelectableTableView * t, int previo * (so the previous one is always visible). */ int appIndex = (t->selectedColumn()+t->selectedRow()*k_numberOfColumns)+1; if (appIndex >= this->numberOfIcons()+1) { - t->selectCellAtLocation((this->numberOfIcons()%3)-1, (this->numberOfIcons() / k_numberOfColumns)); + t->selectCellAtLocation((this->numberOfIcons()%k_numberOfColumns)-1, (this->numberOfIcons() / k_numberOfColumns)); } } diff --git a/apps/home/controller.h b/apps/home/controller.h index 3301fac78fb..a330a75641c 100644 --- a/apps/home/controller.h +++ b/apps/home/controller.h @@ -47,10 +47,19 @@ class Controller : public ViewController, public SimpleTableViewDataSource, publ static constexpr KDCoordinate k_sideMargin = 4; static constexpr KDCoordinate k_bottomMargin = 14; static constexpr KDCoordinate k_indicatorMargin = 61; + + #ifndef _FXCG static constexpr int k_numberOfColumns = 3; - static constexpr int k_maxNumberOfCells = 16; static constexpr int k_cellHeight = 104; static constexpr int k_cellWidth = 104; + #else + // A different screen resolution so different dimensions + static constexpr int k_numberOfColumns = 4; + static constexpr int k_cellHeight = 96; + static constexpr int k_cellWidth = 97; + #endif + + static constexpr int k_maxNumberOfCells = 16; ContentView m_view; AppCell m_cells[k_maxNumberOfCells]; App * m_app; diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index 30af1e1bd32..1d4900c9785 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -854,6 +854,10 @@ const ToolboxMessageTree Physics[] = { const ToolboxMessageTree menu[] = { + #ifdef _FXCG + // There is no factorial button on the fx-CG calculators + ToolboxMessageTree::Leaf(I18n::Message::FactorialCommandWithArg, I18n::Message::Factorial, false, I18n::Message::FactorialCommand), + #endif ToolboxMessageTree::Leaf(I18n::Message::AbsCommandWithArg, I18n::Message::AbsoluteValue), ToolboxMessageTree::Leaf(I18n::Message::RootCommandWithArg, I18n::Message::NthRoot), ToolboxMessageTree::Leaf(I18n::Message::LogCommandWithArg, I18n::Message::BasedLogarithm), diff --git a/apps/probability/app.h b/apps/probability/app.h index d93905c4690..2e8e3fddfd4 100644 --- a/apps/probability/app.h +++ b/apps/probability/app.h @@ -57,7 +57,7 @@ class App : public Shared::TextFieldDelegateApp { void deleteDistributionAndCalculation(); void initializeDistributionAndCalculation(); -#if __EMSCRIPTEN__ +#if (defined __EMSCRIPTEN__) || (defined _FXCG) constexpr static int k_distributionAlignments[] = {alignof(BinomialDistribution),alignof(ExponentialDistribution), alignof(NormalDistribution), alignof(PoissonDistribution), alignof(UniformDistribution), 0}; constexpr static size_t k_distributionAlignment = max(k_distributionAlignments); constexpr static int k_calculationAlignments[] = {alignof(LeftIntegralCalculation),alignof(FiniteIntegralCalculation), alignof(RightIntegralCalculation), 0}; diff --git a/apps/settings/sub_menu/about_controller.cpp b/apps/settings/sub_menu/about_controller.cpp index a022c48f0e4..dcae3211a61 100644 --- a/apps/settings/sub_menu/about_controller.cpp +++ b/apps/settings/sub_menu/about_controller.cpp @@ -1,5 +1,6 @@ #include "about_controller.h" #include "../../../python/src/py/mpconfig.h" +#include "poincare/division.h" #include #include #include diff --git a/apps/settings/sub_menu/datetime_controller.cpp b/apps/settings/sub_menu/datetime_controller.cpp index 1f9e801b922..35fb8c99455 100644 --- a/apps/settings/sub_menu/datetime_controller.cpp +++ b/apps/settings/sub_menu/datetime_controller.cpp @@ -30,7 +30,10 @@ bool DateTimeController::handleEvent(Ion::Events::Event event) { if (selectedRow() == 0) { clockEnabled = !clockEnabled; if (clockEnabled) { + #ifndef _FXCG + // This doesn't apply on Casio calculators Container::activeApp()->displayWarning(I18n::Message::RTCWarning1, I18n::Message::RTCWarning2); + #endif } Ion::RTC::setMode(clockEnabled ? Ion::RTC::Mode::HSE : Ion::RTC::Mode::Disabled); } diff --git a/apps/shared.hu.i18n b/apps/shared.hu.i18n index a916a2e7629..91e45f3d154 100644 --- a/apps/shared.hu.i18n +++ b/apps/shared.hu.i18n @@ -121,3 +121,4 @@ CatalyticActivityDimension = "Katalitikus aktivitás" SurfaceDimension = "Felület" VolumeDimension = "Hangerő" SpeedDimension = "Sebesség" +Factorial = "Faktorál" diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n index 0e078c3011f..d1932fbd34b 100644 --- a/apps/shared.it.i18n +++ b/apps/shared.it.i18n @@ -121,3 +121,4 @@ CatalyticActivityDimension = "Attività catalitica" SurfaceDimension = "Superficie" VolumeDimension = "Volume" SpeedDimension = "Velocità" +Factorial = "Fattoriale" diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index d8a4b076f31..e63e98605c5 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -121,3 +121,4 @@ CatalyticActivityDimension = "Katalytische activiteit" SurfaceDimension = "Oppervlak" VolumeDimension = "Volume" SpeedDimension = "Snelheid" +Factorial = "Faculteit" diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index cad61ea9b97..f38ad608540 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -125,6 +125,8 @@ DotCommandWithArg = "dot(u,v)" E = "e" Equal = "=" FactorCommandWithArg = "factor(n)" +FactorialCommand = "!" +FactorialCommandWithArg = "n!" FccId = "FCC ID" FloorCommandWithArg = "floor(x)" FracCommandWithArg = "frac(x)" diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index e127feed520..39c705ecd08 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -270,7 +270,7 @@ void ContinuousFunction::rangeForDisplay(float * xMin, float * xMax, float * yMi } if (!basedOnCostlyAlgorithms(context)) { - Zoom::ValueAtAbscissa evaluation = [](float x, Context * context, const void * auxiliary) { + Zoom::ValueAtAbscissa evaluation = [](float x, Context * context, const void * auxiliary) -> float { /* When evaluating sin(x)/x close to zero using the standard sine function, * one can detect small variations, while the cardinal sine is supposed to be * locally monotonous. To smooth our such variations, we round the result of diff --git a/apps/shared/continuous_function_cache.cpp b/apps/shared/continuous_function_cache.cpp index 00a8cd687d3..5ff498052b8 100644 --- a/apps/shared/continuous_function_cache.cpp +++ b/apps/shared/continuous_function_cache.cpp @@ -65,7 +65,11 @@ void ContinuousFunctionCache::ComputeNonCartesianSteps(float * tStep, float * tC const int numberOfWholeSteps = static_cast(Graph::GraphView::k_graphStepDenominator); static_assert(numberOfCacheablePoints % numberOfWholeSteps == 0, "numberOfCacheablePoints should be a multiple of numberOfWholeSteps for optimal caching"); const int multiple = numberOfCacheablePoints / numberOfWholeSteps; + // Ignore this on Casio calculators for now, as the screen resolution breaks this + // TODO: fix this. if it's possible + #ifndef _FXCG static_assert(multiple && !(multiple & (multiple - 1)), "multiple should be a power of 2 for optimal caching"); + #endif /* Define cacheStep such that every whole graph steps are equally divided * For instance, with : * graphStepDenominator = 10.1 diff --git a/apps/shared/store_controller.h b/apps/shared/store_controller.h index 1083f8201f5..d0eebb316cb 100644 --- a/apps/shared/store_controller.h +++ b/apps/shared/store_controller.h @@ -49,7 +49,12 @@ class StoreController : public EditableCellTableViewController, public ButtonRow static constexpr KDCoordinate k_cellWidth = Poincare::PrintFloat::glyphLengthForFloatWithPrecision(Poincare::Preferences::LargeNumberOfSignificantDigits) * 7 + 2*Metric::CellMargin + Metric::TableSeparatorThickness; // KDFont::SmallFont->glyphSize().width() = 7 constexpr static int k_maxNumberOfEditableCells = (Ion::Display::Width/k_cellWidth+2) * ((Ion::Display::Height - Metric::TitleBarHeight - Metric::TabHeight)/k_cellHeight+2); + #ifndef _FXCG constexpr static int k_numberOfTitleCells = 4; + #else + // This is different here due to the changed screen resolution + constexpr static int k_numberOfTitleCells = 5; + #endif static constexpr int k_titleCellType = 0; static constexpr int k_editableCellType = 1; diff --git a/apps/solver/solutions_controller.h b/apps/solver/solutions_controller.h index 4fac929a191..2bf90d04bf9 100644 --- a/apps/solver/solutions_controller.h +++ b/apps/solver/solutions_controller.h @@ -87,7 +87,7 @@ class SolutionsController : public ViewController, public AlternateEmptyViewDefa // Number of cells constexpr static int k_maxNumberOfVisibleCells = (Ion::Display::Height - 3 * Metric::TitleBarHeight) / k_defaultCellHeight + 1; // When displaying approximate solutions for cos(x) = 0 between 0 and 1800 and scrolling down - static_assert(k_maxNumberOfVisibleCells == 10, "k_maxNumberOfVisibleCells has changed"); //This assert is just for information purposes + // static_assert(k_maxNumberOfVisibleCells == 10, "k_maxNumberOfVisibleCells has changed"); //This assert is just for information purposes static_assert(k_maxNumberOfVisibleCells <= EquationStore::k_maxNumberOfSolutions + Poincare::Expression::k_maxNumberOfVariables, "We can reduce the number of cells in Solver:SolutionsController."); constexpr static int k_maxNumberOfSymbols = EquationStore::k_maxNumberOfSolutions + Poincare::Expression::k_maxNumberOfVariables; constexpr static int k_numberOfSymbolCells = k_maxNumberOfVisibleCells < k_maxNumberOfSymbols ? k_maxNumberOfVisibleCells : k_maxNumberOfSymbols; diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index 02cc172e35c..2582f6d1bf4 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -511,3 +511,4 @@ MagneticFluxQuantumTag = "Magnetisches Fluss-Quantum" ConductanceQuantumTag = "Leitwertquantum" CirculationQuantumTag = "Auflage-Quantum" MatricesAndVectors = "Matrizen und Vektoren" +Factorial = "Fakultät" diff --git a/apps/toolbox.en.i18n b/apps/toolbox.en.i18n index fd5aaafc814..e6781e868c8 100644 --- a/apps/toolbox.en.i18n +++ b/apps/toolbox.en.i18n @@ -511,3 +511,4 @@ HartreeConstantTag = "Hartree Constant" MagneticFluxQuantumTag = "Magnetic Flux Quantum" ConductanceQuantumTag = "Conductance Quantum" CirculationQuantumTag = "Circulation Quantum" +Factorial = "Factorial" \ No newline at end of file diff --git a/apps/toolbox.es.i18n b/apps/toolbox.es.i18n index 841d37d7e7c..ddb8dcf1c55 100644 --- a/apps/toolbox.es.i18n +++ b/apps/toolbox.es.i18n @@ -511,3 +511,4 @@ MagneticFluxQuantumTag = "Flujo Magnético Cuántico" ConductanceQuantumTag = "Conductancia Quantum" CirculationQuantumTag = "Circulación Quantum" MatricesAndVectors = "Matrices y vectores" +Factorial = "Factorial" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index 283f2bc28ef..ac0f40b36d3 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -515,3 +515,4 @@ MagneticFluxQuantumTag = "Quantum de Flux Magnétique" ConductanceQuantumTag = "Quantum de Conductance" CirculationQuantumTag = "Quantum de Circulation" MatricesAndVectors = "Matrices et vecteurs" +Factorial = "Factorielle" diff --git a/apps/toolbox.pt.i18n b/apps/toolbox.pt.i18n index 219680ae90e..8ba7d55f8cd 100644 --- a/apps/toolbox.pt.i18n +++ b/apps/toolbox.pt.i18n @@ -511,3 +511,4 @@ MagneticFluxQuantumTag = "Fluxo Magnético Quântico" ConductanceQuantumTag = "Quantum de Conduta" CirculationQuantumTag = "Quantum de Circulação" MatricesAndVectors = "Matrizes e vetores" +Factorial = "Fatorial" diff --git a/build/platform.simulator.fxcg.mak b/build/platform.simulator.fxcg.mak new file mode 100644 index 00000000000..fc30c12e842 --- /dev/null +++ b/build/platform.simulator.fxcg.mak @@ -0,0 +1,11 @@ +TOOLCHAIN = sh-elf-gcc +EXE = elf + +EPSILON_TELEMETRY ?= 0 + +HANDY_TARGETS_EXTENSIONS = g3a bin + +USE_LIBA = 0 +POINCARE_TREE_LOG = 0 + +SFLAGS := $(filter-out -fPIE, $(SFLAGS)) diff --git a/build/targets.simulator.fxcg.mak b/build/targets.simulator.fxcg.mak new file mode 100644 index 00000000000..ec26dcc8174 --- /dev/null +++ b/build/targets.simulator.fxcg.mak @@ -0,0 +1,5 @@ +$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf + $(OBJCOPY) -O binary -R .bss -R .gint_bss $< $@ + +$(BUILD_DIR)/%.g3a: $(BUILD_DIR)/%.bin ion/src/simulator/fxcg/assets/icon-uns.png ion/src/simulator/fxcg/assets/icon-sel.png + $(FXGXA) --g3a --icon-uns=ion/src/simulator/fxcg/assets/icon-uns.png --icon-sel=ion/src/simulator/fxcg/assets/icon-sel.png -n Upsilon $< -o $@ diff --git a/build/toolchain.sh-elf-gcc.mak b/build/toolchain.sh-elf-gcc.mak new file mode 100644 index 00000000000..54290ffcec9 --- /dev/null +++ b/build/toolchain.sh-elf-gcc.mak @@ -0,0 +1,10 @@ +CC = sh-elf-gcc +CXX = sh-elf-g++ +LD = sh-elf-g++ +GDB = gdb +OBJCOPY = sh-elf-objcopy +SIZE = sh-elf-size +AS = sh-elf-as +FXGXA = fxgxa + +SFLAGS += -D_FXCG -D_BIG_ENDIAN diff --git a/escher/src/icon_view.cpp b/escher/src/icon_view.cpp index 7a5453d2a4c..1c1db37fecc 100644 --- a/escher/src/icon_view.cpp +++ b/escher/src/icon_view.cpp @@ -1,6 +1,7 @@ #include extern "C" { #include +#include } #include #include @@ -43,6 +44,13 @@ void IconView::drawRect(KDContext * ctx, KDRect rect) const { iconBufferSize * sizeof(KDColor) ); + // If we are on a big-endian CPU, we need to swap the bytes + #if _BIG_ENDIAN + for (uint32_t i = 0; i < iconBufferSize; i++) { + pixelBuffer[i] = KDColor::RGB16(__builtin_bswap16(pixelBuffer[i])); + } + #endif + //We push the first 6 lines of the image so that they are truncated on the sides ctx->fillRectWithPixels(KDRect(6, 0, m_frame.width()-12, 1),pixelBuffer+6, nullptr); ctx->fillRectWithPixels(KDRect(4, 1, m_frame.width()-8, 1),pixelBuffer+4+55, nullptr); diff --git a/escher/src/image_view.cpp b/escher/src/image_view.cpp index 7cb4ee39e1c..5062ef3f7a9 100644 --- a/escher/src/image_view.cpp +++ b/escher/src/image_view.cpp @@ -1,6 +1,7 @@ #include extern "C" { #include +#include } #include @@ -50,6 +51,13 @@ void ImageView::drawRect(KDContext * ctx, KDRect rect) const { pixelBufferSize * sizeof(KDColor) ); + // If we are on a big-endian CPU, we need to swap the bytes + #if _BIG_ENDIAN + for (uint32_t i = 0; i < pixelBufferSize; i++) { + pixelBuffer[i] = KDColor::RGB16(__builtin_bswap16(pixelBuffer[i])); + } + #endif + ctx->fillRectWithPixels(bounds(), pixelBuffer, nullptr); } diff --git a/ion/include/ion/display.h b/ion/include/ion/display.h index 1b7f32508c5..0abd90c1673 100644 --- a/ion/include/ion/display.h +++ b/ion/include/ion/display.h @@ -23,8 +23,15 @@ void pullRect(KDRect r, KDColor * pixels); bool waitForVBlank(); +#ifndef _FXCG constexpr int Width = 320; constexpr int Height = 240; +#else +constexpr int Width = 396; +constexpr int Height = 224; +#endif + +// TODO: Adjust this on the Casio calculator constexpr int WidthInTenthOfMillimeter = 576; constexpr int HeightInTenthOfMillimeter = 432; diff --git a/ion/include/ion/internal_storage.h b/ion/include/ion/internal_storage.h index 257d243ce3c..fcad00c90d8 100644 --- a/ion/include/ion/internal_storage.h +++ b/ion/include/ion/internal_storage.h @@ -195,7 +195,7 @@ class StorageDelegate { class StorageHelper { public: static uint16_t unalignedShort(char * address) { -#if __EMSCRIPTEN__ +#if (defined __EMSCRIPTEN__) || (defined _FXCG) uint8_t f1 = *(address); uint8_t f2 = *(address+1); uint16_t f = (uint16_t)f1 + (((uint16_t)f2)<<8); @@ -205,7 +205,7 @@ class StorageHelper { #endif } static void writeUnalignedShort(uint16_t value, char * address) { -#if __EMSCRIPTEN__ +#if (defined __EMSCRIPTEN__) || (defined _FXCG) *((uint8_t *)address) = (uint8_t)(value & ((1 << 8) - 1)); *((uint8_t *)address+1) = (uint8_t)(value >> 8); #else diff --git a/ion/include/ion/keyboard.h b/ion/include/ion/keyboard.h index ba056ffd2a5..7d448991c7e 100644 --- a/ion/include/ion/keyboard.h +++ b/ion/include/ion/keyboard.h @@ -26,6 +26,12 @@ constexpr Key ValidKeys[] = { constexpr int NumberOfKeys = 54; constexpr int NumberOfValidKeys = 46; +enum class ModSimState : uint8_t { + None, + ForceOn, + ForceOff, +}; + class State { public: constexpr State(uint64_t s = 0) : @@ -50,8 +56,25 @@ class State { void clearKey(Key k) { m_bitField &= ~((uint64_t)1 << (uint8_t)k); } + void setSimulatedShift(ModSimState s) { + m_simulateShiftState = s; + } + ModSimState simulatedShift() const { + return m_simulateShiftState; + } + void setSimulatedAlpha(ModSimState s) { + m_simulateAlphaState = s; + } + ModSimState simulatedAlpha() const { + return m_simulateAlphaState; + } private: uint64_t m_bitField; + + // Simulated key states + // These override the real key states and are used to map keys to keys under modifiers + ModSimState m_simulateShiftState = ModSimState::None; + ModSimState m_simulateAlphaState = ModSimState::None; }; State scan(); diff --git a/ion/src/shared/events_keyboard.cpp b/ion/src/shared/events_keyboard.cpp index 73038b619cc..6d281c482a6 100644 --- a/ion/src/shared/events_keyboard.cpp +++ b/ion/src/shared/events_keyboard.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -84,6 +85,16 @@ static inline Event innerGetEvent(int * timeout) { Keyboard::Key key = (Keyboard::Key)(63-__builtin_clzll(keysSeenTransitioningFromUpToDown)); bool shift = isShiftActive() || state.keyDown(Keyboard::Key::Shift); bool alpha = isAlphaActive() || state.keyDown(Keyboard::Key::Alpha); + + // Allow the detected states to be overriden by the simulated states + // This is used for key mapping + if (state.simulatedShift() != Keyboard::ModSimState::None) { + shift = state.simulatedShift() == Keyboard::ModSimState::ForceOn; + } + if (state.simulatedAlpha() != Keyboard::ModSimState::None) { + alpha = state.simulatedAlpha() == Keyboard::ModSimState::ForceOn; + } + bool lock = isLockActive(); if ( key == Keyboard::Key::Left diff --git a/ion/src/simulator/external/config.fxcg.mak b/ion/src/simulator/external/config.fxcg.mak new file mode 100644 index 00000000000..5c9e873ab47 --- /dev/null +++ b/ion/src/simulator/external/config.fxcg.mak @@ -0,0 +1,2 @@ +undefine sdl_src +undefine ion_simulator_sdl_src diff --git a/ion/src/simulator/fxcg/Makefile b/ion/src/simulator/fxcg/Makefile new file mode 100644 index 00000000000..02a9ed71776 --- /dev/null +++ b/ion/src/simulator/fxcg/Makefile @@ -0,0 +1,66 @@ + +ion_src += $(addprefix ion/src/simulator/fxcg/, \ + main.cpp \ + clipboard.cpp \ + display.cpp \ + framebuffer.cpp \ + telemetry_init.cpp \ + keyboard.cpp \ + events_keyboard.cpp \ + events.cpp \ + timing.cpp \ + console.cpp \ + backlight.cpp \ + power.cpp \ + menuHandler.cpp \ +) + +liba_src += $(addprefix liba/src/, \ + strlcat.c \ + strlcpy.c \ +) + +ion_src += ion/src/shared/collect_registers.cpp + +sdl_simu_needs_to_be_removed += $(addprefix ion/src/simulator/shared/, \ + main.cpp \ + clipboard.cpp \ + display.cpp \ + framebuffer.cpp \ + keyboard.cpp \ + events_keyboard.cpp \ + events_platform.cpp \ + events.cpp \ + layout.cpp \ + actions.cpp \ + window.cpp \ + timing.cpp \ + console.cpp \ +) + +sdl_simu_needs_to_be_removed += $(addprefix ion/src/shared/dummy/, \ + backlight.cpp \ + power.cpp \ +) + +#sdl_simu_needs_to_be_removed += $(addprefix ion/src/simulator/shared/dummy/, \ +# display.cpp \ +# led.cpp \ +# usb.cpp \ +# battery.cpp \ +# store_script.cpp \ +#) + +# Remove the dummy diaplay (re-implemented) and the SDL simulator stuff. +ion_src := $(filter-out $(sdl_simu_needs_to_be_removed),$(ion_src)) + +SFLAGS := $(filter-out -Iion/src/simulator/external/sdl/include,$(SFLAGS)) + +SFLAGS += -DFXCG50 -DTARGET_FXCG50 -m4-nofpu -mb -ffreestanding -nostdlib -Wa,--dsp -fstrict-volatile-bitfields -g -Os +LDFLAGS += -nostdlib -Wl,--no-warn-rwx-segments -lgint-cg -lc -lgint-cg -lc -lgcc -lopenlibm -lstdc++ -lgcc + +ifdef FASTLOAD + LDFLAGS += -T fxcg50_fastload.ld +else + LDFLAGS += -T fxcg50.ld +endif diff --git a/ion/src/simulator/fxcg/assets/icon-sel.png b/ion/src/simulator/fxcg/assets/icon-sel.png new file mode 100644 index 0000000000000000000000000000000000000000..a016c36b39d406e9d21390432635d2daceff1552 GIT binary patch literal 3588 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 KMNUMnLSTYlXV&ci literal 0 HcmV?d00001 diff --git a/ion/src/simulator/fxcg/assets/icon-uns.png b/ion/src/simulator/fxcg/assets/icon-uns.png new file mode 100644 index 0000000000000000000000000000000000000000..c2f71847222784736677a5fa53f067fc9611d68d GIT binary patch literal 1750 zcmV;{1}XW8P) z62R|084ld5RI=Bjg;0K7Si<6>@l=#1-&?3fz<9@um_qclp z`0Hh+njLXK)hr{OObFTBiV%855S{473`Qkp>a(Jlg6H_UhmWs!F`ngp?$6PqZd&xywjx*+i**JYRAI2RrE^UR2mNzV~Sh=pPs%Wcd` zhDtn1992|}^8Hzt70z3n)pC_J@5x^n&TGp{T&FpNBo?p;5dvgXv4Juy#Aww>F_EVI zgol67@r&e=$+ZDSj(Jp|LUR1zfAG6ovoJC4CWR9~@Wr-2hJny7(5Tt=_pxm^PJqBO zaHX~Ul{zr}NqViNMUQ~KZQ$a%rOA81&OpJhmGjI+AuiKc5Z3xE{%)9(Zk-%SbUM6KAM~Av$7KD2bw!?0- z7W^Ag$WoSrQJnwjIzll8&ZRKIF@IHOEXR^cmi-Z zR~U8$3C9#ToGV!H1aN7_X}rUyXFUi;RU}hsrdVN;sWgI771OgG;~nntg&>(NOs~qs zl_a9EI9m#9A#35zhKO*mC4yI9dIre4lK@_M_oAWKzjOAn?4AG!ynpU0=G?bT3K7BQ`3G_=To9bN zn*#_C&veKi>-hF5kXdy3|v`_z~`DPy4|(t0}!zVCgS{QCI5l>ts0uziF@(Lln?aPXpm}!?$yBCs zfhSZM-kXkf`d27lU;JHP^F(g(JNZyQUO06>4(zu<05A!luWL76e(D&;@9WXv3%!Ym zaQSa{<627Awb$!k;as?U6;GTP#b9rjMiB%dfTQ>u0Eeh>TCC9F2M6Q_8i= zWziSJYj1r5uP>;XhtMTNXiMYT(;^~*+aJSwUrr+$Pc$CzQm$<@u0=o2zVRW>efKjI zH30~1zuAQ`i#@I+@$GLn@b(vf;#N$p>1!!l*5RdGyOP8e6(3Jd;j?dk#PerP;OWQ5 zvh4u^aQ?~yewc9~QG-UZZ4+M3wW|UztN7^i?*Re`1k!8f!XM7MfxRWbjkxxPs!C`- zM4f);cT+1Rp~_e?=-SmrpuV$dz$Kld-;_jdDt#Iv1($s82XYb*< zf?YN6t#<9QZ%a!Px(7yJlBBz%Qrhh;P}15pik)!nk_~Q>&^<5$v!$tKp|)5puu2x= z6FxfJgNz)8#Vi$c?S#5>A6<3z9YIsGt!80c%n~wk7^A~I#(_^9@5ew-8`2R^?p~Gc zbnRlWTH9(Cw%uk$I^w}VPa7tV_ZNPL7P}KU98)m))f9gD^$M=tSVlDDhZ>LU z*gs?4wFfr)zM5VnM+Q1DKK2NnJ9DzKo2oc9ernHQ;0&C@z!^9L=P+;v&SBsToWsBw sI0NS}a0bp{;0&C@z!^9L=WJ2_2erpLmnTxXoB#j-07*qoM6N<$f^eoc-v9sr literal 0 HcmV?d00001 diff --git a/ion/src/simulator/fxcg/backlight.cpp b/ion/src/simulator/fxcg/backlight.cpp new file mode 100644 index 00000000000..18360bc0bfc --- /dev/null +++ b/ion/src/simulator/fxcg/backlight.cpp @@ -0,0 +1,67 @@ +#include + +#include +#include + +// From gint: +/* Interface with the controller */ +static volatile uint16_t *intf = (uint16_t *)0xb4000000; +/* Bit 4 of Port R controls the RS bit of the display driver */ +static volatile uint8_t *PRDR = (uint8_t *)0xa405013c; + +GINLINE static void select(uint16_t reg) +{ + /* Clear RS and write the register number */ + *PRDR &= ~0x10; + synco(); + *intf = reg; + synco(); + + /* Set RS back. We don't do this in read()/write() because the display + driver is optimized for consecutive GRAM access. LCD-transfers will + be faster when executing select() followed by several calls to + write(). (Although most applications should use the DMA instead.) */ + *PRDR |= 0x10; + synco(); +} + +// From Utilities addin: +// START OF POWER MANAGEMENT CODE +#define LCDC *(unsigned int*)(0xB4000000) +int getRawBacklightSubLevel() +{ + // Bdisp_DDRegisterSelect(0x5a1); + select(0x5a1); + return (LCDC & 0xFF) - 6; +} +void setRawBacklightSubLevel(int level) +{ + // Bdisp_DDRegisterSelect(0x5a1); + select(0x5a1); + LCDC = (level & 0xFF) + 6; +} +// END OF POWER MANAGEMENT CODE + +namespace Ion { +namespace Backlight { + +uint8_t brightness() { + return getRawBacklightSubLevel(); +} + +void setBrightness(uint8_t b) { + setRawBacklightSubLevel(b); +} + +void init() { +} + +bool isInitialized() { + return true; +} + +void shutdown() { +} + +} +} diff --git a/ion/src/simulator/fxcg/clipboard.cpp b/ion/src/simulator/fxcg/clipboard.cpp new file mode 100644 index 00000000000..c3ce45627c9 --- /dev/null +++ b/ion/src/simulator/fxcg/clipboard.cpp @@ -0,0 +1,18 @@ +#include +#include +#include + +namespace Ion { +namespace Clipboard { + +uint32_t localClipboardVersion; + +void write(const char * text) { +} + +const char * read() { + return nullptr; +} + +} +} diff --git a/ion/src/simulator/fxcg/console.cpp b/ion/src/simulator/fxcg/console.cpp new file mode 100644 index 00000000000..d8957c6e6ca --- /dev/null +++ b/ion/src/simulator/fxcg/console.cpp @@ -0,0 +1,24 @@ +#include +#include "main.h" +#include +#include + +namespace Ion { +namespace Console { + +char readChar() { + return 0; +} + +void writeChar(char c) { + // fxlibc conflicts with this + #undef putchar + KDIonContext::putchar(c); +} + +bool transmissionDone() { + return true; +} + +} +} diff --git a/ion/src/simulator/fxcg/display.cpp b/ion/src/simulator/fxcg/display.cpp new file mode 100644 index 00000000000..27e896beef9 --- /dev/null +++ b/ion/src/simulator/fxcg/display.cpp @@ -0,0 +1,30 @@ +#include "display.h" +#include "framebuffer.h" +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace Ion { +namespace Simulator { +namespace Display { + +void init() { +} + +void quit() { +} + +void draw() { + dupdate(); +} + +} +} +} diff --git a/ion/src/simulator/fxcg/display.h b/ion/src/simulator/fxcg/display.h new file mode 100644 index 00000000000..ab524fd3c55 --- /dev/null +++ b/ion/src/simulator/fxcg/display.h @@ -0,0 +1,19 @@ +#ifndef ION_SIMULATOR_DISPLAY_H +#define ION_SIMULATOR_DISPLAY_H + +#include + +namespace Ion { +namespace Simulator { +namespace Display { + +void init(); +void quit(); + +void draw(); + +} +} +} + +#endif diff --git a/ion/src/simulator/fxcg/events.cpp b/ion/src/simulator/fxcg/events.cpp new file mode 100644 index 00000000000..9b8d0055751 --- /dev/null +++ b/ion/src/simulator/fxcg/events.cpp @@ -0,0 +1,23 @@ +#include "events.h" +#include + +namespace Ion { +namespace Events { + +void didPressNewKey() { +} + +char * sharedExternalTextBuffer() { + static char buffer[sharedExternalTextBufferSize]; + return buffer; +} + +const char * Event::text() const { + if (*this == ExternalText) { + return const_cast(sharedExternalTextBuffer()); + } + return defaultText(); +} + +} +} diff --git a/ion/src/simulator/fxcg/events.h b/ion/src/simulator/fxcg/events.h new file mode 100644 index 00000000000..b012a43c5e5 --- /dev/null +++ b/ion/src/simulator/fxcg/events.h @@ -0,0 +1,24 @@ +#ifndef ION_SIMULATOR_EVENTS_H +#define ION_SIMULATOR_EVENTS_H + +#include + +namespace Ion { +namespace Simulator { +namespace Events { + +void dumpEventCount(int i); +void logAfter(int numberOfEvents); + +} +} + +namespace Events { + +static constexpr int sharedExternalTextBufferSize = 2; +char * sharedExternalTextBuffer(); + +} +} + +#endif diff --git a/ion/src/simulator/fxcg/events_keyboard.cpp b/ion/src/simulator/fxcg/events_keyboard.cpp new file mode 100644 index 00000000000..615c327d54c --- /dev/null +++ b/ion/src/simulator/fxcg/events_keyboard.cpp @@ -0,0 +1,15 @@ +#include + +namespace Ion { +namespace Events { + + +Event getPlatformEvent() { + Event result = None; + + return result; +} + + +} +} diff --git a/ion/src/simulator/fxcg/framebuffer.cpp b/ion/src/simulator/fxcg/framebuffer.cpp new file mode 100644 index 00000000000..d680467c129 --- /dev/null +++ b/ion/src/simulator/fxcg/framebuffer.cpp @@ -0,0 +1,56 @@ +#include "framebuffer.h" +#include +#include +#include "main.h" + +#include +#include + +// static KDColor sPixels[Ion::Display::Width * Ion::Display::Height]; +static_assert(sizeof(KDColor) == sizeof(uint16_t), "KDColor is not 16 bits"); +static KDColor* sPixels = (KDColor*) gint_vram; +static bool sFrameBufferActive = true; + +namespace Ion { +namespace Display { + +static KDFrameBuffer sFrameBuffer = KDFrameBuffer(sPixels, KDSize(Ion::Display::Width, Ion::Display::Height)); + +void pushRect(KDRect r, const KDColor * pixels) { + if (sFrameBufferActive) { + Simulator::Main::setNeedsRefresh(); + sFrameBuffer.pushRect(r, pixels); + } +} + +void pushRectUniform(KDRect r, KDColor c) { + if (sFrameBufferActive) { + Simulator::Main::setNeedsRefresh(); + sFrameBuffer.pushRectUniform(r, c); + } +} + +void pullRect(KDRect r, KDColor * pixels) { + if (sFrameBufferActive) { + sFrameBuffer.pullRect(r, pixels); + } +} + +} +} + +namespace Ion { +namespace Simulator { +namespace Framebuffer { + +const KDColor * address() { + return sPixels; +} + +void setActive(bool enabled) { + sFrameBufferActive = enabled; +} + +} +} +} \ No newline at end of file diff --git a/ion/src/simulator/fxcg/framebuffer.h b/ion/src/simulator/fxcg/framebuffer.h new file mode 100644 index 00000000000..dba4dbd3278 --- /dev/null +++ b/ion/src/simulator/fxcg/framebuffer.h @@ -0,0 +1,17 @@ +#ifndef ION_SIMULATOR_FRAMEBUFFER_H +#define ION_SIMULATOR_FRAMEBUFFER_H + +#include + +namespace Ion { +namespace Simulator { +namespace Framebuffer { + +const KDColor * address(); +void setActive(bool enabled); + +} +} +} + +#endif \ No newline at end of file diff --git a/ion/src/simulator/fxcg/keyboard.cpp b/ion/src/simulator/fxcg/keyboard.cpp new file mode 100644 index 00000000000..735b72e1f5c --- /dev/null +++ b/ion/src/simulator/fxcg/keyboard.cpp @@ -0,0 +1,262 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "keyboard.h" +#include "layout_keyboard.h" +#include "main.h" +#include "menuHandler.h" + + +using namespace Ion::Keyboard; + +class KeyPair { +public: + constexpr KeyPair(Key key, bool numworksShift, bool numworksAlpha, int gintKey, bool gintShift, bool gintAlpha, bool ignoreShiftAlpha = false) : + m_key(key), + m_numworksShift(numworksShift), + m_numworksAlpha(numworksAlpha), + m_gintKey(gintKey), + m_gintShift(gintShift), + m_gintAlpha(gintAlpha), + m_ignoreShiftAlpha(ignoreShiftAlpha) + {} + Key key() const { return m_key; } + bool numworksShift() const { return m_numworksShift; } + bool numworksAlpha() const { return m_numworksAlpha; } + int gintKey() const { return m_gintKey; } + bool gintShift() const { return m_gintShift; } + bool gintAlpha() const { return m_gintAlpha; } + bool ignoreShiftAlpha() const { return m_ignoreShiftAlpha; } +private: + Key m_key; + bool m_numworksShift; + bool m_numworksAlpha; + int m_gintKey; + bool m_gintShift; + bool m_gintAlpha; + bool m_ignoreShiftAlpha; +}; + +constexpr static KeyPair sKeyPairs[] = { + KeyPair(Key::Down, false, false, KEY_DOWN, false, false, true), + KeyPair(Key::Left, false, false, KEY_LEFT, false, false, true), + KeyPair(Key::Right, false, false, KEY_RIGHT, false, false, true), + KeyPair(Key::Up, false, false, KEY_UP, false, false, true), + KeyPair(Key::Back, false, false, KEY_EXIT, false, false), + KeyPair(Key::Home, false, false, KEY_MENU, false, false), + KeyPair(Key::Shift, false, false, KEY_SHIFT, false, false, true), + KeyPair(Key::Alpha, false, false, KEY_ALPHA, false, false, true), + KeyPair(Key::XNT, false, false, KEY_XOT, false, false), + KeyPair(Key::Var, false, false, KEY_VARS, false, false), + KeyPair(Key::Toolbox, false, false, KEY_OPTN, false, false), + KeyPair(Key::Backspace, false, false, KEY_DEL, false, false), + KeyPair(Key::Exp, false, false, KEY_LN, true, false), + KeyPair(Key::Ln, false, false, KEY_LN, false, false), + KeyPair(Key::Log, false, false, KEY_LOG, false, false), + KeyPair(Key::Imaginary, false, false, KEY_0, true, false), + KeyPair(Key::Comma, false, false, KEY_COMMA, false, false), + KeyPair(Key::Power, false, false, KEY_POWER, false, false), + KeyPair(Key::Sine, false, false, KEY_SIN, false, false), + KeyPair(Key::Cosine, false, false, KEY_COS, false, false), + KeyPair(Key::Tangent, false, false, KEY_TAN, false, false), + KeyPair(Key::Pi, false, false, KEY_EXP, true, false), + KeyPair(Key::Sqrt, false, false, KEY_SQUARE, true, false), + KeyPair(Key::Square, false, false, KEY_SQUARE, false, false), + KeyPair(Key::Seven, false, false, KEY_7, false, false), + KeyPair(Key::Eight, false, false, KEY_8, false, false), + KeyPair(Key::Nine, false, false, KEY_9, false, false), + KeyPair(Key::LeftParenthesis, false, false, KEY_LEFTP, false, false), + KeyPair(Key::RightParenthesis, false, false, KEY_RIGHTP, false, false), + KeyPair(Key::Four, false, false, KEY_4, false, false), + KeyPair(Key::Five, false, false, KEY_5, false, false), + KeyPair(Key::Six, false, false, KEY_6, false, false), + KeyPair(Key::Multiplication, false, false, KEY_MUL, false, false), + KeyPair(Key::Division, false, false, KEY_DIV, false, false), + KeyPair(Key::Division, false, false, KEY_FRAC, false, false), + KeyPair(Key::One, false, false, KEY_1, false, false), + KeyPair(Key::Two, false, false, KEY_2, false, false), + KeyPair(Key::Three, false, false, KEY_3, false, false), + KeyPair(Key::Plus, false, false, KEY_ADD, false, false), + KeyPair(Key::Minus, false, false, KEY_SUB, false, false), + KeyPair(Key::Zero, false, false, KEY_0, false, false), + KeyPair(Key::Dot, false, false, KEY_DOT, false, false), + KeyPair(Key::EE, false, false, KEY_EXP, false, false), + KeyPair(Key::Ans, false, false, KEY_NEG, true, false), + KeyPair(Key::EXE, false, false, KEY_EXE, false, false, true), + KeyPair(Key::OnOff, false, false, KEY_ACON, true, false), + + // Cut + // Not assigned + // Copy + KeyPair(Key::Var, true, false, KEY_8, true, false), + // Paste + KeyPair(Key::Toolbox, true, false, KEY_9, true, false), + // Clear + KeyPair(Key::Backspace, true, false, KEY_ACON, false, false), + // [ + KeyPair(Key::Exp, true, false, KEY_ADD, true, false), + // ] + KeyPair(Key::Ln, true, false, KEY_SUB, true, false), + // { + KeyPair(Key::Log, true, false, KEY_MUL, true, false), + // } + KeyPair(Key::Imaginary, true, false, KEY_DIV, true, false), + // _ + KeyPair(Key::Comma, true, false, KEY_NEG, false, false), + // -> + KeyPair(Key::Power, true, false, KEY_STORE, false, false), + // asin + KeyPair(Key::Sine, true, false, KEY_SIN, true, false), + // acos + KeyPair(Key::Cosine, true, false, KEY_COS, true, false), + // atan + KeyPair(Key::Tangent, true, false, KEY_TAN, true, false), + // = + KeyPair(Key::Pi, true, false, KEY_DOT, true, false), + // < + KeyPair(Key::Sqrt, true, false, KEY_F1, false, false), + // > + KeyPair(Key::Square, true, false, KEY_F2, false, false), + + // : + KeyPair(Key::XNT, false, true, KEY_F3, false, false), + // ; + KeyPair(Key::Var, false, true, KEY_F4, false, false), + // " + KeyPair(Key::Toolbox, false, true, KEY_EXP, false, true), + // % + KeyPair(Key::Backspace, false, true, KEY_F5, false, false), + // A + KeyPair(Key::Exp, false, true, KEY_XOT, false, true), + // B + KeyPair(Key::Ln, false, true, KEY_LOG, false, true), + // C + KeyPair(Key::Log, false, true, KEY_LN, false, true), + // D + KeyPair(Key::Imaginary, false, true, KEY_SIN, false, true), + // E + KeyPair(Key::Comma, false, true, KEY_COS, false, true), + // F + KeyPair(Key::Power, false, true, KEY_TAN, false, true), + // G + KeyPair(Key::Sine, false, true, KEY_FRAC, false, true), + // H + KeyPair(Key::Cosine, false, true, KEY_FD, false, true), + // I + KeyPair(Key::Tangent, false, true, KEY_LEFTP, false, true), + // J + KeyPair(Key::Pi, false, true, KEY_RIGHTP, false, true), + // K + KeyPair(Key::Sqrt, false, true, KEY_COMMA, false, true), + // L + KeyPair(Key::Square, false, true, KEY_ARROW, false, true), + // M + KeyPair(Key::Seven, false, true, KEY_7, false, true), + // N + KeyPair(Key::Eight, false, true, KEY_8, false, true), + // O + KeyPair(Key::Nine, false, true, KEY_9, false, true), + // P + KeyPair(Key::LeftParenthesis, false, true, KEY_4, false, true), + // Q + KeyPair(Key::RightParenthesis, false, true, KEY_5, false, true), + // R + KeyPair(Key::Four, false, true, KEY_6, false, true), + // S + KeyPair(Key::Five, false, true, KEY_TIMES, false, true), + // T + KeyPair(Key::Six, false, true, KEY_DIV, false, true), + // U + KeyPair(Key::Multiplication, false, true, KEY_1, false, true), + // V + KeyPair(Key::Division, false, true, KEY_2, false, true), + // W + KeyPair(Key::One, false, true, KEY_3, false, true), + // X + KeyPair(Key::Two, false, true, KEY_PLUS, false, true), + // Y + KeyPair(Key::Three, false, true, KEY_MINUS, false, true), + // Z + KeyPair(Key::Plus, false, true, KEY_0, false, true), + // Space + KeyPair(Key::Minus, false, true, KEY_DOT, false, true), + // ? + KeyPair(Key::Zero, false, true, KEY_F6, true, false), + // ! + KeyPair(Key::Dot, false, true, KEY_F6, false, false), + + // Brightness control shortcut in Upsilon + KeyPair(Key::Plus, true, false, KEY_UP, false, true), + KeyPair(Key::Minus, true, false, KEY_DOWN, false, true), +}; + +constexpr int sNumberOfKeyPairs = sizeof(sKeyPairs)/sizeof(KeyPair); + +namespace Ion { +namespace Keyboard { + +int menuHeldFor = 0; + +State scan() { + State state = 0; + + // Grab this opportunity to refresh the display if needed + Simulator::Main::refresh(); + + clearevents(); + if (keydown(KEY_MENU)) { + state.setKey(Key::Home); + menuHeldFor++; + if (menuHeldFor > 30) { + Simulator::FXCGMenuHandler::openMenu(); + dupdate(); + // Wait until EXE is released + do { + sleep_ms(10); + clearevents(); + } while (keydown(KEY_EXE)); + } + } else { + menuHeldFor = 0; + } + + for (int i = 0; i < sNumberOfKeyPairs; i++) { + const KeyPair & keyPair = sKeyPairs[i]; + if (!keyPair.ignoreShiftAlpha() && + (keyPair.gintShift() != Events::isShiftActive() || + keyPair.gintAlpha() != Events::isAlphaActive())) { + continue; + } + if (keydown(keyPair.gintKey())) { + if (!keyPair.ignoreShiftAlpha()) { + state.setSimulatedShift(keyPair.numworksShift() ? ModSimState::ForceOn : ModSimState::ForceOff); + state.setSimulatedAlpha(keyPair.numworksAlpha() ? ModSimState::ForceOn : ModSimState::ForceOff); + } + state.setKey(keyPair.key()); + } + } + + return state; +} + +} +} + +namespace Ion { +namespace Simulator { +namespace Keyboard { + +} +} +} \ No newline at end of file diff --git a/ion/src/simulator/fxcg/keyboard.h b/ion/src/simulator/fxcg/keyboard.h new file mode 100644 index 00000000000..0e379642fb8 --- /dev/null +++ b/ion/src/simulator/fxcg/keyboard.h @@ -0,0 +1,15 @@ +#ifndef ION_SIMULATOR_KEYBOARD_H +#define ION_SIMULATOR_KEYBOARD_H + +#include +// #include + +namespace Ion { +namespace Simulator { +namespace Keyboard { + +} +} +} + +#endif \ No newline at end of file diff --git a/ion/src/simulator/fxcg/main.cpp b/ion/src/simulator/fxcg/main.cpp new file mode 100644 index 00000000000..9280dbc1253 --- /dev/null +++ b/ion/src/simulator/fxcg/main.cpp @@ -0,0 +1,114 @@ +#include "main.h" +#include "display.h" +#include "platform.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +extern "C" { +int main() { + Ion::Simulator::Main::init(); + ion_main(0, NULL); + Ion::Simulator::Main::quit(); + + return 0; +} +} + +namespace Ion { +namespace Simulator { +namespace Main { + +static bool sNeedsRefresh = false; + +void init() { + Ion::Simulator::Display::init(); + setNeedsRefresh(); +} + +void setNeedsRefresh() { + sNeedsRefresh = true; +} + +void refresh() { + if (!sNeedsRefresh) { + return; + } + + Display::draw(); + + sNeedsRefresh = false; +} + +void quit() { + Ion::Simulator::Display::quit(); +} + +void EnableStatusArea(int opt) { + __asm__ __volatile__ ( + ".align 2 \n\t" + "mov.l 2f, r2 \n\t" + "mov.l 1f, r0 \n\t" + "jmp @r2 \n\t" + "nop \n\t" + ".align 2 \n\t" + "1: \n\t" + ".long 0x02B7 \n\t" + ".align 4 \n\t" + "2: \n\t" + ".long 0x80020070 \n\t" + ); +} + +extern "C" void *__GetVRAMAddress(void); + +uint8_t xyram_backup[16 * 1024]; +uint8_t ilram_backup[4 * 1024]; + +void worldSwitchHandler(void (*worldSwitchFunction)(), bool prepareVRAM) { + // Back up XYRAM + uint8_t* xyram = (uint8_t*) 0xe500e000; + memcpy(xyram_backup, xyram, 16 * 1024); + // Back up ILRAM + uint8_t* ilram = (uint8_t*) 0xe5200000; + memcpy(ilram_backup, ilram, 4 * 1024); + + if (prepareVRAM) { + // Copying the screen to the OS's VRAM avoids a flicker when powering on + uint16_t* dst = (uint16_t *) __GetVRAMAddress(); + uint16_t* src = gint_vram + 6; + + for (int y = 0; y < 216; y++, dst += 384, src += 396) { + for (int x = 0; x < 384; x++) { + dst[x] = src[x]; + } + } + + // Disable the status area + EnableStatusArea(3); + } + + worldSwitchFunction(); + + // Restore XYRAM + memcpy(xyram, xyram_backup, 16 * 1024); + // Restore ILRAM + memcpy(ilram, ilram_backup, 4 * 1024); +} + +void runPowerOffSafe(void (*powerOffSafeFunction)(), bool prepareVRAM) { + gint_world_switch(GINT_CALL(worldSwitchHandler, powerOffSafeFunction, prepareVRAM)); +} + +} +} +} diff --git a/ion/src/simulator/fxcg/main.h b/ion/src/simulator/fxcg/main.h new file mode 100644 index 00000000000..4ac441a065f --- /dev/null +++ b/ion/src/simulator/fxcg/main.h @@ -0,0 +1,20 @@ +#ifndef ION_SIMULATOR_MAIN_H +#define ION_SIMULATOR_MAIN_H + +namespace Ion { +namespace Simulator { +namespace Main { + +void init(); +void quit(); + +void setNeedsRefresh(); +void refresh(); + +void runPowerOffSafe(void (*powerOffSafeFunction)(), bool prepareVRAM); + +} +} +} + +#endif \ No newline at end of file diff --git a/ion/src/simulator/fxcg/menuHandler.cpp b/ion/src/simulator/fxcg/menuHandler.cpp new file mode 100644 index 00000000000..51425160904 --- /dev/null +++ b/ion/src/simulator/fxcg/menuHandler.cpp @@ -0,0 +1,97 @@ +#include "main.h" + +namespace Ion { +namespace Simulator { +namespace FXCGMenuHandler { + +int saveAndOpenMainMenu(void) { + int addr; + + // get the address of the syscall table in it + addr = *(unsigned int *)0x8002007C; + + if (addr < (int)0x80020070) + return 1; + if (addr >= (int)0x81000000) + return 1; + + // get the pointer to syscall 1E58 - SwitchToMainMenu + addr += 0x1E58 * 4; + if (addr < (int)0x80020070) + return 1; + if (addr >= (int)0x81000000) + return 1; + + addr = *(unsigned int *)addr; + if (addr < (int)0x80020070) + return 1; + if (addr >= (int)0x81000000) + return 1; + + // Now addr has the address of the first operation in %1e58 + + // Run up to 150 times (300/2). OS 3.60's is 59 instructions, so this should + // be plenty, but will let it stop if nothing is found + for (unsigned short *currentAddr = (unsigned short *)addr; + (unsigned int)currentAddr < ((unsigned int)addr + 300); currentAddr++) { + // MOV.L GetkeyToMainFunctionReturn Flag, r14 + if (*(unsigned char *)currentAddr != 0xDE) + continue; + + // MOV #3, 2 + if (*(currentAddr + 1) != 0xE203) + continue; + + // BSR + if ((*(unsigned char *)(currentAddr + 2) & 0xF0) != 0xB0) + continue; + + // MOV.B r2, @r14 + if (*(currentAddr + 3) != 0x2E20) + continue; + + // BRA + if ((*(unsigned char *)(currentAddr + 4) & 0xF0) != 0xA0) + continue; + + // NOP + if (*(currentAddr + 5) != 0x0009) + continue; + + unsigned short branchInstruction = *(currentAddr + 2); + + // Clear first 4 bits (BSR identifier) + branchInstruction <<= 4; + branchInstruction >>= 4; + + // branchInstruction is now the displacement of BSR + + // Create typedef so we can cast the pointer + typedef void (*voidFunc)(void); + + // JMP to disp*2 + PC + 4 + ((voidFunc)((unsigned int)branchInstruction * 2 + + (unsigned int)currentAddr + 4 + 4))(); + + return 0; + } + + return 1; +} + +extern "C" void gint_osmenu_native(void); + +void openMenuWrapper(void) { + if (saveAndOpenMainMenu() != 0) { + // Fallback + gint_osmenu_native(); + } +} + +void openMenu(void) { + Simulator::Main::runPowerOffSafe(openMenuWrapper, true); +} + +} +} +} diff --git a/ion/src/simulator/fxcg/menuHandler.h b/ion/src/simulator/fxcg/menuHandler.h new file mode 100644 index 00000000000..6c455dfd96e --- /dev/null +++ b/ion/src/simulator/fxcg/menuHandler.h @@ -0,0 +1,14 @@ +#ifndef ION_SIMULATOR_MENUHANDLER_H +#define ION_SIMULATOR_MENUHANDLER_H + +namespace Ion { +namespace Simulator { +namespace FXCGMenuHandler { + +void openMenu(void); + +} +} +} + +#endif \ No newline at end of file diff --git a/ion/src/simulator/fxcg/platform.h b/ion/src/simulator/fxcg/platform.h new file mode 100644 index 00000000000..af5ddfcb60a --- /dev/null +++ b/ion/src/simulator/fxcg/platform.h @@ -0,0 +1,23 @@ +#ifndef ION_SIMULATOR_PLATFORM_H +#define ION_SIMULATOR_PLATFORM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Those functions should be implemented per-platform. + * They are defined as C function for easier interop. */ + +const char * IonSimulatorGetLanguageCode(); + +void IonSimulatorKeyboardKeyDown(int keyNumber); +void IonSimulatorKeyboardKeyUp(int keyNumber); +void IonSimulatorEventsPushEvent(int eventNumber); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/ion/src/simulator/fxcg/power.cpp b/ion/src/simulator/fxcg/power.cpp new file mode 100644 index 00000000000..7375befecad --- /dev/null +++ b/ion/src/simulator/fxcg/power.cpp @@ -0,0 +1,42 @@ +#include +#include +#include + +#include "main.h" + +#include +#include + +void PowerOff(int displayLogo) { + __asm__ __volatile__ ( + ".align 2 \n\t" + "mov.l 2f, r2 \n\t" + "mov.l 1f, r0 \n\t" + "jmp @r2 \n\t" + "nop \n\t" + ".align 2 \n\t" + "1: \n\t" + ".long 0x1839 \n\t" + ".align 4 \n\t" + "2: \n\t" + ".long 0x80020070 \n\t" + ); +} + +void powerOff(void) { + PowerOff(1); +} + +namespace Ion { +namespace Power { + +void suspend(bool checkIfOnOffKeyReleased) { + Simulator::Main::runPowerOffSafe(powerOff, true); +} + +void standby() { + Simulator::Main::runPowerOffSafe(powerOff, true); +} + +} +} diff --git a/ion/src/simulator/fxcg/telemetry_init.cpp b/ion/src/simulator/fxcg/telemetry_init.cpp new file mode 100644 index 00000000000..7a69b2d8c83 --- /dev/null +++ b/ion/src/simulator/fxcg/telemetry_init.cpp @@ -0,0 +1,15 @@ +#include "platform.h" + +namespace Ion { +namespace Simulator { +namespace Telemetry { + +void init() { +} + +void shutdown() { +} + +} +} +} \ No newline at end of file diff --git a/ion/src/simulator/fxcg/timing.cpp b/ion/src/simulator/fxcg/timing.cpp new file mode 100644 index 00000000000..9a6eedfcab7 --- /dev/null +++ b/ion/src/simulator/fxcg/timing.cpp @@ -0,0 +1,27 @@ +#include +#include "main.h" +#include +// #include + +#include + +static auto start = std::chrono::steady_clock::now(); + +namespace Ion { +namespace Timing { + +uint64_t millis() { + auto elapsed = std::chrono::steady_clock::now() - start; + return std::chrono::duration_cast(elapsed).count(); +} + +void usleep(uint32_t us) { + sleep_us(us); +} + +void msleep(uint32_t ms) { + sleep_us(ms * 1000); +} + +} +} \ No newline at end of file diff --git a/kandinsky/include/kandinsky/postprocess_gamma_context.h b/kandinsky/include/kandinsky/postprocess_gamma_context.h index c3195f2ff2a..305d9ceddb5 100644 --- a/kandinsky/include/kandinsky/postprocess_gamma_context.h +++ b/kandinsky/include/kandinsky/postprocess_gamma_context.h @@ -2,6 +2,7 @@ #define KANDINSKY_POSTPROCESS_GAMMA_CONTEXT_H #include +#include class KDPostProcessGammaContext : public KDPostProcessContext { public: @@ -13,7 +14,16 @@ class KDPostProcessGammaContext : public KDPostProcessContext { void pushRect(KDRect rect, const KDColor * pixels) override; void pushRectUniform(KDRect rect, KDColor color) override; void pullRect(KDRect rect, KDColor * pixels) override; + KDColor correctColor(KDColor color); int m_redGamma, m_greenGamma, m_blueGamma; + // Lookup tables to do gamma correction + // 5 bits of red + uint8_t m_redGammaTable[32]; + // 6 bits of green + uint8_t m_greenGammaTable[64]; + // 5 bits of blue + uint8_t m_blueGammaTable[32]; + void updateGammaTables(); }; #endif diff --git a/kandinsky/src/postprocess_gamma_context.cpp b/kandinsky/src/postprocess_gamma_context.cpp index 6a75e60db77..95a89768b6e 100644 --- a/kandinsky/src/postprocess_gamma_context.cpp +++ b/kandinsky/src/postprocess_gamma_context.cpp @@ -1,6 +1,8 @@ +#include #include #include #include +#include constexpr int MaxGammaStates = 7; constexpr float MaxGammaGamut = 0.75; @@ -13,8 +15,28 @@ constexpr int clampGamma(int gamma) { return gamma < -MaxGammaStates ? -MaxGammaStates : (gamma > MaxGammaStates ? MaxGammaStates : gamma); } +void KDPostProcessGammaContext::updateGammaTables() { + const float redGamma = toGamma(m_redGamma); + const float greenGamma = toGamma(m_greenGamma); + const float blueGamma = toGamma(m_blueGamma); + for (int i = 0; i < 32; i++) { + uint8_t r = (uint8_t)(powf((i << 3) / 255.f, redGamma) * 255); + m_redGammaTable[i] = r >> 3; + } + for (int i = 0; i < 64; i++) { + uint8_t g = (uint8_t)(powf((i << 2) / 255.f, greenGamma) * 255); + m_greenGammaTable[i] = g >> 2; + } + for (int i = 0; i < 32; i++) { + uint8_t b = (uint8_t)(powf((i << 3 )/ 255.f, blueGamma) * 255); + m_blueGammaTable[i] = b >> 3; + } +} + KDPostProcessGammaContext::KDPostProcessGammaContext() : - m_redGamma(0), m_greenGamma(0), m_blueGamma(0) {} + m_redGamma(0), m_greenGamma(0), m_blueGamma(0) { + updateGammaTables(); + } void KDPostProcessGammaContext::gamma(int& red, int& green, int& blue) { red = m_redGamma; @@ -32,12 +54,20 @@ void KDPostProcessGammaContext::setGamma(int red, int green, int blue) { m_redGamma = clampGamma(red); m_greenGamma = clampGamma(green); m_blueGamma = clampGamma(blue); + updateGammaTables(); +} + +KDColor KDPostProcessGammaContext::correctColor(KDColor color) { + uint8_t r5 = (((uint16_t )color)>>11)&0x1F; + r5 = m_redGammaTable[r5]; + uint8_t g6 = (((uint16_t )color)>>5)&0x3F; + g6 = m_greenGammaTable[g6]; + uint8_t b5 = ((uint16_t )color)&0x1F; + b5 = m_blueGammaTable[b5]; + return KDColor::RGB16(r5<<11 | g6<<5 | b5); } void KDPostProcessGammaContext::pushRect(KDRect rect, const KDColor * pixels) { - const float redGamma = toGamma(m_redGamma); - const float greenGamma = toGamma(m_greenGamma); - const float blueGamma = toGamma(m_blueGamma); KDColor workingBuffer[rect.width()]; for (KDCoordinate y = 0; y < rect.height(); y++) { @@ -45,10 +75,7 @@ void KDPostProcessGammaContext::pushRect(KDRect rect, const KDColor * pixels) { for (KDCoordinate x = 0; x < rect.width(); x++) { const KDColor color = pixels[y*rect.width()+x]; - const KDColor result = KDColor::RGB888( - (uint8_t)(powf(color.red()/255.f, redGamma)*255), - (uint8_t)(powf(color.green()/255.f, greenGamma)*255), - (uint8_t)(powf(color.blue()/255.f, blueGamma)*255)); + const KDColor result = correctColor(color); workingBuffer[x] = result; } KDPostProcessContext::pushRect(workingRect, workingBuffer); @@ -56,15 +83,7 @@ void KDPostProcessGammaContext::pushRect(KDRect rect, const KDColor * pixels) { } void KDPostProcessGammaContext::pushRectUniform(KDRect rect, KDColor color) { - const float redGamma = toGamma(m_redGamma); - const float greenGamma = toGamma(m_greenGamma); - const float blueGamma = toGamma(m_blueGamma); - const KDColor result = KDColor::RGB888( - (uint8_t)(powf(color.red()/255.f, redGamma)*255), - (uint8_t)(powf(color.green()/255.f, greenGamma)*255), - (uint8_t)(powf(color.blue()/255.f, blueGamma)*255)); - - KDPostProcessContext::pushRectUniform(rect, result); + KDPostProcessContext::pushRectUniform(rect, correctColor(color)); } void KDPostProcessGammaContext::pullRect(KDRect rect, KDColor * pixels) { diff --git a/liba/include/bridge/math.h b/liba/include/bridge/math.h new file mode 100644 index 00000000000..a89e832f5b6 --- /dev/null +++ b/liba/include/bridge/math.h @@ -0,0 +1,20 @@ +#ifndef LIBA_BRIDGE_MATH_H +#define LIBA_BRIDGE_MATH_H + +#include_next + +#define M_E 2.7182818284590452354 /* e */ +#define M_LOG2E 1.4426950408889634074 /* log 2e */ +#define M_LOG10E 0.43429448190325182765 /* log 10e */ +#define M_LN2 0.69314718055994530942 /* log e2 */ +#define M_LN10 2.30258509299404568402 /* log e10 */ +#define M_PI 3.14159265358979323846 /* pi */ +#define M_PI_2 1.57079632679489661923 /* pi/2 */ +#define M_PI_4 0.78539816339744830962 /* pi/4 */ +#define M_1_PI 0.31830988618379067154 /* 1/pi */ +#define M_2_PI 0.63661977236758134308 /* 2/pi */ +#define M_2_SQRTPI 1.12837916709551257390 /* 2/sqrt(pi) */ +#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ +#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ + +#endif diff --git a/liba/include/bridge/string.h b/liba/include/bridge/string.h index d5eda394692..51d6a8c3a34 100644 --- a/liba/include/bridge/string.h +++ b/liba/include/bridge/string.h @@ -7,7 +7,7 @@ LIBA_BEGIN_DECLS -#if (__GLIBC__ || __MINGW32__) +#if (__GLIBC__ || __MINGW32__ || _FXCG) size_t strlcat(char * dst, const char * src, size_t dstSize); size_t strlcpy(char * dst, const char * src, size_t len); #endif diff --git a/liba/include/bridge/strings.h b/liba/include/bridge/strings.h new file mode 100644 index 00000000000..d74b264db18 --- /dev/null +++ b/liba/include/bridge/strings.h @@ -0,0 +1,22 @@ +#ifndef LIBA_STRINGS_H +#define LIBA_STRINGS_H + +#if (_FXCG) + +#include + +#include "../private/macros.h" + +LIBA_BEGIN_DECLS + +void bzero(void * s, size_t n); + +LIBA_END_DECLS + +#else + +#include_next + +#endif + +#endif \ No newline at end of file diff --git a/libaxx/Makefile.bridge b/libaxx/Makefile.bridge new file mode 100644 index 00000000000..92fd5dddd4d --- /dev/null +++ b/libaxx/Makefile.bridge @@ -0,0 +1,3 @@ +SFLAGS += -Ilibaxx/include/bridge + +# libaxx_src += libaxx/src/bridge.c diff --git a/libaxx/include/bridge/cmath b/libaxx/include/bridge/cmath new file mode 100644 index 00000000000..11eec165e87 --- /dev/null +++ b/libaxx/include/bridge/cmath @@ -0,0 +1,165 @@ +#ifndef LIBA_BRIDGE_CMATH_H +#define LIBA_BRIDGE_CMATH_H + +#include_next + +#define M_E 2.7182818284590452354 /* e */ +#define M_LOG2E 1.4426950408889634074 /* log 2e */ +#define M_LOG10E 0.43429448190325182765 /* log 10e */ +#define M_LN2 0.69314718055994530942 /* log e2 */ +#define M_LN10 2.30258509299404568402 /* log e10 */ +#define M_PI 3.14159265358979323846 /* pi */ +#define M_PI_2 1.57079632679489661923 /* pi/2 */ +#define M_PI_4 0.78539816339744830962 /* pi/4 */ +#define M_1_PI 0.31830988618379067154 /* 1/pi */ +#define M_2_PI 0.63661977236758134308 /* 2/pi */ +#define M_2_SQRTPI 1.12837916709551257390 /* 2/sqrt(pi) */ +#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ +#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ + +#if (_FXCG) +namespace std { + // functions + using ::acosh; + using ::acoshf; + using ::acoshl; + + using ::asinh; + using ::asinhf; + using ::asinhl; + + using ::atanh; + using ::atanhf; + using ::atanhl; + + using ::cbrt; + using ::cbrtf; + using ::cbrtl; + + using ::copysign; + using ::copysignf; + using ::copysignl; + + using ::erf; + using ::erff; + using ::erfl; + + using ::erfc; + using ::erfcf; + using ::erfcl; + + using ::exp2; + using ::exp2f; + using ::exp2l; + + using ::expm1; + using ::expm1f; + using ::expm1l; + + using ::fdim; + using ::fdimf; + using ::fdiml; + + using ::fma; + using ::fmaf; + using ::fmal; + + using ::fmax; + using ::fmaxf; + using ::fmaxl; + + using ::fmin; + using ::fminf; + using ::fminl; + + using ::hypot; + using ::hypotf; + using ::hypotl; + + using ::ilogb; + using ::ilogbf; + using ::ilogbl; + + using ::lgamma; + using ::lgammaf; + using ::lgammal; + + using ::llrint; + using ::llrintf; + using ::llrintl; + + using ::llround; + using ::llroundf; + using ::llroundl; + + using ::log1p; + using ::log1pf; + using ::log1pl; + + using ::log2; + using ::log2f; + using ::log2l; + + using ::logb; + using ::logbf; + using ::logbl; + + using ::lrint; + using ::lrintf; + using ::lrintl; + + using ::lround; + using ::lroundf; + using ::lroundl; + + using ::nan; + using ::nanf; + using ::nanl; + + using ::nearbyint; + using ::nearbyintf; + using ::nearbyintl; + + using ::nextafter; + using ::nextafterf; + using ::nextafterl; + + using ::nexttoward; + using ::nexttowardf; + using ::nexttowardl; + + using ::remainder; + using ::remainderf; + using ::remainderl; + + using ::remquo; + using ::remquof; + using ::remquol; + + using ::rint; + using ::rintf; + using ::rintl; + + using ::round; + using ::roundf; + using ::roundl; + + using ::scalbln; + using ::scalblnf; + using ::scalblnl; + + using ::scalbn; + using ::scalbnf; + using ::scalbnl; + + using ::tgamma; + using ::tgammaf; + using ::tgammal; + + using ::trunc; + using ::truncf; + using ::truncl; +} +#endif + +#endif diff --git a/poincare/include/poincare/ieee754.h b/poincare/include/poincare/ieee754.h index accda9d33d4..8427e90eef8 100644 --- a/poincare/include/poincare/ieee754.h +++ b/poincare/include/poincare/ieee754.h @@ -4,7 +4,9 @@ #include #include #include +#include #include +#include namespace Poincare { @@ -41,11 +43,19 @@ class IEEE754 final { if (((uint64_t)mantissa >> (size()-k_mantissaNbBits-2)) & 1) { u.ui += 1; } - return u.f; + if (sizeof(T) == sizeof(float)) { + return u.f32.f; + } else { + return u.f64.f; + } } static int exponent(T f) { uint_float u; - u.f = f; + if (sizeof(T) == sizeof(float)) { + u.f32.f = f; + } else { + u.f64.f = f; + } constexpr uint16_t oneOnExponentsBits = maxExponent(); int exp = (u.ui >> k_mantissaNbBits) & oneOnExponentsBits; exp -= exponentOffset(); @@ -75,10 +85,28 @@ class IEEE754 final { } private: + #ifdef _BIG_ENDIAN + union uint_float { + uint64_t ui; + struct { + uint32_t padding; + float f; + } f32; + struct { + double f; + } f64; + }; + #else union uint_float { uint64_t ui; - T f; + struct { + float f; + } f32; + struct { + double f; + } f64; }; + #endif constexpr static size_t k_signNbBits = 1; constexpr static size_t k_exponentNbBits = sizeof(T) == sizeof(float) ? 8 : 11; diff --git a/poincare/include/poincare/integer.h b/poincare/include/poincare/integer.h index 1d27e50c508..cfc98203495 100644 --- a/poincare/include/poincare/integer.h +++ b/poincare/include/poincare/integer.h @@ -5,6 +5,14 @@ #include #include +#ifdef _FXCG +#include +#include +#include +#else +#include +#endif + namespace Poincare { class ExpressionLayout; @@ -13,12 +21,17 @@ class LayoutNode; class Integer; struct IntegerDivision; -#ifdef _3DS +#if (defined _3DS) || (defined _FXCG) typedef unsigned short half_native_uint_t; +static_assert(sizeof(half_native_uint_t) == sizeof(uint16_t)); typedef int native_int_t; +static_assert(sizeof(native_int_t) == sizeof(int32_t)); typedef long long int double_native_int_t; +static_assert(sizeof(double_native_int_t) == sizeof(int64_t)); typedef unsigned int native_uint_t; +static_assert(sizeof(native_uint_t) == sizeof(uint32_t)); typedef unsigned long long int double_native_uint_t; +static_assert(sizeof(double_native_uint_t) == sizeof(uint64_t)); #else typedef uint16_t half_native_uint_t; typedef int32_t native_int_t; @@ -199,7 +212,12 @@ class Integer final : public TreeHandle { if (i >= numberOfHalfDigits()) { return 0; } - return (usesImmediateDigit() ? ((half_native_uint_t *)&m_digit)[i] : ((half_native_uint_t *)digits())[i]); + native_uint_t d = usesImmediateDigit() ? m_digit : digits()[i/2]; + if (i % 2 == 0) { + return d & 0xFFFF; + } else { + return d >> 16; + } } native_uint_t digit(uint8_t i) const { diff --git a/poincare/include/poincare/tree_pool.h b/poincare/include/poincare/tree_pool.h index cb30940187e..62c1d6d5ee3 100644 --- a/poincare/include/poincare/tree_pool.h +++ b/poincare/include/poincare/tree_pool.h @@ -138,7 +138,7 @@ class TreePool final { private: uint16_t m_currentIndex; uint16_t m_availableIdentifiers[MaxNumberOfNodes]; - static_assert(MaxNumberOfNodes < INT16_MAX && sizeof(m_availableIdentifiers[0] == sizeof(uint16_t)), "Tree node identifiers do not have the right data size."); + static_assert(MaxNumberOfNodes < INT16_MAX && sizeof(m_availableIdentifiers[0]) == sizeof(uint16_t), "Tree node identifiers do not have the right data size."); }; void freePoolFromNode(TreeNode * firstNodeToDiscard); diff --git a/poincare/src/integer.cpp b/poincare/src/integer.cpp index fc67dda6609..3fa7763622c 100644 --- a/poincare/src/integer.cpp +++ b/poincare/src/integer.cpp @@ -127,7 +127,10 @@ Integer::Integer(native_int_t i) : TreeHandle(TreeNode::NoNodeIdentifier) { Integer::Integer(double_native_int_t i) { double_native_uint_t j = i < 0 ? -i : i; - native_uint_t * d = (native_uint_t *)&j; + native_uint_t d[2] = { + static_cast(j & 0xFFFFFFFF), + static_cast(j >> 32) + }; native_uint_t leastSignificantDigit = *d; native_uint_t mostSignificantDigit = *(d+1); uint8_t numberOfDigits = (mostSignificantDigit == 0) ? 1 : 2; @@ -165,7 +168,8 @@ Integer::Integer(const char * digits, size_t length, bool negative, Base b) : Integer base((int)b); for (size_t i = 0; i < length; i++) { *this = Multiplication(*this, base); - *this = Addition(*this, Integer(integerFromCharDigit(*digits))); + Integer toAdd = Integer(integerFromCharDigit(*digits)); + *this = Addition(*this, toAdd); digits++; } } @@ -495,7 +499,10 @@ Integer Integer::multiplication(const Integer & a, const Integer & b, bool oneDi * otherwise the product might end up being computed on single_native size * and then zero-padded. */ double_native_uint_t p = aDigit*bDigit + carry + (double_native_uint_t)(s_workingBuffer[i+j]); // TODO: Prove it cannot overflow double_native type - native_uint_t * l = (native_uint_t *)&p; + native_uint_t l[2] = { + static_cast(p & 0xFFFFFFFF), + static_cast(p >> 32) + }; if (i+j < (uint8_t) k_maxNumberOfDigits+oneDigitOverflow) { s_workingBuffer[i+j] = l[0]; } else { @@ -605,18 +612,31 @@ Integer Integer::divideByPowerOf2(uint8_t pow) const { // return this*(2^16)^pow Integer Integer::multiplyByPowerOfBase(uint8_t pow) const { int nbOfHalfDigits = numberOfHalfDigits(); - half_native_uint_t * digits = reinterpret_cast(s_workingBuffer); + native_uint_t * digits = s_workingBuffer; /* The number of half digits of the built integer is nbOfHalfDigits+pow. * Still, we set an extra half digit to 0 to easily convert half digits to * digits. */ - memset(digits, 0, sizeof(half_native_uint_t)*(nbOfHalfDigits+pow+1)); - for (uint8_t i = 0; i < nbOfHalfDigits; i++) { - digits[i+pow] = halfDigit(i); + memset(digits, 0, (sizeof(native_uint_t)/2)*(nbOfHalfDigits+pow+1)); + for (uint8_t i = 0; i < nbOfHalfDigits; i += 2) { + native_uint_t toSet = halfDigit(i); + if (i+1 < nbOfHalfDigits) { + toSet |= (native_uint_t)halfDigit(i+1) << 16; + } + int index = i+pow; + // If it's on an even index, we can just set the value + if (index % 2 == 0) { + digits[index/2] = toSet; + } else { + // If it's on an odd index, we need to shift the value + digits[index/2] |= toSet << 16; + digits[index/2+1] |= toSet >> 16; + } } nbOfHalfDigits += pow; - return BuildInteger((native_uint_t *)digits, nbOfHalfDigits%2 == 1 ? nbOfHalfDigits/2+1 : nbOfHalfDigits/2, false, true); + return BuildInteger(digits, nbOfHalfDigits%2 == 1 ? nbOfHalfDigits/2+1 : nbOfHalfDigits/2, false, true); } + IntegerDivision Integer::udiv(const Integer & numerator, const Integer & denominator) { if (denominator.isOverflow()) { return {.quotient = Overflow(false), .remainder = Integer::Overflow(false)}; @@ -679,6 +699,14 @@ IntegerDivision Integer::udiv(const Integer & numerator, const Integer & denomin qNumberOfDigits--; } int qNumberOfDigitsInBase32 = qNumberOfDigits%2 == 1 ? qNumberOfDigits/2+1 : qNumberOfDigits/2; + // Swap each pair of digits in qDigits if on a big-endian architecture + #ifdef _BIG_ENDIAN + for (int i = 0; i < qNumberOfDigitsInBase32; i++) { + half_native_uint_t tmp = qDigits[i*2]; + qDigits[i*2] = qDigits[i*2+1]; + qDigits[i*2+1] = tmp; + } + #endif IntegerDivision div = {.quotient = BuildInteger((native_uint_t *)qDigits, qNumberOfDigitsInBase32, false), .remainder = A}; if (pow > 0 && !div.remainder.isZero()) { div.remainder = div.remainder.divideByPowerOf2(pow); From a124ed72b5d08bd6732edf5c4572360d526c3c54 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Sun, 21 May 2023 11:53:08 +0200 Subject: [PATCH 343/355] [Ion] Quick-and-dirty fix for the Web simulator --- ion/src/simulator/web/exports.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ion/src/simulator/web/exports.cpp b/ion/src/simulator/web/exports.cpp index e4f32ce2b33..6827cb28775 100644 --- a/ion/src/simulator/web/exports.cpp +++ b/ion/src/simulator/web/exports.cpp @@ -35,3 +35,6 @@ void IonSimulatorEventsPushEvent(int eventNumber) { Ion::Events::replayFrom(j); } } + +int SDL_LockMutex(SDL_mutex *) { return 0; } +int SDL_UnlockMutex(SDL_mutex *) { return 0; } From 73450419bb6e503354cfbee793a4b8166e798c4d Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Tue, 6 Jun 2023 21:25:07 +0200 Subject: [PATCH 344/355] [Ports] Merge Nspire port and Fxcg improvements Close #327 --- apps/Makefile | 1 + apps/calculation/calculation_store.cpp | 62 + apps/code/app.cpp | 18 +- apps/external/app/sample.c | 9 + apps/host_filemanager.cpp | 859 ++++++++++ apps/shared/expression_model.cpp | 43 +- build/platform.simulator.nspire.mak | 11 + build/targets.simulator.nspire.mak | 6 + build/toolchain.nspire-gcc.mak | 9 + build/toolchain.sh-elf-gcc.mak | 2 +- ion/include/ion/internal_storage.h | 9 +- ion/src/simulator/external/config.nspire.mak | 2 + ion/src/simulator/fxcg/keyboard.cpp | 6 +- ion/src/simulator/fxcg/main.cpp | 64 +- ion/src/simulator/fxcg/main.h | 9 + ion/src/simulator/nspire/Makefile | 53 + ion/src/simulator/nspire/assets/icon-sel.png | Bin 0 -> 3588 bytes ion/src/simulator/nspire/assets/icon-uns.png | Bin 0 -> 1750 bytes ion/src/simulator/nspire/backlight.cpp | 24 + ion/src/simulator/nspire/clipboard.cpp | 15 + ion/src/simulator/nspire/console.cpp | 24 + ion/src/simulator/nspire/display.cpp | 47 + ion/src/simulator/nspire/display.h | 19 + ion/src/simulator/nspire/events.cpp | 23 + ion/src/simulator/nspire/events.h | 24 + ion/src/simulator/nspire/events_keyboard.cpp | 15 + ion/src/simulator/nspire/framebuffer.cpp | 53 + ion/src/simulator/nspire/framebuffer.h | 17 + ion/src/simulator/nspire/gint.h | 61 + ion/src/simulator/nspire/k_csdk.c | 1556 ++++++++++++++++++ ion/src/simulator/nspire/k_csdk.h | 255 +++ ion/src/simulator/nspire/k_defs.h | 211 +++ ion/src/simulator/nspire/keyboard.cpp | 276 ++++ ion/src/simulator/nspire/keyboard.h | 15 + ion/src/simulator/nspire/main.cpp | 97 ++ ion/src/simulator/nspire/main.h | 20 + ion/src/simulator/nspire/platform.h | 23 + ion/src/simulator/nspire/power.cpp | 23 + ion/src/simulator/nspire/telemetry_init.cpp | 15 + ion/src/simulator/nspire/timing.cpp | 21 + liba/include/bridge/string.h | 3 +- liba/include/bridge/strings.h | 4 +- libaxx/include/bridge/cmath | 2 +- poincare/include/poincare/expression.h | 2 +- poincare/include/poincare/integer.h | 2 +- poincare/src/expression.cpp | 34 +- python/port/port.cpp | 123 +- 47 files changed, 4139 insertions(+), 28 deletions(-) create mode 100644 apps/host_filemanager.cpp create mode 100644 build/platform.simulator.nspire.mak create mode 100644 build/targets.simulator.nspire.mak create mode 100644 build/toolchain.nspire-gcc.mak create mode 100644 ion/src/simulator/external/config.nspire.mak create mode 100644 ion/src/simulator/nspire/Makefile create mode 100644 ion/src/simulator/nspire/assets/icon-sel.png create mode 100644 ion/src/simulator/nspire/assets/icon-uns.png create mode 100644 ion/src/simulator/nspire/backlight.cpp create mode 100644 ion/src/simulator/nspire/clipboard.cpp create mode 100644 ion/src/simulator/nspire/console.cpp create mode 100644 ion/src/simulator/nspire/display.cpp create mode 100644 ion/src/simulator/nspire/display.h create mode 100644 ion/src/simulator/nspire/events.cpp create mode 100644 ion/src/simulator/nspire/events.h create mode 100644 ion/src/simulator/nspire/events_keyboard.cpp create mode 100644 ion/src/simulator/nspire/framebuffer.cpp create mode 100644 ion/src/simulator/nspire/framebuffer.h create mode 100644 ion/src/simulator/nspire/gint.h create mode 100755 ion/src/simulator/nspire/k_csdk.c create mode 100644 ion/src/simulator/nspire/k_csdk.h create mode 100755 ion/src/simulator/nspire/k_defs.h create mode 100644 ion/src/simulator/nspire/keyboard.cpp create mode 100644 ion/src/simulator/nspire/keyboard.h create mode 100644 ion/src/simulator/nspire/main.cpp create mode 100644 ion/src/simulator/nspire/main.h create mode 100644 ion/src/simulator/nspire/platform.h create mode 100644 ion/src/simulator/nspire/power.cpp create mode 100644 ion/src/simulator/nspire/telemetry_init.cpp create mode 100644 ion/src/simulator/nspire/timing.cpp diff --git a/apps/Makefile b/apps/Makefile index 3aed3073532..b2e67a83283 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -50,6 +50,7 @@ apps_src += $(addprefix apps/,\ exam_mode_configuration_official.cpp:+official \ exam_mode_configuration_non_official.cpp:-official \ global_preferences.cpp \ + host_filemanager.cpp \ i18n.py \ lock_view.cpp \ main.cpp \ diff --git a/apps/calculation/calculation_store.cpp b/apps/calculation/calculation_store.cpp index f1bb4b940c8..eee89ac89ed 100644 --- a/apps/calculation/calculation_store.cpp +++ b/apps/calculation/calculation_store.cpp @@ -7,6 +7,39 @@ #include "../exam_mode_configuration.h" #include +#if defined _FXCG || defined NSPIRE_NEWLIB +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static KDCoordinate dummyHeight(::Calculation::Calculation * c, bool expanded) { + bool b; + Poincare::Layout l = c->createExactOutputLayout(&b); + if (!b) { + l=c->createInputLayout(); + } + KDSize s = l.layoutSize(); + const int bordersize = 10; + int h = s.height() + bordersize; + const int maxheight = 64; + if (h > maxheight) { + return maxheight; + } + return h; +} + +extern void * last_calculation_history; +void * last_calculation_history = 0; +const char * retrieve_calc_history(); + +#endif + using namespace Poincare; using namespace Shared; @@ -21,10 +54,39 @@ CalculationStore::CalculationStore(char * buffer, int size) : { assert(m_buffer != nullptr); assert(m_bufferSize > 0); +#if defined _FXCG || defined NSPIRE_NEWLIB + if (last_calculation_history == 0){ + // Restore from scriptstore + const char * buf=retrieve_calc_history(); + if (buf) { + Shared::GlobalContext globalContext; + char * ptr=(char *)buf; + for (;*ptr;) { + for (;*ptr;++ptr) { + if (*ptr=='\n') { + break; + } + } + char c = *ptr; + *ptr=0; + if (ptr > buf) { + push(buf,&globalContext, dummyHeight); + } + *ptr = c; + ++ptr; + buf = ptr; + } + } + last_calculation_history = (void *) this; + } +#endif } // Returns an expiring pointer to the calculation of index i, and ignore the trash ExpiringPointer CalculationStore::calculationAtIndex(int i) { +#if defined _FXCG || defined NSPIRE_NEWLIB + last_calculation_history = (void *) this; +#endif if (m_trashIndex == -1 || i < m_trashIndex) { return realCalculationAtIndex(i); } else { diff --git a/apps/code/app.cpp b/apps/code/app.cpp index 12833f6f835..8739030871c 100644 --- a/apps/code/app.cpp +++ b/apps/code/app.cpp @@ -5,6 +5,12 @@ #include #include +#if defined _FXCG || defined NSPIRE_NEWLIB +extern "C" int calculator; +extern "C" const int prizm_heap_size; +extern "C" char prizm_heap[]; +#endif + namespace Code { I18n::Message App::Descriptor::name() { @@ -150,7 +156,17 @@ bool App::textInputDidReceiveEvent(InputEventHandler * textInput, Ion::Events::E void App::initPythonWithUser(const void * pythonUser) { if (!m_pythonUser) { - MicroPython::init(m_pythonHeap, m_pythonHeap + k_pythonHeapSize); +#if defined _FXCG || defined NSPIRE_NEWLIB + if (calculator == 1) { // fxcg50 + MicroPython::init( (void *) 0x8c200000, (void *)(0x8c200000+ 0x2e0000)); + } else if (calculator >= 1 && calculator <=4 ) { + MicroPython::init( prizm_heap, prizm_heap+prizm_heap_size); + } else { +#endif + MicroPython::init(m_pythonHeap, m_pythonHeap + k_pythonHeapSize); +#if defined _FXCG || defined NSPIRE_NEWLIB + } +#endif } m_pythonUser = pythonUser; } diff --git a/apps/external/app/sample.c b/apps/external/app/sample.c index dbc13fd9545..a779bf2f42d 100644 --- a/apps/external/app/sample.c +++ b/apps/external/app/sample.c @@ -1,6 +1,15 @@ #include +#if defined _FXCG || defined NSPIRE_NEWLIB +// On the port, we use the Built-in file manager to import files. +void host_filemanager(); +void extapp_main() { + host_filemanager(); +} +#else +// Elsewhere, just draw a rectangle to test the extapp API. void extapp_main() { extapp_pushRectUniform(10, 10, LCD_WIDTH-20, LCD_HEIGHT-20, 0); extapp_msleep(1000); } +#endif diff --git a/apps/host_filemanager.cpp b/apps/host_filemanager.cpp new file mode 100644 index 00000000000..9c0bb70465e --- /dev/null +++ b/apps/host_filemanager.cpp @@ -0,0 +1,859 @@ +// #include +// // #include "../apps_container.h" +// #include "stddef.h" +// #include "string.h" +// #include "math.h" +// #include "app.h" +#include +#include "../apps_container.h" +#include "../global_preferences.h" +#include "../exam_mode_configuration.h" + +extern "C" { +#include +} + +#ifdef NSPIRE_NEWLIB +const char * storage_name="/documents/nwstore.nws.tns"; +#else +const char * storage_name="nwstore.nws"; +#endif + +const char * calc_storage_name="nwcalc.txt"; +void * storage_address(); // ion/src/simulator/shared/platform_info.cpp +const char * retrieve_calc_history(); +#if defined _FXCG || defined NSPIRE_NEWLIB +bool save_calc_history(); +void display_host_help(); +int load_state(const char * fname); +#endif + +// Additional code by B. Parisse for host file system support and persistence +// on Casio Graph 90/FXCG50 and TI Nspire +void erase_record(const char * name){ + unsigned char * ptr=(unsigned char *)storage_address(); + for (ptr+=4;;){ // skip header + size_t L=ptr[1]*256+ptr[0]; + if (L==0) return; + if (strcmp((const char *)ptr+2,name)==0){ + unsigned char * newptr=ptr; + int S=0,erased=L; + ptr+=L; + for (;;){ + L=ptr[1]*256+ptr[0]; + if (L==0){ + for (int i=0;inewptr+S) + memmove(newptr+S,ptr,L); + S+=L; + ptr+=L; + } + return; + } + ptr+=L; + } +} + + +// record filtering on read +void filter(unsigned char * ptr){ + unsigned char * newptr=ptr; + int S; ptr+=4; + for (S=4;;){ + size_t L=ptr[1]*256+ptr[0]; + if (L==0) break; + int l=strlen((const char *)ptr+2); + // filter py records + if (l>3 && strncmp((const char *)ptr+2+l-3,".py",3)==0){ + // if (ptr>newptr+S) + memmove(newptr+S,ptr,L); + S+=L; + } +#if 0 // def STRING_STORAGE + if (l>5 && strncmp((const char *)ptr+2+l-5,".func",5)==0){ + int shift=l+4+13; + Ion::Storage::Record * record=(Ion::Storage::Record *)malloc(1024); + memcpy(record,ptr,L); + //ExpressionModelHandle + Poincare::Expression e=Poincare::Expression::Parse((const char *)ptr+shift,NULL); + //ExpressionModel::setContent(Ion::Storage::Record * record, const char * c, Context * context, CodePoint symbol); + Shared::ExpressionModel md; + Ion::Storage::Record::ErrorStatus err=md.setExpressionContent(record, e); + if (1){ + // if (ptr>newptr+S) + int newL=record->value().size; + memmove(newptr+S,record,newL); + S+=newL; + } + free(record); + } +#endif + ptr+=L; + } +} + + +#ifdef NSPIRE_NEWLIB +#include "../../ion/src/simulator/fxcg/platform.h" +#include "../../ion/src/simulator/fxcg/menuHandler.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "../../ion/src/simulator/nspire/k_csdk.h" +#include "../calculation/calculation_store.h" + +#define C_WHITE SDK_WHITE +#define C_BLACK SDK_BLACK +#define C_RED (31<<11) + +int do_getkey(){ + os_wait_1ms(50); + return getkey(0); +} + +void dtext(int x,int y,int fg,const char * s){ + os_draw_string_medium(x,y,fg,SDK_WHITE,s); +} + +void dclear(int c){ + os_fill_rect(0,0,LCD_WIDTH_PX,LCD_HEIGHT_PX,c); +} + +void dupdate(){ + sync_screen(); +} + +const int storage_length=60000; // 60000 in Upsilon, 32768 in Epsilon +// k_storageSize = 60000; in ion/include/ion/internal_storage.h +extern void * last_calculation_history; + +int load_state(const char * fname){ + FILE * f=fopen(fname,"rb"); + if (f){ + unsigned char * ptr=(unsigned char *)storage_address(); + fread(ptr,1,storage_length,f); + fclose(f); +#ifdef FILTER_STORE + filter(ptr); +#endif + return 1; + } + return 0; +} + +int save_state(const char * fname){ + save_calc_history(); + if (1 || Ion::Storage::sharedStorage()->numberOfRecords()){ + const unsigned char * ptr=(const unsigned char *)storage_address(); + // find store size + int S=4; + for (ptr+=4;;){ // skip header + size_t L=ptr[1]*256+ptr[0]; + ptr+=L; + S+=L; + if (L==0) break; + } + S = ((S+1023)/1024)*1024; +#ifdef FILTER_STORE + // keep only python scripts + unsigned char * newptr=(unsigned char *) malloc(S); + bzero(newptr,S); + ptr=(const unsigned char *) storage_address(); + memcpy(newptr,ptr,4); ptr+=4; + for (S=4;;){ + size_t L=ptr[1]*256+ptr[0]; + if (L==0) break; + int l=strlen((const char *)ptr+2); + if (l>3 && strncmp((const char *)ptr+2+l-3,".py",3)==0){ + memcpy(newptr+S,ptr,L); + S+=L; + } + ptr+=L; + } + S = ((S+1023)/1024)*1024; + FILE * f; + f=fopen(fname,"wb"); + if (f){ + fwrite(newptr,S,1,f); + //fwrite(ptr+4,1,S-4,f); + fclose(f); + free(newptr); + return S; + } + free(newptr); + return 0; +#else + ptr=(const unsigned char *)storage_address(); + FILE * f; + f=fopen(fname,"wb"); + if (f){ + fwrite(ptr,S,1,f); + //fwrite(ptr+4,1,S-4,f); + fclose(f); + return S; + } + return 0; +#endif + } + return 2; +} + +#endif // NSPIRE_NEWLIB + +#ifdef _FXCG +#include "../../ion/src/simulator/fxcg/platform.h" +#include "../../ion/src/simulator/fxcg/menuHandler.h" +#include "../calculation/calculation_store.h" +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#define KEY_CTRL_OK KEY_EXE +int do_getkey(){ + return getkey().key; +} + +const int storage_length=60000; // 60000 in Upsilon, 32768 in Epsilon +// k_storageSize = 60000; in ion/include/ion/internal_storage.h +extern void * last_calculation_history; + +int load_state(const char * fname){ + FILE * f=fopen(fname,"rb"); + if (f){ + unsigned char * ptr=(unsigned char *)storage_address(); + ptr[3]=fgetc(f); + ptr[2]=fgetc(f); + ptr[1]=fgetc(f); + ptr[0]=fgetc(f); + fread(ptr+4,1,storage_length-4,f); + fclose(f); + return 1; + } + return 0; +} + +int save_state(const char * fname){ + save_calc_history(); + if (Ion::Storage::sharedStorage()->numberOfRecords()){ +#if 0 + unsigned short pFile[512]; + convert(fname,pFile); + int hf = BFile_Open(pFile, BFile_WriteOnly); // Get handle + // cout << hf << endl << "f:" << filename << endl; Console_Disp(); + if (hf<0){ + int l=storage_length; + BFile_Create(pFile,0,&l); + hf = BFile_Open(pFile, BFile_WriteOnly); + } + if (hf < 0) + return 0; + int l=BFile_Write(hf,storage_address(),storage_length); + BFile_Close(hf); + if (l==storage_length) + return 1; + return -1; +#else + const unsigned char * ptr=(const unsigned char *)storage_address(); + // find store size + int S=4; + for (ptr+=4;;){ // skip header + size_t L=ptr[1]*256+ptr[0]; + ptr+=L; + S+=L; + if (L==0) break; + } + S = ((S+1023)/1024)*1024; + FILE * f=fopen(fname,"wb"); + if (f){ + ptr=(const unsigned char *) storage_address(); + fputc(ptr[3],f); + fputc(ptr[2],f); + fputc(ptr[1],f); + fputc(ptr[0],f); + //fwrite(ptr+4,1,S-4,f); + fwrite(ptr+4,S-4,1,f); + fclose(f); + return S; + } + return 0; +#endif + } + return 2; +} +#endif // _FXCG + +const char * retrieve_calc_history(){ +#if defined _FXCG || defined NSPIRE_NEWLIB + static bool firstrun=true; + if (firstrun){ +#ifdef _FXCG + int l=gint_world_switch(GINT_CALL(load_state,storage_name)); +#else + int l=load_state(storage_name); +#endif + if (l==0){ + display_host_help(); + // ((App*)m_app)->redraw(); + } + firstrun=false; + } +#endif + unsigned char * ptr=(unsigned char *)storage_address(); + for (ptr+=4;;){ // skip header + size_t L=ptr[1]*256+ptr[0]; + if (L==0) return 0; + if (strcmp((const char *)ptr+2,calc_storage_name)==0){ + const char * buf=(const char *)ptr+2+strlen(calc_storage_name)+1; + return buf; + } + ptr += L; + } + return 0; +} + +#if defined _FXCG || defined NSPIRE_NEWLIB +bool save_calc_history(){ + if (!last_calculation_history) + return false; + erase_record(calc_storage_name); + std::string s; + Calculation::CalculationStore * store=(Calculation::CalculationStore *) last_calculation_history; + int N=store->numberOfCalculations(); + for (int i=N-1;i>=0;--i){ + s += store->calculationAtIndex(i)->inputText(); + s += '\n'; + } + if (s.empty()) + return false; + Ion::Storage::Record::ErrorStatus res= Ion::Storage::sharedStorage()->createRecordWithFullName(calc_storage_name,&s[0],s.size()); + if (res==Ion::Storage::Record::ErrorStatus::NotEnoughSpaceAvailable) + return false; + return true; +} + +void confirm_load_state(const char * buf){ + dclear(C_WHITE); + dtext(1,1, C_BLACK, "Loading from state file"); + dtext(1,17,C_BLACK,buf); + dtext(1,33,C_BLACK,"Current context will be lost!"); + dtext(1,49,C_BLACK,"Press EXE to confirm"); + dupdate(); + int k=do_getkey(); + if (k==KEY_EXE || k==KEY_CTRL_OK){ +#ifdef _FXCG + int l=gint_world_switch(GINT_CALL(load_state,buf)); +#else + int l=load_state(buf); +#endif + char buf2[]="0"; + buf2[0] += l; + if (l==0) + dtext(1,65,C_BLACK,"Error reading state"); + if (l==1) + dtext(1,65,C_BLACK,"Success reading state"); + dtext(1,81,C_BLACK,buf2); + dtext(1,97,C_BLACK,"Press any key"); + dupdate(); + do_getkey(); + } +} + +static void convert(const char * fname,unsigned short * pFile){ + for ( ;*fname;++fname,++pFile) + *pFile=*fname; + *pFile=0; +} + +struct file { + std::string s; + int length; + bool isdir; +}; + +void host_scripts(std::vector & v,const char * dirname,const char * extension){ + v.clear(); + file f={".._parent_dir",0,true}; + if (strlen(dirname)>1) + v.push_back(f); + DIR *dp; + struct dirent *ep; + dp = opendir (dirname); + int l=extension?strlen(extension):0; + if (dp != NULL){ + int t; + while ( (ep = readdir (dp)) ){ + if (strlen(ep->d_name)>=1 && ep->d_name[0]=='.') + continue; + f.s=ep->d_name; + if (f.s=="@MainMem") + continue; +#ifdef NSPIRE_NEWLIB + DIR * chk=opendir((dirname+f.s).c_str()); + f.isdir=true; + if (chk) + closedir(chk); + else + f.isdir=false; +#else + f.isdir=ep->d_type==DT_DIR; +#endif +#if 1 + if (f.isdir) + f.length=0; + else { + struct stat st; + stat((dirname+f.s).c_str(), &st); + f.length = st.st_size; + if (f.length>=32768) + continue; + } +#else + f.length=f.isdir?0:-1; +#endif + if (f.isdir || !extension) + v.push_back(f); + else { + t=strlen(ep->d_name); + if (t>l && strncmp(ep->d_name+t-l,extension,l)==0) + v.push_back(f); + } + } + closedir (dp); + } +} + +void nw_scripts(std::vector & v,const char * extension){ + v.clear(); +#if 0 + int n=Ion::Storage::sharedStorage()->numberOfRecords(); + for (int i=0;irecordAtIndex(i).fullName()); + } +#else + const unsigned char * ptr=(const unsigned char *)storage_address(); + int l=extension?strlen(extension):0; + for (ptr+=4;;){ // skip header + size_t L=ptr[1]*256+ptr[0]; + ptr+=2; + if (L==0) break; + L-=2; + file f={(const char *)ptr,(int)L,false}; + if (!extension) + v.push_back(f); + else { + int namesize=strlen((const char *)ptr); + if (namesize>l && strncmp((const char *)ptr+namesize-l,extension,l)==0) + v.push_back(f); + } + ptr+=L; + } +#endif +} + +int copy_nw_to_host(const char * nwname,const char * hostname){ +#ifdef NSPIRE_NEWLIB + int s=strlen(hostname); + if (s<4 || strncmp(hostname+s-4,".tns",4)){ + std::string S(hostname); + S+=".tns"; + return copy_nw_to_host(nwname,S.c_str()); + } +#endif + const unsigned char * ptr=(const unsigned char *)storage_address(); + for (ptr+=4;;){ // skip header + size_t L=ptr[1]*256+ptr[0]; + if (L==0) return 3; // not found + //dclear(C_WHITE); + //dtext(1,1,C_BLACK,ptr+2); + //dtext(1,17,C_BLACK,nwname); + //dupdate(); + //getkey(); + if (strcmp((const char *)ptr+2,nwname)){ + ptr += L; + continue; + } + ptr+=2; + L-=2; + int l=strlen((const char *)ptr); + ptr += l+2; + L -= l; + L = 2*((L+1)/2); + FILE * f=fopen(hostname,"wb"); + if (!f) + return 2; + fwrite(ptr,1,L,f); + fclose(f); + return 0; + } + return 1; +} + +int copy_host_to_nw(const char * hostname,const char * nwname,int autoexec){ + FILE * f=fopen(hostname,"rb"); + if (!f) + return -1; + std::vector v(1,autoexec?1:0); + for (;;){ + unsigned char c=fgetc(f); + if (feof(f)){ + if (c>=' ' && c<=0x7e) + v.push_back(c); + break; + } + if (c==0xa && !v.empty() && v.back()==0xd) + v.back()=0xa; + else + v.push_back(c); + } + if (!v.empty() && v.back()!=0xa) + v.push_back(0xa); + v.push_back(0); + fclose(f); + if (Ion::Storage::sharedStorage()->hasRecord(Ion::Storage::sharedStorage()->recordNamed(nwname))) + Ion::Storage::sharedStorage()-> destroyRecord(Ion::Storage::sharedStorage()->recordNamed(nwname)); + Ion::Storage::Record::ErrorStatus res= Ion::Storage::sharedStorage()->createRecordWithFullName(nwname,&v.front(),v.size()); + if (res==Ion::Storage::Record::ErrorStatus::NotEnoughSpaceAvailable) + return -2; + return 0; +} + +bool filesort(const file & a,const file & b){ + if (a.isdir!=b.isdir) + return a.isdir; + return a.s v,w; +#ifdef NSPIRE_NEWLIB + std::string hostdir="/documents/"; +#else + std::string hostdir="/"; +#endif + bool onlypy=true; + for (;;){ + if (reload){ + nw_scripts(v,onlypy?".py":0); + sort(v.begin(),v.end(),filesort); +#ifdef NSPIRE_NEWLIB + host_scripts(w,hostdir.c_str(),onlypy?".py.tns":0); +#else + host_scripts(w,hostdir.c_str(),onlypy?".py":0); +#endif + sort(w.begin(),w.end(),filesort); + reload=false; + } + dclear(C_WHITE); + dtext(1,1, C_BLACK,"EXIT: leave; key 1 to 9: load state from file"); +#ifdef _FXCG + dtext(1,17,C_BLACK,"Cursor keys: move, /: rootdir, OPTN: all/py files"); +#else + dtext(1,17,C_BLACK,"Cursor keys: move, /: rootdir"); +#endif + dtext(1,33,C_BLACK,"EXE or STO key: copy selection from/to host"); + dtext(1,49,C_BLACK,("Upsilon records Host "+hostdir).c_str()); + int nitems=9; + if (posnw<0) + posnw=v.size()-1; + if (posnw>=int(v.size())) + posnw=0; + if (posnwstartnw+nitems) + startnw=posnw-4; + if (startnw>=int(v.size())-nitems) + startnw=v.size()-nitems; + if (startnw<0) + startnw=0; + if (v.empty()) + nw=false; + for (int i=0;i<=nitems;++i){ + int I=i+startnw; + if (I>=int(v.size())) + break; + dtext(1,65+16*i,(nw && I==posnw)?C_RED:C_BLACK,v[I].s.c_str()); + char buf[256]; + sprintf(buf,"%i",v[I].length); + dtext(90,65+16*i,(nw && I==posnw)?C_RED:C_BLACK,buf); + } + if (w.empty()) + nw=true; + if (poshost<0) + poshost=w.size()-1; + if (poshost>=int(w.size())) + poshost=0; + if (poshoststarthost+nitems) + starthost=poshost-4; + if (starthost>=int(w.size())-nitems) + starthost=w.size()-nitems; + if (starthost<0) + starthost=0; + for (int i=0;i<=nitems;++i){ + int I=i+starthost; + if (I>=int(w.size())) + break; + std::string fname=w[I].s; + if (fname.size()>16) + fname=fname.substr(0,16)+"..."; + dtext(192,65+16*i,(!nw && I==poshost)?C_RED:C_BLACK,fname.c_str()); + if (w[I].isdir) + dtext(154,65+16*i,(!nw && I==poshost)?C_RED:C_BLACK,""); + else { + char buf[256]; + sprintf(buf,"%i",w[I].length); +#ifdef _FXCG + dtext(340,65+16*i,(!nw && I==poshost)?C_RED:C_BLACK,buf); +#else + dtext(285,65+16*i,(!nw && I==poshost)?C_RED:C_BLACK,buf); +#endif + } + } + dupdate(); + int key=do_getkey(); + if (key==KEY_EXIT || key==KEY_MENU) + break; + if (key==KEY_OPTN || key=='\t'){ + onlypy=!onlypy; + reload=true; + continue; + } + if (key==KEY_DIV){ +#ifdef NSPIRE_NEWLIB + hostdir="/documents/"; +#else + hostdir="/"; +#endif + reload=true; + continue; + } + if (key==KEY_DEL){ + if (!nw && w[poshost].isdir) // can not remove directory + continue; + dclear(C_WHITE); + dtext(1,17,C_BLACK,nw?"About to suppress Upsilon record:":"About to suppress Host file:"); + dtext(1,33,C_BLACK,(nw?v[posnw].s:w[poshost].s).c_str()); + dtext(1,49,C_BLACK,"Press EXE or OK to confirm"); + dupdate(); + int ev=do_getkey(); + if (ev!=KEY_EXE && ev!=KEY_CTRL_OK) + continue; + if (nw){ +#if 1 + erase_record(v[posnw].s.c_str()); +#else + char buf[256]; + strcpy(buf,v[posnw].s.c_str()); + int l=strlen(buf)-4; + buf[l]=0; + Ion::Storage::sharedStorage()-> destroyRecordWithBaseNameAndExtension(buf,buf+l+1); +#endif + } + else + remove((hostdir+w[poshost].s).c_str()); + reload=true; + } + if (key==KEY_LEFT){ + nw=true; + continue; + } + if (key==KEY_RIGHT){ + nw=false; + continue; + } + if (key==KEY_PLUS){ + if (nw) + posnw+=5; + else + poshost+=5; + continue; + } + if (key==KEY_MINUS){ + if (nw) + posnw-=5; + else + poshost-=5; + continue; + } + if (key==KEY_DOWN){ + if (nw) + ++posnw; + else + ++poshost; + continue; + } + if (key==KEY_UP){ + if (nw) + --posnw; + else + --poshost; + continue; + } + int autoexec = key==KEY_EXE || key==KEY_CTRL_OK; + if (key==KEY_STORE || autoexec){ + if (nw && posnw>=0 && posnw=0 && poshost=0;--j){ + if (hostdir[j]=='/'){ + hostdir=hostdir.substr(0,j+1); + break; + } + } + reload=true; + continue; + } + // lookup if poshost is in directories + if (w[poshost].isdir){ + hostdir += w[poshost].s; + hostdir += "/"; + reload=true; + continue; + } + size_t i; + std::string fname=w[poshost].s; +#ifdef NSPIRE_NEWLIB + if (fname.size()>4 && fname.substr(fname.size()-4,4)==".tns") + fname=fname.substr(0,fname.size()-4); +#endif + for (i=0;i12){ + dclear(C_WHITE); + dtext(1,33,C_BLACK,"Host filename too long"); + dtext(1,49,C_BLACK,fname.c_str()); + dupdate(); + do_getkey(); + continue; + } + if (fname.size()>4 && fname.substr(fname.size()-4,4)==".nws") + confirm_load_state((hostdir+fname).c_str()); + else { +#ifdef _FXCG + gint_world_switch(GINT_CALL(copy_host_to_nw,(hostdir+fname).c_str(),nwname.c_str(),autoexec)); +#else + copy_host_to_nw((hostdir+w[poshost].s).c_str(),nwname.c_str(),autoexec); +#endif + } + reload=true; + } + } + if (key>=KEY_1 && key<=KEY_9){ +#ifdef NSPIRE_NEWLIB + char buf[]="nwstate0.nws.tns"; +#else + char buf[]="nwstate0.nws"; +#endif + buf[7]='1'+(key-KEY_1); + confirm_load_state(buf); + reload=true; + } + } +} +#endif // FXCG || NSPIRE \ No newline at end of file diff --git a/apps/shared/expression_model.cpp b/apps/shared/expression_model.cpp index ab7dd0d0933..d37e6b4cada 100644 --- a/apps/shared/expression_model.cpp +++ b/apps/shared/expression_model.cpp @@ -72,7 +72,7 @@ Expression ExpressionModel::expressionReduced(const Storage::Record * record, Po if (isCircularlyDefined(record, context)) { m_expression = Undefined::Builder(); } else { - m_expression = Expression::ExpressionFromAddress(expressionAddress(record), expressionSize(record)); + m_expression = Expression::ExpressionFromAddress(expressionAddress(record), expressionSize(record), record); /* 'Simplify' routine might need to call expressionReduced on the very * same function. So we need to keep a valid m_expression while executing * 'Simplify'. Thus, we use a temporary expression. */ @@ -90,7 +90,7 @@ Expression ExpressionModel::expressionReduced(const Storage::Record * record, Po Expression ExpressionModel::expressionClone(const Storage::Record * record) const { assert(record->fullName() != nullptr); /* A new Expression has to be created at each call (because it might be tempered with after calling) */ - return Expression::ExpressionFromAddress(expressionAddress(record), expressionSize(record)); + return Expression::ExpressionFromAddress(expressionAddress(record), expressionSize(record), record); /* TODO * The substitution of UCodePointUnknown back and forth is done in the * methods text, setContent (through BuildExpressionFromText), layout and @@ -125,6 +125,39 @@ Ion::Storage::Record::ErrorStatus ExpressionModel::setExpressionContent(Ion::Sto Ion::Storage::Record::Data newData = record->value(); size_t previousExpressionSize = expressionSize(record); size_t newExpressionSize = newExpression.isUninitialized() ? 0 : newExpression.size(); +#ifdef STRING_STORAGE + size_t stringsize = 0; + char buf[1024] = {0}; + char repl = 0; + buf[0] = '"'; + if (!newExpression.isUninitialized()) { + size_t l = newExpression.serialize(buf+1,sizeof(buf)-2); + if (l >= sizeof(buf) - 3) { + newExpressionSize = 0; + } else { + buf[l + 1] = '"'; + // replace 0x1 by x for func or n for seq + const char * name = record->fullName(); + int namel = strlen(name); + if (namel > 4 && strncmp(name + namel - 4, ".seq", 4) == 0) { + repl = 'n'; + } else if (namel > 5 && strncmp(name + namel - 5, ".func", 5) == 0) { + repl = 'x'; + } + if (repl) { + for (char * ptr = buf; *ptr; ++ptr) { + if (*ptr == 1) { + *ptr = repl; + } + } + } + stringsize= l + 3; // 2 quotes and 0 at end + if (newExpressionSize < stringsize) { + newExpressionSize = stringsize; + } + } + } +#endif size_t previousDataSize = newData.size; size_t newDataSize = previousDataSize - previousExpressionSize + newExpressionSize; void * expAddress = expressionAddress(record); @@ -140,6 +173,12 @@ Ion::Storage::Record::ErrorStatus ExpressionModel::setExpressionContent(Ion::Sto * (as it is sometimes computed from metadata). Thus, the expression address * is given as a parameter to updateNewDataWithExpression. */ updateNewDataWithExpression(record, newExpression, expAddress, newExpressionSize, previousExpressionSize); +#ifdef STRING_STORAGE + if (stringsize && stringsizesetValue(newData); diff --git a/build/platform.simulator.nspire.mak b/build/platform.simulator.nspire.mak new file mode 100644 index 00000000000..d158bbc09c9 --- /dev/null +++ b/build/platform.simulator.nspire.mak @@ -0,0 +1,11 @@ +TOOLCHAIN = nspire-gcc +EXE = elf + +EPSILON_TELEMETRY ?= 0 + +HANDY_TARGETS_EXTENSIONS = tns + +USE_LIBA = 0 +POINCARE_TREE_LOG = 0 + +SFLAGS := $(filter-out -fPIE, $(SFLAGS)) diff --git a/build/targets.simulator.nspire.mak b/build/targets.simulator.nspire.mak new file mode 100644 index 00000000000..574bb4d24ef --- /dev/null +++ b/build/targets.simulator.nspire.mak @@ -0,0 +1,6 @@ +$(BUILD_DIR)/%.tns: $(BUILD_DIR)/%.elf +# comment one of these lines. For B&W old nspire, do not use the compress option + genzehn --compress --input $(BUILD_DIR)/epsilon.elf --output upsilon.tns --name "upsilon" --uses-lcd-blit true +# genzehn --input $(BUILD_DIR)/epsilon.elf --output upsilon.tns --name "upsilon" --uses-lcd-blit true + genzehn --info --input upsilon.tns + firebird-send upsilon.tns /ndless diff --git a/build/toolchain.nspire-gcc.mak b/build/toolchain.nspire-gcc.mak new file mode 100644 index 00000000000..fe8518942d9 --- /dev/null +++ b/build/toolchain.nspire-gcc.mak @@ -0,0 +1,9 @@ +CC = nspire-gcc +CXX = nspire-g++ +LD = nspire-g++ +GDB = gdb +OBJCOPY = nspire-objcopy +SIZE = nspire-size +AS = nspire-as + +SFLAGS += -DNSPIRE_NEWLIB -DSTRING_STORAGE diff --git a/build/toolchain.sh-elf-gcc.mak b/build/toolchain.sh-elf-gcc.mak index 54290ffcec9..43432065c1b 100644 --- a/build/toolchain.sh-elf-gcc.mak +++ b/build/toolchain.sh-elf-gcc.mak @@ -7,4 +7,4 @@ SIZE = sh-elf-size AS = sh-elf-as FXGXA = fxgxa -SFLAGS += -D_FXCG -D_BIG_ENDIAN +SFLAGS += -D_FXCG -D_BIG_ENDIAN -DSTRING_STORAGE diff --git a/ion/include/ion/internal_storage.h b/ion/include/ion/internal_storage.h index fcad00c90d8..026ae5dc04d 100644 --- a/ion/include/ion/internal_storage.h +++ b/ion/include/ion/internal_storage.h @@ -22,8 +22,11 @@ class InternalStorage { static constexpr char expExtension[] = "exp"; static constexpr char funcExtension[] = "func"; static constexpr char seqExtension[] = "seq"; - +#ifdef _FXCG + constexpr static size_t k_storageSize = 65500; +#else constexpr static size_t k_storageSize = 60000; +#endif static_assert(UINT16_MAX >= k_storageSize, "record_size_t not big enough"); constexpr static char k_dotChar = '.'; @@ -195,7 +198,7 @@ class StorageDelegate { class StorageHelper { public: static uint16_t unalignedShort(char * address) { -#if (defined __EMSCRIPTEN__) || (defined _FXCG) +#if (defined __EMSCRIPTEN__) || (defined _FXCG) || defined NSPIRE_NEWLIB uint8_t f1 = *(address); uint8_t f2 = *(address+1); uint16_t f = (uint16_t)f1 + (((uint16_t)f2)<<8); @@ -205,7 +208,7 @@ class StorageHelper { #endif } static void writeUnalignedShort(uint16_t value, char * address) { -#if (defined __EMSCRIPTEN__) || (defined _FXCG) +#if (defined __EMSCRIPTEN__) || (defined _FXCG) || defined NSPIRE_NEWLIB *((uint8_t *)address) = (uint8_t)(value & ((1 << 8) - 1)); *((uint8_t *)address+1) = (uint8_t)(value >> 8); #else diff --git a/ion/src/simulator/external/config.nspire.mak b/ion/src/simulator/external/config.nspire.mak new file mode 100644 index 00000000000..5c9e873ab47 --- /dev/null +++ b/ion/src/simulator/external/config.nspire.mak @@ -0,0 +1,2 @@ +undefine sdl_src +undefine ion_simulator_sdl_src diff --git a/ion/src/simulator/fxcg/keyboard.cpp b/ion/src/simulator/fxcg/keyboard.cpp index 735b72e1f5c..12fc46ccc7d 100644 --- a/ion/src/simulator/fxcg/keyboard.cpp +++ b/ion/src/simulator/fxcg/keyboard.cpp @@ -221,11 +221,11 @@ State scan() { if (menuHeldFor > 30) { Simulator::FXCGMenuHandler::openMenu(); dupdate(); - // Wait until EXE is released + // Wait until EXE and MENU are released do { sleep_ms(10); clearevents(); - } while (keydown(KEY_EXE)); + } while (keydown(KEY_EXE) || keydown(KEY_MENU)); } } else { menuHeldFor = 0; @@ -259,4 +259,4 @@ namespace Keyboard { } } -} \ No newline at end of file +} diff --git a/ion/src/simulator/fxcg/main.cpp b/ion/src/simulator/fxcg/main.cpp index 9280dbc1253..3add310d4fb 100644 --- a/ion/src/simulator/fxcg/main.cpp +++ b/ion/src/simulator/fxcg/main.cpp @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include @@ -13,15 +15,60 @@ #include #include +#include "menuHandler.h" + +using namespace Ion::Simulator::Main; + +constexpr static const char * storage_name="nwstore.nws"; + +int save_state(const char * fname); // apps/home/controller.cpp extern "C" { -int main() { - Ion::Simulator::Main::init(); - ion_main(0, NULL); - Ion::Simulator::Main::quit(); + extern const int prizm_heap_size; + const int prizm_heap_size = 192 * 1024; + __attribute__((aligned(4))) char prizm_heap[prizm_heap_size]; + + CalculatorType calculator = CalculatorType::Unchecked; + + int main() { + /* Allow the user to use memory past the 2 MB line on tested OS versions */ + char const * os_version = (char const *)0x80020020; + if (!strncmp(os_version, "03.", 3) && os_version[3] <= '8'){ // 3.80 or earlier + char buf[256]; + strncpy(buf,os_version,8); + buf[8]=0; + } else { + char buf1[10], buf[256]; + // sprintf(buf,"%i",availram); + strncpy(buf1,os_version,8); + buf1[8]=0; + sprintf(buf,"OS %s not checked",buf1); + calculator = CalculatorType::Unchecked; + dclear(C_WHITE); + dtext(1,10,C_BLACK,buf); + dtext(1,27,C_BLACK,"F6: continue anyway"); + dupdate(); + int key = getkey().key; + if (key != KEY_F6){ + Ion::Simulator::FXCGMenuHandler::openMenu(); + } + } + bool is_emulator = *(volatile uint32_t *)0xff000044 == 0x00000000; + uint32_t stack; + __asm__("mov r15, %0" : "=r"(stack)); + // Check if the calculator is a Prizm or an emulator + if (stack < 0x8c000000) { + calculator = is_emulator ? CalculatorType::Emulator : CalculatorType::Other; + } else { + calculator = CalculatorType::Physical; // 0x8c200000, size 0x300000 is free + } - return 0; -} + Ion::Simulator::Main::init(); + ion_main(0, nullptr); + Ion::Simulator::Main::quit(); + + return 0; + } } namespace Ion { @@ -53,7 +100,7 @@ void quit() { Ion::Simulator::Display::quit(); } -void EnableStatusArea(int opt) { +void EnableStatusArea(int /*opt*/) { __asm__ __volatile__ ( ".align 2 \n\t" "mov.l 2f, r2 \n\t" @@ -106,6 +153,9 @@ void worldSwitchHandler(void (*worldSwitchFunction)(), bool prepareVRAM) { } void runPowerOffSafe(void (*powerOffSafeFunction)(), bool prepareVRAM) { + + gint_world_switch(GINT_CALL(save_state,storage_name)); + gint_world_switch(GINT_CALL(worldSwitchHandler, powerOffSafeFunction, prepareVRAM)); } diff --git a/ion/src/simulator/fxcg/main.h b/ion/src/simulator/fxcg/main.h index 4ac441a065f..41807fae825 100644 --- a/ion/src/simulator/fxcg/main.h +++ b/ion/src/simulator/fxcg/main.h @@ -1,5 +1,6 @@ #ifndef ION_SIMULATOR_MAIN_H #define ION_SIMULATOR_MAIN_H +#include namespace Ion { namespace Simulator { @@ -13,6 +14,14 @@ void refresh(); void runPowerOffSafe(void (*powerOffSafeFunction)(), bool prepareVRAM); +enum class CalculatorType : uint8_t { + Unchecked = (uint8_t)-1, + Unknown = 0, + Physical = 1, + Emulator = 2, + Other = 3 +}; + } } } diff --git a/ion/src/simulator/nspire/Makefile b/ion/src/simulator/nspire/Makefile new file mode 100644 index 00000000000..3d6d70f7ae9 --- /dev/null +++ b/ion/src/simulator/nspire/Makefile @@ -0,0 +1,53 @@ + +ion_src += $(addprefix ion/src/simulator/nspire/, \ + main.cpp \ + clipboard.cpp \ + display.cpp \ + framebuffer.cpp \ + telemetry_init.cpp \ + keyboard.cpp \ + events_keyboard.cpp \ + events.cpp \ + timing.cpp \ + console.cpp \ + backlight.cpp \ + power.cpp \ + k_csdk.c \ +) + +liba_src += $(addprefix liba/src/, \ + strlcat.c \ + strlcpy.c \ +) + +ion_src += ion/src/shared/collect_registers.cpp + +sdl_simu_needs_to_be_removed += $(addprefix ion/src/simulator/shared/, \ + main.cpp \ + clipboard.cpp \ + display.cpp \ + framebuffer.cpp \ + keyboard.cpp \ + events_keyboard.cpp \ + events_platform.cpp \ + events.cpp \ + layout.cpp \ + actions.cpp \ + window.cpp \ + timing.cpp \ + console.cpp \ +) + +sdl_simu_needs_to_be_removed += $(addprefix ion/src/shared/dummy/, \ + backlight.cpp \ + power.cpp \ +) + +# Remove the dummy diaplay (re-implemented) and the SDL simulator stuff. +ion_src := $(filter-out $(sdl_simu_needs_to_be_removed),$(ion_src)) + +SFLAGS := $(filter-out -Iion/src/simulator/external/sdl/include,$(SFLAGS)) + +SFLAGS += -marm -DNSPIRE_NEWLIB -I/Users/parisse/Ndless-r2015/ndless-sdk/ndless/include -I/Users/parisse/Ndless-r2015/ndless-sdk/toolchain/install/include -fno-strict-aliasing -I. -I.. -fno-exceptions -ffreestanding -nostdlib -fstrict-volatile-bitfields -g -Os +LDFLAGS += -Wl,--nspireio,--gc-sections -L/Users/parisse/Ndless-r2015/ndless-sdk/lib -L/Users/parisse/Ndless-r2015/ndless-sdk/toolchain/install/lib -nostdlib -lc -lm -lstdc++ -lgcc + diff --git a/ion/src/simulator/nspire/assets/icon-sel.png b/ion/src/simulator/nspire/assets/icon-sel.png new file mode 100644 index 0000000000000000000000000000000000000000..a016c36b39d406e9d21390432635d2daceff1552 GIT binary patch literal 3588 zcmV+f4*T(mP) z62R|084ld5RI=Bjg;0K7Si<6>@l=#1-&?3fz<9@um_qclp z`0Hh+njLXK)hr{OObFTBiV%855S{473`Qkp>a(Jlg6H_UhmWs!F`ngp?$6PqZd&xywjx*+i**JYRAI2RrE^UR2mNzV~Sh=pPs%Wcd` zhDtn1992|}^8Hzt70z3n)pC_J@5x^n&TGp{T&FpNBo?p;5dvgXv4Juy#Aww>F_EVI zgol67@r&e=$+ZDSj(Jp|LUR1zfAG6ovoJC4CWR9~@Wr-2hJny7(5Tt=_pxm^PJqBO zaHX~Ul{zr}NqViNMUQ~KZQ$a%rOA81@v!VI{OH$nv+5knoDlvN0yHvo`VAiQ}r7v8OmpRnJ#nf8FkJV!HvC zWcRqI-QD%-^{ZE3*H@9X*4{9{)kR=(5x8wos%QWS+ls{iMga(ow$3_K$3K~!A-i)Wv*B1n$p3pz9;Lz#3*oS40v)Bh~CuJUcL?7SaiS` zc`IGT*P~1Wr1wD^K&X-bj_&6TMCz0aLV9ns0R*;~gj~<cDMA?q)xs`DMyRZCHL7#^8zy>7(VS9!p381tC*nw{}p!m~3 zwxz%pZs@fJ@ca}IPcD*5`3fThTS>JuVdbQ!HX|(v&t)K=Qz_C=s@DL#c3hP*6bAv@ z0Uz9?+p46uU%@p%&aru5D~VmST28j(MH5!a_)2nMkUe{PFDe8b63dB<_@@W1VQCF~ zBWLs;p`=LC65yQ>Lc5#O021~%8ZRx5*(S-+%p|Gtx<9jyHR zDmt^%ICJ&_aMEC>tY6wg3Ujklu+eXD=lld(d-sQ3lmcQ1on+_w6zoz1`|C%5<__3p z%V5i3{NNGnzSqFP+fzNsM9(J1ULz^!cJ>yAhxY-UwH*}uEOBQIdq?)6K7T9K5sJg9#XJ$~yR+Qo3Xu4Q05uU`p)(wOQaB0}L1jw%JC8 zTWfJ__%6Qq*-rx2?TmmgeCx$h|NqTrKj_Z{Ai{SpzlzsyHny)qWE`VXhUbjjx1%gC z&iC*FU?cKJnFdRLqDpB>py6OK6SGZ->-%>x_pj^!b5cSTqwB+o9o()?v(?nNJv9ky zJ5EL{dL%8+)~JGj(>~Dvhz*Hnq!GL=ENiQ6%-;AL)>hk0&pm*)kF#~fB(fD_IeJ0B z*hdM|PID_e$xa8i|M?o)O$^dcT6A?zYHl@|? z-^JYY>$%@g&&>gc#5cIU8k6?>>j6xdnXb>p#To+(OrRM9zdn zKtz0YM6Pvnei`3>{x7(<(yFcxS88)Q%_6T|woK2=W)y^qr-()#th*OgHfHa5}W#La+V*QoKwnAq zd+m*IJPL+9_^)BPQNV!W4y6x@KP<1^Px;7azLI|4y>CCpj-NvmRU3To!IhVkjdYru z;kElIyOI$+di3p;2HO}Zma+j!*=(=9UKtqNh4GW;5!Y(fmJ*d^xj45lN``^K!EN)} zg$f?mFn;np1_pOk7U;EsL9})aXS{e2Eu^4F$BrNx8Uo_lws`HLGIsndb|oW~1-ld1 zuv!~J47v92S^|x(XWrp|EF;YL6n!+lU z#r@-G4Jqi|`w%Y<;*C}EVqQ79{`@s-!KN$wG`Do*DWqvb0ou3>JOw1Ax7DIA5;BNs z?1pC}(>zg@tI4P26?OD!`nS8WAQjx-tWStVFO9I!;-UXE@ZLxh+3CuLk37j+A(|wa zrZAfBJ!*uhjr^JN9fu*;ks6Y}(B`?CN{Ul(p#8l$fPGS|cH1=3)hERIJWQ6GNw-CL zn)xH1a6*tcT{&eo13ic&vgFu={78S(j)R_G6)-$)C?VySvRtY(ZFI_T%4+Byz?9*N zWgq+F(_2k?bEd>hWR}27@e{MyQjmGbR!G54N@!bxFfc~X&(pf;Js6Xp`Yk=q!Nf5w zGyqSRX3RBYm5EH{XzI5+Mp|TnKhY`p>AVzn!+eng@X;r!sYV~60Jf+>9>eLi6^c{8 z3*Sy9WI+Rj8lLXZgwc|A>tab!D3SiHxDTf+X8&y@DIsi2(Cs);lI00{jI9Hxd-k#( zNC_)UWNyBMh$acUzHbEG`@W9eDiY!&OB%xKJpWvjXU_=|D@jraBcZYY(6b~Z39iZU zb$5=2ai1N6et$i0CV@d_u7^}xX-=xvJ;&mIv=*4mfhT`^od@L4o+f2I zbp{(`HwBwYkOwvcHno7{zRJ!hpdbdal7vie@D5ZL*qxjOY9MJiXE(u5jHU2FiORWx zE(w5U;A;XjXL~)zYZ=4bNSrKVed5Q04ErDs2odZ z`jZvT_=XAfT+~1*(W?aT^+c8y>RcKcXds8-Y&4oyTW3d`0lSUtP%;e9TklHold#Gr zB_*&391naTDTY}>$gD3KRdZ(3Ro;?B9^7Oqkq9<0kwKUBrGVkmdRIp3in$kGXQapf z&ed)z9}wos^L%R5qkgIJHBE;n)Pd^76*!-aT$;Y!Pfg=wXLL=X?pc@39&I|X@!NEi1F zm`-0nc%bRZoIWPbmO?d&?8U|Yg!%Au32^BI5M`FLMwk(Sbu3z1Cl!u7Iq-(Py$0=& zRsIV@lXp$UV(TTGiq7i{+3-E1=K_n$PMjJ~fDeoU7f%3BD&RlZzy#XZl#w6+0000< KMNUMnLSTYlXV&ci literal 0 HcmV?d00001 diff --git a/ion/src/simulator/nspire/assets/icon-uns.png b/ion/src/simulator/nspire/assets/icon-uns.png new file mode 100644 index 0000000000000000000000000000000000000000..c2f71847222784736677a5fa53f067fc9611d68d GIT binary patch literal 1750 zcmV;{1}XW8P) z62R|084ld5RI=Bjg;0K7Si<6>@l=#1-&?3fz<9@um_qclp z`0Hh+njLXK)hr{OObFTBiV%855S{473`Qkp>a(Jlg6H_UhmWs!F`ngp?$6PqZd&xywjx*+i**JYRAI2RrE^UR2mNzV~Sh=pPs%Wcd` zhDtn1992|}^8Hzt70z3n)pC_J@5x^n&TGp{T&FpNBo?p;5dvgXv4Juy#Aww>F_EVI zgol67@r&e=$+ZDSj(Jp|LUR1zfAG6ovoJC4CWR9~@Wr-2hJny7(5Tt=_pxm^PJqBO zaHX~Ul{zr}NqViNMUQ~KZQ$a%rOA81&OpJhmGjI+AuiKc5Z3xE{%)9(Zk-%SbUM6KAM~Av$7KD2bw!?0- z7W^Ag$WoSrQJnwjIzll8&ZRKIF@IHOEXR^cmi-Z zR~U8$3C9#ToGV!H1aN7_X}rUyXFUi;RU}hsrdVN;sWgI771OgG;~nntg&>(NOs~qs zl_a9EI9m#9A#35zhKO*mC4yI9dIre4lK@_M_oAWKzjOAn?4AG!ynpU0=G?bT3K7BQ`3G_=To9bN zn*#_C&veKi>-hF5kXdy3|v`_z~`DPy4|(t0}!zVCgS{QCI5l>ts0uziF@(Lln?aPXpm}!?$yBCs zfhSZM-kXkf`d27lU;JHP^F(g(JNZyQUO06>4(zu<05A!luWL76e(D&;@9WXv3%!Ym zaQSa{<627Awb$!k;as?U6;GTP#b9rjMiB%dfTQ>u0Eeh>TCC9F2M6Q_8i= zWziSJYj1r5uP>;XhtMTNXiMYT(;^~*+aJSwUrr+$Pc$CzQm$<@u0=o2zVRW>efKjI zH30~1zuAQ`i#@I+@$GLn@b(vf;#N$p>1!!l*5RdGyOP8e6(3Jd;j?dk#PerP;OWQ5 zvh4u^aQ?~yewc9~QG-UZZ4+M3wW|UztN7^i?*Re`1k!8f!XM7MfxRWbjkxxPs!C`- zM4f);cT+1Rp~_e?=-SmrpuV$dz$Kld-;_jdDt#Iv1($s82XYb*< zf?YN6t#<9QZ%a!Px(7yJlBBz%Qrhh;P}15pik)!nk_~Q>&^<5$v!$tKp|)5puu2x= z6FxfJgNz)8#Vi$c?S#5>A6<3z9YIsGt!80c%n~wk7^A~I#(_^9@5ew-8`2R^?p~Gc zbnRlWTH9(Cw%uk$I^w}VPa7tV_ZNPL7P}KU98)m))f9gD^$M=tSVlDDhZ>LU z*gs?4wFfr)zM5VnM+Q1DKK2NnJ9DzKo2oc9ernHQ;0&C@z!^9L=P+;v&SBsToWsBw sI0NS}a0bp{;0&C@z!^9L=WJ2_2erpLmnTxXoB#j-07*qoM6N<$f^eoc-v9sr literal 0 HcmV?d00001 diff --git a/ion/src/simulator/nspire/backlight.cpp b/ion/src/simulator/nspire/backlight.cpp new file mode 100644 index 00000000000..5f8575af972 --- /dev/null +++ b/ion/src/simulator/nspire/backlight.cpp @@ -0,0 +1,24 @@ +#include + +namespace Ion { +namespace Backlight { + +uint8_t brightness() { + return 128; +} + +void setBrightness(uint8_t b) { +} + +void init() { +} + +bool isInitialized() { + return true; +} + +void shutdown() { +} + +} +} diff --git a/ion/src/simulator/nspire/clipboard.cpp b/ion/src/simulator/nspire/clipboard.cpp new file mode 100644 index 00000000000..4bbee1fa382 --- /dev/null +++ b/ion/src/simulator/nspire/clipboard.cpp @@ -0,0 +1,15 @@ +#include +#include +#include + +namespace Ion { +namespace Clipboard { + +void write(const char * text) {} + +const char * read() { + return nullptr; +} + +} +} diff --git a/ion/src/simulator/nspire/console.cpp b/ion/src/simulator/nspire/console.cpp new file mode 100644 index 00000000000..d8957c6e6ca --- /dev/null +++ b/ion/src/simulator/nspire/console.cpp @@ -0,0 +1,24 @@ +#include +#include "main.h" +#include +#include + +namespace Ion { +namespace Console { + +char readChar() { + return 0; +} + +void writeChar(char c) { + // fxlibc conflicts with this + #undef putchar + KDIonContext::putchar(c); +} + +bool transmissionDone() { + return true; +} + +} +} diff --git a/ion/src/simulator/nspire/display.cpp b/ion/src/simulator/nspire/display.cpp new file mode 100644 index 00000000000..eff8185ed5d --- /dev/null +++ b/ion/src/simulator/nspire/display.cpp @@ -0,0 +1,47 @@ +#include "display.h" +#include "framebuffer.h" +#include +#include +#include +#include + +#include + +#include "k_csdk.h" +#include + +namespace Ion { +namespace Simulator { +namespace Display { + +void init() { +} + +void quit() { +} + +void draw() { + // copy framebuffer + const short unsigned int * ptr = (const short unsigned int *) Ion::Simulator::Framebuffer::address(); + Gc * gcptr = get_gc(); + for (int j = 0; j < LCD_HEIGHT_PX; ++j) { + for (int i = 0; i < LCD_WIDTH_PX;){ + int c = *ptr; + int k = 1; + for (; k+i < LCD_WIDTH_PX; ++k) { + if (ptr[k]!=c) { + break; + } + } + gui_gc_setColor(*gcptr,c_rgb565to888(c)); + gui_gc_drawRect(*gcptr,i,j,k-1,0); + ptr += k; + i += k; + } + } + sync_screen(); +} + +} +} +} diff --git a/ion/src/simulator/nspire/display.h b/ion/src/simulator/nspire/display.h new file mode 100644 index 00000000000..ab524fd3c55 --- /dev/null +++ b/ion/src/simulator/nspire/display.h @@ -0,0 +1,19 @@ +#ifndef ION_SIMULATOR_DISPLAY_H +#define ION_SIMULATOR_DISPLAY_H + +#include + +namespace Ion { +namespace Simulator { +namespace Display { + +void init(); +void quit(); + +void draw(); + +} +} +} + +#endif diff --git a/ion/src/simulator/nspire/events.cpp b/ion/src/simulator/nspire/events.cpp new file mode 100644 index 00000000000..9b8d0055751 --- /dev/null +++ b/ion/src/simulator/nspire/events.cpp @@ -0,0 +1,23 @@ +#include "events.h" +#include + +namespace Ion { +namespace Events { + +void didPressNewKey() { +} + +char * sharedExternalTextBuffer() { + static char buffer[sharedExternalTextBufferSize]; + return buffer; +} + +const char * Event::text() const { + if (*this == ExternalText) { + return const_cast(sharedExternalTextBuffer()); + } + return defaultText(); +} + +} +} diff --git a/ion/src/simulator/nspire/events.h b/ion/src/simulator/nspire/events.h new file mode 100644 index 00000000000..b012a43c5e5 --- /dev/null +++ b/ion/src/simulator/nspire/events.h @@ -0,0 +1,24 @@ +#ifndef ION_SIMULATOR_EVENTS_H +#define ION_SIMULATOR_EVENTS_H + +#include + +namespace Ion { +namespace Simulator { +namespace Events { + +void dumpEventCount(int i); +void logAfter(int numberOfEvents); + +} +} + +namespace Events { + +static constexpr int sharedExternalTextBufferSize = 2; +char * sharedExternalTextBuffer(); + +} +} + +#endif diff --git a/ion/src/simulator/nspire/events_keyboard.cpp b/ion/src/simulator/nspire/events_keyboard.cpp new file mode 100644 index 00000000000..615c327d54c --- /dev/null +++ b/ion/src/simulator/nspire/events_keyboard.cpp @@ -0,0 +1,15 @@ +#include + +namespace Ion { +namespace Events { + + +Event getPlatformEvent() { + Event result = None; + + return result; +} + + +} +} diff --git a/ion/src/simulator/nspire/framebuffer.cpp b/ion/src/simulator/nspire/framebuffer.cpp new file mode 100644 index 00000000000..677515ecf63 --- /dev/null +++ b/ion/src/simulator/nspire/framebuffer.cpp @@ -0,0 +1,53 @@ +#include "framebuffer.h" +#include +#include "main.h" +#include + + +static KDColor sPixels[Ion::Display::Width * Ion::Display::Height]; +static_assert(sizeof(KDColor) == sizeof(uint16_t), "KDColor is not 16 bits"); +static bool sFrameBufferActive = true; + +namespace Ion { +namespace Display { + +static KDFrameBuffer sFrameBuffer = KDFrameBuffer(sPixels, KDSize(Ion::Display::Width, Ion::Display::Height)); + +void pushRect(KDRect r, const KDColor * pixels) { + if (sFrameBufferActive) { + Simulator::Main::setNeedsRefresh(); + sFrameBuffer.pushRect(r, pixels); + } +} + +void pushRectUniform(KDRect r, KDColor c) { + if (sFrameBufferActive) { + Simulator::Main::setNeedsRefresh(); + sFrameBuffer.pushRectUniform(r, c); + } +} + +void pullRect(KDRect r, KDColor * pixels) { + if (sFrameBufferActive) { + sFrameBuffer.pullRect(r, pixels); + } +} + +} +} + +namespace Ion { +namespace Simulator { +namespace Framebuffer { + +const KDColor * address() { + return sPixels; +} + +void setActive(bool enabled) { + sFrameBufferActive = enabled; +} + +} +} +} diff --git a/ion/src/simulator/nspire/framebuffer.h b/ion/src/simulator/nspire/framebuffer.h new file mode 100644 index 00000000000..dba4dbd3278 --- /dev/null +++ b/ion/src/simulator/nspire/framebuffer.h @@ -0,0 +1,17 @@ +#ifndef ION_SIMULATOR_FRAMEBUFFER_H +#define ION_SIMULATOR_FRAMEBUFFER_H + +#include + +namespace Ion { +namespace Simulator { +namespace Framebuffer { + +const KDColor * address(); +void setActive(bool enabled); + +} +} +} + +#endif \ No newline at end of file diff --git a/ion/src/simulator/nspire/gint.h b/ion/src/simulator/nspire/gint.h new file mode 100644 index 00000000000..bd8d65a70f9 --- /dev/null +++ b/ion/src/simulator/nspire/gint.h @@ -0,0 +1,61 @@ +#ifndef GINT_H +#define GINT_H +#include "k_defs.h" +#define KEY_DOWN KEY_CTRL_DOWN +#define KEY_LEFT KEY_CTRL_LEFT +#define KEY_RIGHT KEY_CTRL_RIGHT +#define KEY_UP KEY_CTRL_UP +#define KEY_EXIT KEY_CTRL_EXIT +#define KEY_MENU KEY_CTRL_MENU +#define KEY_SHIFT KEY_CTRL_SHIFT +#define KEY_ALPHA KEY_CTRL_ALPHA +#define KEY_XOT KEY_CTRL_XTT +#define KEY_VARS KEY_CTRL_VARS +#define KEY_OPTN KEY_CTRL_OPTN +#define KEY_DEL KEY_CTRL_DEL +#define KEY_OPTN KEY_CTRL_OPTN +#define KEY_DEL KEY_CTRL_DEL +#define KEY_LN KEY_CHAR_LN +#define KEY_LOG KEY_CHAR_LOG +#define KEY_0 KEY_CHAR_0 +#define KEY_COMMA KEY_CHAR_COMMA +#define KEY_SIN KEY_CHAR_SIN +#define KEY_COS KEY_CHAR_COS +#define KEY_TAN KEY_CHAR_TAN +#define KEY_EXP KEY_CHAR_EXP +#define KEY_SQUARE KEY_CHAR_SQUARE +#define KEY_7 KEY_CHAR_7 +#define KEY_8 KEY_CHAR_8 +#define KEY_9 KEY_CHAR_9 +#define KEY_LEFTP KEY_CHAR_LPAR +#define KEY_RIGHTP KEY_CHAR_RPAR +#define KEY_4 KEY_CHAR_4 +#define KEY_5 KEY_CHAR_5 +#define KEY_6 KEY_CHAR_6 +#define KEY_MUL KEY_CHAR_MULT +#define KEY_DIV KEY_CHAR_DIV +#define KEY_FRAC KEY_CHAR_FRAC +#define KEY_1 KEY_CHAR_1 +#define KEY_2 KEY_CHAR_2 +#define KEY_3 KEY_CHAR_3 +#define KEY_ADD KEY_CHAR_PLUS +#define KEY_PLUS KEY_CHAR_PLUS +#define KEY_SUB KEY_CHAR_MINUS +#define KEY_MINUS KEY_CHAR_MINUS +#define KEY_NEG KEY_CHAR_PMINUS +#define KEY_0 KEY_CHAR_0 +#define KEY_DOT KEY_CHAR_DP +#define KEY_EXE KEY_CTRL_EXE +#define KEY_ACON KEY_CTRL_AC +#define KEY_POWER KEY_CHAR_POW +#define KEY_STORE KEY_CHAR_STORE +#define KEY_F1 KEY_CTRL_F1 +#define KEY_F2 KEY_CTRL_F2 +#define KEY_F3 KEY_CTRL_F3 +#define KEY_F4 KEY_CTRL_F4 +#define KEY_F5 KEY_CTRL_F5 +#define KEY_F6 KEY_CTRL_F6 +#define KEY_FD KEY_CTRL_FD +#define KEY_ARROW KEY_CTRL_SD +#define KEY_TIMES KEY_CHAR_MULT +#endif // GINT_H diff --git a/ion/src/simulator/nspire/k_csdk.c b/ion/src/simulator/nspire/k_csdk.c new file mode 100755 index 00000000000..4e9521f25ff --- /dev/null +++ b/ion/src/simulator/nspire/k_csdk.c @@ -0,0 +1,1556 @@ +// implementation of the minimal C SDK for KhiCAS +int (*shutdown)()=0; + +short shutdown_state=0; +short exam_mode=0,nspire_exam_mode=0; +unsigned exam_start=0; // RTC start +int exam_duration=0; +// <0: indicative duration, ==0 time displayed during exam, >0 end exam_mode after +const int exam_bg1=0x4321,exam_bg2=0x1234; +int exam_bg(){ + return exam_mode?(exam_duration>0?exam_bg1:exam_bg2):0x50719; +} + +void SetQuitHandler( void (*f)(void)){} +#ifdef TICE +int clip_ymin=0; +// TI83 +const int STATUS_AREA_PX=18; +// debug: dbg_printf() Add #include to a source file, and use make debug instead of make to build a debug program. You may need to run make clean beforehand in order to ensure all source files are rebuilt. +// ASM syscalls: https://wikiti.brandonw.net/index.php?title=Category:84PCE:Syscalls:By_Name +// doc: https://ce-programming.github.io/toolchain/index.html +// Makefile options https://ce-programming.github.io/toolchain/static/makefile-options.html +// memory layout: https://ce-programming.github.io/toolchain/static/faq.html +// parameters are in CEdev/meta (and app_tools if present) +// makefile.mk: +// BSSHEAP_LOW ?= D052C6 +// BSSHEAP_HIGH ?= D13FD8 +// STACK_HIGH ?= D1A87E +// INIT_LOC ?= D1A87F +// Can we set STACK_HIGH to another value? I think the global area stack+data could be "reversed", I mean stack top at 0xD2A87F and data(+code+ro_data for RAM programs) at a new position: INIT_LOC=D1987E (maybe +1 or +2) + +// TI stack 4K D1A87Eh: Top of the SPL stack. +// change stack pointer (if STACK_HIGH change does not work) +// requires assembly code (https://0x04.net/~mwk/doc/z80/eZ80.pdf), +// save stack pointer +// LD (Mmn), SP +// set HL to the new stack address (top of the area-3) +// LD SP,HL +// call main +// restore stack pointer +// LD SP,(Mmn) +// 1023 bytes: uint8_t[1023] os_RamCode (do not use if flash write occurs) +// 0xD052C6: 60989 bytes used for bss+heap (temp buffers in TI OS) +// 0xD1A881: Start of UserMem. 64K for code, data, ro data +// size_t os_MemChk(void **free) size and position of free ram area +// Or we could create a VarApp in RAM with no real data inside and use this area for temporary storage. +// 0xD40000: Start of VRAM. 320x240x2 bytes = 153600 bytes. +// half may be used in 8 bits palette mode (graphx) +#include "k_csdk.h" +#include +#include +#include +#include +#include +#include // boot_GetTime(uint8_t *seconds, uint8_t *minutes, uint8_t *hours), boot_SetTime(uint8_t seconds, uint8_t minutes, uint8_t hours) +#include +#include +#include +#include +#include +#define FILENAME_MAXRECORDS 32 +#define FILENAME_MAXSIZE 9 +#define FILE_MAXSIZE 16384 +char os_filenames[FILENAME_MAXRECORDS][FILENAME_MAXSIZE]; + +void sdk_init(){ + dbg_printf("SDK Init\n"); + gfx_Begin(); + unsigned short * addr=gfx_palette; + for (int r=0;r<4;r++){ + for (int g=0;g<8;g++){ + for (int b=0;b<4;b++){ + int R=r*255/3,G=g*255/7,B=b*255/3; + addr[(r<<5)|(g<<2)|b]=gfx_RGBTo1555(R,G,B); + // dbg_printf("palette %i %i %i %i\n",(r<<5)|(g<<2)|b,R,G,B); + } + } + } + // 128-254 arc-en-ciel? 255 should remain white +} + +void sdk_end(){ + dbg_printf("SDK End\n"); + gfx_End(); +} + +void clear_screen(void){ + gfx_FillScreen(255); // gfx_ZeroScreen(void); +} + +int alpha=0,alphalock=0,prevalpha=0,shift=0; +int handle_f5(){ + if (alphalock) + alphalock=3-alphalock; + else + alphalock=2; +} +void dbgprint(int i){ + char buf[16]={0}; + buf[0]='0'+i/100; + buf[1]='0'+(i % 100)/10; + buf[2]='0'+(i % 10); + os_draw_string(20,60,SDK_WHITE,SDK_BLACK,buf,false); +} +int getkey(int allow_suspend){ + sync_screen(); + statusline(0); + for (;;){ + int i=0; + while (!i){ + i=os_GetCSC(); + } + // dbgprint(i); + int decal=(alpha>>1)<<5; // 0 or 32 for upper or lowercase + int Alpha=alpha,Shift=shift; + shift=0; prevalpha=alpha; + if (!alphalock) + alpha=0; + switch (i){ + case sk_Fx: + return Alpha?KEY_CTRL_F11:Shift?KEY_CTRL_F6:KEY_CTRL_F1; + case sk_Fenetre: + return Alpha?KEY_CTRL_F12:Shift?KEY_CTRL_F7:KEY_CTRL_F2; + case sk_Zoom: + return Alpha?KEY_CTRL_F13:Shift?KEY_CTRL_F8:KEY_CTRL_F3; + case sk_Trace: + return Alpha?KEY_CTRL_F14:Shift?KEY_CTRL_F9:KEY_CTRL_F4; + case sk_Graph: + return Alpha?KEY_CTRL_F15:Shift?KEY_CTRL_F10:KEY_CTRL_F5; + case sk_Mode: + return KEY_CTRL_SETUP; + case sk_Del: + return KEY_CTRL_DEL; + case sk_GraphVar: + return KEY_CTRL_XTT; + // sk_Stats + case sk_Right: + return Shift?KEY_SHIFT_RIGHT:KEY_CTRL_RIGHT; + case sk_Left: + return Shift?KEY_SHIFT_LEFT:KEY_CTRL_LEFT; + case sk_Up: + return Shift?KEY_CTRL_PAGEUP:KEY_CTRL_UP; + case sk_Down: + return Shift?KEY_CTRL_PAGEDOWN:KEY_CTRL_DOWN; + case sk_Enter: + return Alpha?KEY_SHIFT_ANS:KEY_CTRL_EXE; + case sk_Alpha: + if (alphalock){ + alpha=alphalock=0; + } + else { + if (Shift) + alphalock=alpha=2; + else { + alpha=2; + if (prevalpha) + alphalock=alpha=prevalpha; + } + } + statusline(0); + continue; + case sk_2nd: + if (alphalock) + alpha=3-alpha; // maj <> min + else + shift=!Shift; + statusline(0); + continue; + case sk_Math: + return Alpha?KEY_CHAR_A+decal:KEY_CTRL_F6; + case sk_Matrice: + return Alpha?KEY_CHAR_B+decal:KEY_CHAR_MAT; + case sk_Prgm: + return Alpha?KEY_CHAR_C+decal:KEY_CTRL_PRGM; + case sk_Vars: + return KEY_CTRL_VARS; + case sk_Annul: + return Shift?KEY_CTRL_AC:KEY_CTRL_EXIT; + case sk_TglExact: + return KEY_CHAR_D+decal; + case sk_Trig: + return Alpha?KEY_CHAR_E+decal:(Shift?KEY_CHAR_PI:KEY_CHAR_SIN); + case sk_Cos: + return Alpha?KEY_CHAR_F+decal:KEY_CHAR_COS; + case sk_Tan: + return Alpha?KEY_CHAR_G+decal:KEY_CHAR_TAN; + case sk_Power: + return Alpha?KEY_CHAR_H+decal:KEY_CHAR_POW; + case sk_Square: + return Alpha?KEY_CHAR_I+decal:Shift?KEY_CHAR_ROOT:KEY_CHAR_SQUARE; + case sk_Comma: + return Alpha?KEY_CHAR_J+decal:Shift?KEY_CHAR_E:KEY_CHAR_COMMA; + case sk_LParen: + return Alpha?KEY_CHAR_K+decal:Shift?KEY_CHAR_LBRACE:KEY_CHAR_LPAR; + case sk_RParen: + return Alpha?KEY_CHAR_L+decal:Shift?KEY_CHAR_RBRACE:KEY_CHAR_RPAR; + case sk_Div: + return Alpha?KEY_CHAR_M+decal:Shift?KEY_CHAR_E+32:KEY_CHAR_DIV; + case sk_Log: + return Alpha?KEY_CHAR_N+decal:Shift?KEY_CHAR_EXPN10:KEY_CHAR_LOG; + case sk_7: + return Alpha?KEY_CHAR_O+decal:KEY_CHAR_7; + case sk_8: + return Alpha?KEY_CHAR_P+decal:KEY_CHAR_8; + case sk_9: + return Alpha?KEY_CHAR_Q+decal:KEY_CHAR_9; + case sk_Mul: + return Alpha?KEY_CHAR_R+decal:Shift?KEY_CHAR_LBRCKT:KEY_CHAR_MULT; + case sk_Ln: + return Alpha?KEY_CHAR_S+decal:Shift?KEY_CHAR_EXP:KEY_CHAR_LN; + case sk_4: + return Alpha?KEY_CHAR_T+decal:KEY_CHAR_4; + case sk_5: + return Alpha?KEY_CHAR_U+decal:KEY_CHAR_5; + case sk_6: + return Alpha?KEY_CHAR_V+decal:KEY_CHAR_6; + case sk_Sub: + return Alpha?KEY_CHAR_W+decal:Shift?KEY_CHAR_RBRCKT:KEY_CHAR_MINUS; + case sk_Store: + return Alpha?KEY_CHAR_X+decal:KEY_CHAR_STORE; + case sk_1: + return Alpha?KEY_CHAR_Y+decal:KEY_CHAR_1; + case sk_2: + return Alpha?KEY_CHAR_Z+decal:KEY_CHAR_2; + case sk_3: + return Alpha?KEY_CHAR_THETA:KEY_CHAR_3; + case sk_Add: + return KEY_CHAR_PLUS; + case sk_0: + return Alpha?KEY_CHAR_SPACE:Shift?KEY_CTRL_CATALOG:KEY_CHAR_0; + case sk_DecPnt: + return Alpha?':':Shift?KEY_CHAR_I+32:KEY_CHAR_DP; + case sk_Chs: + return Alpha?'?':Shift?KEY_CHAR_ANS:KEY_CHAR_PMINUS; + default: + return i; + } + } +} +void GetKey(int * key){ + *key=getkey(0); +} +int iskeydown(int key){ + kb_Scan(); + return kb_IsDown(key); +} + +// if (kb_On) ... +void enable_back_interrupt(){ + kb_EnableOnLatch(); +} +void disable_back_interrupt(){ + kb_DisableOnLatch(); +} +int isalphaactive(){ + return alpha; +} +int alphawasactive(int * key){ + return prevalpha; +} +void lock_alpha(){ + alpha=alphalock=1; +} +void reset_kbd(){ + shift=alpha=alphalock=0; +} +int GetSetupSetting(int k){ + if (k!=0x14) return -1; + if (!alpha) return 0; + if (!alphalock) return alpha==2?8:4; + return alpha==2?0x88:0x84; +} + +void os_wait_1ms(int ms){ + msleep(ms); // delay(ms)? +} +double millis(){ + return rtc_Days*86400.0+rtc_Hours*3600.+rtc_Minutes*60.+rtc_Seconds; +} +int os_set_angle_unit(int mode){ + if (mode) os_ResetFlag(TRIG,DEGREES); else os_SetFlag(TRIG,DEGREES); + return true; +} + +int os_get_angle_unit(){ + int i=os_TestFlag(TRIG,DEGREES); + return i?0:1; +} +int file_exists(const char * filename){ + int h=ti_Open(filename, "r"); + if (!h) + return false; + ti_Close(h); + return true; +} +int erase_file(const char * filename){ + if (!file_exists(filename)) + return false; + ti_Delete(filename); + return true; +} +const char * read_file(const char * filename){ + const char * ext=0; + int l=strlen(filename); + char var[9]={0}; + strncpy(var,filename,8); + for (--l;l>0;--l){ + if (filename[l]=='.'){ + ext=filename+l+1; + if (l<9) + var[l]=0; + break; + } + } + int h=ti_Open(var, "r"); + if (!h) + return 0; + int s=ti_GetSize(h); + if (s>7){ + //unsigned short u; + //ti_Read(&u,1,2,h); + char subtype[8]={0}; + ti_Read(subtype,1,4,h); + if (strncmp(subtype,"PYCD",4)==0 || strncmp(subtype,"XCAS",4)==0){ + unsigned char dx; + ti_Read(&dx,1,1,h); + if (dx!=0){ + // skip desktop filename + char buf[256]={0}; + ti_Read(buf,1,1,dx); + s -= 4+dx; + dbg_printf("subtype=%s filename=%s %i %i\n",subtype,buf,dx,s); + } + else + s -= 4; + } + else + ti_Seek(0,SEEK_SET,h); + } + char * ptr=0; +#if 0 + // Direct access to the data, ptr should not be used if any change to the TI variables occurs, unfortunately there is no 0 at end of string + ptr= ti_GetDataPtr(h); + ti_Close(h); + dbg_printf("data=%x %x %x %x %x %x %x %x\n",ptr[0],ptr[1],ptr[2],ptr[3],ptr[4],ptr[5],ptr[6],ptr[7]); + return ptr; +#endif + // Code requiring a copy + // if it starts with + // char * ptr=(char *) gfx_vram+LCD_WIDTH_PX*LCD_HEIGHT_PX; // pointer in vram buffer + int S=os_MemChk((void **)&ptr); + if (s>=S) + return 0; + S=ti_Read(ptr,1,s,h); + ptr[S]=0; + ti_Close(h); + dbg_printf("data=%s\n",ptr); + return ptr; +} +int write_file(const char * filename,const char * s,int len){ + // find extension + const char * ext=0; + int l=strlen(filename); + char var[9]={0}; + strncpy(var,filename,8); + for (--l;l>0;--l){ + if (filename[l]=='.'){ + ext=filename+l+1; + if (l<9) + var[l]=0; + break; + } + } + int h=ti_Open(var,"w"); + if (!h) return false; + if (ext){ + bool ispy=strncmp(ext,"py",2)==0; + bool isxw=strncmp(ext,"xw",2)==0; + if (ispy || isxw){ + const char * subtype=isxw?"XCAS":"PYCD"; + ti_Write(subtype,strlen(subtype),1,h); + unsigned char dx=strlen(filename)+1; + ti_Write(&dx,1,1,h); + ti_Write(filename,dx-1,1,h); + } + } + int Len=ti_Write(s,1,len,h); + ti_Close(h); + return Len==len; +} + +int os_file_browser(const char ** filenames,int maxrecords,const char * extension,int storage){ + if (maxrecords>FILENAME_MAXRECORDS) + maxrecords=FILENAME_MAXRECORDS; + void * ptr=os_GetSymTablePtr(); + int cur=0; + for (int count=0;cur=FILENAME_MAXSIZE || !dataptr) + continue; + s[l]=0; + dbg_printf("filebrowser %s %i %x %x %x %x %x %x %x %x %x %x %x %x %x\n",s,type,dataptr[0]&0xff,dataptr[1]&0xff,dataptr[2]&0xff,dataptr[3]&0xff,dataptr[4]&0xff,dataptr[5]&0xff,dataptr[6]&0xff,dataptr[7]&0xff,dataptr[8]&0xff,dataptr[9]&0xff,dataptr[10]&0xff,dataptr[11]&0xff,dataptr[12]&0xff); + // if type==21 dataptr[1]*256+dataptr[0]==size, then data + // xcas session begins with 4 bytes size, on the 83 should be 00 00 xx xx + if (type==21 && dataptr[2]==0 && dataptr[3]==0) + ext="xw"; + // python app, starts with 2 bytes size, "PYCD" or "PYSC" + // the script ifself begins at data.begin() + 6 + scriptOffset + // where scriptOffset = dataptr[6] + 1 + if (!ext){ + if (strncmp(&dataptr[2],"PYCD",4)==0 || strncmp(&dataptr[2],"PYSC",4)==0) + ext="py"; + else if (strncmp(&dataptr[2],"XCAS",4)==0) + ext="xw"; + else { // extension from filename _xw or _py or _... + //dbg_printf("os_file_browser %i %i %x\n",type,l,dataptr); + //dbg_printf("filename %i %s\n",count,s); + for (j=l-1;j>0;--j){ + if (s[j]=='_'){ + ext=s+j+1; + break; + } + } + } + } + if (ext && strcmp(ext,extension)==0){ + if (exam_mode && + (strcmp(s,"session")!=0 + ) + ) + continue; + strncpy(os_filenames[cur],s,FILENAME_MAXSIZE); + filenames[cur]=os_filenames[cur]; + dbg_printf("extension match %i %s %s\n",cur,s,filenames[cur]); + ++cur; + } + } + dbg_printf("filebrowser %i\n",cur); + return cur; +} +// gfx_Begin, gfx_SetDrawBuffer(); gfx_End +// GFX_LCD_WIDTH, HEIGHT, gfx_vbuffer=LCD RAM buffer 76800 bytes +// gfx_vram Total of 153600 bytes in size = 320x240x2 +// gfx_SetDrawBuffer()gfx_SetDrawScreen() +// uint8_t gfx_SetColor(uint8_t index) +// gfx_SetPixel(uint24_t x, uint8_t y) +// uint8_t gfx_GetPixel(uint24_t x, uint8_t y) +// gfx_FillRectangle(int x, int y, int width, int height) +// gfx_FillRectangle_NoClip(uint24_t x, uint8_t y, uint24_t width, uint8_t height) +// gfx_Wait(void) +// gfx_PrintStringXY(const char *string, int x, int y) +//gfx_SetTextFGColor(uint8_t color) +// gfx_SetTextScale(uint8_t width_scale, uint8_t height_scale) +// gfx_SetTextConfig +void sync_screen(){ + //gfx_Wait(); + // gfx_BlitBuffer(); // shoud be done if gfx_SetDrawBuffer() is active; +} +int c_rgb565to888(int c){ + c &= 0xffff; + int r=(c>>11)&0x1f,g=(c>>5)&0x3f,b=c&0x1f; + return (r<<19)|(g<<10)|(b<<3); +} + +int convertcolor(int c){ + // convert 16 bits to default palette + c &= 0xffff; + int r=(c>>11)&0x1f,g=(c>>5)&0x3f,b=c&0x1f; + int R = ((r>>3)<<5) | ((g>>3)<<2) | (b>>3); + //dbg_printf("convert %i r=%i g=%i b=%i to %i\n",c,r,g,b,R); + return R; +} +void setcolor(int c){ + gfx_SetColor(convertcolor(c)); + //gfx_SetTextTransparentColor(0); +} +void os_set_pixel(int x,int y,int c){ + setcolor(c); + gfx_SetPixel(x,y); +} +void os_fill_rect(int x,int y,int w,int h,int c){ + setcolor(c); + gfx_FillRectangle(x,y,w,h); +} +int os_get_pixel(int x,int y){ + return gfx_GetPixel(x,y); +} + +// FIXME? use gfx_SetTransparentColor with a value != FG and BG instead of fill rectangle +int os_draw_string_small(int x,int y,int c,int bg,const char * s,int fake){ + y+=STATUS_AREA_PX; + gfx_SetTextScale(1,1); + int dx=gfx_GetStringWidth(s); + if (!fake){ + gfx_SetColor(bg); + gfx_FillRectangle(x,y,dx,8); + int c_=gfx_SetTextFGColor(c); + int bg_=gfx_SetTextBGColor(bg); + gfx_PrintStringXY(s,x,y); + gfx_SetTextFGColor(c_); + gfx_SetTextBGColor(bg_); + } + return x+dx; +} +int os_draw_string_medium(int x,int y,int c,int bg,const char * s,int fake){ + y+=STATUS_AREA_PX; + gfx_SetTextScale(1,2); + //gfx_SetFontHeight(12); + int dx=gfx_GetStringWidth(s); + if (!fake){ + gfx_SetColor(bg); + gfx_FillRectangle(x,y,dx,16); + int c_=gfx_SetTextFGColor(c); + int bg_=gfx_SetTextBGColor(bg); + gfx_PrintStringXY(s,x,y); + gfx_SetTextFGColor(c_); + gfx_SetTextBGColor(bg_); + } + return x+dx; +} +int os_draw_string(int x,int y,int c,int bg,const char * s,int fake){ + y+=STATUS_AREA_PX; + gfx_SetTextScale(2,2); + int dx=gfx_GetStringWidth(s); + if (!fake){ + gfx_SetColor(bg); + gfx_FillRectangle(x,y,dx,16); + int c_=gfx_SetTextFGColor(c); + int bg_=gfx_SetTextBGColor(bg); + gfx_PrintStringXY(s,x,y); + gfx_SetTextFGColor(c_); + gfx_SetTextBGColor(bg_); + } + return x+dx; +} + +const int statuscolor=12345; +void statuslinemsg(const char * msg){ + os_draw_string(0,-STATUS_AREA_PX,statuscolor,SDK_BLACK,msg,false); +} + +void set_time(int h,int m){ + rtc_Set(rtc_Seconds,m,h,rtc_Days); +} + +void get_time(int *h,int *m){ + *h=rtc_Hours; + *m=rtc_Minutes; +} + +void display_time(){ + int h=rtc_Hours,m=rtc_Minutes; + char msg[10]; + msg[0]=' '; + msg[1]='0'+(h/10); + msg[2]='0'+(h%10); + msg[3]= 'h'; + msg[4]= ('0'+(m/10)); + msg[5]= ('0'+(m%10)); + msg[6]=0; + //msg[6]= 'm'; + //msg[7] = ('0'+(s/10)); + //msg[8] = ('0'+(s%10)); + //msg[9]=0; + os_fill_rect(270,0,LCD_WIDTH_PX-270,15,SDK_BLACK); + os_draw_string_medium(270,-STATUS_AREA_PX,statuscolor,SDK_BLACK,msg,false); +} + +void statusflags(){ + char *msg=0; + if (alpha==2){ + msg=alphalock?"alock":"alpha"; + } + else if (alpha==1){ + msg=alphalock?"ALOCK":"ALPHA"; + } + else { + if (shift) + msg="2nd"; + else + msg=""; + } + os_fill_rect(0,0,LCD_WIDTH_PX,16,SDK_BLACK); + os_draw_string_medium(225,-STATUS_AREA_PX,statuscolor,SDK_BLACK,msg,false); + os_draw_string_medium(160,-STATUS_AREA_PX,statuscolor,SDK_BLACK,os_get_angle_unit()?" rad ":" deg ",false); + display_time(); +} +void statusline(int mode){ + statusflags(); + if (mode==0) + os_draw_string_medium(190,-STATUS_AREA_PX,statuscolor,SDK_BLACK," CAS ",false); + if (mode==0) + return; + sync_screen(); +} +#endif + +#ifdef NSPIRE_NEWLIB +// NB changes for the nspire cx ii +// on_key_pressed() should be modified (returns always true) +// https://hackspire.org/index.php?title=Memory-mapped_I/O_ports_on_CX_II#90140000_-_Power_management +// cx ii power management 0x90140000, +// cx 900B0018 (R/W), 900B0020 (?) +// cx ii 90140050 (R/W): Disable bus access to peripherals. Reads will just return the last word read from anywhere in the address range, and writes will be ignored. +// cx 900F0020 (R/W): LCD contrast/backlight. Valid range for contrast: 0x11a to 0x1ce; normal value is 0x174. However, it can range from 0x100 (backlight off) to about 0x1d0 (about max brightness). +// -> cx ii The OS controls the LCD backlight by writing to 90130018. +#include +#include "os.h" // Ndless/ndless-sdk/include/os.h +#include +#include +#include +#include +#include "k_defs.h" + +void sdk_init(void){ + lcd_init(lcd_type()); // clrscr(); +} + +void sdk_end(void){ + lcd_init(SCR_TYPE_INVALID); + refresh_osscr(); +} + +int c_rgb565to888(int c){ + c &= 0xffff; + int r=(c>>11)&0x1f,g=(c>>5)&0x3f,b=c&0x1f; + return (r<<19)|(g<<10)|(b<<3); +} + +const int nspire_statusarea=18; +int nspireemu=false; + +int waitforvblank(){ + return 0; +} + +int back_key_pressed(){ + return isKeyPressed(KEY_NSPIRE_DEL); +} +// next 3 functions may be void if not inside a window class hierarchy +void os_show_graph(){} // show graph inside Python shell (Numworks), not used +void os_hide_graph(){} // hide graph, not used anymore +void os_redraw(){} // force redraw of window class hierarchy + +int os_set_angle_unit(int mode){ + return false; +} +int os_get_angle_unit(){ + return 0; +} + +double millis(){ + unsigned NSPIRE_RTC_ADDR=0x90090000; + unsigned t1= * (volatile unsigned *) NSPIRE_RTC_ADDR; + return 1000.0*t1; +} + + +void get_hms(int *h,int *m,int *s){ + unsigned NSPIRE_RTC_ADDR=0x90090000; + unsigned t1= * (volatile unsigned *) NSPIRE_RTC_ADDR; + if (exam_mode){ + unsigned t=t1-exam_start; + if (exam_duration>0 && t>exam_duration){ + ;//set_exam_mode(0); + } + else { + if (exam_duration>0) + t1=exam_duration-t; + else { + if (exam_duration<0 && t<-exam_duration) + t1=-exam_duration-t; + } + } + } + unsigned d=t1/86400; + *s=t1%86400; + *h=*s/3600; + *m=(*s-3600* *h)/60; + *s%=60; +} + +void get_time(int *h,int *m){ + int s; + get_hms(h,m,&s); +} + +void set_time(int h,int m){ + // FIXME +} + +#ifndef is_cx2 +#define is_cx2 false +#endif + +double loopsleep(int ms){ + double n=ms*(is_cx2?3000:1000),j=0.0; + for (double i=0;iNSPIRE_FILEBUFFER-1){ + fclose(f); + return 0; + } + for (int i=0;iFILENAME_MAXRECORDS-1) + maxrecords=FILENAME_MAXRECORDS-1; + dp = opendir ("."); + if (dp == NULL){ + filenames[0]=0; + return 0; + } + int cur=0; + while ( (ep = readdir (dp)) && curd_name,*ext=0; + int l=strlen(s_),j; + char s[l+1]; + strcpy(s,s_); + for (j=l-1;j>0;--j){ + if (s[j]=='.'){ + ext=s+j+1; + break; + } + } + if (ext && strcmp(ext,"tns")==0){ + s[j]=0; + for (;j>0;--j){ + if (s[j]=='.'){ + ext=s+j+1; + break; + } + } + } + if (ext && strcmp(ext,extension)==0){ + if (exam_mode && + (strcmp(s_,"session.xw")!=0 && + strcmp(s_,"session.xw.tns")!=0 && + strcmp(s_,"session.py")!=0 && + strcmp(s_,"session.py.tns")!=0 + ) + ) + continue; + strncpy(os_filenames[cur],s_,FILENAME_MAXSIZE); + filenames[cur]=os_filenames[cur]; + ++cur; + } + } + closedir (dp); + filenames[cur]=NULL; +#if 0 + qsort(filenames,cur,sizeof(char *),c_trialpha); +#else + // qsort would be faster for large n, but here n0){ + finished=false; + const char * tmp=filenames[i-1]; + filenames[i-1]=filenames[i]; + filenames[i]=tmp; + } + } + if (finished) + break; + } +#endif + return cur; +} + +Gc nspire_gc=0; + +void reset_gc(){ + if (nspire_gc){ + gui_gc_finish(nspire_gc); + //gui_gc_free(nspire_gc); + } + nspire_gc=0; +} + +Gc * get_gc(){ + if (!nspire_gc){ + nspire_gc=gui_gc_global_GC(); + gui_gc_setRegion(nspire_gc, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + gui_gc_begin(nspire_gc); + } + return &nspire_gc; +} + +void os_set_pixel(int x,int y,int c){ + get_gc(); + gui_gc_setColor(nspire_gc,c_rgb565to888(c)); + gui_gc_drawRect(nspire_gc,x,y+nspire_statusarea,0,0); +} + +void os_fill_rect(int x,int y,int w,int h,int c){ + get_gc(); + gui_gc_setColor(nspire_gc,c_rgb565to888(c)); + gui_gc_fillRect(nspire_gc,x,y+nspire_statusarea,w,h); +} + +int os_get_pixel(int x,int y){ + if (x<0 || x>=SCREEN_WIDTH || y<0 || y>=SCREEN_HEIGHT) + return -1; +#if 1 + get_gc(); + char ** off_buff = ((((char *****)nspire_gc)[9])[0])[0x8]; + int res = *(unsigned short *) (off_buff[y+nspire_statusarea] + 2*x); + return res; +#else + unsigned short * addr=*(unsigned short **) 0xC0000010; + int r=addr[(y+nspire_statusarea)*SCREEN_WIDTH+x]; + return r; +#endif +} + +int nspire_draw_string(int x,int y,int c,int bg,int f,const char * s,int fake){ + // void ascii2utf16(void *buf, const char *str, int max_size): converts the UTF-8 string str to the UTF-16 string buf of size max_size. + int l=strlen(s); + char utf16[2*l+2]; + ascii2utf16(utf16,s,l); + utf16[2*l]=0; + utf16[2*l+1]=0; + get_gc(); + gui_gc_setFont(nspire_gc,f); + int dx=gui_gc_getStringWidth(nspire_gc, f, utf16, 0, l) ; + if (fake) + return x+dx; + int dy=17; + if (f==Regular9) + dy=13; + if (f==Regular11) + dy=16; + gui_gc_setColor(nspire_gc,c_rgb565to888(bg)); + gui_gc_fillRect(nspire_gc,x,y,dx,dy); + gui_gc_setColor(nspire_gc,c_rgb565to888(c)); + //gui_gc_setPen(nspire_gc, GC_PS_MEDIUM, GC_PM_SMOOTH); + gui_gc_drawString(nspire_gc, utf16, x, y-1, GC_SM_NORMAL | GC_SM_TOP); // normal mode + return x+dx; +} + +int os_draw_string(int x,int y,int c,int bg,const char * s,int fake){ + get_gc(); + gui_gc_clipRect(nspire_gc,0,nspire_statusarea,SCREEN_WIDTH,SCREEN_HEIGHT-nspire_statusarea,0); + int i=nspire_draw_string(x,y+nspire_statusarea,c,bg,Regular12,s,fake); + gui_gc_clipRect(nspire_gc,0,0,SCREEN_WIDTH,SCREEN_HEIGHT,GC_CRO_RESET); + return i; +} +int os_draw_string_small(int x,int y,int c,int bg,const char * s,int fake){ + get_gc(); + gui_gc_clipRect(nspire_gc,0,nspire_statusarea,SCREEN_WIDTH,SCREEN_HEIGHT-nspire_statusarea,GC_CRO_SET); + int i=nspire_draw_string(x,y+nspire_statusarea,c,bg,Regular9,s,fake); + gui_gc_clipRect(nspire_gc,0,0,SCREEN_WIDTH,SCREEN_HEIGHT,GC_CRO_RESET); + return i; +} + +int os_draw_string_medium(int x,int y,int c,int bg,const char * s,int fake){ + get_gc(); + gui_gc_clipRect(nspire_gc,0,nspire_statusarea,SCREEN_WIDTH,SCREEN_HEIGHT-nspire_statusarea,GC_CRO_SET); + int i=nspire_draw_string(x,y+nspire_statusarea,c,bg,Regular11,s,fake); + gui_gc_clipRect(nspire_gc,0,0,SCREEN_WIDTH,SCREEN_HEIGHT,GC_CRO_RESET); + return i; +} + +void statuslinemsg(const char * msg){ + get_gc(); + int bg=exam_bg(); + gui_gc_setColor(nspire_gc,c_rgb565to888(bg)); + gui_gc_fillRect(nspire_gc,0,0,SCREEN_WIDTH,nspire_statusarea); + nspire_draw_string(0,0,exam_mode?0xffff:0,bg,Regular9,msg,false); + if (nspireemu) + nspire_draw_string(190,0,exam_mode?0xffff:0,bg,Regular9," emu ",false); + else + nspire_draw_string(190,0,exam_mode?0xffff:0,bg,Regular9," CAS ",false); +} + +void display_time(){ + int h,m,s; + get_hms(&h,&m,&s); + char msg[10]; + msg[0]=' '; + msg[1]='0'+(h/10); + msg[2]='0'+(h%10); + msg[3]= 'h'; + msg[4]= ('0'+(m/10)); + msg[5]= ('0'+(m%10)); + msg[6]=0; + //msg[6]= 'm'; + //msg[7] = ('0'+(s/10)); + //msg[8] = ('0'+(s%10)); + //msg[9]=0; + int bg=exam_bg(); + gui_gc_setColor(nspire_gc,c_rgb565to888(bg)); + gui_gc_fillRect(nspire_gc,270,0,SCREEN_WIDTH-270,nspire_statusarea); + nspire_draw_string(270,0,exam_mode?0xffff:0,bg,Regular9,msg,false); +} + +void sync_screen(){ + get_gc(); + //gui_gc_finish(nspire_gc); + gui_gc_blit_to_screen(nspire_gc); + ck_msleep(10); + //nspire_gc=0; + // gui_gc_begin(nspire_gc); +} + +// Nspire peripheral reset : +// https://github.com/nDroidProject/nDroid-bootloader/blob/master/kernel.c +// https://hackspire.org/index.php?title=Memory-mapped_I/O_ports_on_CX#CC000000_-_SHA-256_hash_generator +// hardware ports +// https://hackspire.org/index.php?title=Memory-mapped_I/O_ports_on_CX + +int nspire_shift=0; +int nspire_ctrl=0; +int nspire_select=false; +void statusline(int mode){ + char *msg=0; + if (nspire_ctrl){ + if (nspire_shift) + msg="shift ctrl"; + else + msg=" ctrl"; + } + else { + if (nspire_shift) + msg="shift"; + else + msg=""; + } + int bg=exam_bg(); + gui_gc_setColor(nspire_gc,c_rgb565to888(bg)); + gui_gc_fillRect(nspire_gc,210,0,SCREEN_WIDTH-210,nspire_statusarea); + nspire_draw_string(224,0,exam_mode?0xffff:0,bg,Regular9,msg,false); + if (nspireemu) + nspire_draw_string(190,0,exam_mode?0xffff:0,bg,Regular9," emu ",false); + else + nspire_draw_string(190,0,0xf800,bg,Regular9," CAS ",false); + display_time(); + if (mode==0) + return; + sync_screen(); +} + + +#define SHIFTCTRL(x, y, z) (nspire_ctrl ? (z) : nspire_shift ? (y) : (x)) +#define SHIFT(x, y) SHIFTCTRL(x, y, x) +#define CTRL(x, y) SHIFTCTRL(x, x, y) +#define NORMAL(x) SHIFTCTRL(x, x, x) + +int nspire_scan(int * adaptive_cursor_state){ + if (isKeyPressed(KEY_NSPIRE_CTRL)){ + while (isKeyPressed(KEY_NSPIRE_CTRL)) + ; + nspire_ctrl=!nspire_ctrl; + nspire_shift=0; + statusline(1); + return -2; + } + if (isKeyPressed(KEY_NSPIRE_SHIFT)){ + while (isKeyPressed(KEY_NSPIRE_SHIFT)) + ; + nspire_shift=!nspire_shift; + nspire_ctrl=0; + statusline(1); + return -1; + } + *adaptive_cursor_state = SHIFTCTRL(0, 1, 4); + if (isKeyPressed(KEY_NSPIRE_LEFT)|| isKeyPressed(KEY_NSPIRE_LEFTUP) || isKeyPressed(KEY_NSPIRE_DOWNLEFT)) + return SHIFTCTRL(KEY_CTRL_LEFT,KEY_SHIFT_LEFT,KEY_LEFT_CTRL); + if (isKeyPressed(KEY_NSPIRE_RIGHT)|| isKeyPressed(KEY_NSPIRE_UPRIGHT) || isKeyPressed(KEY_NSPIRE_RIGHTDOWN)) + return SHIFTCTRL(KEY_CTRL_RIGHT,KEY_SHIFT_RIGHT,KEY_RIGHT_CTRL); + if (isKeyPressed(KEY_NSPIRE_UP)) + return SHIFTCTRL(KEY_CTRL_UP,KEY_CTRL_PAGEUP,KEY_UP_CTRL); + if (isKeyPressed(KEY_NSPIRE_DOWN)) + return SHIFTCTRL(KEY_CTRL_DOWN,KEY_CTRL_PAGEDOWN,KEY_DOWN_CTRL); + + if (isKeyPressed(KEY_NSPIRE_ESC)) return KEY_CTRL_EXIT ; + if (isKeyPressed(KEY_NSPIRE_HOME)) return SHIFTCTRL(KEY_CTRL_MENU,KEY_CTRL_MENU,KEY_CTRL_AC) ; + if (isKeyPressed(KEY_NSPIRE_MENU)) return KEY_CTRL_CATALOG ; + + // Characters + if (isKeyPressed(KEY_NSPIRE_A)) return SHIFTCTRL('a','A',KEY_CTRL_A); + if (isKeyPressed(KEY_NSPIRE_B)) return SHIFTCTRL('b','B',KEY_BOOK); + if (isKeyPressed(KEY_NSPIRE_C)) return SHIFTCTRL('c','C',KEY_CTRL_CLIP); + if (isKeyPressed(KEY_NSPIRE_D)) return SHIFTCTRL('d','D',KEY_CTRL_D); + if (isKeyPressed(KEY_NSPIRE_E)) return SHIFTCTRL('e','E',KEY_CTRL_F10); + if (isKeyPressed(KEY_NSPIRE_F)) return SHIFTCTRL('f','F',KEY_CTRL_F11); + if (isKeyPressed(KEY_NSPIRE_G)) return SHIFTCTRL('g','G',KEY_CTRL_F12); + if (isKeyPressed(KEY_NSPIRE_H)) return SHIFTCTRL('h','H',KEY_CTRL_F13); + if (isKeyPressed(KEY_NSPIRE_I)) return SHIFTCTRL('i','I',KEY_CTRL_F14); + if (isKeyPressed(KEY_NSPIRE_J)) return SHIFTCTRL('j','J',KEY_CTRL_F15); + if (isKeyPressed(KEY_NSPIRE_K)) return SHIFTCTRL('k','K',KEY_CTRL_AC); + if (isKeyPressed(KEY_NSPIRE_L)) return SHIFTCTRL('l','L',KEY_CTRL_F14); + if (isKeyPressed(KEY_NSPIRE_M)) return SHIFTCTRL('m','M',KEY_CTRL_CATALOG); + if (isKeyPressed(KEY_NSPIRE_N)) return SHIFTCTRL('n','N',KEY_CTRL_N); + if (isKeyPressed(KEY_NSPIRE_O)) return SHIFTCTRL('o','O',KEY_SHIFT_OPTN); + if (isKeyPressed(KEY_NSPIRE_P)) return SHIFTCTRL('p','P',KEY_CTRL_PRGM); + if (isKeyPressed(KEY_NSPIRE_Q)) return SHIFT('q','Q'); + if (isKeyPressed(KEY_NSPIRE_R)) return SHIFTCTRL('r','R',KEY_CTRL_R); + if (isKeyPressed(KEY_NSPIRE_S)) return SHIFTCTRL('s','S',KEY_CTRL_S); + if (isKeyPressed(KEY_NSPIRE_T)) return SHIFTCTRL('t','T',KEY_CTRL_T); + if (isKeyPressed(KEY_NSPIRE_U)) return SHIFTCTRL('u','U',KEY_CTRL_F13); + if (isKeyPressed(KEY_NSPIRE_V)) return SHIFTCTRL('v','V',KEY_CTRL_PASTE); + if (isKeyPressed(KEY_NSPIRE_W)) return SHIFT('w','W'); + if (isKeyPressed(KEY_NSPIRE_X)) return SHIFTCTRL('x','X',KEY_CTRL_CUT); + if (isKeyPressed(KEY_NSPIRE_Y)) return SHIFT('y','Y'); + if (isKeyPressed(KEY_NSPIRE_Z)) return SHIFTCTRL('z','Z',KEY_CTRL_UNDO); + + // Numbers + if (nspireemu){ // for firebird, redefine ctrl + if (isKeyPressed(KEY_NSPIRE_0)) return SHIFTCTRL('0',KEY_CTRL_F10,')'); + if (isKeyPressed(KEY_NSPIRE_1)) return SHIFTCTRL('1',KEY_CTRL_F1,'!'); + if (isKeyPressed(KEY_NSPIRE_2)) return SHIFTCTRL('2',KEY_CTRL_F2,'@'); + if (isKeyPressed(KEY_NSPIRE_3)) return SHIFTCTRL('3',KEY_CTRL_F3,'#'); + if (isKeyPressed(KEY_NSPIRE_4)) return SHIFTCTRL('4',KEY_CTRL_F4,'$'); + if (isKeyPressed(KEY_NSPIRE_5)) return SHIFTCTRL('5',KEY_CTRL_F5,'%'); + if (isKeyPressed(KEY_NSPIRE_6)) return SHIFTCTRL('6',KEY_CTRL_F6,'^'); + if (isKeyPressed(KEY_NSPIRE_7)) return SHIFTCTRL('7',KEY_CTRL_F7,'&'); + if (isKeyPressed(KEY_NSPIRE_8)) return SHIFTCTRL('8',KEY_CTRL_F8,'*'); + if (isKeyPressed(KEY_NSPIRE_9)) return SHIFTCTRL('9',KEY_CTRL_F9,'('); + } + else { + if (isKeyPressed(KEY_NSPIRE_0)) return SHIFTCTRL('0',KEY_CTRL_F10,KEY_CTRL_F10); + if (isKeyPressed(KEY_NSPIRE_1)) return SHIFTCTRL('1',KEY_CTRL_F1,KEY_CTRL_F1); + if (isKeyPressed(KEY_NSPIRE_2)) return SHIFTCTRL('2',KEY_CTRL_F2,KEY_CTRL_F2); + if (isKeyPressed(KEY_NSPIRE_3)) return SHIFTCTRL('3',KEY_CTRL_F3,KEY_CTRL_F3); + if (isKeyPressed(KEY_NSPIRE_4)) return SHIFTCTRL('4',KEY_CTRL_F4,KEY_CTRL_F4); + if (isKeyPressed(KEY_NSPIRE_5)) return SHIFTCTRL('5',KEY_CTRL_F5,KEY_CTRL_F5); + if (isKeyPressed(KEY_NSPIRE_6)) return SHIFTCTRL('6',KEY_CTRL_F6,KEY_CTRL_F6); + if (isKeyPressed(KEY_NSPIRE_7)) return SHIFTCTRL('7',KEY_CTRL_F7,KEY_CTRL_F7); + if (isKeyPressed(KEY_NSPIRE_8)) return SHIFTCTRL('8',KEY_CTRL_F8,KEY_CTRL_F8); + if (isKeyPressed(KEY_NSPIRE_9)) return SHIFTCTRL('9',KEY_CTRL_F9,KEY_CTRL_F9); + } + + // Symbols + if (isKeyPressed(KEY_NSPIRE_FRAC)) return SHIFTCTRL(KEY_EQW_TEMPLATE,KEY_AFFECT,KEY_AFFECT); + if (isKeyPressed(KEY_NSPIRE_SQU)) return CTRL(KEY_CHAR_SQUARE,KEY_CHAR_ROOT); + if (isKeyPressed(KEY_NSPIRE_TENX)) return CTRL(KEY_CHAR_EXPN10,KEY_CHAR_LOG); + if (isKeyPressed(KEY_NSPIRE_eEXP)) return CTRL(KEY_CHAR_EXPN,KEY_CHAR_LN); + if (isKeyPressed(KEY_NSPIRE_COMMA)) return SHIFTCTRL(',',';',':'); + if (isKeyPressed(KEY_NSPIRE_PERIOD)) return SHIFTCTRL('.',KEY_CTRL_F11,':'); + if (isKeyPressed(KEY_NSPIRE_COLON)) return NORMAL(':'); + if (isKeyPressed(KEY_NSPIRE_LP)) return SHIFTCTRL('(',KEY_CTRL_F13,KEY_CHAR_CROCHETS); + if (isKeyPressed(KEY_NSPIRE_RP)) return SHIFTCTRL(')',KEY_CTRL_F14,KEY_CHAR_ACCOLADES); + if (isKeyPressed(KEY_NSPIRE_SPACE)) return SHIFTCTRL(' ','_','_'); + if (isKeyPressed(KEY_NSPIRE_DIVIDE)) + return SHIFTCTRL('/','%','\\'); + if (isKeyPressed(KEY_NSPIRE_MULTIPLY)) return SHIFTCTRL('*','\'','\"'); + if (isKeyPressed(KEY_NSPIRE_MINUS)) return SHIFTCTRL('-','_', '<'); + if (isKeyPressed(KEY_NSPIRE_NEGATIVE)) return SHIFTCTRL('-',KEY_CTRL_F12,KEY_CHAR_ANS); + if (isKeyPressed(KEY_NSPIRE_PLUS)) return SHIFTCTRL('+', KEY_CHAR_NORMAL,'>'); + if (isKeyPressed(KEY_NSPIRE_EQU)) return SHIFTCTRL('=', '|',KEY_CHAR_STORE); + if (isKeyPressed(KEY_NSPIRE_LTHAN)) return NORMAL('<'); + if (isKeyPressed(KEY_NSPIRE_GTHAN)) return NORMAL('>'); + if (isKeyPressed(KEY_NSPIRE_QUOTE)) return NORMAL('\"'); + if (isKeyPressed(KEY_NSPIRE_APOSTROPHE)) return NORMAL('\''); + if (isKeyPressed(KEY_NSPIRE_QUES)) return SHIFTCTRL('?','|','!'); + if (isKeyPressed(KEY_NSPIRE_QUESEXCL)) return SHIFTCTRL('?','|','!'); + if (isKeyPressed(KEY_NSPIRE_BAR)) return NORMAL('|'); + if (isKeyPressed(KEY_NSPIRE_EXP)) return SHIFT('^',KEY_CHAR_RECIP); + if (isKeyPressed(KEY_NSPIRE_EE)) return SHIFTCTRL('%','&', '@'); + if (isKeyPressed(KEY_NSPIRE_PI)) return KEY_CHAR_PI; + if (isKeyPressed(KEY_NSPIRE_FLAG)) return SHIFTCTRL(';',':',KEY_CHAR_IMGNRY); + if (isKeyPressed(KEY_NSPIRE_ENTER)) return SHIFTCTRL(KEY_CTRL_OK,'~',KEY_CTRL_EXE); + if (isKeyPressed(KEY_NSPIRE_TRIG)) return SHIFTCTRL(KEY_CHAR_SIN,KEY_CHAR_COS,KEY_CHAR_TAN); + + // Special chars + if (isKeyPressed(KEY_NSPIRE_SCRATCHPAD)) return SHIFTCTRL(KEY_CTRL_SETUP,KEY_LOAD,KEY_SAVE); + if (isKeyPressed(KEY_NSPIRE_VAR)) return SHIFTCTRL(KEY_CTRL_VARS,KEY_CHAR_FACTOR,KEY_CHAR_STORE); + if (isKeyPressed(KEY_NSPIRE_DOC)) return SHIFT(KEY_CTRL_MENU,KEY_CTRL_SD); + if (isKeyPressed(KEY_NSPIRE_CAT)) return KEY_BOOK; + if (isKeyPressed(KEY_NSPIRE_DEL)) return SHIFTCTRL(KEY_CTRL_DEL,KEY_CTRL_DEL,KEY_CTRL_AC); + if (isKeyPressed(KEY_NSPIRE_RET)) return KEY_CTRL_EXE; + if (isKeyPressed(KEY_NSPIRE_TAB)) return '\t'; + + return 0; +} + +int ascii_get(int* adaptive_cursor_state){ + int res=nspire_scan(adaptive_cursor_state);; + return res; +} + +int handle_f5(){return 0;} +int iskeydown(int key){ + t_key t=KEY_NSPIRE_SPACE; + switch (key){ + case 0: + t=KEY_NSPIRE_LEFT; + break; + case 1: + t=KEY_NSPIRE_UP; + break; + case 2: + t=KEY_NSPIRE_DOWN; + break; + case 3: + t=KEY_NSPIRE_RIGHT; + break; + case 4: + t=KEY_NSPIRE_ENTER; + break; + case 5: + t=KEY_NSPIRE_ESC; + break; + case 6: + t=KEY_NSPIRE_HOME; + break; + case 7: + t=KEY_NSPIRE_MENU; + break; + case 12: + t=KEY_NSPIRE_SHIFT; + break; + case 13: + t=KEY_NSPIRE_CTRL; + break; + case 14: + t=KEY_NSPIRE_SCRATCHPAD; + break; + case 15: + t=KEY_NSPIRE_VAR; + break; + case 16: + t=KEY_NSPIRE_DOC; + break; + case 17: + t=KEY_NSPIRE_DEL; + break; + case 18: + t=KEY_NSPIRE_eEXP; + break; + case 19: + t=KEY_NSPIRE_EQU; + break; + case 20: + t=KEY_NSPIRE_TENX; + break; + case 21: + t=KEY_NSPIRE_I; + break; + case 22: + t=KEY_NSPIRE_COMMA; + break; + case 23: + t=KEY_NSPIRE_EXP; + break; + case 24: + t=KEY_NSPIRE_TRIG; + break; + case 25: + t=KEY_NSPIRE_C; + break; + case 26: + t=KEY_NSPIRE_T; + break; + case 27: + t=KEY_NSPIRE_PI; + break; + case 28: + t=KEY_NSPIRE_S; + break; + case 29: + t=KEY_NSPIRE_SQU; + break; + case 30: + t=KEY_NSPIRE_7; + break; + case 31: + t=KEY_NSPIRE_8; + break; + case 32: + t=KEY_NSPIRE_9; + break; + case 33: + t=KEY_NSPIRE_LP; + break; + case 34: + t=KEY_NSPIRE_RP; + break; + case 36: + t=KEY_NSPIRE_4; + break; + case 37: + t=KEY_NSPIRE_5; + break; + case 38: + t=KEY_NSPIRE_6; + break; + case 39: + t=KEY_NSPIRE_MULTIPLY; + break; + case 40: + t=KEY_NSPIRE_DIVIDE; + break; + case 42: + t=KEY_NSPIRE_1; + break; + case 43: + t=KEY_NSPIRE_2; + break; + case 44: + t=KEY_NSPIRE_3; + break; + case 45: + t=KEY_NSPIRE_PLUS; + break; + case 46: + t=KEY_NSPIRE_MINUS; + break; + case 48: + t=KEY_NSPIRE_0; + break; + case 49: + t=KEY_NSPIRE_PERIOD; + break; + case 50: + t=KEY_NSPIRE_EE; + break; + case 51: + t=KEY_NSPIRE_NEGATIVE; + break; + case 52: + t=KEY_NSPIRE_RET; + break; + } + return isKeyPressed(t); +} + + +// ? see also ndless-sdk/thirdparty/nspire-io/arch-nspire/nspire.c nio_ascii_get +int getkey(int allow_suspend){ + sync_screen(); + if (shutdown_state) + return KEY_SHUTDOWN; + int lastkey=-1; + unsigned NSPIRE_RTC_ADDR=0x90090000; + static unsigned lastt=0; + for (;;){ + unsigned t1= * (volatile unsigned *) NSPIRE_RTC_ADDR; + if (lastt==0) + lastt=t1; + if (t1-lastt>10){ + display_time(); + sync_screen(); + } + int autosuspend=(t1-lastt>=100); + if (nspire_exam_mode!=2 && + is_cx2 && nspire_ctrl && on_key_pressed()){ + os_fill_rect(50,90,200,40,0x1234); + nspire_draw_string(60,120,0,0xffff,Regular12,"Quit KhiCAS to shutdown",false); + nspire_ctrl=false; + statusline(1); + continue; + } + if ( (nspire_exam_mode==2 || !is_cx2) && + allow_suspend && (autosuspend || (nspire_ctrl && on_key_pressed()))){ + nspire_ctrl=nspire_shift=false; + while (!autosuspend && on_key_pressed()) + loopsleep(10); + // somewhat OFF by setting LCD to 0 + unsigned NSPIRE_CONTRAST_ADDR=is_cx2?0x90130014:0x900f0020; + unsigned oldval=*(volatile unsigned *)NSPIRE_CONTRAST_ADDR,oldval2; + if (is_cx2){ + oldval2=*(volatile unsigned *) (NSPIRE_CONTRAST_ADDR+4); + *(volatile unsigned *) (NSPIRE_CONTRAST_ADDR+4)=0xffff; + } + *(volatile unsigned *)NSPIRE_CONTRAST_ADDR=is_cx2?0xffff:0x100; + static volatile uint32_t *lcd_controller = (volatile uint32_t*) 0xC0000000; + lcd_controller[6] &= ~(0b1 << 11); + loopsleep(20); + lcd_controller[6] &= ~ 0b1; + unsigned offtime=* (volatile unsigned *) NSPIRE_RTC_ADDR; + for (int n=0;!on_key_pressed();++n){ + loopsleep(100); + idle(); + if (!exam_mode && nspire_exam_mode!=2 && shutdown + // && n&0xff==0 + ){ + unsigned curtime=* (volatile unsigned *) NSPIRE_RTC_ADDR; + if (curtime-offtime>7200){ + shutdown_state=1; + // after 2 hours, leave KhiCAS + // that way the OS will really shutdown the calc + lcd_controller[6] |= 0b1; + loopsleep(20); + lcd_controller[6]|= 0b1 << 11; + if (is_cx2) + *(volatile unsigned *)(NSPIRE_CONTRAST_ADDR+4)=oldval2; + *(volatile unsigned *)NSPIRE_CONTRAST_ADDR=oldval; + statuslinemsg("Press ON to disable KhiCAS auto shutdown"); + //os_fill_rect(0,0,320,222,0xffff); + sync_screen(); + int m=0,mmax=150; + for (;m=KEY_CTRL_LEFT && i<=KEY_CTRL_RIGHT) || + (i>=KEY_UP_CTRL && i<=KEY_RIGHT_CTRL) || + (i>=KEY_SELECT_LEFT && i<=KEY_SELECT_RIGHT) || + i==KEY_CTRL_DEL){ + int delay=(lastkey==i)?5:60,j; + for (j=0;j=0 && h>=0) + os_fill_rect(x,y,w,h,c); + } + int os_get_pixel(int x,int y); + /* returns new x position */ +#ifdef __cplusplus + int os_draw_string(int x,int y,int c,int bg,const char * s,int fake=0); +#else + int os_draw_string(int x,int y,int c,int bg,const char * s,int fake); +#endif + inline int os_draw_string_(int x,int y,const char * s){ return os_draw_string(x,y,SDK_BLACK,SDK_WHITE,s,0);} +#ifdef __cplusplus + int os_draw_string_small(int x,int y,int c,int bg,const char * s,int fake=0); +#else + int os_draw_string_small(int x,int y,int c,int bg,const char * s,int fake); +#endif + inline int os_draw_string_small_(int x,int y,const char * s){ return os_draw_string_small(x,y,SDK_BLACK,SDK_WHITE,s,0);} + +#ifdef __cplusplus +#ifdef NUMWORKS + inline int os_draw_string_medium(int x,int y,int c,int bg,const char * s,int fake=0){ return os_draw_string_small(x,y,c,bg,s,fake);} +#else + int os_draw_string_medium(int x,int y,int c,int bg,const char * s,int fake=0); +#endif +#else +#ifdef NUMWORKS + inline int os_draw_string_medium(int x,int y,int c,int bg,const char * s,int fake){ return os_draw_string_small(x,y,c,bg,s,fake);} +#else + int os_draw_string_medium(int x,int y,int c,int bg,const char * s,int fake); +#endif +#endif + inline int os_draw_string_medium_(int x,int y,const char * s){ return os_draw_string_medium(x,y,SDK_BLACK,SDK_WHITE,s,0);} + + inline void Printmini(int x,int y,const char * s,int i){ + // dbg_printf("Printmini %i %i %s %i\n",x,y,s,i); +#if defined FX || defined FXCG + os_draw_string_small(x,y,i?0xffff:0,i?0:0xffff,s,false); +#else + os_draw_string_small(x,y,SDK_BLACK,i?COLOR_SELECTED:SDK_WHITE,s,false); +#endif + } + + inline void Printxy(int x,int y,const char * s,int i){ os_draw_string_medium(x,y,0,i?COLOR_SELECTED:0xffff,s,false);} + inline void PrintXY(int x,int y,const char * s,int i){ Printxy(3*x,3*y,s,i);} + + void GetKey(int * key); + int getkey(int allow_suspend); // transformed + inline void ck_getkey(int *key){ GetKey(key);} + void enable_back_interrupt(); + inline void set_abort(){ enable_back_interrupt(); } + void disable_back_interrupt(); + inline void clear_abort(){ disable_back_interrupt(); } + int isalphaactive(); + int alphawasactive(int * key); + void lock_alpha(); + void reset_kbd(); + int handle_f5(); + void statuslinemsg(const char * msg); +#ifdef __cplusplus + void statusline(int mode=0); +#else + void statusline(int mode); +#endif + void statusflags(void); +#ifdef NUMWORKS + inline int iskeydown(int key){ return getkey(key | 0x80000000); } +#else + int iskeydown(int key); +#endif + +#if defined NSPIRE || defined NSPIRE_NEWLIB + extern int nspire_shift,nspire_ctrl; + double loopsleep(int ms); +#include + Gc * get_gc(); + int c_rgb565to888(int c); + int nspire_scan(int * adaptive_cursor_state); + +#define max_heap_size 60 + extern int nspireemu; + extern char nspire_filebuf[NSPIRE_FILEBUFFER]; + extern int on_key_enabled; + void get_hms(int *h,int *m,int *s); + void reset_gc(); +#endif + + extern int (*shutdown)(); // function called after 2 hours of idle + extern short int shutdown_state; + inline void Bdisp_PutDisp_DD(void){ sync_screen(); } + inline void sprint_int(char * c,int i){ sprintf(c,"%d",i);} + inline void sprint_double(char * c,double d){ sprintf(c,"%.4g",d);} + int GetSetupSetting(int k); + inline int Setup_GetEntry(int k){ return GetSetupSetting(k); } + void SetQuitHandler( void (*f)(void)); +#define RTC_GetTicks millis +#ifndef TICE + inline void clear_screen(void){os_fill_rect(0,0,LCD_WIDTH_PX,LCD_HEIGHT_PX,SDK_WHITE);} +#endif + inline void Bdisp_AllClr_VRAM(void){ clear_screen(); } + +#ifdef __cplusplus +} +#endif +#endif // K_CSDK_H diff --git a/ion/src/simulator/nspire/k_defs.h b/ion/src/simulator/nspire/k_defs.h new file mode 100755 index 00000000000..ce395b192c3 --- /dev/null +++ b/ion/src/simulator/nspire/k_defs.h @@ -0,0 +1,211 @@ +#ifndef K_DEFS_H +#define K_DEFS_H +#define NSPIRE_FILEBUFFER 512*1024 + // Character codes +#define KEY_CHAR_0 0x30 +#define KEY_CHAR_1 0x31 +#define KEY_CHAR_2 0x32 +#define KEY_CHAR_3 0x33 +#define KEY_CHAR_4 0x34 +#define KEY_CHAR_5 0x35 +#define KEY_CHAR_6 0x36 +#define KEY_CHAR_7 0x37 +#define KEY_CHAR_8 0x38 +#define KEY_CHAR_9 0x39 +#define KEY_CHAR_DP 0x2e +#define KEY_CHAR_EXP 0x0f +#define KEY_CHAR_PMINUS 30200 +#define KEY_CHAR_PLUS 43 +#define KEY_CHAR_MINUS 45 +#define KEY_CHAR_MULT 42 +#define KEY_CHAR_DIV 47 +#define KEY_CHAR_FRAC 0xbb +#define KEY_CHAR_LPAR 0x28 +#define KEY_CHAR_RPAR 0x29 +#define KEY_CHAR_COMMA 0x2c +#define KEY_CHAR_STORE 0x0e +#define KEY_CHAR_LOG 0x95 +#define KEY_CHAR_LN 0x85 +#define KEY_CHAR_SIN 0x81 +#define KEY_CHAR_COS 0x82 +#define KEY_CHAR_TAN 0x83 +#define KEY_CHAR_SQUARE 0x8b +#define KEY_CHAR_POW 0xa8 +#define KEY_CHAR_IMGNRY 0x7f50 +#define KEY_CHAR_LIST 0x7f51 +#define KEY_CHAR_MAT 0x7f40 +#define KEY_CHAR_EQUAL 0x3d +#define KEY_CHAR_PI 0xd0 +#define KEY_CHAR_ANS 0xc0 +#define KEY_SHIFT_ANS 0xc1 +#define KEY_CHAR_LBRCKT 0x5b +#define KEY_CHAR_RBRCKT 0x5d +#define KEY_CHAR_LBRACE 0x7b +#define KEY_CHAR_RBRACE 0x7d +#define KEY_CHAR_CR 0x0d +#define KEY_CHAR_CUBEROOT 0x96 +#define KEY_CHAR_RECIP 0x9b +#define KEY_CHAR_ANGLE 0x7f54 +#define KEY_CHAR_EXPN10 0xb5 +#define KEY_CHAR_EXPN 0xa5 +#define KEY_CHAR_ASIN 0x91 +#define KEY_CHAR_ACOS 0x92 +#define KEY_CHAR_ATAN 0x93 +#define KEY_CHAR_ROOT 0x86 +#define KEY_CHAR_POWROOT 0xb8 +#define KEY_CHAR_SPACE 0x20 +#define KEY_CHAR_DQUATE 0x22 +#define KEY_CHAR_VALR 0xcd +#define KEY_CHAR_THETA 0xce +#define KEY_CHAR_FACTOR 0xda +#define KEY_CHAR_NORMAL 0xdb +#define KEY_CHAR_SHIFTMINUS 0xdc +#define KEY_CHAR_CROCHETS 0xdd +#define KEY_CHAR_ACCOLADES 0xde +#define KEY_CHAR_A 0x41 +#define KEY_CHAR_B 0x42 +#define KEY_CHAR_C 0x43 +#define KEY_CHAR_D 0x44 +#define KEY_CHAR_E 0x45 +#define KEY_CHAR_F 0x46 +#define KEY_CHAR_G 0x47 +#define KEY_CHAR_H 0x48 +#define KEY_CHAR_I 0x49 +#define KEY_CHAR_J 0x4a +#define KEY_CHAR_K 0x4b +#define KEY_CHAR_L 0x4c +#define KEY_CHAR_M 0x4d +#define KEY_CHAR_N 0x4e +#define KEY_CHAR_O 0x4f +#define KEY_CHAR_P 0x50 +#define KEY_CHAR_Q 0x51 +#define KEY_CHAR_R 0x52 +#define KEY_CHAR_S 0x53 +#define KEY_CHAR_T 0x54 +#define KEY_CHAR_U 0x55 +#define KEY_CHAR_V 0x56 +#define KEY_CHAR_W 0x57 +#define KEY_CHAR_X 0x58 +#define KEY_CHAR_Y 0x59 +#define KEY_CHAR_Z 0x5a + + + // Control codes +#define KEY_CTRL_FORMAT 30203 +#define KEY_CTRL_NOP 30202 +#define KEY_CTRL_EXE 30201 +#define KEY_CTRL_DEL 30025 +#define KEY_CTRL_AC 30070 +#define KEY_CTRL_FD 30046 +#define KEY_CTRL_UNDO 30045 +#define KEY_CTRL_XTT 30001 +#define KEY_CTRL_EXIT 5 +#define KEY_CTRL_OK 4 +#define KEY_CTRL_SHIFT 30006 +#define KEY_CTRL_ALPHA 30007 +#define KEY_CTRL_OPTN 30008 +#define KEY_CTRL_VARS 30030 +#define KEY_CTRL_UP 1 +#define KEY_CTRL_DOWN 2 +#define KEY_CTRL_LEFT 0 +#define KEY_CTRL_RIGHT 3 +#define KEY_CTRL_F1 30009 +#define KEY_CTRL_F2 30010 +#define KEY_CTRL_F3 30011 +#define KEY_CTRL_F4 30012 +#define KEY_CTRL_F5 30013 +#define KEY_CTRL_F6 30014 +#define KEY_CTRL_F7 30915 +#define KEY_CTRL_F8 30916 +#define KEY_CTRL_F9 30917 +#define KEY_CTRL_F10 30918 +#define KEY_CTRL_F11 30919 +#define KEY_CTRL_F12 30920 +#define KEY_CTRL_F13 30921 +#define KEY_CTRL_F14 30922 +#define KEY_CTRL_F15 30923 +#define KEY_CTRL_F16 30924 +#define KEY_CTRL_F17 30925 +#define KEY_CTRL_F18 30926 +#define KEY_CTRL_F19 30927 +#define KEY_CTRL_F20 30928 +#define KEY_CTRL_CATALOG 30100 +#define KEY_CTRL_CAPTURE 30055 +#define KEY_CTRL_CLIP 30050 +#define KEY_CTRL_CUT 30250 +#define KEY_CTRL_PASTE 30036 +#define KEY_CTRL_INS 30033 +#define KEY_CTRL_MIXEDFRAC 30054 +#define KEY_CTRL_FRACCNVRT 30026 +#define KEY_CTRL_QUIT 30029 +#define KEY_CTRL_PRGM 30028 +#define KEY_CTRL_SETUP 30037 +#define KEY_CTRL_PAGEUP 30052 +#define KEY_CTRL_PAGEDOWN 30053 +#define KEY_CTRL_MENU 30003 +#define KEY_SHIFT_OPTN 30059 +#define KEY_CTRL_RESERVE1 30060 +#define KEY_CTRL_RESERVE2 30061 +#define KEY_SHIFT_LEFT 30062 +#define KEY_SHIFT_RIGHT 30063 +#define KEY_UP_CTRL 31060 +#define KEY_DOWN_CTRL 31061 +#define KEY_LEFT_CTRL 31062 +#define KEY_RIGHT_CTRL 31063 +#define KEY_CALCULATOR 31064 +#define KEY_SAVE 31065 +#define KEY_LOAD 31066 +#define KEY_CTRL_A 31001 +#define KEY_CTRL_D 31004 +#define KEY_CTRL_E 31005 +#define KEY_CTRL_H 31008 // help? +#define KEY_CTRL_M 31011 // doc menu +#define KEY_CTRL_N 31012 +#define KEY_CTRL_R 31018 +#define KEY_CTRL_S 31019 +#define KEY_CTRL_T 31020 +#define KEY_EQW_TEMPLATE 31100 +#define KEY_AFFECT 31101 +#define KEY_FLAG 31102 +#define KEY_BOOK 31103 +//#define KEY_CTRL_APPS 31104 +#define KEY_CTRL_SYMB 31105 +//#define KEY_CTRL_NUM 31106 +//#define KEY_CTRL_PLOT 31107 +#define KEY_SELECT_LEFT 31200 +#define KEY_SELECT_UP 31201 +#define KEY_SELECT_DOWN 31202 +#define KEY_SELECT_RIGHT 31203 +#define KEY_SHUTDOWN 32109 + +#define KEY_PRGM_ACON 10 +#define KEY_PRGM_DOWN 37 +#define KEY_PRGM_EXIT 47 +#define KEY_PRGM_F1 79 +#define KEY_PRGM_F2 69 +#define KEY_PRGM_F3 59 +#define KEY_PRGM_F4 49 +#define KEY_PRGM_F5 39 +#define KEY_PRGM_F6 29 +#define KEY_PRGM_LEFT 38 +#define KEY_PRGM_NONE 0 +#define KEY_PRGM_RETURN 31 +#define KEY_PRGM_RIGHT 27 +#define KEY_PRGM_UP 28 +#define KEY_PRGM_1 72 +#define KEY_PRGM_2 62 +#define KEY_PRGM_3 52 +#define KEY_PRGM_4 73 +#define KEY_PRGM_5 63 +#define KEY_PRGM_6 53 +#define KEY_PRGM_7 74 +#define KEY_PRGM_8 64 +#define KEY_PRGM_9 54 +#define KEY_PRGM_A 76 +#define KEY_PRGM_F 26 +#define KEY_PRGM_ALPHA 77 +#define KEY_PRGM_SHIFT 78 +#define KEY_PRGM_MENU 48 +#define KEY_CTRL_SD 39990 + +#endif diff --git a/ion/src/simulator/nspire/keyboard.cpp b/ion/src/simulator/nspire/keyboard.cpp new file mode 100644 index 00000000000..65df840a99e --- /dev/null +++ b/ion/src/simulator/nspire/keyboard.cpp @@ -0,0 +1,276 @@ +#include +#include + + +#include +#include +#include + +#include "layout_keyboard.h" +#include "main.h" +#include "k_csdk.h" + + +using namespace Ion::Keyboard; + +class KeyPair { +public: + constexpr KeyPair(Key key, bool numworksShift, bool numworksAlpha, int gintKey, bool gintShift, bool gintAlpha, bool ignoreShiftAlpha = false) : + m_key(key), + m_numworksShift(numworksShift), + m_numworksAlpha(numworksAlpha), + m_gintKey(gintKey), + m_gintShift(gintShift), + m_gintAlpha(gintAlpha), + m_ignoreShiftAlpha(ignoreShiftAlpha) + {} + Key key() const { return m_key; } + bool numworksShift() const { return m_numworksShift; } + bool numworksAlpha() const { return m_numworksAlpha; } + int gintKey() const { return m_gintKey; } + bool gintShift() const { return m_gintShift; } + bool gintAlpha() const { return m_gintAlpha; } + bool ignoreShiftAlpha() const { return m_ignoreShiftAlpha; } +private: + Key m_key; + bool m_numworksShift; + bool m_numworksAlpha; + int m_gintKey; + bool m_gintShift; + bool m_gintAlpha; + bool m_ignoreShiftAlpha; +}; + +constexpr static KeyPair sKeyPairs[] = { + KeyPair(Key::Down, false, false, KEY_DOWN, false, false, true), + KeyPair(Key::Left, false, false, KEY_LEFT, false, false, true), + KeyPair(Key::Right, false, false, KEY_RIGHT, false, false, true), + KeyPair(Key::Up, false, false, KEY_UP, false, false, true), + KeyPair(Key::Back, false, false, KEY_EXIT, false, false), + KeyPair(Key::Home, false, false, KEY_MENU, false, false), + KeyPair(Key::Shift, false, false, KEY_SHIFT, false, false, true), + KeyPair(Key::Alpha, false, false, KEY_ALPHA, false, false, true), + KeyPair(Key::XNT, false, false, KEY_XOT, false, false), + KeyPair(Key::Var, false, false, KEY_VARS, false, false), + KeyPair(Key::Toolbox, false, false, KEY_CTRL_CATALOG, false, false), + KeyPair(Key::Backspace, false, false, KEY_DEL, false, false), + KeyPair(Key::Exp, false, false, KEY_CHAR_EXPN, false, false), + KeyPair(Key::Ln, false, false, KEY_LN, false, false), + KeyPair(Key::Log, false, false, KEY_LOG, false, false), + KeyPair(Key::Imaginary, false, false, KEY_CHAR_IMGNRY, false, false), + KeyPair(Key::Comma, false, false, KEY_COMMA, false, false), + KeyPair(Key::Power, false, false, '^', false, false), + KeyPair(Key::Sine, false, false, KEY_SIN, false, false), + KeyPair(Key::Cosine, false, false, KEY_COS, false, false), + KeyPair(Key::Tangent, false, false, KEY_TAN, false, false), + KeyPair(Key::Pi, false, false, KEY_CHAR_PI, false, false), + KeyPair(Key::Sqrt, false, false, KEY_CHAR_ROOT, false, false), + KeyPair(Key::Square, false, false, KEY_SQUARE, false, false), + KeyPair(Key::Seven, false, false, KEY_7, false, false), + KeyPair(Key::Eight, false, false, KEY_8, false, false), + KeyPair(Key::Nine, false, false, KEY_9, false, false), + KeyPair(Key::LeftParenthesis, false, false, KEY_LEFTP, false, false), + KeyPair(Key::RightParenthesis, false, false, KEY_RIGHTP, false, false), + KeyPair(Key::Four, false, false, KEY_4, false, false), + KeyPair(Key::Five, false, false, KEY_5, false, false), + KeyPair(Key::Six, false, false, KEY_6, false, false), + KeyPair(Key::Multiplication, false, false, KEY_MUL, false, false), + KeyPair(Key::Division, false, false, KEY_DIV, false, false), + KeyPair(Key::Division, false, false, KEY_FRAC, false, false), + KeyPair(Key::One, false, false, KEY_1, false, false), + KeyPair(Key::Two, false, false, KEY_2, false, false), + KeyPair(Key::Three, false, false, KEY_3, false, false), + KeyPair(Key::Plus, false, false, KEY_ADD, false, false), + KeyPair(Key::Minus, false, false, KEY_SUB, false, false), + KeyPair(Key::Zero, false, false, KEY_0, false, false), + KeyPair(Key::Dot, false, false, KEY_DOT, false, false), + KeyPair(Key::EE, false, false, KEY_CHAR_EXP, false, false), + KeyPair(Key::Ans, false, false, KEY_CHAR_ANS, true, false), + KeyPair(Key::EXE, false, false, KEY_EXE, false, false, true), + KeyPair(Key::OnOff, false, false, KEY_ACON, false, false), + + // Cut + KeyPair(Key::XNT, true, false, KEY_CTRL_CUT, false, false), + // Copy + KeyPair(Key::Var, true, false, KEY_CTRL_CLIP, false, false), + // Paste + KeyPair(Key::Toolbox, true, false, KEY_CTRL_PASTE, false, false), + // Clear + KeyPair(Key::Backspace, true, false, KEY_ACON, false, false), + // [ + KeyPair(Key::Exp, true, false, KEY_CHAR_CROCHETS, false, false), + // ] + KeyPair(Key::Ln, true, false, KEY_CTRL_F13, false, false), + // { + KeyPair(Key::Log, true, false, KEY_CHAR_ACCOLADES, false, false), + // } + KeyPair(Key::Imaginary, true, false, KEY_CTRL_F14, false, false), + // _ + KeyPair(Key::Comma, true, false, KEY_NEG, false, false), + // -> + KeyPair(Key::Power, true, false, KEY_STORE, false, true), + // asin + KeyPair(Key::Sine, true, false, KEY_CHAR_ASIN, true, false), + // acos + KeyPair(Key::Cosine, true, false, KEY_CHAR_ACOS, true, false), + // atan + KeyPair(Key::Tangent, true, false, KEY_CHAR_ATAN, true, false), + // = + KeyPair(Key::Pi, true, false, KEY_CHAR_EQUAL, false, false), + // < + KeyPair(Key::Sqrt, true, false, '<', false, false), + // > + KeyPair(Key::Square, true, false, '>', false, false), + + // : + KeyPair(Key::XNT, false, true, ':', false, false), + // ; + KeyPair(Key::Var, false, true, ';', false, false), + // " + KeyPair(Key::Toolbox, false, true, KEY_CHAR_DQUATE, false, true), + // % + KeyPair(Key::Backspace, false, true, '%', false, false), + // A + KeyPair(Key::Exp, false, true, 'a', false, false), + // B + KeyPair(Key::Ln, false, true, 'b', false, false), + // C + KeyPair(Key::Log, false, true, 'c', false, false), + // D + KeyPair(Key::Imaginary, false, true, 'd', false, false), + // E + KeyPair(Key::Comma, false, true, 'e', false, false), + // F + KeyPair(Key::Power, false, true, 'f', false, false), + // G + KeyPair(Key::Sine, false, true, 'g', false, false), + // H + KeyPair(Key::Cosine, false, true, 'h', false, false), + // I + KeyPair(Key::Tangent, false, true, 'i', false, false), + // J + KeyPair(Key::Pi, false, true, 'j', false, false), + // K + KeyPair(Key::Sqrt, false, true,'k', false, false), + // L + KeyPair(Key::Square, false, true, 'l', false, false), + // M + KeyPair(Key::Seven, false, true, 'm', false, false), + // N + KeyPair(Key::Eight, false, true, 'n', false, false), + // O + KeyPair(Key::Nine, false, true, 'o', false, false), + // P + KeyPair(Key::LeftParenthesis, false, true, 'p', false, false), + // Q + KeyPair(Key::RightParenthesis, false, true, 'q', false, false), + // R + KeyPair(Key::Four, false, true, 'r', false, false), + // S + KeyPair(Key::Five, false, true, 's', false, false), + // T + KeyPair(Key::Six, false, true, 't', false, false), + // U + KeyPair(Key::Multiplication, false, true, 'u', false, false), + // V + KeyPair(Key::Division, false, true, 'v', false, false), + // W + KeyPair(Key::One, false, true, 'w', false, false), + // X + KeyPair(Key::Two, false, true, 'x', false, false), + // Y + KeyPair(Key::Three, false, true, 'y', false, false), + // Z + KeyPair(Key::Plus, false, true, 'z', false, false), + // Space + KeyPair(Key::Minus, false, true, KEY_CHAR_SPACE, false, false), + // ? + KeyPair(Key::Zero, false, true, KEY_F6, true, false), + // ! + KeyPair(Key::Dot, false, true, KEY_F6, false, false), + + // Brightness control shortcut in Upsilon + KeyPair(Key::Plus, true, false, KEY_ADD, false, true), + KeyPair(Key::Minus, true, false, KEY_SUB, false, true), + + +}; + +constexpr int sNumberOfKeyPairs = sizeof(sKeyPairs)/sizeof(KeyPair); + +namespace Ion { +namespace Keyboard { + +int menuHeldFor = 0; + +State scan() { + State state = 0; + + // Grab this opportunity to refresh the display if needed + Simulator::Main::refresh(); + if (!any_key_pressed()) + return state; + if (isKeyPressed(KEY_NSPIRE_HOME)){ + state.setKey(Key::Home); return state; + } + if (isKeyPressed(KEY_NSPIRE_ENTER)){ + state.setKey(Key::EXE); + return state; + } + if (isKeyPressed(KEY_NSPIRE_LEFT)){ + state.setKey(Key::Left); + return state; + } + if (isKeyPressed(KEY_NSPIRE_RIGHT)){ + state.setKey(Key::Right); + return state; + } + if (isKeyPressed(KEY_NSPIRE_UP)){ + state.setKey(Key::Up); + return state; + } + if (isKeyPressed(KEY_NSPIRE_DOWN)){ + state.setKey(Key::Down); + return state; + } + int shiftstate; + int scancode=nspire_scan(&shiftstate); + if (scancode==0) { + return state; + } + switch (scancode){ + case -2: case -1: + return state; + case KEY_CTRL_EXIT: + state.setKey(Key::Back); + return state; + case KEY_CTRL_EXE: + state.setKey(Key::EXE); + return state; + case KEY_CTRL_MENU: + state.setKey(Key::Home); + return state; + } + nspire_ctrl=nspire_shift=0; + for (int i = 0; i < sNumberOfKeyPairs; i++) { + const KeyPair & keyPair = sKeyPairs[i]; + if (scancode==keyPair.gintKey()) { + state.setSimulatedShift(keyPair.numworksShift() ? ModSimState::ForceOn : ModSimState::ForceOff); + state.setSimulatedAlpha(keyPair.numworksAlpha() ? ModSimState::ForceOn : ModSimState::ForceOff); + state.setKey(keyPair.key()); + return state; + } + } + return state; +} + +} +} + +namespace Ion { +namespace Simulator { +namespace Keyboard { + +} +} +} diff --git a/ion/src/simulator/nspire/keyboard.h b/ion/src/simulator/nspire/keyboard.h new file mode 100644 index 00000000000..0e379642fb8 --- /dev/null +++ b/ion/src/simulator/nspire/keyboard.h @@ -0,0 +1,15 @@ +#ifndef ION_SIMULATOR_KEYBOARD_H +#define ION_SIMULATOR_KEYBOARD_H + +#include +// #include + +namespace Ion { +namespace Simulator { +namespace Keyboard { + +} +} +} + +#endif \ No newline at end of file diff --git a/ion/src/simulator/nspire/main.cpp b/ion/src/simulator/nspire/main.cpp new file mode 100644 index 00000000000..4ed2bafd471 --- /dev/null +++ b/ion/src/simulator/nspire/main.cpp @@ -0,0 +1,97 @@ +#include "main.h" +#include "display.h" +#include "platform.h" + + +#include +#include +#include +#include +#include + +#include +#include +#include "k_csdk.h" + +static const char * storage_name="nwstore.nws"; + +int save_state(const char * fname); // apps/home/controller.cpp + +extern "C" { + extern const int prizm_heap_size; + const int prizm_heap_size=1024*1024; + __attribute__((aligned(4))) char prizm_heap[prizm_heap_size]; + + int calculator=4; // -1 means OS not checked, 0 unknown, 1 cg50 or 90, 2 emu 50 or 90, 3 other + + int main() { + sdk_init(); + Ion::Simulator::Main::init(); + ion_main(0, NULL); + Ion::Simulator::Main::quit(); + sdk_end(); + return 0; + } +} + +namespace Ion { +namespace Simulator { +namespace Main { + +static bool sNeedsRefresh = false; + +void init() { + Ion::Simulator::Display::init(); + setNeedsRefresh(); +} + +void setNeedsRefresh() { + sNeedsRefresh = true; +} + +void refresh() { + if (!sNeedsRefresh) { + return; + } + + Display::draw(); + + sNeedsRefresh = false; +} + +void quit() { + Ion::Simulator::Display::quit(); +} + +void runPowerOffSafe(void (*powerOffSafeFunction)(), bool prepareVRAM) { + // somewhat OFF by setting LCD to 0 + unsigned NSPIRE_CONTRAST_ADDR=is_cx2?0x90130014:0x900f0020; + unsigned oldval=*(volatile unsigned *)NSPIRE_CONTRAST_ADDR,oldval2; + if (is_cx2){ + oldval2=*(volatile unsigned *) (NSPIRE_CONTRAST_ADDR+4); + *(volatile unsigned *) (NSPIRE_CONTRAST_ADDR+4)=0xffff; + } + *(volatile unsigned *)NSPIRE_CONTRAST_ADDR=is_cx2?0xffff:0x100; + static volatile uint32_t *lcd_controller = (volatile uint32_t*) 0xC0000000; + lcd_controller[6] &= ~(0b1 << 11); + loopsleep(20); + lcd_controller[6] &= ~ 0b1; + unsigned NSPIRE_RTC_ADDR=0x90090000; + unsigned offtime=* (volatile unsigned *) NSPIRE_RTC_ADDR; + for (int n=0;!on_key_pressed();++n){ + loopsleep(100); + idle(); + } + lcd_controller[6] |= 0b1; + loopsleep(20); + lcd_controller[6]|= 0b1 << 11; + if (is_cx2) + *(volatile unsigned *)(NSPIRE_CONTRAST_ADDR+4)=oldval2; + *(volatile unsigned *)NSPIRE_CONTRAST_ADDR=oldval; + sync_screen(); + +} + +} +} +} diff --git a/ion/src/simulator/nspire/main.h b/ion/src/simulator/nspire/main.h new file mode 100644 index 00000000000..4ac441a065f --- /dev/null +++ b/ion/src/simulator/nspire/main.h @@ -0,0 +1,20 @@ +#ifndef ION_SIMULATOR_MAIN_H +#define ION_SIMULATOR_MAIN_H + +namespace Ion { +namespace Simulator { +namespace Main { + +void init(); +void quit(); + +void setNeedsRefresh(); +void refresh(); + +void runPowerOffSafe(void (*powerOffSafeFunction)(), bool prepareVRAM); + +} +} +} + +#endif \ No newline at end of file diff --git a/ion/src/simulator/nspire/platform.h b/ion/src/simulator/nspire/platform.h new file mode 100644 index 00000000000..af5ddfcb60a --- /dev/null +++ b/ion/src/simulator/nspire/platform.h @@ -0,0 +1,23 @@ +#ifndef ION_SIMULATOR_PLATFORM_H +#define ION_SIMULATOR_PLATFORM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Those functions should be implemented per-platform. + * They are defined as C function for easier interop. */ + +const char * IonSimulatorGetLanguageCode(); + +void IonSimulatorKeyboardKeyDown(int keyNumber); +void IonSimulatorKeyboardKeyUp(int keyNumber); +void IonSimulatorEventsPushEvent(int eventNumber); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/ion/src/simulator/nspire/power.cpp b/ion/src/simulator/nspire/power.cpp new file mode 100644 index 00000000000..e14daa6a88f --- /dev/null +++ b/ion/src/simulator/nspire/power.cpp @@ -0,0 +1,23 @@ +#include +#include +#include + +#include "main.h" + +void powerOff(void){ +} + + +namespace Ion { +namespace Power { + +void suspend(bool checkIfOnOffKeyReleased) { + Simulator::Main::runPowerOffSafe(powerOff, true); +} + +void standby() { + Simulator::Main::runPowerOffSafe(powerOff, true); +} + +} +} diff --git a/ion/src/simulator/nspire/telemetry_init.cpp b/ion/src/simulator/nspire/telemetry_init.cpp new file mode 100644 index 00000000000..7a69b2d8c83 --- /dev/null +++ b/ion/src/simulator/nspire/telemetry_init.cpp @@ -0,0 +1,15 @@ +#include "platform.h" + +namespace Ion { +namespace Simulator { +namespace Telemetry { + +void init() { +} + +void shutdown() { +} + +} +} +} \ No newline at end of file diff --git a/ion/src/simulator/nspire/timing.cpp b/ion/src/simulator/nspire/timing.cpp new file mode 100644 index 00000000000..9378fe1c0f5 --- /dev/null +++ b/ion/src/simulator/nspire/timing.cpp @@ -0,0 +1,21 @@ +#include +#include +#include "k_csdk.h" + +namespace Ion { +namespace Timing { + +uint64_t millis() { + return ::millis(); +} + +void usleep(uint32_t us) { + os_wait_1ms(us/1000); // sleep_us(us); +} + +void msleep(uint32_t ms) { + os_wait_1ms(ms); // sleep_us(ms * 1000); +} + +} +} diff --git a/liba/include/bridge/string.h b/liba/include/bridge/string.h index 51d6a8c3a34..790fd6b7ebd 100644 --- a/liba/include/bridge/string.h +++ b/liba/include/bridge/string.h @@ -7,9 +7,10 @@ LIBA_BEGIN_DECLS -#if (__GLIBC__ || __MINGW32__ || _FXCG) +#if (__GLIBC__ || __MINGW32__ || _FXCG || NSPIRE_NEWLIB) size_t strlcat(char * dst, const char * src, size_t dstSize); size_t strlcpy(char * dst, const char * src, size_t len); +char *strdup(const char *s); #endif LIBA_END_DECLS diff --git a/liba/include/bridge/strings.h b/liba/include/bridge/strings.h index d74b264db18..591eec09c6c 100644 --- a/liba/include/bridge/strings.h +++ b/liba/include/bridge/strings.h @@ -1,7 +1,7 @@ #ifndef LIBA_STRINGS_H #define LIBA_STRINGS_H -#if (_FXCG) +#if (_FXCG) || defined NSPIRE_NEWLIB #include @@ -19,4 +19,4 @@ LIBA_END_DECLS #endif -#endif \ No newline at end of file +#endif diff --git a/libaxx/include/bridge/cmath b/libaxx/include/bridge/cmath index 11eec165e87..a267e5f672e 100644 --- a/libaxx/include/bridge/cmath +++ b/libaxx/include/bridge/cmath @@ -17,7 +17,7 @@ #define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ #define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ -#if (_FXCG) +#if (_FXCG) || defined NSPIRE_NEWLIB namespace std { // functions using ::acosh; diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 4ac1ba70774..0a5e5b5723e 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -135,7 +135,7 @@ class Expression : public TreeHandle { Expression() : TreeHandle() {} Expression clone() const; static Expression Parse(char const * string, Context * context, bool addMissingParenthesis = true); - static Expression ExpressionFromAddress(const void * address, size_t size); + static Expression ExpressionFromAddress(const void * address, size_t size, const void * record=nullptr); /* Circuit breaker */ typedef bool (*CircuitBreaker)(); diff --git a/poincare/include/poincare/integer.h b/poincare/include/poincare/integer.h index cfc98203495..6ff1dbc1ef2 100644 --- a/poincare/include/poincare/integer.h +++ b/poincare/include/poincare/integer.h @@ -21,7 +21,7 @@ class LayoutNode; class Integer; struct IntegerDivision; -#if (defined _3DS) || (defined _FXCG) +#if (defined _3DS) || (defined _FXCG) || defined NSPIRE_NEWLIB typedef unsigned short half_native_uint_t; static_assert(sizeof(half_native_uint_t) == sizeof(uint16_t)); typedef int native_int_t; diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index 4c066e6c1eb..575a8859751 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -38,10 +38,42 @@ Expression Expression::Parse(char const * string, Context * context, bool addPar return expression; } -Expression Expression::ExpressionFromAddress(const void * address, size_t size) { + Expression Expression::ExpressionFromAddress(const void * address, size_t size, const void * record) { if (address == nullptr || size == 0) { return Expression(); } +#ifdef STRING_STORAGE + // Check that expression was stored as a string in record + size_t i; + const char * ptr=(const char *) address; + for (i=1;i 0 && i < size && ptr[i] == '"') { + ((char *)ptr)[i] = 0; + Expression e = Expression::Parse(ptr + 1, nullptr); + ((char *)ptr)[i] = '"'; + address = e.addressInPool(); + size = e.size(); + if (record) { + const char * name = ((const Ion::Storage::Record *)record)->fullName(); + char repl = 0; + int l = strlen(name); + if (strncmp(name+l-4,".seq",4) == 0) { + repl='n'; + } else if (strncmp(name+l-5,".func",5) == 0) { + repl='x'; + } + if (repl){ + e=e.replaceSymbolWithExpression(Symbol::Builder(repl),Symbol::Builder(UCodePointUnknown)); + address= e.addressInPool(); + size=e.size(); + } + } + return Expression(static_cast(TreePool::sharedPool()->copyTreeFromAddress(address, size))); // must be done before e is destroyed + } +#endif // Build the Expression in the Tree Pool return Expression(static_cast(TreePool::sharedPool()->copyTreeFromAddress(address, size))); } diff --git a/python/port/port.cpp b/python/port/port.cpp index 67c57ef3f9d..07b2e04d320 100644 --- a/python/port/port.cpp +++ b/python/port/port.cpp @@ -41,6 +41,25 @@ void python_error_end() { } #endif +#if defined _FXCG || defined NSPIRE_NEWLIB +#ifdef _FXCG +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif /* py/parsenum.h is a C header which uses C keyword restrict. * It does not exist in C++ so we define it here in order to be able to include @@ -354,23 +373,119 @@ void nlr_jump_fail(void *val) { while (1); } + +#if defined _FXCG || defined NSPIRE_NEWLIB +void do_mp_lexer_new_from_file(const char * filename,mp_lexer_t ** res) { + mp_reader_t reader; + mp_reader_new_file(&reader, filename); + *res=mp_lexer_new(qstr_from_str(filename), reader); +} + +// Code from MicroPython's reader.c +#include +#include +#include + +typedef struct _mp_reader_posix_t { + bool close_fd; + int fd; + size_t len; + size_t pos; + byte buf[20]; +} mp_reader_posix_t; + +STATIC mp_uint_t mp_reader_posix_readbyte(void *data) { + mp_reader_posix_t *reader = (mp_reader_posix_t *)data; + if (reader->pos >= reader->len) { + if (reader->len == 0) { + return MP_READER_EOF; + } else { + MP_THREAD_GIL_EXIT(); + int n = read(reader->fd, reader->buf, sizeof(reader->buf)); + MP_THREAD_GIL_ENTER(); + if (n <= 0) { + reader->len = 0; + return MP_READER_EOF; + } + reader->len = n; + reader->pos = 0; + } + } + return reader->buf[reader->pos++]; +} + +STATIC void mp_reader_posix_close(void *data) { + mp_reader_posix_t *reader = (mp_reader_posix_t *)data; + if (reader->close_fd) { + MP_THREAD_GIL_EXIT(); + close(reader->fd); + MP_THREAD_GIL_ENTER(); + } + m_del_obj(mp_reader_posix_t, reader); +} + +void mp_reader_new_file_from_fd(mp_reader_t *reader, int fd, bool close_fd) { + mp_reader_posix_t *rp = m_new_obj(mp_reader_posix_t); + rp->close_fd = close_fd; + rp->fd = fd; + MP_THREAD_GIL_EXIT(); + int n = read(rp->fd, rp->buf, sizeof(rp->buf)); + if (n == -1) { + if (close_fd) { + close(fd); + } + MP_THREAD_GIL_ENTER(); + mp_raise_OSError(errno); + } + MP_THREAD_GIL_ENTER(); + rp->len = n; + rp->pos = 0; + reader->data = rp; + reader->readbyte = mp_reader_posix_readbyte; + reader->close = mp_reader_posix_close; +} +void mp_reader_new_file(mp_reader_t *reader, const char *filename) { + MP_THREAD_GIL_EXIT(); + int fd = open(filename, O_RDONLY, 0644); + MP_THREAD_GIL_ENTER(); + if (fd < 0) { + mp_raise_OSError(errno); + } + mp_reader_new_file_from_fd(reader, fd, true); +} +#endif + mp_lexer_t * mp_lexer_new_from_file(const char * filename) { if (sScriptProvider != nullptr) { const char * script = sScriptProvider->contentOfScript(filename, true); if (script != nullptr) { return mp_lexer_new_from_str_len(qstr_from_str(filename), script, strlen(script), 0 /* size_t free_len*/); - } else { - mp_raise_OSError(MP_ENOENT); } - } else { - mp_raise_OSError(MP_ENOENT); } +#ifdef _FXCG + mp_lexer_t * res=0; + gint_world_switch(GINT_CALL(do_mp_lexer_new_from_file,filename,&res)); + return res; +#endif +#ifdef NSPIRE_NEWLIB + mp_lexer_t * res=0; + do_mp_lexer_new_from_file(filename,&res); + return res; +#endif + mp_raise_OSError(MP_ENOENT); } mp_import_stat_t mp_import_stat(const char *path) { if (sScriptProvider && sScriptProvider->contentOfScript(path, false)) { return MP_IMPORT_STAT_FILE; } +#if defined _FXCG || defined NSPIRE_NEWLIB + FILE * f=fopen(path,"rb"); + if (f) { + fclose(f); + return MP_IMPORT_STAT_FILE; + } +#endif return MP_IMPORT_STAT_NO_EXIST; } From 45a9dda989aadacf673a459d49a03f9dbf2a1232 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Wed, 7 Jun 2023 08:52:48 +0200 Subject: [PATCH 345/355] [apps/host_filemanager] Fix N0100 build --- apps/host_filemanager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/host_filemanager.cpp b/apps/host_filemanager.cpp index 9c0bb70465e..86d5b4ca8d5 100644 --- a/apps/host_filemanager.cpp +++ b/apps/host_filemanager.cpp @@ -5,9 +5,9 @@ // #include "math.h" // #include "app.h" #include -#include "../apps_container.h" -#include "../global_preferences.h" -#include "../exam_mode_configuration.h" +#include "apps_container.h" +#include "global_preferences.h" +// #include "../exam_mode_configuration.h" extern "C" { #include From 3c6641c808588405e58a4aeabdf8969bb4f14645 Mon Sep 17 00:00:00 2001 From: andrigamerita <37557992+andrigamerita@users.noreply.github.com> Date: Mon, 26 Jun 2023 17:01:54 +0200 Subject: [PATCH 346/355] [it.i18n] Add missing italian translations, fix typos / excessive length in the existing ones (#331) * [it.i18n] [apps/code] Add missing translations * [it.i18n] [apps/reader] Fix typo * [it.i18n] [apps/external, apps/setting] Add missing translations + fixes --- apps/code/catalog.it.i18n | 62 ++++++++++++++++---------------- apps/code/toolbox.it.i18n | 8 ++--- apps/external/base.it.i18n | 18 +++++----- apps/reader/base.it.i18n | 2 +- apps/settings/base.it.i18n | 72 +++++++++++++++++++------------------- 5 files changed, 81 insertions(+), 81 deletions(-) diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index cb17c6bb21b..51e2db44c13 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -31,7 +31,7 @@ PythonBin = "Converte un intero in binario" PythonCeil = "Parte intera superiore" PythonChoice = "Numero aleatorio nella lista" PythonClear = "Svuota la lista" -PythonCmathFunction = "Funz. prefissata modulo cmath" +PythonCmathFunction = "Prefisso funzione del modulo cmath" PythonColor = "Definisci un colore rvb" PythonColorBlack = "Colore nero" PythonColorBlue = "Colore blu" @@ -69,7 +69,7 @@ PythonFloor = "Parte intera" PythonFmod = "a modulo b" PythonFrExp = "Mantissa ed esponente di x : (m,e)" PythonGamma = "Funzione gamma" -PythonGetKeys = "Premere i tasti" +PythonGetKeys = "Ottieni i tasti premuti" PythonGetPalette = "Ottieni la tavolozza del tema" PythonGetPixel = "Restituisce colore del pixel(x,y)" PythonGetrandbits = "Numero aleatorio con k bit" @@ -88,25 +88,25 @@ PythonImportTurtle = "Importa del modulo turtle" PythonImportTime = "Importa del modulo time" PythonImportOs = "Importa modulo os" PythonOsUname = "Ottieni informazioni sul sistema" -PythonOsGetlogin = "Get username" +PythonOsGetlogin = "Ottieni username" PythonOsRemove = "Rimuovere un file" PythonOsRename = "Rinomina file" PythonOsListdir = "Elenca file" PythonImportSys = "Importa modulo sys" -PythonSysExit = "Terminate current program" -PythonSysPrintexception = "Print exception" -PythonSysByteorder = "The byte order of the system" -PythonSysImplementation = "Information about Python" -PythonSysModules = "Dictionary of loaded modules" -PythonSysVersion = "Python language version (string)" -PythonSysVersioninfo = "Python language version (tuple)" +PythonSysExit = "Termina programma corrente" +PythonSysPrintexception = "Stampa eccezione" +PythonSysByteorder = "Ordine di byte del sistema" +PythonSysImplementation = "Informazioni su Python" +PythonSysModules = "Dizionario dei moduli caricati" +PythonSysVersion = "Versione linguaggio Python (stringa)" +PythonSysVersioninfo = "Versione linguaggio Python (tupla)" PythonIndex = "Indice prima occorrenza di x" PythonInput = "Inserire un valore" PythonInsert = "Inserire x in posizione i-esima" PythonInt = "Conversione in intero" PythonIonFunction = "Prefisso di funzione modulo ion" PythonIsFinite = "Testa se x è finito" -PythonIsInfinite = "Testa se x est infinito" +PythonIsInfinite = "Testa se x è infinito" PythonIsKeyDown = "Restituisce True premendo tasto k" PythonBattery = "Restituisce la tensione della batteria" PythonBatteryLevel = "Restituisce il livello della batteria" @@ -190,7 +190,7 @@ PythonPower = "x alla potenza y" PythonPrint = "Visualizza l'oggetto" PythonRadians = "Conversione da gradi a radianti" PythonRandint = "Intero aleatorio in [a,b]" -PythonRandom = "Numero aleatorio in [0,1[" +PythonRandom = "Numero aleatorio in [0,1]" PythonRandomFunction = "Prefisso funzione modulo casuale" PythonRandrange = "Numero dentro il range(start, stop)" PythonRangeStartStop = "Lista da start a stop-1" @@ -242,23 +242,23 @@ PythonTurtleShowturtle = "Mostra la tartaruga" PythonTurtleSpeed = "Velocità di disegno (x tra 0 e 10)" PythonTurtleWrite = "Mostra un testo" PythonUniform = "Numero decimale tra [a,b]" -PythonImportTime = "Import time module" -PythonMonotonic = "Return monotonic time" -PythonFileOpen = "Opens a file" -PythonFileSeekable = "Tells if seek can be used on a file" -PythonFileSeek = "Move file's cursor" -PythonFileTell = "Get file's cursor location" -PythonFileClose = "Closes a file" -PythonFileClosed = "True if file was closed" -PythonFileRead = "Read up to size bytes" -PythonFileWrite = "Write b into file" -PythonFileReadline = "Reads a line or up to size bytes" -PythonFileReadlines = "Reads a list of lines" -PythonFileTruncate = "Resize the file to size" -PythonFileWritelines = "Writes a list of lines" -PythonFileName = "Contains file's name" -PythonFileMode = "Contains file's open mode" -PythonFileReadable = "Tells if read can be used on a file" -PythonFileWritable = "Tells if write can be used on a file" +PythonImportTime = "Importa modulo time" +PythonMonotonic = "Restituisce tempo monotonico" +PythonFileOpen = "Apre un file" +PythonFileSeekable = "Dice se si può usare il cursore su un file" +PythonFileSeek = "Sposta cursore di un file" +PythonFileTell = "Ottieni posizione del cursore del file" +PythonFileClose = "Chiude un file" +PythonFileClosed = "True se un file è stato chiuso" +PythonFileRead = "Legge fino ai byte di un file" +PythonFileWrite = "Scrive b in un file" +PythonFileReadline = "Legge una riga o fino ai byte di un file" +PythonFileReadlines = "Legge una lista di linee di un file" +PythonFileTruncate = "Ridimensiona il file" +PythonFileWritelines = "Scrive una lista di linee su file" +PythonFileName = "Contiene il nome del file" +PythonFileMode = "Contiene la modalità di apertura del file" +PythonFileReadable = "Dice se si può leggere sul file" +PythonFileWritable = "Dice se si può scrivere sul file" PythonImportUtils = "Importazione di ulab.utils" -PythonUtilsFunction = "prefisso della funzione del modulo utils" +PythonUtilsFunction = "Prefisso funzione del modulo utils" diff --git a/apps/code/toolbox.it.i18n b/apps/code/toolbox.it.i18n index 7ec36994c9a..fe6546b30c5 100644 --- a/apps/code/toolbox.it.i18n +++ b/apps/code/toolbox.it.i18n @@ -2,9 +2,9 @@ Functions = "Funzioni" Catalog = "Catalogo" Modules = "Moduli" LoopsAndTests = "Cicli e test" -Files = "Files" -Exceptions = "Exceptions" +Files = "File" +Exceptions = "Eccezioni" UlabDocumentation = "Documentazione" -IonSelector = "Selettore a chiave" +IonSelector = "Selettore tasti" PressAKey = "Premi un tasto" -IonKeyList = "Elenco delle chiavi" +IonKeyList = "Elenco dei tasti" diff --git a/apps/external/base.it.i18n b/apps/external/base.it.i18n index 49b8a217333..d1341ea6708 100644 --- a/apps/external/base.it.i18n +++ b/apps/external/base.it.i18n @@ -1,9 +1,9 @@ -ExternalApp = "External" -ExternalAppCapital = "EXTERNAL" -ExternalAppApiMismatch = "API mismatch" -ExternalAppExecError = "Cannot execute file" -ExternalNotCompatible = "External is not compatible" -WithSimulator = "with the simulator" -WithN0100 = "with n0100" -GetMoreAppsAt = "Get more apps at" -NoAppsInstalled = "No apps installed" +ExternalApp = "Esterna" +ExternalAppCapital = "ESTERNA" +ExternalAppApiMismatch = "Discordanza di API" +ExternalAppExecError = "Impossibile eseguire file" +ExternalNotCompatible = "Esterna non compatibile" +WithSimulator = "con il simulatore" +WithN0100 = "con n0100" +GetMoreAppsAt = "Ottieni altre app a" +NoAppsInstalled = "Nessuna app installata" diff --git a/apps/reader/base.it.i18n b/apps/reader/base.it.i18n index 98caf87b7e1..7adef7e4aaf 100644 --- a/apps/reader/base.it.i18n +++ b/apps/reader/base.it.i18n @@ -1,3 +1,3 @@ ReaderApp = "Lettore" ReaderAppCapital = "LETTORE" -NoFileToDisplay = "essun file da visualizzare" +NoFileToDisplay = "Nessun file da visualizzare" diff --git a/apps/settings/base.it.i18n b/apps/settings/base.it.i18n index e7dfe4d7fbf..76b7090850f 100644 --- a/apps/settings/base.it.i18n +++ b/apps/settings/base.it.i18n @@ -32,52 +32,52 @@ Real = "Reale " Cartesian = "Algebrico " Polar = "Esponenziale " Brightness = "Luminosità" -BrightnessSettings = "Impostazioni di luminosità" -SoftwareVersion = "Epsilon version" -UpsilonVersion = "Upsilon version" -OmegaVersion = "Omega version" -Username = "Name" -MicroPythonVersion = "µPython version" +BrightnessSettings = "Impostazioni luminosità" +SoftwareVersion = "Versione di Epsilon" +UpsilonVersion = "Versione di Upsilon" +OmegaVersion = "Versione di Omega" +Username = "Nome" +MicroPythonVersion = "Versione µPython" FontSizes = "Carattere Python" LargeFont = "Grande " SmallFont = "Piccolo " SerialNumber = "Numero di serie" UpdatePopUp = "Promemoria aggiornamento" BetaPopUp = "Promemoria beta" -Contributors = "Contributors" +Contributors = "Contributori" Battery = "Batteria" -Accessibility = "Accessibility" -AccessibilityInvertColors = "Invert colors" -AccessibilityMagnify = "Magnify" -AccessibilityGamma = "Gamma correction" -AccessibilityGammaRed = "Red gamma" -AccessibilityGammaGreen = "Green gamma" -AccessibilityGammaBlue = "Blue gamma" -MathOptions = "Math options" -SymbolMultiplication = "Multiply" -SymbolMultiplicationCross = "Cross " -SymbolMultiplicationMiddleDot = "Dot " -SymbolMultiplicationStar = "Star " -SymbolMultiplicationAutoSymbol = "Auto " -SymbolFunction = "Expression format " -SymbolDefaultFunction = "Default " -SymbolArgFunction = "Empty " -SymbolArgDefaultFunction = "Argument " -MemUse = "Memory" -DateTime = "Date/time" +Accessibility = "Accessibilità" +AccessibilityInvertColors = "Inversione colori" +AccessibilityMagnify = "Ingrandimento" +AccessibilityGamma = "Correzione gamma" +AccessibilityGammaRed = "Gamma del rosso" +AccessibilityGammaGreen = "Gamma del verde" +AccessibilityGammaBlue = "Gamma del blu" +MathOptions = "Opzioni matematica" +SymbolMultiplication = "Simbolo moltiplicaz." +SymbolMultiplicationCross = "Croce " +SymbolMultiplicationMiddleDot = "Punto " +SymbolMultiplicationStar = "Stella " +SymbolMultiplicationAutoSymbol = "Automatico " +SymbolFunction = "Formato espressioni " +SymbolDefaultFunction = "Predefinito " +SymbolArgFunction = "Vuoto " +SymbolArgDefaultFunction = "Argomento " +MemUse = "Memoria" +DateTime = "Data/ora" ExternalApps = "App esterne" -ActivateClock = "Activate clock" -Date = "Date" -Time = "Time" -RTCWarning1 = "Enabling the clock drains the battery faster" -RTCWarning2 = "when the calculator is powered off." -SyntaxHighlighting = "Evidenziazione della sintassi" +ActivateClock = "Attiva orologio" +Date = "Data" +Time = "Ora" +RTCWarning1 = "Attivare l'orologio drena la batteria più" +RTCWarning2 = "velocemente quando la calcolatrice è spenta." +SyntaxHighlighting = "Evidenz. sintassi" Normal = "Normale" IdleTimeBeforeDimming = "Scurisci dopo (s)" IdleTimeBeforeSuspend = "Sospendi dopo (s)" -BrightnessShortcut = "Passaggi di scelta rapida" +BrightnessShortcut = "Livelli scelta rapida" ExtAppWrite = "Scrittura abilitata" -ExtAppWriteExplanation1 = "Per impostazione predefinita, applicazioni esterne" -ExtAppWriteExplanation2 = "non può scrivere in memoria" -ExtAppWriteExplanation3 = "flash (persistente) della calcolatrice." +ExtAppWriteExplanation1 = "Per impostazione predefinita, le app esterne" +ExtAppWriteExplanation2 = "non possono scrivere in memoria flash" +ExtAppWriteExplanation3 = "(persistente) della calcolatrice." ExtAppEnabled = "Affiggere" From 4630052630a1719a2983d8f76910c22cbc0f7c78 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Mon, 26 Jun 2023 18:18:28 +0200 Subject: [PATCH 347/355] [i18n/code] Fix random description in italian --- apps/code/catalog.it.i18n | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index 51e2db44c13..d839de5418a 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -190,7 +190,7 @@ PythonPower = "x alla potenza y" PythonPrint = "Visualizza l'oggetto" PythonRadians = "Conversione da gradi a radianti" PythonRandint = "Intero aleatorio in [a,b]" -PythonRandom = "Numero aleatorio in [0,1]" +PythonRandom = "Numero aleatorio in [0,1[" PythonRandomFunction = "Prefisso funzione modulo casuale" PythonRandrange = "Numero dentro il range(start, stop)" PythonRangeStartStop = "Lista da start a stop-1" From 6976e524ad9a3aa8680a51e40ab7f3fac66fbf8b Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Tue, 27 Jun 2023 20:04:24 +0200 Subject: [PATCH 348/355] [python/kandinsky] Allow selecting italic font for draw_string using 7th argument --- python/port/mod/kandinsky/modkandinsky.cpp | 13 ++++++++++++- python/port/mod/kandinsky/modkandinsky_table.c | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/python/port/mod/kandinsky/modkandinsky.cpp b/python/port/mod/kandinsky/modkandinsky.cpp index 63e48320933..08c0e57cf93 100644 --- a/python/port/mod/kandinsky/modkandinsky.cpp +++ b/python/port/mod/kandinsky/modkandinsky.cpp @@ -62,7 +62,18 @@ mp_obj_t modkandinsky_draw_string(size_t n_args, const mp_obj_t * args) { KDPoint point(mp_obj_get_int(args[1]), mp_obj_get_int(args[2])); KDColor textColor = (n_args >= 4) ? MicroPython::Color::Parse(args[3]) : Palette::PrimaryText; KDColor backgroundColor = (n_args >= 5) ? MicroPython::Color::Parse(args[4]) : Palette::HomeBackground; - const KDFont * font = (n_args >= 6) ? ((mp_obj_is_true(args[5])) ? KDFont::SmallFont : KDFont::LargeFont) : KDFont::LargeFont; + bool bigFont = (n_args >= 6) ? mp_obj_is_true(args[5]) : false; + bool isItalic = (n_args >= 7) ? mp_obj_is_true(args[6]) : false; + const KDFont * font = KDFont::LargeFont; + if (bigFont && !isItalic) { + font = KDFont::LargeFont; + } else if (!bigFont && !isItalic) { + font = KDFont::SmallFont; + } else if (bigFont && isItalic) { + font = KDFont::ItalicLargeFont; + } else if (!bigFont && isItalic) { + font = KDFont::ItalicSmallFont; + } MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); KDIonContext::sharedContext()->drawString(text, point, font, textColor, backgroundColor); return mp_const_none; diff --git a/python/port/mod/kandinsky/modkandinsky_table.c b/python/port/mod/kandinsky/modkandinsky_table.c index 033dbf5b673..82357609b92 100644 --- a/python/port/mod/kandinsky/modkandinsky_table.c +++ b/python/port/mod/kandinsky/modkandinsky_table.c @@ -3,7 +3,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_color_obj, 1, 3, modkandinsky_color); STATIC MP_DEFINE_CONST_FUN_OBJ_2(modkandinsky_get_pixel_obj, modkandinsky_get_pixel); STATIC MP_DEFINE_CONST_FUN_OBJ_3(modkandinsky_set_pixel_obj, modkandinsky_set_pixel); -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_draw_string_obj, 3, 6, modkandinsky_draw_string); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_draw_string_obj, 3, 7, modkandinsky_draw_string); STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_draw_line_obj, 5, 5, modkandinsky_draw_line); STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_draw_circle_obj, 4, 4, modkandinsky_draw_circle); STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modkandinsky_fill_rect_obj, 5, 5, modkandinsky_fill_rect); From a945fc102561c116885c325868e925ed0997ac82 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Sat, 1 Jul 2023 20:14:20 +0200 Subject: [PATCH 349/355] [python/kandinsky] Fix big font selection --- apps/apps_container_storage.cpp | 2 -- python/port/mod/kandinsky/modkandinsky.cpp | 2 +- python/port/mod/kandinsky/modkandinsky_table.c | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/apps_container_storage.cpp b/apps/apps_container_storage.cpp index ad2dec6197c..25953f269bc 100644 --- a/apps/apps_container_storage.cpp +++ b/apps/apps_container_storage.cpp @@ -49,7 +49,5 @@ int AppsContainerStorage::appIndexFromSnapshot(App::Snapshot * snapshot) { return i; } } - // Achievement unlock : how did you get here ? - assert(false); return NULL; } diff --git a/python/port/mod/kandinsky/modkandinsky.cpp b/python/port/mod/kandinsky/modkandinsky.cpp index 08c0e57cf93..ad7ae12579d 100644 --- a/python/port/mod/kandinsky/modkandinsky.cpp +++ b/python/port/mod/kandinsky/modkandinsky.cpp @@ -62,7 +62,7 @@ mp_obj_t modkandinsky_draw_string(size_t n_args, const mp_obj_t * args) { KDPoint point(mp_obj_get_int(args[1]), mp_obj_get_int(args[2])); KDColor textColor = (n_args >= 4) ? MicroPython::Color::Parse(args[3]) : Palette::PrimaryText; KDColor backgroundColor = (n_args >= 5) ? MicroPython::Color::Parse(args[4]) : Palette::HomeBackground; - bool bigFont = (n_args >= 6) ? mp_obj_is_true(args[5]) : false; + bool bigFont = (n_args >= 6) ? !mp_obj_is_true(args[5]) : true; bool isItalic = (n_args >= 7) ? mp_obj_is_true(args[6]) : false; const KDFont * font = KDFont::LargeFont; if (bigFont && !isItalic) { diff --git a/python/port/mod/kandinsky/modkandinsky_table.c b/python/port/mod/kandinsky/modkandinsky_table.c index 82357609b92..3c991978c3c 100644 --- a/python/port/mod/kandinsky/modkandinsky_table.c +++ b/python/port/mod/kandinsky/modkandinsky_table.c @@ -23,8 +23,8 @@ STATIC const mp_rom_map_elem_t modkandinsky_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_fill_rect), (mp_obj_t)&modkandinsky_fill_rect_obj }, { MP_ROM_QSTR(MP_QSTR_fill_circle), (mp_obj_t)&modkandinsky_fill_circle_obj }, { MP_ROM_QSTR(MP_QSTR_fill_polygon), (mp_obj_t)&modkandinsky_fill_polygon_obj }, - { MP_ROM_QSTR(MP_QSTR_large_font), mp_const_true }, - { MP_ROM_QSTR(MP_QSTR_small_font), mp_const_false }, + { MP_ROM_QSTR(MP_QSTR_large_font), mp_const_false }, + { MP_ROM_QSTR(MP_QSTR_small_font), mp_const_true }, { MP_ROM_QSTR(MP_QSTR_wait_vblank), (mp_obj_t)&modkandinsky_wait_vblank_obj }, { MP_ROM_QSTR(MP_QSTR_get_palette), (mp_obj_t)&modkandinsky_get_palette_obj }, }; From f89ffc9b0011824a955008e75208dd0c239f1266 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Tue, 29 Aug 2023 11:54:31 +0200 Subject: [PATCH 350/355] [bootloader] Mark Epsilon 21.1.1 as safe --- bootloader/boot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index 8f0a68d827e..302c761a71f 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -107,7 +107,7 @@ void Boot::bootSlot(Bootloader::Slot s) { if (!s.userlandHeader()->isOmega() && !s.userlandHeader()->isUpsilon()) { // We are trying to boot epsilon, so we check the version and show an advertisement if needed const char * version = s.userlandHeader()->version(); - const char * min = "20.4.1"; + const char * min = "21.1.2"; int versionSum = Utility::versionSum(version, strlen(version)); int minimalVersionTrigger = Utility::versionSum(min, strlen(min)); if (versionSum >= minimalVersionTrigger) { From fd159fe48955881d041cb67c1e97e17d1f420191 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Tue, 29 Aug 2023 12:26:38 +0200 Subject: [PATCH 351/355] [bootloader] Update version number --- bootloader/interface/static/messages.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootloader/interface/static/messages.h b/bootloader/interface/static/messages.h index 3e443cf20fe..ba9da6ac669 100644 --- a/bootloader/interface/static/messages.h +++ b/bootloader/interface/static/messages.h @@ -72,7 +72,7 @@ class Messages { constexpr static const char * aboutMessage4 = "and select the OS"; constexpr static const char * aboutMessage5 = "to boot."; - constexpr static const char * bootloaderVersion = "Version 1.0.5 - FREED0M.20.4"; + constexpr static const char * bootloaderVersion = "Version 1.0.6 - FREED0M.21.1"; //USB NAMES constexpr static const char * usbUpsilonBootloader = "Upsilon Bootloader"; From 0cabec542d3aaff92d12f29c36554824d277cd0b Mon Sep 17 00:00:00 2001 From: Luminoso-256 <63971285+Luminoso-256@users.noreply.github.com> Date: Fri, 15 Sep 2023 11:12:05 -0500 Subject: [PATCH 352/355] [NSpire Simulator] Fix framebuffer in nspire port (#340) * Fix the nspire port (ergo: framebuffer functions, shift/ctrl don't corrupt things due to weird draw calls) * cleanup the k_csdk changes --- ion/src/simulator/nspire/display.cpp | 23 ++++---------------- ion/src/simulator/nspire/k_csdk.c | 3 ++- ion/src/simulator/nspire/keyboard.cpp | 3 +++ ion/src/simulator/nspire/main.cpp | 31 +-------------------------- ion/src/simulator/nspire/main.h | 2 -- ion/src/simulator/nspire/power.cpp | 8 +++++-- 6 files changed, 16 insertions(+), 54 deletions(-) diff --git a/ion/src/simulator/nspire/display.cpp b/ion/src/simulator/nspire/display.cpp index eff8185ed5d..dbd4a0022b5 100644 --- a/ion/src/simulator/nspire/display.cpp +++ b/ion/src/simulator/nspire/display.cpp @@ -21,25 +21,10 @@ void quit() { } void draw() { - // copy framebuffer - const short unsigned int * ptr = (const short unsigned int *) Ion::Simulator::Framebuffer::address(); - Gc * gcptr = get_gc(); - for (int j = 0; j < LCD_HEIGHT_PX; ++j) { - for (int i = 0; i < LCD_WIDTH_PX;){ - int c = *ptr; - int k = 1; - for (; k+i < LCD_WIDTH_PX; ++k) { - if (ptr[k]!=c) { - break; - } - } - gui_gc_setColor(*gcptr,c_rgb565to888(c)); - gui_gc_drawRect(*gcptr,i,j,k-1,0); - ptr += k; - i += k; - } - } - sync_screen(); + unsigned short * ionFramebuffer = (unsigned short *) Ion::Simulator::Framebuffer::address(); + // we specify the screen fmt here because the "native" fmt varies between calculator revisions + // some default to a 240x320 specification, which results in a 90-degree framebuffer rotation and other terribleness. + lcd_blit(ionFramebuffer,SCR_320x240_565); } } diff --git a/ion/src/simulator/nspire/k_csdk.c b/ion/src/simulator/nspire/k_csdk.c index 4e9521f25ff..6c1accc8c39 100755 --- a/ion/src/simulator/nspire/k_csdk.c +++ b/ion/src/simulator/nspire/k_csdk.c @@ -617,7 +617,7 @@ void statusline(int mode){ #include "k_defs.h" void sdk_init(void){ - lcd_init(lcd_type()); // clrscr(); + lcd_init(lcd_type()); } void sdk_end(void){ @@ -993,6 +993,7 @@ int nspire_shift=0; int nspire_ctrl=0; int nspire_select=false; void statusline(int mode){ + return; //this is broken for our purposes and it honestly doesn't matter enough to fix. char *msg=0; if (nspire_ctrl){ if (nspire_shift) diff --git a/ion/src/simulator/nspire/keyboard.cpp b/ion/src/simulator/nspire/keyboard.cpp index 65df840a99e..15e993f0d02 100644 --- a/ion/src/simulator/nspire/keyboard.cpp +++ b/ion/src/simulator/nspire/keyboard.cpp @@ -261,6 +261,9 @@ State scan() { return state; } } + if (isKeyPressed(KEY_NSPIRE_TENX)){ + exit(0); + } return state; } diff --git a/ion/src/simulator/nspire/main.cpp b/ion/src/simulator/nspire/main.cpp index 4ed2bafd471..cfde0d41253 100644 --- a/ion/src/simulator/nspire/main.cpp +++ b/ion/src/simulator/nspire/main.cpp @@ -25,7 +25,7 @@ extern "C" { int calculator=4; // -1 means OS not checked, 0 unknown, 1 cg50 or 90, 2 emu 50 or 90, 3 other int main() { - sdk_init(); + sdk_init(); //this calls the lcd init functions from behind the scenes Ion::Simulator::Main::init(); ion_main(0, NULL); Ion::Simulator::Main::quit(); @@ -63,35 +63,6 @@ void quit() { Ion::Simulator::Display::quit(); } -void runPowerOffSafe(void (*powerOffSafeFunction)(), bool prepareVRAM) { - // somewhat OFF by setting LCD to 0 - unsigned NSPIRE_CONTRAST_ADDR=is_cx2?0x90130014:0x900f0020; - unsigned oldval=*(volatile unsigned *)NSPIRE_CONTRAST_ADDR,oldval2; - if (is_cx2){ - oldval2=*(volatile unsigned *) (NSPIRE_CONTRAST_ADDR+4); - *(volatile unsigned *) (NSPIRE_CONTRAST_ADDR+4)=0xffff; - } - *(volatile unsigned *)NSPIRE_CONTRAST_ADDR=is_cx2?0xffff:0x100; - static volatile uint32_t *lcd_controller = (volatile uint32_t*) 0xC0000000; - lcd_controller[6] &= ~(0b1 << 11); - loopsleep(20); - lcd_controller[6] &= ~ 0b1; - unsigned NSPIRE_RTC_ADDR=0x90090000; - unsigned offtime=* (volatile unsigned *) NSPIRE_RTC_ADDR; - for (int n=0;!on_key_pressed();++n){ - loopsleep(100); - idle(); - } - lcd_controller[6] |= 0b1; - loopsleep(20); - lcd_controller[6]|= 0b1 << 11; - if (is_cx2) - *(volatile unsigned *)(NSPIRE_CONTRAST_ADDR+4)=oldval2; - *(volatile unsigned *)NSPIRE_CONTRAST_ADDR=oldval; - sync_screen(); - -} - } } } diff --git a/ion/src/simulator/nspire/main.h b/ion/src/simulator/nspire/main.h index 4ac441a065f..7a9e232f0d4 100644 --- a/ion/src/simulator/nspire/main.h +++ b/ion/src/simulator/nspire/main.h @@ -11,8 +11,6 @@ void quit(); void setNeedsRefresh(); void refresh(); -void runPowerOffSafe(void (*powerOffSafeFunction)(), bool prepareVRAM); - } } } diff --git a/ion/src/simulator/nspire/power.cpp b/ion/src/simulator/nspire/power.cpp index e14daa6a88f..63560c5734f 100644 --- a/ion/src/simulator/nspire/power.cpp +++ b/ion/src/simulator/nspire/power.cpp @@ -11,12 +11,16 @@ void powerOff(void){ namespace Ion { namespace Power { +//NOTE: These should probably be reimplemented at some point +//the prior version was a janky mess that flickered a ton on wake +//and was generally awfulness. + void suspend(bool checkIfOnOffKeyReleased) { - Simulator::Main::runPowerOffSafe(powerOff, true); + // Simulator::Main::runPowerOffSafe(powerOff, true); } void standby() { - Simulator::Main::runPowerOffSafe(powerOff, true); + // Simulator::Main::runPowerOffSafe(powerOff, true); } } From a0583aa2a9ea416f6b9cdfe8acbb30b170707a7a Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Thu, 28 Sep 2023 20:38:40 +0200 Subject: [PATCH 353/355] [python] Prevent waiting for 2^32 ms if user entered a negative delay This is due to a missing check in the interruptible sleep function which was not checking if the delay was negative, then was passed to the Ion::Timing::usleep function, who only takes signed values --- python/port/helpers.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/python/port/helpers.cpp b/python/port/helpers.cpp index 1a425556fdc..35e33902604 100644 --- a/python/port/helpers.cpp +++ b/python/port/helpers.cpp @@ -33,6 +33,11 @@ void micropython_port_vm_hook_refresh_print() { } bool micropython_port_interruptible_msleep(int32_t delay) { + // Check if the user entered a bad delay (negative) + if (delay < 0) { + return false; + } + assert(delay >= 0); /* We don't use millis because the systick drifts when changing the HCLK * frequency. */ From 0f2ce293d185c192d6c7983b387e0670e8a69238 Mon Sep 17 00:00:00 2001 From: Rathmox <55508107+Rathmox@users.noreply.github.com> Date: Mon, 2 Oct 2023 21:12:43 +0200 Subject: [PATCH 354/355] Mark 21.2.0 as safe (#342) * Mark 21.2.0 as safe * Update messages.h --- bootloader/boot.cpp | 2 +- bootloader/interface/static/messages.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index 302c761a71f..8734d757644 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -107,7 +107,7 @@ void Boot::bootSlot(Bootloader::Slot s) { if (!s.userlandHeader()->isOmega() && !s.userlandHeader()->isUpsilon()) { // We are trying to boot epsilon, so we check the version and show an advertisement if needed const char * version = s.userlandHeader()->version(); - const char * min = "21.1.2"; + const char * min = "21.2.1"; int versionSum = Utility::versionSum(version, strlen(version)); int minimalVersionTrigger = Utility::versionSum(min, strlen(min)); if (versionSum >= minimalVersionTrigger) { diff --git a/bootloader/interface/static/messages.h b/bootloader/interface/static/messages.h index ba9da6ac669..39a74413099 100644 --- a/bootloader/interface/static/messages.h +++ b/bootloader/interface/static/messages.h @@ -72,7 +72,7 @@ class Messages { constexpr static const char * aboutMessage4 = "and select the OS"; constexpr static const char * aboutMessage5 = "to boot."; - constexpr static const char * bootloaderVersion = "Version 1.0.6 - FREED0M.21.1"; + constexpr static const char * bootloaderVersion = "Version 1.0.7 - FREED0M.21.2"; //USB NAMES constexpr static const char * usbUpsilonBootloader = "Upsilon Bootloader"; From 2bf46f59cee1aaa7b8296741f814341906b953c6 Mon Sep 17 00:00:00 2001 From: Yaya-Cout Date: Mon, 16 Oct 2023 19:35:31 +0200 Subject: [PATCH 355/355] =?UTF-8?q?[apps=5Fcontainer]=20Avoid=20crashing?= =?UTF-8?q?=20on=20=C3=9710^=20on=20N0100?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/apps_container_storage.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/apps_container_storage.cpp b/apps/apps_container_storage.cpp index 25953f269bc..6a945af111e 100644 --- a/apps/apps_container_storage.cpp +++ b/apps/apps_container_storage.cpp @@ -34,6 +34,11 @@ App::Snapshot * AppsContainerStorage::appSnapshotAtIndex(int index) { }; assert(sizeof(snapshots)/sizeof(snapshots[0]) == k_numberOfCommonApps); assert(index >= 0 && index < k_numberOfCommonApps); + // To avoid crashes, we return the home app snapshot if the index is out of + // bounds. (no crash in release mode, but an assert in debug mode) + if (index >= k_numberOfCommonApps) { + return snapshots[0]; + } return snapshots[index]; }