├── .github └── workflows │ └── build.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── Makefile.emscripten ├── README.md ├── TIVarsLib.js ├── TIVarsLib.wasm ├── cli ├── cli.cpp └── cxxopts.hpp ├── index.html ├── programs_tokens.csv ├── src ├── BinaryFile.cpp ├── BinaryFile.h ├── CommonTypes.h ├── TIModel.cpp ├── TIModel.h ├── TIModels.cpp ├── TIModels.h ├── TIVarFile.cpp ├── TIVarFile.h ├── TIVarType.cpp ├── TIVarType.h ├── TIVarTypes.cpp ├── TIVarTypes.h ├── TypeHandlers │ ├── DummyHandler.cpp │ ├── STH_DataAppVar.cpp │ ├── STH_ExactFraction.cpp │ ├── STH_ExactFractionPi.cpp │ ├── STH_ExactPi.cpp │ ├── STH_ExactRadical.cpp │ ├── STH_FP.cpp │ ├── STH_PythonAppVar.cpp │ ├── TH_GDB.cpp │ ├── TH_GenericAppVar.cpp │ ├── TH_GenericComplex.cpp │ ├── TH_GenericList.cpp │ ├── TH_GenericReal.cpp │ ├── TH_Matrix.cpp │ ├── TH_TempEqu.cpp │ ├── TH_Tokenized.cpp │ └── TypeHandlers.h ├── json.hpp ├── main_emscripten.cpp ├── tivarslib_utils.cpp └── tivarslib_utils.h ├── testData ├── ALLTOKS.8Xp ├── AppVar.8xv ├── Complex.8xc ├── ComplexList.8xl ├── Equation_X1T.8xy ├── Equation_Y1.8xy ├── Equation_Y1T.8xy ├── Equation_r1.8xy ├── Equation_u.8xy ├── Exact_ComplexFrac.8xc ├── Exact_ComplexPi.8xc ├── Exact_ComplexPiFrac.8xc ├── Exact_ComplexRadical.8xc ├── Exact_RealPi.8xn ├── Exact_RealPiFrac.8xn ├── Exact_RealRadical.8xn ├── GDB_Parametric.json ├── GraphDataBase_Func.8xd ├── GraphDataBase_Param.8xd ├── Group.8xg ├── Matrix_2x2_exact.8xm ├── Matrix_3x3_standard.8xm ├── Program.8xp ├── ProtectedProgram.8xp ├── ProtectedProgram_long.8xp ├── Real.8xn ├── RealList.8xl ├── RecallWindow.8xz ├── String.8xs ├── TableRange.8xt ├── Window.8xw ├── clibs.8xg ├── python_HELLO.8xv └── testPrgmQuotes.8xp └── tests.cpp /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: '${{ matrix.os }}' 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [ubuntu-22.04,ubuntu-latest,macOS-13,macOS-latest] 16 | steps: 17 | - uses: actions/checkout@v3 18 | - run: make -j4 19 | - run: ./tivars_tests 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | 30 | # Profiling and Coverage 31 | *.out.* 32 | *.prof* 33 | *.supp 34 | 35 | # Qt stuff 36 | *.pro.user* 37 | gui/build* 38 | moc_*.cpp 39 | 40 | # Other 41 | *.xcodeproj* 42 | *.autosave 43 | *~ 44 | \#*\# 45 | cmake-build-*/ 46 | out/ 47 | 48 | # Windows and Mac crap 49 | Thumbs.db 50 | .DS_Store 51 | **.dsym* 52 | 53 | # Stuff used in the Emscripten version 54 | *.rom 55 | *.data 56 | *.bc 57 | *.js.mem 58 | 59 | # IDEs things 60 | **/.idea/ 61 | .vs/ 62 | .vscode/ 63 | 64 | testData/*new.8* 65 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(tivars_lib_cpp 4 | VERSION 1.3.0 5 | LANGUAGES CXX) 6 | 7 | include(CheckCXXCompilerFlag) 8 | if(MSVC) 9 | CHECK_CXX_COMPILER_FLAG("/std:c++20" COMPILER_SUPPORTS_CXX20) 10 | else() 11 | CHECK_CXX_COMPILER_FLAG("-std=c++2a" COMPILER_SUPPORTS_CXX20) 12 | endif() 13 | if(COMPILER_SUPPORTS_CXX20) 14 | set(CMAKE_CXX_STANDARD 20) 15 | add_definitions(-DTH_GDB_SUPPORT=1) 16 | else() 17 | set(CMAKE_CXX_STANDARD 11) 18 | endif() 19 | 20 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 21 | 22 | if(MSVC) 23 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8 ") 24 | else() 25 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wall -Wextra -Werror=write-strings -Werror=redundant-decls -Werror=format -Werror=format-security -Werror=date-time -Werror=return-type -Werror=pointer-arith -Winit-self -Wno-unused-parameter ") 26 | endif() 27 | 28 | file(GLOB HEADER_FILES "src/*.h" "src/TypeHandlers/*.h") 29 | file(GLOB COMMON_SOURCE ${HEADER_FILES} "src/*.cpp" "src/TypeHandlers/*.cpp") 30 | 31 | if(NOT WIN32) 32 | set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address,undefined") 33 | set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address,undefined") 34 | endif() 35 | 36 | add_executable(tivars_lib_cpp_tests ${COMMON_SOURCE} tests.cpp) 37 | add_executable(tivars_lib_cpp_cli ${COMMON_SOURCE} cli/cli.cpp) 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2024 Adrien "Adriweb" Bertrand 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS := -O3 -std=c++2a -DTH_GDB_SUPPORT=1 -W -Wall -Wextra -Werror=write-strings -Werror=redundant-decls -Werror=format -Werror=format-security -Werror=date-time -Werror=return-type -Werror=pointer-arith -Winit-self 2 | 3 | SOURCES_COMMON := $(wildcard src/*.cpp) $(wildcard src/TypeHandlers/*.cpp) 4 | 5 | SOURCES_TESTS := $(SOURCES_COMMON) tests.cpp 6 | SOURCES_CLI := $(SOURCES_COMMON) cli/cli.cpp 7 | 8 | OBJS_TESTS = $(patsubst %.cpp, %.o, $(SOURCES_TESTS)) 9 | OBJS_CLI = $(patsubst %.cpp, %.o, $(SOURCES_CLI)) 10 | 11 | OUTPUT := tivars_tests tivars_cli 12 | 13 | all: $(OUTPUT) 14 | 15 | %.o: %.cpp 16 | $(CXX) $(CXXFLAGS) -c $< -o $@ 17 | 18 | tivars_tests: $(OBJS_TESTS) 19 | $(CXX) $(CXXFLAGS) $(LFLAGS) $^ -o $@ 20 | 21 | tivars_cli: $(OBJS_CLI) 22 | $(CXX) $(CXXFLAGS) $(LFLAGS) $^ -o $@ 23 | 24 | clean: 25 | $(RM) -f $(OBJS_TESTS) $(OBJS_CLI) $(OUTPUT) 26 | 27 | .PHONY: all clean 28 | -------------------------------------------------------------------------------- /Makefile.emscripten: -------------------------------------------------------------------------------- 1 | CXX := em++ 2 | 3 | # Emscripten stuff 4 | EMFLAGS := --bind --memory-init-file 0 -s WASM=1 -s MODULARIZE=1 -s EXPORT_ES6=1 -s EXPORT_NAME="'TIVarsLib'" -s NO_EXIT_RUNTIME=1 -s ASSERTIONS=0 -s DISABLE_EXCEPTION_CATCHING=1 -s EXPORTED_RUNTIME_METHODS="['FS']" --embed-file programs_tokens.csv 5 | 6 | CXXFLAGS := -O3 -flto -std=c++2a -DTH_GDB_SUPPORT=1 -W -Wall -Wextra 7 | LFLAGS := -flto $(EMFLAGS) 8 | 9 | SOURCES := $(wildcard src/*.cpp) $(wildcard src/TypeHandlers/*.cpp) 10 | 11 | OBJS = $(patsubst %.cpp, %.bc, $(SOURCES)) 12 | 13 | OUTPUT := TIVarsLib 14 | 15 | wasm: $(OUTPUT).js 16 | 17 | all: wasm 18 | 19 | %.bc: %.cpp 20 | $(CXX) $(CXXFLAGS) -c $< -o $@ 21 | 22 | $(OUTPUT).js: $(OBJS) 23 | $(CXX) $(CXXFLAGS) $(LFLAGS) $^ -o $@ 24 | 25 | clean: 26 | $(RM) -f $(OBJS) $(OUTPUT).js* $(OUTPUT).was* 27 | 28 | .PHONY: all clean wasm 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tivars_lib_cpp [![Build](https://github.com/adriweb/tivars_lib_cpp/actions/workflows/build.yml/badge.svg)](https://github.com/adriweb/tivars_lib_cpp/actions/workflows/build.yml) 2 | A C++ "library" to interact with TI-Z80/eZ80 (82/83/84 series) calculators files (programs, lists, matrices...). 3 | JavaScript bindings (for use with emscripten) are provided for convenience. 4 | 5 | ### How to use 6 | 7 | #### In C++ 8 | 9 | Right now, the best documentation is [the tests file](tests.cpp) itself, which uses the main API methods. 10 | Basically, though, there are loading/saving/conversion (data->string, string->data) methods you just have to call. 11 | 12 | **Example 1**: Here's how to read the source of TI-Basic program from an .8xp file and print it: 13 | ```cpp 14 | auto myPrgm = TIVarFile::loadFromFile("the/path/to/myProgram.8xp"); 15 | auto basicSource = myPrgm.getReadableContent(); // You can pass options like { {"reindent", true} }... 16 | std::cout << basicSource << std::endl; 17 | ``` 18 | **Example 2**: Here's how to create a TI-Basic program (output: .8xp file) from a string: 19 | ```cpp 20 | auto newPrgm = TIVarFile::createNew("Program"); // Create an empty "container" first 21 | newPrgm.setVarName("TEST"); // (also an optional parameter above) 22 | newPrgm.setContentFromString("ClrHome:Disp \"Hello World!\""); // Set the var's content from a string 23 | newPrgm.saveVarToFile("path/to/output/directory/", "myNewPrgrm"); // The extension is added automatically 24 | ``` 25 | 26 | Several optional parameters for the functions are available. For instance, French is a supported input/output language for the program vartype, which is choosable with a boolean in an options array to pass. 27 | 28 | _Note: The code throws exceptions for you to catch in case of trouble._ 29 | 30 | #### In JavaScript (via Emscripten) 31 | 32 | Bindings are done for the necessary classes, so it should be pretty obvious. 33 | Integration example: 34 | ```html 35 | 44 | ``` 45 | 46 | You can find code that use this project as a JS lib here: https://github.com/TI-Planet/zText (look at `generator.js`) 47 | 48 | ### Vartype handlers implementation: current status 49 | 50 | | Vartype | data->string | string->data | 51 | |---------------------------|:------------:|:------------:| 52 | | Real | **✓** | **✓** | 53 | | Real List | **✓** | **✓** | 54 | | Matrix | **✓** | **✓** | 55 | | Equation | **✓** | **✓** | 56 | | String | **✓** | **✓** | 57 | | Program | **✓** | **✓** | 58 | | Protected Program | **✓** | **✓** | 59 | | Graph DataBase (GDB) | **✓** (JSON) | **✓** (JSON) | 60 | | Complex | **✓** | **✓** | 61 | | Complex List | **✓** | **✓** | 62 | | Application Variable | **✓** | **✓** | 63 | | Python AppVar | **✓** | **✓** | 64 | | Exact Complex Fraction | **✓** | | 65 | | Exact Real Radical | **✓** | | 66 | | Exact Complex Radical | **✓** | | 67 | | Exact Complex Pi | **✓** | | 68 | | Exact Complex Pi Fraction | **✓** | | 69 | | Exact Real Pi | **✓** | | 70 | | Exact Real Pi Fraction | **✓** | | 71 | 72 | Note that some of the special varnames restrictions (for strings, matrices, list...) aren't implemented yet. 73 | 74 | To this date, there are no plans to support other types (except maybe some fancy things with the image/picture vartypes...). 75 | Pull Requests are welcome, though :) 76 | -------------------------------------------------------------------------------- /TIVarsLib.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/TIVarsLib.wasm -------------------------------------------------------------------------------- /cli/cli.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../src/TypeHandlers/TypeHandlers.h" 8 | #include "../src/TIVarFile.h" 9 | #include "../src/TIModels.h" 10 | #include "../src/TIVarTypes.h" 11 | 12 | #include "cxxopts.hpp" 13 | 14 | using namespace std; 15 | using namespace tivars; 16 | using namespace tivars::TypeHandlers; 17 | 18 | enum FileType 19 | { 20 | RAW, 21 | READABLE, 22 | VARFILE 23 | }; 24 | 25 | enum FileType getType(const cxxopts::ParseResult& options, const string& filename, const string& option); 26 | 27 | int main(int argc, char** argv) 28 | { 29 | TIModels::initTIModelsArray(); 30 | TIVarTypes::initTIVarTypesArray(); 31 | 32 | cxxopts::Options options("tivars_lib_cpp", "A program to interact with TI-z80 calculator files"); 33 | options.add_options() 34 | ("i,input", "Input file", cxxopts::value()) 35 | ("o,output", "Output file", cxxopts::value()) 36 | ("j,iformat", "Input format (raw|readable|varfile)", cxxopts::value()) 37 | ("k,oformat", "Output format (raw|readable|varfile)", cxxopts::value()) 38 | ("n,name", "Variable name", cxxopts::value()) 39 | ("t,type", "Variable type", cxxopts::value()) 40 | ("m,calc", "Calc. model", cxxopts::value()) 41 | ("c,csv", "Tokens CSV File", cxxopts::value()) 42 | ("l,lang", "Language", cxxopts::value()->default_value("en")) 43 | ("a,archive", "Archive status", cxxopts::value()) 44 | ("r,reindent", "Re-indent", cxxopts::value()) 45 | ("p,prettify", "Prettify", cxxopts::value()) 46 | ("s,detect_strings", "Detect strings", cxxopts::value()) 47 | ("h,help", "Print usage"); 48 | 49 | try 50 | { 51 | auto result = options.parse(argc, argv); 52 | 53 | if (result.count("help")) 54 | { 55 | cout << options.help() << endl; 56 | return 0; 57 | } 58 | 59 | string ipath, opath; 60 | 61 | if (!result.count("input")) 62 | { 63 | cout << "-i/--input is a required argument." << endl; 64 | return 1; 65 | } 66 | ipath = result["input"].as(); 67 | 68 | if (!result.count("output")) 69 | { 70 | cout << "-o/--output is a required argument." << endl; 71 | return 1; 72 | } 73 | opath = result["output"].as(); 74 | 75 | if (result.count("csv")) 76 | { 77 | string csvFilePath = result["csv"].as(); 78 | TH_Tokenized::initTokensFromCSVFilePath(csvFilePath); 79 | } else { 80 | TH_Tokenized::initTokens(); 81 | } 82 | 83 | enum FileType iformat = getType(result, ipath, "iformat"); 84 | enum FileType oformat = getType(result, opath, "oformat"); 85 | 86 | TIVarType varvarType; 87 | 88 | if (iformat != VARFILE) 89 | { 90 | if (!result.count("type")) 91 | { 92 | cout << "-t/--type is required when the input is not a varfile." << endl; 93 | return 1; 94 | } 95 | string typeName = result["type"].as(); 96 | 97 | try 98 | { 99 | varvarType = TIVarType(typeName); 100 | } catch (std::invalid_argument& e) 101 | { 102 | cout << typeName << "is not a valid variable type." << endl; 103 | cout << "Valid types:"; 104 | for (const auto& type: TIVarTypes::all()) 105 | { 106 | cout << " " << type.first; 107 | } 108 | cout << endl; 109 | return 1; 110 | } 111 | } 112 | 113 | try 114 | { 115 | TIVarFile file = iformat == VARFILE ? TIVarFile::loadFromFile(ipath) : TIVarFile::createNew(varvarType); 116 | 117 | if (result.count("name")) 118 | { 119 | string name = result["name"].as(); 120 | file.setVarName(name); 121 | } 122 | 123 | if (result.count("calc")) 124 | { 125 | string modelStr = result["calc"].as(); 126 | try 127 | { 128 | TIModel model{modelStr}; 129 | file.setCalcModel(model); 130 | } catch (invalid_argument& e) 131 | { 132 | cout << modelStr << "is not a valid calc model." << endl; 133 | cout << "Valid models:"; 134 | for (const auto& model: TIModels::all()) 135 | { 136 | cout << " " << model.first; 137 | } 138 | cout << endl; 139 | return 1; 140 | } 141 | } 142 | 143 | file.setArchived(result["archive"].as()); 144 | 145 | if (iformat == RAW) 146 | { 147 | ifstream in(ipath, ios::in | ios::binary); 148 | if (!in) 149 | { 150 | cout << ipath << ": Failed to open file" << endl; 151 | return 1; 152 | } 153 | in.seekg(0, ios::end); 154 | int filesize = in.tellg(); 155 | in.seekg(0, ios::beg); 156 | 157 | data_t data; 158 | data.resize(filesize + 2); 159 | data[0] = filesize & 0xFF; 160 | data[1] = (filesize >> 8) & 0xFF; 161 | in.read((char*) &data[2], filesize); 162 | in.close(); 163 | 164 | file.setContentFromData(data); 165 | } else if (iformat == READABLE) 166 | { 167 | ifstream in(ipath, ios::in); 168 | if (!in) 169 | { 170 | cout << ipath << ": Failed to open file" << endl; 171 | return 1; 172 | } 173 | 174 | ostringstream str; 175 | str << in.rdbuf(); 176 | in.close(); 177 | 178 | options_t contentOptions; 179 | contentOptions["detect_strings"] = result["detect_strings"].as(); 180 | 181 | file.setContentFromString(str.str(), contentOptions); 182 | } 183 | 184 | switch (oformat) 185 | { 186 | case RAW: 187 | { 188 | ofstream out(opath, ios::out | ios::binary); 189 | if (!out) 190 | { 191 | cout << opath << ": Failed to open file" << endl; 192 | return 1; 193 | } 194 | out.write((char*) (&file.getRawContent()[2]), file.getRawContent().size() - 2); 195 | break; 196 | } 197 | case READABLE: 198 | { 199 | ofstream out(opath, ios::out); 200 | if (!out) 201 | { 202 | cout << opath << ": Failed to open file" << endl; 203 | return 1; 204 | } 205 | 206 | options_t contentOptions; 207 | contentOptions["reindent"] = result["reindent"].as(); 208 | contentOptions["prettify"] = result["prettify"].as(); 209 | 210 | if (result.count("lang")) 211 | { 212 | string langStr = result["lang"].as(); 213 | if (langStr == "en") 214 | { 215 | contentOptions["lang"] = TH_Tokenized::LANG_EN; 216 | } else if (langStr == "fr") 217 | { 218 | contentOptions["lang"] = TH_Tokenized::LANG_FR; 219 | } else 220 | { 221 | cout << langStr << " is not a valid language code" << endl; 222 | cout << "Valid languages: en, fr" << endl; 223 | return 1; 224 | } 225 | } 226 | 227 | out << file.getReadableContent(contentOptions); 228 | break; 229 | } 230 | case VARFILE: 231 | { 232 | try 233 | { 234 | file.saveVarToFile(opath); 235 | } catch (runtime_error& e) 236 | { 237 | cout << opath << ": failed to write file." << endl; 238 | return 1; 239 | } 240 | break; 241 | } 242 | } 243 | 244 | } catch (runtime_error& e) 245 | { 246 | if ((string) e.what() == "No such file") 247 | { 248 | cout << ipath << ": no such file or directory" << endl; 249 | } else 250 | throw e; 251 | } 252 | 253 | } catch (cxxopts::OptionParseException& e) 254 | { 255 | cout << options.help() << endl; 256 | return 0; 257 | } 258 | } 259 | 260 | static unordered_map const fileTypes = { 261 | {"raw", RAW}, 262 | {"readable", READABLE}, 263 | {"varfile", VARFILE} 264 | }; 265 | 266 | enum FileType getType(const cxxopts::ParseResult& options, const string& filename, const string& option) 267 | { 268 | // Type manually specified, use that 269 | if (options.count(option)) 270 | { 271 | const string& typeStr = options[option].as(); 272 | const auto& i = fileTypes.find(typeStr); 273 | if (i != fileTypes.end()) 274 | { 275 | return i->second; 276 | } else 277 | { 278 | cout << typeStr << " is not a valid file type." << endl; 279 | cout << "Valid types: raw, readable, varfile" << endl; 280 | exit(1); 281 | } 282 | } 283 | 284 | // File type not specified, guess by file extension 285 | const auto& pos = filename.find_last_of('.'); 286 | if (pos == string::npos) 287 | { 288 | cout << "--" << option << " is required for files without an extension." << endl; 289 | exit(1); 290 | } 291 | 292 | string extension = filename.substr(pos + 1); 293 | transform(extension.begin(), extension.end(), extension.begin(), ::tolower); 294 | 295 | if (extension == "bin") 296 | return RAW; 297 | 298 | if (extension == "txt") 299 | return READABLE; 300 | 301 | for (const auto& type: TIVarTypes::all()) 302 | { 303 | const vector& exts = type.second.getExts(); 304 | if (std::find(exts.begin(), exts.end(), extension) != exts.end()) 305 | return VARFILE; 306 | } 307 | 308 | cout << "Could not guess file type from file extension. Use --" << option << " to specify file type." << endl; 309 | exit(1); 310 | } 311 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/BinaryFile.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "BinaryFile.h" 9 | #include "tivarslib_utils.h" 10 | 11 | #include 12 | 13 | namespace tivars 14 | { 15 | 16 | /** 17 | * @param null filePath 18 | * @throws \Exception 19 | */ 20 | BinaryFile::BinaryFile(const std::string& filePath) 21 | { 22 | if (!filePath.empty()) 23 | { 24 | if (file_exists(filePath)) 25 | { 26 | this->file = fopen(filePath.c_str(), "rb+"); 27 | if (!this->file) 28 | { 29 | throw std::runtime_error("Can't open the input file"); 30 | } 31 | this->filePath = filePath; 32 | fseek(this->file, 0L, SEEK_END); 33 | this->fileSize = (size_t) ftell(this->file); 34 | fseek(this->file, 0L, SEEK_SET); 35 | } else { 36 | throw std::runtime_error("No such file"); 37 | } 38 | } else { 39 | throw std::invalid_argument("Empty file path given"); 40 | } 41 | } 42 | 43 | /** 44 | * Returns one byte read from the file 45 | * 46 | * @return uint8_t 47 | * @throws runtime_error 48 | */ 49 | uint8_t BinaryFile::get_raw_byte() 50 | { 51 | if (file) 52 | { 53 | uint8_t byte; 54 | const size_t n = fread(&byte, sizeof(uint8_t), 1, file); 55 | if (n != 1 || ferror(file)) 56 | { 57 | throw std::runtime_error("Error in get_raw_byte"); 58 | } 59 | return byte; 60 | } else { 61 | throw std::runtime_error("No file loaded"); 62 | } 63 | } 64 | 65 | /** 66 | * Returns an array of bytes bytes read from the file 67 | * 68 | * @param size_t bytes 69 | * @return data_t 70 | * @throws runtime_error 71 | */ 72 | data_t BinaryFile::get_raw_bytes(size_t bytes) 73 | { 74 | if (file) 75 | { 76 | data_t v(bytes); 77 | const size_t n = fread(v.data(), sizeof(uint8_t), bytes, file); 78 | if (n != bytes || ferror(file)) 79 | { 80 | throw std::runtime_error("Error in get_raw_bytes"); 81 | } 82 | return v; 83 | } else { 84 | throw std::runtime_error("No file loaded"); 85 | } 86 | } 87 | 88 | /** 89 | * Returns a string of bytes bytes read from the file (doesn't stop at NUL) 90 | * 91 | * @param size_t bytes The number of bytes to read 92 | * @return string 93 | * @throws runtime_error 94 | */ 95 | std::string BinaryFile::get_string_bytes(size_t bytes) 96 | { 97 | if (file) 98 | { 99 | std::string buf(bytes, '\0'); 100 | const size_t n = fread(&buf[0], sizeof(char), bytes, file); 101 | if (n != bytes || ferror(file)) 102 | { 103 | throw std::runtime_error("Error in get_string_bytes"); 104 | } 105 | return buf; 106 | } else { 107 | throw std::runtime_error("No file loaded"); 108 | } 109 | } 110 | 111 | void BinaryFile::close() 112 | { 113 | if (file) 114 | { 115 | fclose(file); 116 | file = nullptr; 117 | } 118 | } 119 | 120 | size_t BinaryFile::size() const 121 | { 122 | return fileSize; 123 | } 124 | } -------------------------------------------------------------------------------- /src/BinaryFile.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #ifndef TIVARS_LIB_CPP_BINARYFILE_H 9 | #define TIVARS_LIB_CPP_BINARYFILE_H 10 | 11 | #include "CommonTypes.h" 12 | 13 | namespace tivars 14 | { 15 | class BinaryFile 16 | { 17 | public: 18 | BinaryFile() = default; 19 | 20 | explicit BinaryFile(const std::string& filePath); 21 | 22 | BinaryFile(const BinaryFile&) = delete; 23 | BinaryFile& operator=(const BinaryFile) = delete; 24 | 25 | BinaryFile(BinaryFile&& o) noexcept : file(o.file), fileSize(o.fileSize) 26 | { o.file = nullptr; } 27 | 28 | ~BinaryFile() 29 | { 30 | close(); 31 | } 32 | 33 | uint8_t get_raw_byte(); 34 | data_t get_raw_bytes(size_t bytes); 35 | std::string get_string_bytes(size_t bytes); 36 | size_t size() const; 37 | void close(); 38 | 39 | protected: 40 | FILE* file = nullptr; 41 | std::string filePath = ""; 42 | size_t fileSize = 0; 43 | 44 | }; 45 | } 46 | 47 | #endif //TIVARS_LIB_CPP_BINARYFILE_H 48 | -------------------------------------------------------------------------------- /src/CommonTypes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #ifndef COMMON_H 9 | #define COMMON_H 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | using data_t = std::vector; 18 | using options_t = std::map; 19 | 20 | #define u8enum(NAME) enum NAME : uint8_t 21 | 22 | #endif -------------------------------------------------------------------------------- /src/TIModel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/TIs_lib 5 | * License: MIT 6 | */ 7 | 8 | #include "TIModel.h" 9 | #include "TIModels.h" 10 | 11 | #include 12 | 13 | namespace tivars 14 | { 15 | 16 | bool TIModel::supportsType(const TIVarType& type) const 17 | { 18 | const std::vector& exts = type.getExts(); 19 | return this->orderID >= 0 && this->orderID < (int)exts.size() && !exts[this->orderID].empty(); 20 | } 21 | 22 | /*** "Constructors" ***/ 23 | 24 | TIModel::TIModel(const std::string& name) 25 | { 26 | if (TIModels::isValidName(name)) 27 | { 28 | *this = TIModels::fromName(name); 29 | } else 30 | { 31 | throw std::invalid_argument("Invalid model name"); 32 | } 33 | } 34 | } 35 | 36 | #ifdef __EMSCRIPTEN__ 37 | #include 38 | using namespace emscripten; 39 | EMSCRIPTEN_BINDINGS(_timodel) { 40 | class_("TIModel") 41 | .constructor<>() 42 | .constructor() 43 | .constructor() 44 | 45 | .function("getOrderId" , &tivars::TIModel::getOrderId) 46 | .function("getProductId", &tivars::TIModel::getProductId) 47 | .function("getName" , &tivars::TIModel::getName) 48 | .function("getFlags" , &tivars::TIModel::getFlags) 49 | .function("getSig" , &tivars::TIModel::getSig) 50 | .function("supportsType", &tivars::TIModel::supportsType) 51 | ; 52 | } 53 | #endif 54 | -------------------------------------------------------------------------------- /src/TIModel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #ifndef TIMODEL_H 9 | #define TIMODEL_H 10 | 11 | #include "CommonTypes.h" 12 | #include "TIVarType.h" 13 | 14 | namespace tivars 15 | { 16 | 17 | class TIModel 18 | { 19 | 20 | public: 21 | TIModel() = default; 22 | TIModel(const std::string& name); 23 | TIModel(const char* name) { *this = TIModel{std::string{name}}; }; 24 | 25 | TIModel(int orderId, const std::string& name, uint32_t flags, const std::string& sig, uint8_t productId) 26 | : orderID(orderId), name(name), flags(flags), sig(sig), productId(productId) 27 | {} 28 | 29 | ~TIModel() = default; 30 | 31 | /* Getters */ 32 | int getOrderId() const { return this->orderID; } 33 | int getProductId() const { return this->productId; } 34 | std::string getName() const { return this->name; } 35 | uint32_t getFlags() const { return this->flags; } 36 | std::string getSig() const { return this->sig; } 37 | 38 | bool supportsType(const TIVarType& type) const; 39 | 40 | private: 41 | int orderID = -1; 42 | std::string name = "Unknown"; 43 | uint32_t flags = 0; 44 | std::string sig = ""; 45 | uint8_t productId = 0; 46 | 47 | }; 48 | 49 | } 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /src/TIModels.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "TIModels.h" 9 | 10 | namespace tivars 11 | { 12 | namespace 13 | { 14 | std::unordered_map models; 15 | const TIModel unknownModel{}; 16 | } 17 | 18 | TIModel TIModels::fromName(const std::string& name) 19 | { 20 | return isValidName(name) ? models[name] : unknownModel; 21 | } 22 | 23 | TIModel TIModels::fromSignature(const std::string& sig) 24 | { 25 | return isValidSignature(sig) ? models[sig] : unknownModel; 26 | } 27 | 28 | TIModel TIModels::fromPID(uint8_t pid) 29 | { 30 | return isValidPID(pid) ? models[std::to_string(pid)] : unknownModel; 31 | } 32 | 33 | // orderID is for the extensions association 34 | void TIModels::insertModel(int orderID, uint32_t flags, const std::string& name, const std::string& sig, uint8_t productId) 35 | { 36 | const TIModel model(orderID, name, flags, sig, productId); 37 | 38 | if (!models.count(name)) 39 | models[name] = model; 40 | 41 | const std::string pid_str = std::to_string(productId); 42 | if (!models.count(pid_str)) 43 | models[pid_str] = model; 44 | 45 | if (!models.count(sig)) 46 | models[sig] = model; 47 | } 48 | 49 | void TIModels::initTIModelsArray() 50 | { 51 | const uint32_t flags82 = 0 | has82things; 52 | const uint32_t flags83 = flags82 | hasComplex; 53 | const uint32_t flags83p = flags83 | hasFlash | hasApps; 54 | const uint32_t flags84p = flags83p | hasClock; 55 | const uint32_t flags82a = flags84p &~hasApps; 56 | const uint32_t flags84pcse = flags84p | hasColorLCD; 57 | const uint32_t flags84pce = flags84pcse | hasEZ80CPU; 58 | const uint32_t flags83pce = flags84pce | hasExactMath; 59 | const uint32_t flags83pceep= flags83pce | hasPython; 60 | const uint32_t flags84pcepy= flags84pce | hasPython; 61 | const uint32_t flags82aep = flags83pceep&~hasApps; 62 | 63 | // In case of duplicate ProductID for a given orderID, we first insert the default model for that ProductID 64 | insertModel(0, flags82, "82", "**TI82**", 0); 65 | insertModel(1, flags83, "83", "**TI83**", 0); 66 | insertModel(2, flags82a, "82A", "**TI83F*", 0x0B); 67 | insertModel(3, flags84p, "84+T", "**TI83F*", 0x1B); 68 | insertModel(4, flags83p, "83+", "**TI83F*", 0x04); 69 | insertModel(4, flags83p, "82+", "**TI83F*", 0x04); 70 | insertModel(4, flags84p, "84+", "**TI83F*", 0x0A); 71 | insertModel(5, flags84pcse, "84+CSE", "**TI83F*", 0x0F); 72 | insertModel(6, flags84pce, "84+CE", "**TI83F*", 0x13); 73 | insertModel(6, flags84pce, "84+CET", "**TI83F*", 0x13); 74 | insertModel(6, flags84pcepy,"84+CETPE","**TI83F*", 0x13); 75 | insertModel(6, flags84pcepy,"84+CEPy", "**TI83F*", 0x13); 76 | insertModel(7, flags83pce, "83PCE", "**TI83F*", 0x13); 77 | insertModel(7, flags83pceep,"83PCEEP", "**TI83F*", 0x13); 78 | insertModel(8, flags82aep, "82AEP", "**TI83F*", 0x15); 79 | } 80 | 81 | const std::unordered_map& TIModels::all() 82 | { 83 | return models; 84 | } 85 | 86 | bool TIModels::isValidPID(uint8_t pid) 87 | { 88 | return (pid > 0 && models.count(std::to_string(pid))); 89 | } 90 | 91 | bool TIModels::isValidName(const std::string& name) 92 | { 93 | return (!name.empty() && models.count(name)); 94 | } 95 | 96 | bool TIModels::isValidSignature(const std::string& sig) 97 | { 98 | return (!sig.empty() && models.count(sig)); 99 | } 100 | 101 | }; 102 | 103 | // TIModels::initTIModelsArray(); -------------------------------------------------------------------------------- /src/TIModels.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #ifndef TIMODELS_H 9 | #define TIMODELS_H 10 | 11 | #include "CommonTypes.h" 12 | #include "TIModel.h" 13 | #include 14 | 15 | namespace tivars 16 | { 17 | enum TIFeatureFlags 18 | { 19 | has82things = 0b000000001, // (1 << 0); 20 | hasComplex = 0b000000010, // (1 << 1); 21 | hasFlash = 0b000000100, // (1 << 2); 22 | hasApps = 0b000001000, // (1 << 3); 23 | hasClock = 0b000010000, // (1 << 4); 24 | hasColorLCD = 0b000100000, // (1 << 5); 25 | hasEZ80CPU = 0b001000000, // (1 << 6); 26 | hasExactMath = 0b010000000, // (1 << 7); 27 | hasPython = 0b100000000, // (1 << 8); 28 | }; 29 | 30 | class TIModels 31 | { 32 | 33 | public: 34 | 35 | static TIModel fromName(const std::string& name); 36 | static TIModel fromSignature(const std::string& sig); 37 | static TIModel fromPID(uint8_t pid); 38 | 39 | static void initTIModelsArray(); 40 | static const std::unordered_map& all(); 41 | 42 | static bool isValidPID(uint8_t pid); 43 | static bool isValidName(const std::string& name); 44 | static bool isValidSignature(const std::string& sig); 45 | 46 | private: 47 | static void insertModel(int orderID, uint32_t flags, const std::string& name, const std::string& sig, uint8_t productId); 48 | 49 | }; 50 | 51 | } 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /src/TIVarFile.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "TIVarFile.h" 9 | 10 | #include 11 | 12 | #include "tivarslib_utils.h" 13 | #include "TIModels.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "TIVarTypes.h" 22 | 23 | namespace tivars 24 | { 25 | 26 | /*** Constructors ***/ 27 | 28 | /** 29 | * Internal constructor, called from loadFromFile and createNew. 30 | * If filePath empty or not given, it's a programmer error, and it will throw in BinaryFile(filePath) anyway. 31 | * To create a TIVarFile not from a file, use TIVarFile::createNew 32 | * @param string filePath 33 | * @throws \Exception 34 | */ 35 | TIVarFile::TIVarFile(const std::string& filePath) : BinaryFile(filePath) 36 | { 37 | this->fromFile = true; 38 | if (this->fileSize < 76) // bare minimum for header + a var entry 39 | { 40 | throw std::runtime_error("This file is not a valid TI-[e]z80 variable file"); 41 | } 42 | this->makeHeaderFromFile(); 43 | this->makeVarEntriesFromFile(); 44 | this->checkVarEntriesVersionCompat(); 45 | this->computedChecksum = this->computeChecksumFromFileData(); 46 | this->fileChecksum = this->getChecksumValueFromFile(); 47 | if (this->computedChecksum != this->fileChecksum) 48 | { 49 | printf("[Warning] File is corrupt (file checksum = 0x%02X, calculated checksum = 0x%02X)\n", this->fileChecksum, this->computedChecksum); 50 | this->corrupt = true; 51 | } 52 | this->close(); // let's free the resource up as early as possible 53 | } 54 | 55 | TIVarFile TIVarFile::loadFromFile(const std::string& filePath) 56 | { 57 | if (!filePath.empty()) 58 | { 59 | return TIVarFile(filePath); 60 | } else { 61 | throw std::runtime_error("No file path given"); 62 | } 63 | } 64 | 65 | TIVarFile::TIVarFile(const TIVarType& type, const std::string& name, const TIModel& model) : calcModel(model) 66 | { 67 | if (!this->calcModel.supportsType(type)) 68 | { 69 | throw std::runtime_error("This calculator model (" + this->calcModel.getName() + ") does not support the type " + type.getName()); 70 | } 71 | 72 | const std::string signature = this->calcModel.getSig(); 73 | const std::string comment = str_pad("Created by tivars_lib_cpp", sizeof(var_header_t::comment), "\0"); 74 | 75 | std::copy(signature.begin(), signature.end(), this->header.signature); 76 | std::copy(comment.begin(), comment.end(), this->header.comment); 77 | this->header.entries_len = 0; // will have to be overwritten later 78 | 79 | this->entries.resize(1); 80 | auto& entry = this->entries[0]; 81 | entry.meta_length = (this->calcModel.getFlags() & TIFeatureFlags::hasFlash) ? varEntryNewLength : varEntryOldLength; 82 | entry.data_length = 0; // will have to be overwritten later 83 | entry.typeID = (uint8_t) type.getId(); 84 | entry._type = type; 85 | entry.setVarName(name); 86 | entry.data_length2 = 0; // will have to be overwritten later 87 | } 88 | 89 | TIVarFile TIVarFile::createNew(const TIVarType& type, const std::string& name, const TIModel& model) 90 | { 91 | return TIVarFile(type, name, model); 92 | } 93 | 94 | TIVarFile TIVarFile::createNew(const TIVarType& type, const std::string& name) 95 | { 96 | return TIVarFile(type, name, TIModel{"84+CE"}); 97 | } 98 | 99 | TIVarFile TIVarFile::createNew(const TIVarType& type) 100 | { 101 | return createNew(type, ""); 102 | } 103 | 104 | /*** Makers ***/ 105 | 106 | void TIVarFile::makeHeaderFromFile() 107 | { 108 | rewind(this->file); 109 | 110 | const auto signature = this->get_string_bytes(sizeof(var_header_t::signature)); 111 | const auto sig_extra = this->get_raw_bytes(sizeof(var_header_t::sig_extra)); 112 | this->header.ownerPID = this->get_raw_byte(); 113 | const auto comment = this->get_string_bytes(sizeof(var_header_t::comment)); 114 | std::copy(signature.begin(), signature.end(), this->header.signature); 115 | std::copy(sig_extra.begin(), sig_extra.end(), this->header.sig_extra); 116 | std::copy(comment.begin(), comment.end(), this->header.comment); 117 | this->header.entries_len = this->get_two_bytes_swapped(); 118 | 119 | // the calcModel may later get updated with a more precise one 120 | if (TIModels::isValidPID(header.ownerPID)) 121 | this->calcModel = TIModels::fromPID(header.ownerPID); 122 | else if (TIModels::isValidSignature(signature)) 123 | this->calcModel = TIModels::fromSignature(signature); 124 | else 125 | throw std::invalid_argument("Unhandled file type. No known model usable for this header."); 126 | } 127 | 128 | void TIVarFile::makeVarEntriesFromFile() 129 | { 130 | fseek(this->file, TIVarFile::firstVarEntryOffset, SEEK_SET); 131 | 132 | while (ftell(this->file) < (long)(this->fileSize - 2)) 133 | { 134 | var_entry_t entry{}; 135 | entry.meta_length = this->get_two_bytes_swapped(); 136 | entry.data_length = this->get_two_bytes_swapped(); 137 | entry.typeID = this->get_raw_byte(); 138 | const std::string varNameFromFile = this->get_string_bytes(sizeof(var_entry_t::varname)); 139 | if (entry.meta_length == varEntryNewLength) 140 | { 141 | if ((this->calcModel.getFlags() & TIFeatureFlags::hasFlash) == 0) 142 | { 143 | fprintf(stderr, "Something is wrong with your file... The var entry meta length indicates is has flash-related fields, but the signature doesn't match...\n"); 144 | } 145 | entry.version = this->get_raw_byte(); 146 | entry.archivedFlag = this->get_raw_byte(); 147 | } 148 | else if (entry.meta_length != varEntryOldLength) 149 | { 150 | throw std::invalid_argument("Invalid file. The var entry meta length has an unexpected value. Don't know what to do with that file."); 151 | } 152 | entry.data_length2 = this->get_two_bytes_swapped(); 153 | entry.data = this->get_raw_bytes(entry.data_length); 154 | 155 | // Now that we have the data, we can retrieve the (full) type... 156 | entry.determineFullType(); 157 | 158 | // ... and now that we have the full type, we can safely set the name. 159 | // TODO: when setVarName knows about all the possible names from types: entry.setVarName(varNameFromFile); 160 | std::copy(varNameFromFile.begin(), varNameFromFile.end(), entry.varname); 161 | 162 | this->entries.push_back(entry); 163 | } 164 | } 165 | 166 | 167 | /*** Private actions ***/ 168 | 169 | uint16_t TIVarFile::computeChecksumFromFileData() 170 | { 171 | if (this->fromFile) 172 | { 173 | fseek(this->file, TIVarFile::firstVarEntryOffset, SEEK_SET); 174 | 175 | uint16_t sum = 0; 176 | for (size_t i = firstVarEntryOffset; i < this->fileSize - 2; i++) 177 | { 178 | sum += this->get_raw_byte(); 179 | } 180 | return (uint16_t) (sum & 0xFFFF); 181 | } else { 182 | throw std::runtime_error("[Error] No file loaded"); 183 | } 184 | } 185 | 186 | uint16_t TIVarFile::computeChecksumFromInstanceData() 187 | { 188 | uint16_t sum = 0; 189 | for (const auto& entry : this->entries) 190 | { 191 | sum += entry.meta_length; 192 | sum += entry.typeID; 193 | sum += 2 * ((entry.data_length & 0xFF) + ((entry.data_length >> 8) & 0xFF)); // 2* because of the two same length fields 194 | sum += std::accumulate(entry.varname, entry.varname + sizeof(var_entry_t::varname), 0); 195 | sum += std::accumulate(entry.data.begin(), entry.data.end(), 0); 196 | if (this->calcModel.getFlags() & TIFeatureFlags::hasFlash) 197 | { 198 | sum += entry.version; 199 | sum += entry.archivedFlag; 200 | } 201 | } 202 | return (uint16_t) (sum & 0xFFFF); 203 | } 204 | 205 | uint16_t TIVarFile::getChecksumValueFromFile() 206 | { 207 | if (this->fromFile) 208 | { 209 | fseek(this->file, this->fileSize - 2, SEEK_SET); 210 | return this->get_two_bytes_swapped(); 211 | } else { 212 | throw std::runtime_error("[Error] No file loaded"); 213 | } 214 | } 215 | 216 | /** 217 | * Updates the length fields in both the header and the var entries, as well as the checksum 218 | */ 219 | void TIVarFile::refreshMetadataFields() 220 | { 221 | this->header.entries_len = 0; 222 | for (auto& entry : this->entries) 223 | { 224 | entry.data_length2 = entry.data_length = (uint16_t) entry.data.size(); 225 | this->header.entries_len += sizeof(var_entry_t::data_length) + sizeof(var_entry_t::data_length2) + entry.meta_length + entry.data_length; 226 | entry.version = (this->calcModel.getFlags() & TIFeatureFlags::hasFlash) ? std::get<2>(entry._type.getHandlers())(entry.data) : 0; 227 | } 228 | this->computedChecksum = this->computeChecksumFromInstanceData(); 229 | } 230 | 231 | void TIVarFile::checkVarEntriesVersionCompat() const 232 | { 233 | if (this->header.ownerPID > 0 && this->calcModel.getFlags() & TIFeatureFlags::hasFlash) 234 | { 235 | uint8_t max_version = 0; 236 | for (const auto& entry : this->entries) 237 | { 238 | const uint8_t maskedVersion = entry.version & ~0x20; 239 | if (maskedVersion > max_version) max_version = maskedVersion; 240 | } 241 | if (this->header.ownerPID < max_version) 242 | { 243 | printf("[Warning] The declared calculator model (owner ID 0x%02X) may not support the var entry version 0x%02X found in this file.\n", this->header.ownerPID, max_version); 244 | } 245 | } 246 | } 247 | 248 | void TIVarFile::var_entry_t::setVarName(const std::string &name) { 249 | std::string newName(name); 250 | if (newName.empty()) 251 | { 252 | newName = "FILE" + (_type.getExts().empty() ? "" : _type.getExts()[0]); 253 | } 254 | 255 | // Here we handle various theta chars. Note thata in TI-ASCII, theta is at 0x5B which is "[" in ASCII. 256 | newName = std::regex_replace(newName, std::regex("(\u03b8|\u0398|\u03F4|\u1DBF)"), "["); 257 | 258 | // TODO: handle names according to _type 259 | newName = std::regex_replace(newName, std::regex("[^[a-zA-Z0-9]"), ""); 260 | if (newName.length() > sizeof(varname) || newName.empty() || is_numeric(newName.substr(0, 1))) 261 | { 262 | throw std::invalid_argument("Invalid name given. 8 chars (A-Z, 0-9, θ) max, starting by a letter or θ."); 263 | } 264 | 265 | // TODO: again, properly handle names according to _type... (needs to be implemented by each type) 266 | // Quick hack for now... 267 | const auto& typeName = _type.getName(); 268 | if (typeName == "Real" || typeName == "Complex" || typeName == "Program" || typeName == "ProtectedProgram") 269 | { 270 | for (auto & c: newName) c = (char) toupper(c); 271 | } 272 | 273 | newName = str_pad(newName, sizeof(varname), "\0"); 274 | std::copy(newName.begin(), newName.end(), varname); 275 | } 276 | 277 | void TIVarFile::var_entry_t::determineFullType() 278 | { 279 | _type = TIVarType{typeID}; 280 | if (_type.getName() == "AppVar") 281 | { 282 | if (data.size() >= 6) 283 | { 284 | if (memcmp(&data[2], TypeHandlers::STH_PythonAppVar::ID_CODE, 4) == 0 285 | || memcmp(&data[2], TypeHandlers::STH_PythonAppVar::ID_SCRIPT, 4) == 0) 286 | { 287 | _type = TIVarType{"PythonAppVar"}; 288 | } 289 | } 290 | } 291 | } 292 | 293 | /*** Public actions **/ 294 | 295 | /** 296 | * @param array data The array of bytes 297 | */ 298 | void TIVarFile::setContentFromData(const data_t& data, uint16_t entryIdx) 299 | { 300 | if (!data.empty()) 301 | { 302 | this->entries[entryIdx].data = data; 303 | this->refreshMetadataFields(); 304 | } else { 305 | throw std::runtime_error("[Error] No data given"); 306 | } 307 | } 308 | void TIVarFile::setContentFromData(const data_t& data) 309 | { 310 | setContentFromData(data, 0); 311 | } 312 | 313 | void TIVarFile::setContentFromString(const std::string& str, const options_t& options, uint16_t entryIdx) 314 | { 315 | auto& entry = this->entries[entryIdx]; 316 | entry.data = std::get<0>(entry._type.getHandlers())(str, options, this); 317 | this->refreshMetadataFields(); 318 | } 319 | void TIVarFile::setContentFromString(const std::string& str, const options_t& options) 320 | { 321 | setContentFromString(str, options, 0); 322 | } 323 | void TIVarFile::setContentFromString(const std::string& str) 324 | { 325 | setContentFromString(str, {}, 0); 326 | } 327 | 328 | void TIVarFile::setCalcModel(const TIModel& model) 329 | { 330 | this->calcModel = model; 331 | std::string signature = model.getSig(); 332 | std::copy(signature.begin(), signature.end(), this->header.signature); 333 | } 334 | 335 | void TIVarFile::setVarName(const std::string& name, uint16_t entryIdx) 336 | { 337 | this->entries[entryIdx].setVarName(name); 338 | this->refreshMetadataFields(); 339 | } 340 | void TIVarFile::setVarName(const std::string& name) 341 | { 342 | this->setVarName(name, 0); 343 | } 344 | 345 | void TIVarFile::setArchived(bool flag, uint16_t entryIdx) 346 | { 347 | if (this->calcModel.getFlags() & TIFeatureFlags::hasFlash) 348 | { 349 | this->entries[entryIdx].setArchived(flag); 350 | this->refreshMetadataFields(); 351 | } else { 352 | throw std::runtime_error("[Error] Archived flag not supported on this calculator model"); 353 | } 354 | } 355 | void TIVarFile::setArchived(bool flag) 356 | { 357 | this->setArchived(flag, 0); 358 | } 359 | 360 | 361 | bool TIVarFile::isCorrupt() const 362 | { 363 | return corrupt; 364 | } 365 | 366 | data_t TIVarFile::getRawContent(uint16_t entryIdx) 367 | { 368 | return this->entries[entryIdx].data; 369 | } 370 | data_t TIVarFile::getRawContent() 371 | { 372 | return this->getRawContent(0); 373 | } 374 | 375 | std::string TIVarFile::getRawContentHexStr() 376 | { 377 | const data_t rawContent = getRawContent(); 378 | std::ostringstream result; 379 | for (const auto& v : rawContent) 380 | { 381 | result << std::setfill('0') << std::setw(sizeof(v) * 2) << std::hex << +v; 382 | } 383 | return result.str(); 384 | } 385 | 386 | std::string TIVarFile::getReadableContent(const options_t& options, uint16_t entryIdx) 387 | { 388 | const auto& entry = this->entries[entryIdx]; 389 | return std::get<1>(entry._type.getHandlers())(entry.data, options, this); 390 | } 391 | std::string TIVarFile::getReadableContent(const options_t& options) 392 | { 393 | return getReadableContent(options, 0); 394 | } 395 | std::string TIVarFile::getReadableContent() 396 | { 397 | return getReadableContent({}, 0); 398 | } 399 | 400 | data_t TIVarFile::make_bin_data() 401 | { 402 | data_t bin_data; 403 | 404 | // Header 405 | { 406 | bin_data.insert(bin_data.end(), this->header.signature, this->header.signature + sizeof(var_header_t::signature)); 407 | bin_data.insert(bin_data.end(), this->header.sig_extra, this->header.sig_extra + sizeof(var_header_t::sig_extra)); 408 | bin_data.push_back(this->header.ownerPID); 409 | bin_data.insert(bin_data.end(), this->header.comment, this->header.comment + sizeof(var_header_t::comment)); 410 | bin_data.push_back((uint8_t) (this->header.entries_len & 0xFF)); bin_data.push_back((uint8_t) ((this->header.entries_len >> 8) & 0xFF)); 411 | } 412 | 413 | // Var entries 414 | for (const auto& entry : this->entries) 415 | { 416 | bin_data.push_back((uint8_t) (entry.meta_length & 0xFF)); bin_data.push_back((uint8_t) ((entry.meta_length >> 8) & 0xFF)); 417 | bin_data.push_back((uint8_t) (entry.data_length & 0xFF)); bin_data.push_back((uint8_t) ((entry.data_length >> 8) & 0xFF)); 418 | bin_data.push_back(entry.typeID); 419 | bin_data.insert(bin_data.end(), entry.varname, entry.varname + + sizeof(var_entry_t::varname)); 420 | if (this->calcModel.getFlags() & TIFeatureFlags::hasFlash) 421 | { 422 | bin_data.push_back(entry.version); 423 | bin_data.push_back(entry.archivedFlag); 424 | } 425 | bin_data.push_back((uint8_t) (entry.data_length2 & 0xFF)); bin_data.push_back((uint8_t) ((entry.data_length2 >> 8) & 0xFF)); 426 | bin_data.insert(bin_data.end(), entry.data.begin(), entry.data.end()); 427 | } 428 | 429 | return bin_data; 430 | } 431 | 432 | /** 433 | * Writes a variable to an actual file on the FS 434 | * If the variable was already loaded from a file, it will be used and overwritten, 435 | * except if a specific directory and name are provided. 436 | * 437 | * @param string directory Directory to save the file to 438 | * @param string name Name of the file, without the extension 439 | * @return string the full path 440 | */ 441 | std::string TIVarFile::saveVarToFile(std::string directory, std::string name) 442 | { 443 | std::string fullPath; 444 | 445 | if (this->fromFile && directory.empty()) 446 | { 447 | fullPath = this->filePath; 448 | } else { 449 | if (name.empty()) 450 | { 451 | name = std::string(this->hasMultipleEntries() ? "GROUP" : (char*)(this->entries[0].varname)); 452 | } 453 | std::string fileName; 454 | if (this->hasMultipleEntries()) 455 | { 456 | fileName = name + ".8xg"; 457 | } else { 458 | const int extIndex = std::max(0, this->calcModel.getOrderId()); 459 | fileName = name + "." + this->entries[0]._type.getExts()[extIndex]; 460 | } 461 | if (directory.empty()) 462 | { 463 | directory = "."; 464 | } 465 | fullPath = directory + "/" + fileName; 466 | } 467 | 468 | return saveVarToFile(fullPath); 469 | } 470 | 471 | std::string TIVarFile::saveVarToFile(std::string path) 472 | { 473 | FILE* handle = fopen(path.c_str(), "wb"); 474 | if (!handle) 475 | { 476 | throw std::runtime_error("Can't open the output file"); 477 | } 478 | 479 | this->refreshMetadataFields(); 480 | 481 | // Make and write file data 482 | const data_t bin_data = make_bin_data(); 483 | fwrite(&bin_data[0], sizeof(bin_data[0]), bin_data.size(), handle); 484 | 485 | // Write checksum 486 | const char buf[2] = {(char) (this->computedChecksum & 0xFF), (char) ((this->computedChecksum >> 8) & 0xFF)}; 487 | fwrite(buf, sizeof(char), 2, handle); 488 | 489 | fclose(handle); 490 | 491 | this->corrupt = false; 492 | 493 | return path; 494 | } 495 | 496 | std::string TIVarFile::saveVarToFile() 497 | { 498 | return saveVarToFile("", ""); 499 | } 500 | } 501 | 502 | #ifdef __EMSCRIPTEN__ 503 | #include 504 | using namespace emscripten; 505 | EMSCRIPTEN_BINDINGS(_tivarfile) { 506 | 507 | register_map("options_t"); 508 | 509 | class_("TIVarFile") 510 | .function("getHeader" , &tivars::TIVarFile::getHeader) 511 | .function("getVarEntries" , &tivars::TIVarFile::getVarEntries) 512 | .function("getInstanceChecksum" , &tivars::TIVarFile::getInstanceChecksum) 513 | 514 | .function("getChecksumValueFromFile" , &tivars::TIVarFile::getChecksumValueFromFile) 515 | .function("setContentFromData" , select_overload(&tivars::TIVarFile::setContentFromData)) 516 | .function("setContentFromString" , select_overload(&tivars::TIVarFile::setContentFromString)) 517 | .function("setContentFromString" , select_overload(&tivars::TIVarFile::setContentFromString)) 518 | .function("setCalcModel" , &tivars::TIVarFile::setCalcModel) 519 | .function("setVarName" , select_overload(&tivars::TIVarFile::setVarName)) 520 | .function("setArchived" , select_overload(&tivars::TIVarFile::setArchived)) 521 | .function("isCorrupt" , &tivars::TIVarFile::isCorrupt) 522 | .function("getRawContent" , select_overload(&tivars::TIVarFile::getRawContent)) 523 | .function("getRawContentHexStr" , &tivars::TIVarFile::getRawContentHexStr) 524 | .function("getReadableContent" , select_overload(&tivars::TIVarFile::getReadableContent)) 525 | .function("getReadableContent" , select_overload(&tivars::TIVarFile::getReadableContent)) 526 | 527 | .function("saveVarToFile" , select_overload(&tivars::TIVarFile::saveVarToFile)) 528 | .function("saveVarToFile" , select_overload(&tivars::TIVarFile::saveVarToFile)) 529 | .function("saveVarToFile" , select_overload(&tivars::TIVarFile::saveVarToFile)) 530 | 531 | .class_function("loadFromFile", &tivars::TIVarFile::loadFromFile) 532 | .class_function("createNew", select_overload(&tivars::TIVarFile::createNew)) 533 | .class_function("createNew", select_overload(&tivars::TIVarFile::createNew)) 534 | .class_function("createNew", select_overload(&tivars::TIVarFile::createNew)) 535 | ; 536 | } 537 | #endif 538 | -------------------------------------------------------------------------------- /src/TIVarFile.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #ifndef TIVARS_LIB_CPP_TIVARFILE_H 9 | #define TIVARS_LIB_CPP_TIVARFILE_H 10 | 11 | #include "CommonTypes.h" 12 | #include "BinaryFile.h" 13 | #include "TIVarType.h" 14 | #include "TIModel.h" 15 | 16 | namespace tivars 17 | { 18 | 19 | class TIVarFile : public BinaryFile 20 | { 21 | public: 22 | // For the record, 83+ = 4, 84+ = 10, 82A = 11, 84+CSE = 15, CE = 19, 84+T = 27... 23 | static const constexpr uint8_t OWNER_PID_NONE = 0; 24 | 25 | struct var_header_t 26 | { 27 | uint8_t signature[8] = {}; 28 | uint8_t sig_extra[2] = { 0x1A, 0x0A }; // this never actually changes 29 | uint8_t ownerPID = OWNER_PID_NONE; // informational - may reflect what's on the version field in the var entries 30 | uint8_t comment[42] = {}; 31 | uint16_t entries_len = 0; 32 | }; 33 | 34 | struct var_entry_t 35 | { 36 | uint16_t meta_length = 0; // byte count of the next 3 or 5 fields (== 11 or 13) depending on calcFlags, see below 37 | uint16_t data_length = 0; 38 | uint8_t typeID = 0; 39 | uint8_t varname[8] = {}; 40 | uint8_t version = 0; // present only if calcFlags >= TIFeatureFlags::hasFlash 41 | uint8_t archivedFlag = 0; // present only if calcFlags >= TIFeatureFlags::hasFlash 42 | uint16_t data_length2 = 0; // same as data_length 43 | data_t data; 44 | 45 | TIVarType _type{}; // has "full" type information (useful for appvars that have a "sub"type) 46 | void setArchived(bool archived) { archivedFlag = (archived ? 0x80 : 0); } 47 | void setVarName(const std::string& name); 48 | void determineFullType(void); 49 | }; 50 | 51 | // comes right after the var header, so == its size 52 | static const constexpr uint16_t firstVarEntryOffset = sizeof(var_header_t::signature) + sizeof(var_header_t::sig_extra) + sizeof(var_header_t::ownerPID) + sizeof(var_header_t::comment) + sizeof(var_header_t::entries_len); 53 | static_assert(firstVarEntryOffset == 55, "firstVarEntryOffset size needs to be 55"); 54 | 55 | static const constexpr uint16_t varEntryOldLength = sizeof(var_entry_t::data_length) + sizeof(var_entry_t::typeID) + sizeof(var_entry_t::varname); 56 | static_assert(varEntryOldLength == 0x0B, "varEntryOldLength size needs to be 11"); 57 | 58 | static const constexpr uint16_t varEntryNewLength = varEntryOldLength + sizeof(var_entry_t::version) + sizeof(var_entry_t::archivedFlag); 59 | static_assert(varEntryNewLength == 0x0D, "varEntryNewLength size needs to be 13"); 60 | 61 | TIVarFile() = delete; 62 | 63 | const var_header_t& getHeader() const { return header; } 64 | const std::vector& getVarEntries() const { return entries; } 65 | uint16_t getInstanceChecksum() const { return computedChecksum; } 66 | bool hasMultipleEntries() const { return entries.size() > 1; } 67 | 68 | static TIVarFile loadFromFile(const std::string& filePath); 69 | 70 | static TIVarFile createNew(const TIVarType& type, const std::string& name, const TIModel& model); 71 | static TIVarFile createNew(const TIVarType& type, const std::string& name); 72 | static TIVarFile createNew(const TIVarType& type); 73 | 74 | // Additional overloads for easier Emscripten usage 75 | #ifdef __EMSCRIPTEN__ 76 | static TIVarFile createNew(const std::string& type, const std::string& name, const std::string& model) 77 | { 78 | return TIVarFile{TIVarType{type}, name, TIModel{model}}; 79 | } 80 | static TIVarFile createNew(const std::string& type, const std::string& name) 81 | { 82 | return createNew(TIVarType{type}, name); 83 | } 84 | static TIVarFile createNew(const std::string& type) 85 | { 86 | return createNew(TIVarType{type}); 87 | } 88 | #endif 89 | 90 | uint16_t getChecksumValueFromFile(); 91 | 92 | void setContentFromData(const data_t& data, uint16_t entryIdx); 93 | void setContentFromData(const data_t& data); 94 | 95 | void setContentFromString(const std::string& str, const options_t& options, uint16_t entryIdx); 96 | void setContentFromString(const std::string& str, const options_t& options); 97 | void setContentFromString(const std::string& str); 98 | 99 | void setCalcModel(const TIModel& model); 100 | 101 | void setVarName(const std::string& name, uint16_t entryIdx); 102 | void setVarName(const std::string& name); 103 | void setArchived(bool flag, uint16_t entryIdx); 104 | void setArchived(bool flag); 105 | 106 | bool isCorrupt() const; 107 | 108 | data_t getRawContent(uint16_t entryIdx); 109 | data_t getRawContent(); 110 | 111 | std::string getRawContentHexStr(); 112 | 113 | std::string getReadableContent(const options_t& options, uint16_t entryIdx); 114 | std::string getReadableContent(const options_t& options); 115 | std::string getReadableContent(); 116 | 117 | std::string saveVarToFile(std::string directory, std::string name); 118 | std::string saveVarToFile(std::string path); 119 | std::string saveVarToFile(); 120 | 121 | private: 122 | TIVarFile(const TIVarType& type, const std::string& name, const TIModel& model); 123 | explicit TIVarFile(const std::string& filePath); 124 | 125 | void refreshMetadataFields(); 126 | void checkVarEntriesVersionCompat() const; 127 | 128 | void makeHeaderFromFile(); 129 | void makeVarEntriesFromFile(); 130 | 131 | uint16_t computeChecksumFromInstanceData(); 132 | uint16_t computeChecksumFromFileData(); 133 | 134 | // Extends BinaryFile. 135 | uint16_t get_two_bytes_swapped() 136 | { 137 | uint8_t low = this->get_raw_byte(); 138 | uint8_t high = this->get_raw_byte(); 139 | return low + (high << 8); 140 | } 141 | 142 | var_header_t header; 143 | std::vector entries; 144 | TIModel calcModel; 145 | uint16_t computedChecksum = 0; 146 | uint16_t fileChecksum = 0; 147 | bool fromFile = false; 148 | bool corrupt = false; 149 | 150 | data_t make_bin_data(); 151 | 152 | }; 153 | } 154 | 155 | #endif //TIVARS_LIB_CPP_TIVARFILE_H 156 | -------------------------------------------------------------------------------- /src/TIVarType.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "TIVarType.h" 9 | #include "TIVarTypes.h" 10 | 11 | #include 12 | 13 | namespace tivars 14 | { 15 | /*** "Constructors" ***/ 16 | 17 | TIVarType::TIVarType(uint8_t id) 18 | { 19 | if (TIVarTypes::isValidID(id)) 20 | { 21 | *this = TIVarTypes::fromId(id); 22 | } else { 23 | throw std::invalid_argument("Invalid type ID"); 24 | } 25 | } 26 | 27 | TIVarType::TIVarType(const std::string& name) 28 | { 29 | if (TIVarTypes::isValidName(name)) 30 | { 31 | *this = TIVarTypes::fromName(name); 32 | } else { 33 | throw std::invalid_argument("Invalid type name"); 34 | } 35 | } 36 | } 37 | 38 | #ifdef __EMSCRIPTEN__ 39 | #include 40 | using namespace emscripten; 41 | EMSCRIPTEN_BINDINGS(_tivartype) { 42 | class_("TIVarType") 43 | .constructor<>() 44 | .constructor() 45 | .constructor&, const tivars::TypeHandlers::TypeHandlersTuple&>() 46 | 47 | .function("getId" , &tivars::TIVarType::getId) 48 | .function("getName" , &tivars::TIVarType::getName) 49 | .function("getExts" , &tivars::TIVarType::getExts) 50 | .function("getHandlers", &tivars::TIVarType::getHandlers) 51 | 52 | .class_function("createFromID", &tivars::TIVarType::createFromID) 53 | .class_function("createFromName", &tivars::TIVarType::createFromName) 54 | ; 55 | } 56 | #endif 57 | -------------------------------------------------------------------------------- /src/TIVarType.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #ifndef TIVARTYPE_H 9 | #define TIVARTYPE_H 10 | 11 | #include "CommonTypes.h" 12 | #include "TypeHandlers/TypeHandlers.h" 13 | 14 | namespace tivars 15 | { 16 | 17 | class TIVarType 18 | { 19 | 20 | public: 21 | TIVarType() = default; 22 | 23 | explicit TIVarType(uint8_t id); 24 | TIVarType(const std::string& name); 25 | TIVarType(const char* name) { *this = TIVarType{std::string{name}}; } 26 | 27 | static TIVarType createFromID(uint8_t id) { return TIVarType{id}; } 28 | static TIVarType createFromName(const std::string& name) { return TIVarType{name}; } 29 | 30 | TIVarType(int id, const std::string& name, const std::vector& exts, const TypeHandlers::TypeHandlersTuple& handlers) 31 | : id(id), name(name), exts(exts), handlers(handlers) 32 | {} 33 | 34 | ~TIVarType() = default; 35 | 36 | /* Getters */ 37 | int getId() const { return this->id; } 38 | std::string getName() const { return this->name; } 39 | const std::vector& getExts() const { return this->exts; } 40 | TypeHandlers::TypeHandlersTuple getHandlers() const { return this->handlers; }; 41 | 42 | private: 43 | int id = -1; 44 | std::string name = "Unknown"; 45 | std::vector exts; 46 | TypeHandlers::TypeHandlersTuple handlers; 47 | 48 | }; 49 | 50 | } 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /src/TIVarTypes.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2022 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "TIVarTypes.h" 9 | #include "TIVarFile.h" 10 | 11 | namespace tivars 12 | { 13 | using namespace TypeHandlers; 14 | 15 | namespace 16 | { 17 | std::unordered_map types; 18 | const TIVarType unknownVarType{}; 19 | } 20 | 21 | const std::unordered_map& TIVarTypes::all() 22 | { 23 | return types; 24 | } 25 | 26 | TIVarType TIVarTypes::fromName(const std::string& name) 27 | { 28 | return isValidName(name) ? types[name] : unknownVarType; 29 | } 30 | 31 | TIVarType TIVarTypes::fromId(uint8_t id) 32 | { 33 | return isValidID(id) ? types[std::to_string(id)] : unknownVarType; 34 | } 35 | 36 | // Wrap the makeDataFromStr function by one that adds the type/subtype in the options 37 | // Ideally, the handlers would parse the string and select the correct handler to dispatch... 38 | #define GenericHandlerTuple(which, type) TypeHandlersTuple{ \ 39 | [](const std::string& str, const options_t& options, const TIVarFile* _ctx) -> data_t { \ 40 | options_t options_withType = options; \ 41 | options_withType["_type"] = type; \ 42 | return (TypeHandlers::TH_Generic##which::makeDataFromString)(str, options_withType, _ctx); \ 43 | }, \ 44 | &(TypeHandlers::TH_Generic##which::makeStringFromData), \ 45 | &(TypeHandlers::TH_Generic##which::getMinVersionFromData), \ 46 | } 47 | 48 | void TIVarTypes::insertType(const std::string& name, int id, const std::vector& exts, const TypeHandlersTuple& handlers) 49 | { 50 | const TIVarType varType(id, name, exts, handlers); 51 | types[name] = varType; 52 | const std::string id_str = std::to_string(id); 53 | if (types.count(id_str) == 0) 54 | { 55 | types[id_str] = varType; 56 | } 57 | for (const std::string& ext : exts) 58 | { 59 | if (!ext.empty() && !types.count(ext)) 60 | { 61 | types[ext] = varType; 62 | } 63 | } 64 | } 65 | 66 | // 82+/83+/84+ are grouped since only the clock is the difference, and it doesn't have an actual varType. 67 | // For number vartypes, Real and Complex are the generic handlers we use, they'll dispatch to specific ones. 68 | void TIVarTypes::initTIVarTypesArray() // order: 82 83 82A 84+T 82+/83+ 84+C 84+CE 83PCE 82AEP 69 | // 84+ 84+CE-T 70 | { 71 | const std::string _; 72 | 73 | /* Standard types */ 74 | insertType("Real", 0x00, {"82n", "83n", "8xn", "8xn", "8xn", "8xn", "8xn", "8xn", "8xn"}, GenericHandlerTuple(Real, 0x00) ); 75 | insertType("RealList", 0x01, {"82l", "83l", "8xl", "8xl", "8xl", "8xl", "8xl", "8xl", "8xl"}, GenericHandlerTuple(List, 0x00) ); 76 | insertType("Matrix", 0x02, {"82m", "83m", "8xm", "8xm", "8xm", "8xm", "8xm", "8xm", "8xm"}, SpecificHandlerTuple(TH_Matrix) ); 77 | insertType("Equation", 0x03, {"82y", "83y", "8xy", "8xy", "8xy", "8xy", "8xy", "8xy", "8xy"}, SpecificHandlerTuple(TH_Tokenized) ); 78 | insertType("String", 0x04, {"82s", "83s", "8xs", "8xs", "8xs", "8xs", "8xs", "8xs", "8xs"}, SpecificHandlerTuple(TH_Tokenized) ); 79 | insertType("Program", 0x05, {"82p", "83p", "8xp", "8xp", "8xp", "8xp", "8xp", "8xp", "8xp"}, SpecificHandlerTuple(TH_Tokenized) ); 80 | insertType("ProtectedProgram", 0x06, {"82p", "83p", "8xp", "8xp", "8xp", "8xp", "8xp", "8xp", "8xp"}, SpecificHandlerTuple(TH_Tokenized) ); 81 | insertType("Picture", 0x07, {"82i", "83i", "8xi", "8xi", "8xi", "8ci", "8ci", "8ci", "8ci"}); 82 | insertType("GraphDataBase", 0x08, {"82d", "83d", "8xd", "8xd", "8xd", "8xd", "8xd", "8xd", "8xd"}, SpecificHandlerTuple(TH_GDB) ); 83 | // insertType("Unknown", 0x09, { _ , _ , _ , _ , _ , _ , _ , _ , _ }); 84 | // insertType("UnknownEqu", 0x0A, { _ , _ , _ , _ , _ , _ , _ , _ , _ }); 85 | insertType("SmartEquation", 0x0B, {"82y", "83y", "8xy", "8xy", "8xy", "8xy", "8xy", "8xy", "8xy"}, SpecificHandlerTuple(TH_Tokenized) ); // aka "New Equation" 86 | insertType("Complex", 0x0C, { _ , "83c", "8xc", "8xc", "8xc", "8xc", "8xc", "8xc", "8xc"}, GenericHandlerTuple(Complex, 0x0C) ); 87 | insertType("ComplexList", 0x0D, { _ , "83l", "8xl", "8xl", "8xl", "8xl", "8xl", "8xl", "8xl"}, GenericHandlerTuple(List, 0x0C) ); 88 | // insertType("Undef", 0x0E, { _ , _ , _ , _ , _ , _ , _ , _ , _ }); 89 | insertType("WindowSettings", 0x0F, {"82w", "83w", "8xw", "8xw", "8xw", "8xw", "8xw", "8xw", "8xw"}); 90 | insertType("RecallWindow", 0x10, {"82z", "83z", "8xz", "8xz", "8xz", "8xz", "8xz", "8xz", "8xz"}); 91 | insertType("TableRange", 0x11, {"82t", "83t", "8xt", "8xt", "8xt", "8xt", "8xt", "8xt", "8xt"}); 92 | insertType("ScreenImage", 0x12, { _ , _ , _ , _ , _ , _ , _ , _ , _ }); 93 | insertType("Backup", 0x13, {"82b", "83b", "8xb", _ , "8xb", "8cb", _ , _ , _ }); 94 | insertType("App", 0x14, { _ , _ , _ , _ , _ , _ , _ , _ , _ }); 95 | insertType("AppVar", 0x15, { _ , _ , "8xv", "8xv", "8xv", "8xv", "8xv", "8xv", "8xv"}, GenericHandlerTuple(AppVar, 0x15) ); 96 | insertType("PythonAppVar", 0x15, { _ , _ , _ , _ , _ , _ , "8xv", "8xv", "8xv"}, SpecificHandlerTuple(STH_PythonAppVar) ); 97 | insertType("TemporaryItem", 0x16, { _ , _ , _ , _ , _ , _ , _ , _ , _ }); 98 | insertType("GroupObject", 0x17, {"82g", "83g", "8xg", "8xg", "8xg", "8xg", "8cg", "8cg", "8cg"}); 99 | insertType("RealFraction", 0x18, { _ , _ , _ , _ , "8xn", "8xn", "8xn", "8xn", "8xn"}, GenericHandlerTuple(Real, 0x18) ); 100 | insertType("MixedFraction", 0x19, { _ , _ , _ , _ , _ , _ , _ , _ , _ }); 101 | insertType("Image", 0x1A, { _ , _ , _ , _ , _ , _ , "8ca", "8ca", "8ca"}); 102 | 103 | /* Exact values (TI-83 Premium CE [Edition Python] and TI-82 Advanced Edition Python) */ 104 | /* See https://docs.google.com/document/d/1P_OUbnZMZFg8zuOPJHAx34EnwxcQZ8HER9hPeOQ_dtI and especially this lib's implementation */ 105 | insertType("ExactComplexFrac", 0x1B, { _ , _ , _ , _ , _ , _ , _ , "8xc", "8xc"}, GenericHandlerTuple(Complex, 0x1B) ); 106 | insertType("ExactRealRadical", 0x1C, { _ , _ , _ , _ , _ , _ , _ , "8xn", "8xn"}, GenericHandlerTuple(Real, 0x1C) ); 107 | insertType("ExactComplexRadical", 0x1D, { _ , _ , _ , _ , _ , _ , _ , "8xc", "8xc"}, GenericHandlerTuple(Complex, 0x1D) ); 108 | insertType("ExactComplexPi", 0x1E, { _ , _ , _ , _ , _ , _ , _ , "8xc", "8xc"}, GenericHandlerTuple(Complex, 0x1E) ); 109 | insertType("ExactComplexPiFrac", 0x1F, { _ , _ , _ , _ , _ , _ , _ , "8xc", "8xc"}, GenericHandlerTuple(Complex, 0x1F) ); 110 | insertType("ExactRealPi", 0x20, { _ , _ , _ , _ , _ , _ , _ , "8xn", "8xn"}, GenericHandlerTuple(Real, 0x20) ); 111 | insertType("ExactRealPiFrac", 0x21, { _ , _ , _ , _ , _ , _ , _ , "8xn", "8xn"}, GenericHandlerTuple(Real, 0x21) ); 112 | 113 | /* System/Flash-related things */ 114 | // 0x22 - IDList (68k calcs) 115 | insertType("OperatingSystem", 0x23, { _ , _ , "82u", "8xu", "8xu", "8cu", "8eu", "8pu", "8yu"}); 116 | insertType("FlashApp", 0x24, { _ , _ , _ , _ , "8xk", "8ck", "8ek", "8ek", _ }); 117 | insertType("Certificate", 0x25, { _ , _ , _ , _ , "8xq", "8cq", _ , _ , _ }); 118 | insertType("AppIDList", 0x26, { _ , _ , _ , _ , _ , _ , _ , _ , _ }); 119 | insertType("CertificateMemory", 0x27, { _ , _ , _ , _ , _ , _ , _ , _ , _ }); 120 | insertType("UnitCertificate", 0x28, { _ , _ , _ , _ , _ , _ , _ , _ , _ }); 121 | insertType("Clock", 0x29, { _ , _ , _ , _ , _ , _ , _ , _ , _ }); 122 | insertType("FlashLicense", 0x3E, { _ , _ , _ , _ , _ , _ , _ , _ , _ }); 123 | } 124 | 125 | bool TIVarTypes::isValidID(uint8_t id) 126 | { 127 | return types.count(std::to_string(id)); 128 | } 129 | 130 | bool TIVarTypes::isValidName(const std::string& name) 131 | { 132 | return (!name.empty() && types.count(name)); 133 | } 134 | } 135 | 136 | //TIVarTypes::initTIVarTypesArray(); 137 | -------------------------------------------------------------------------------- /src/TIVarTypes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #ifndef TIVARTYPES_H 9 | #define TIVARTYPES_H 10 | 11 | #include "CommonTypes.h" 12 | #include "TIVarType.h" 13 | #include 14 | 15 | namespace tivars 16 | { 17 | class TIVarTypes 18 | { 19 | 20 | public: 21 | static TIVarType fromName(const std::string& name); 22 | static TIVarType fromId(uint8_t id); 23 | 24 | static void initTIVarTypesArray(); 25 | static const std::unordered_map& all(); 26 | 27 | static bool isValidName(const std::string& name); 28 | static bool isValidID(uint8_t id); 29 | 30 | private: 31 | static void insertType(const std::string& name, int id, const std::vector& exts, const TypeHandlers::TypeHandlersTuple& handlers = { &TypeHandlers::DummyHandler::makeDataFromString, &TypeHandlers::DummyHandler::makeStringFromData, &TypeHandlers::DummyHandler::getMinVersionFromData }); 32 | 33 | }; 34 | } 35 | 36 | #endif //TIVARTYPES_H 37 | -------------------------------------------------------------------------------- /src/TypeHandlers/DummyHandler.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "../CommonTypes.h" 9 | #include "TypeHandlers.h" 10 | 11 | #include 12 | 13 | namespace tivars::TypeHandlers 14 | { 15 | data_t DummyHandler::makeDataFromString(const std::string& str, const options_t& options, const TIVarFile* _ctx) 16 | { 17 | (void)str; 18 | (void)options; 19 | (void)_ctx; 20 | throw std::runtime_error("This type is not supported / implemented (yet?)"); 21 | } 22 | 23 | std::string DummyHandler::makeStringFromData(const data_t& data, const options_t& options, const TIVarFile* _ctx) 24 | { 25 | (void)data; 26 | (void)options; 27 | (void)_ctx; 28 | throw std::runtime_error("This type is not supported / implemented (yet?)"); 29 | } 30 | 31 | uint8_t DummyHandler::getMinVersionFromData(const data_t& data) 32 | { 33 | (void)data; 34 | throw std::runtime_error("This type is not supported / implemented (yet?)"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/TypeHandlers/STH_DataAppVar.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "TypeHandlers.h" 9 | #include "../tivarslib_utils.h" 10 | 11 | #include 12 | #include 13 | 14 | namespace tivars::TypeHandlers 15 | { 16 | data_t STH_DataAppVar::makeDataFromString(const std::string& str, const options_t& options, const TIVarFile* _ctx) 17 | { 18 | (void)options; 19 | (void)_ctx; 20 | 21 | const bool formatOk = regex_match(str, std::regex("^([0-9a-fA-F]{2})+$")); 22 | 23 | const size_t length = str.size(); 24 | const size_t bytes = length / 2; 25 | 26 | if (length == 0 || !formatOk || bytes > 0xFFFF) 27 | { 28 | throw std::invalid_argument("Invalid input string. Needs to be a valid hex data block"); 29 | } 30 | 31 | data_t data = { (uint8_t)(bytes & 0xFF), (uint8_t)((bytes >> 8) & 0xFF) }; 32 | 33 | for (size_t i = 0; i < length; i += 2) 34 | { 35 | data.push_back(hexdec(str.substr(i, 2))); 36 | } 37 | 38 | return data; 39 | } 40 | 41 | std::string STH_DataAppVar::makeStringFromData(const data_t& data, const options_t& options, const TIVarFile* _ctx) 42 | { 43 | (void)options; 44 | (void)_ctx; 45 | 46 | const size_t byteCount = data.size(); 47 | if (byteCount < 2) 48 | { 49 | throw std::invalid_argument("Invalid data array. Needs to contain at least 2 bytes"); 50 | } 51 | 52 | const size_t lengthExp = (size_t) ((data[0] & 0xFF) + ((data[1] & 0xFF) << 8)); 53 | const size_t lengthDat = byteCount - 2; 54 | 55 | if (lengthExp != lengthDat) 56 | { 57 | throw std::invalid_argument("Invalid data array. Expected " + std::to_string(lengthExp) + " bytes, got " + std::to_string(lengthDat)); 58 | } 59 | 60 | std::string str; 61 | 62 | for (size_t i=2; i 12 | 13 | namespace tivars::TypeHandlers 14 | { 15 | 16 | data_t STH_ExactFraction::makeDataFromString(const std::string& str, const options_t& options, const TIVarFile* _ctx) 17 | { 18 | (void)options; 19 | (void)_ctx; 20 | 21 | throw std::runtime_error("Unimplemented"); 22 | 23 | if (str.empty() || !is_numeric(str)) 24 | { 25 | throw std::invalid_argument("Invalid input string. Needs to be a valid Exact Real Pi Fraction"); 26 | } 27 | } 28 | 29 | std::string STH_ExactFraction::makeStringFromData(const data_t& data, const options_t& options, const TIVarFile* _ctx) 30 | { 31 | (void)_ctx; 32 | 33 | if (data.size() != dataByteCount) 34 | { 35 | throw std::invalid_argument("Invalid data array. Needs to contain " + std::to_string(dataByteCount) + " bytes"); 36 | } 37 | 38 | return dec2frac(stod(STH_FP::makeStringFromData(data, options))); 39 | } 40 | 41 | uint8_t STH_ExactFraction::getMinVersionFromData(const data_t& data) 42 | { 43 | // handled in TH_GenericXXX 44 | (void)data; 45 | return 0; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/TypeHandlers/STH_ExactFractionPi.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "TypeHandlers.h" 9 | #include "../tivarslib_utils.h" 10 | 11 | #include 12 | 13 | namespace tivars::TypeHandlers 14 | { 15 | 16 | data_t STH_ExactFractionPi::makeDataFromString(const std::string& str, const options_t& options, const TIVarFile* _ctx) 17 | { 18 | (void)options; 19 | (void)_ctx; 20 | 21 | throw std::runtime_error("Unimplemented"); 22 | 23 | if (str.empty() || !is_numeric(str)) 24 | { 25 | throw std::invalid_argument("Invalid input string. Needs to be a valid Exact Real Pi Fraction"); 26 | } 27 | } 28 | 29 | std::string STH_ExactFractionPi::makeStringFromData(const data_t& data, const options_t& options, const TIVarFile* _ctx) 30 | { 31 | (void)_ctx; 32 | 33 | if (data.size() != dataByteCount) 34 | { 35 | throw std::invalid_argument("Invalid data array. Needs to contain " + std::to_string(dataByteCount) + " bytes"); 36 | } 37 | 38 | return dec2frac(stod(STH_FP::makeStringFromData(data, options)), "π"); 39 | } 40 | 41 | uint8_t STH_ExactFractionPi::getMinVersionFromData(const data_t& data) 42 | { 43 | // handled in TH_GenericXXX 44 | (void)data; 45 | return 0; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/TypeHandlers/STH_ExactPi.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "TypeHandlers.h" 9 | #include "../tivarslib_utils.h" 10 | 11 | #include 12 | 13 | namespace tivars::TypeHandlers 14 | { 15 | 16 | data_t STH_ExactPi::makeDataFromString(const std::string& str, const options_t& options, const TIVarFile* _ctx) 17 | { 18 | (void)options; 19 | (void)_ctx; 20 | 21 | throw std::runtime_error("Unimplemented"); 22 | 23 | if (str.empty() || !is_numeric(str)) 24 | { 25 | throw std::invalid_argument("Invalid input string. Needs to be a valid Exact Real Pi"); 26 | } 27 | } 28 | 29 | std::string STH_ExactPi::makeStringFromData(const data_t& data, const options_t& options, const TIVarFile* _ctx) 30 | { 31 | (void)_ctx; 32 | 33 | if (data.size() != dataByteCount) 34 | { 35 | throw std::invalid_argument("Invalid data array. Needs to contain " + std::to_string(dataByteCount) + " bytes"); 36 | } 37 | 38 | return multiple(stoi(STH_FP::makeStringFromData(data, options)), "π"); 39 | } 40 | 41 | uint8_t STH_ExactPi::getMinVersionFromData(const data_t& data) 42 | { 43 | // handled in TH_GenericXXX 44 | (void)data; 45 | return 0; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/TypeHandlers/STH_ExactRadical.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "TypeHandlers.h" 9 | #include "../tivarslib_utils.h" 10 | #include "../TIVarTypes.h" 11 | 12 | #include 13 | #include 14 | 15 | namespace tivars::TypeHandlers 16 | { 17 | 18 | data_t STH_ExactRadical::makeDataFromString(const std::string& str, const options_t& options, const TIVarFile* _ctx) 19 | { 20 | (void)options; 21 | (void)_ctx; 22 | 23 | throw std::runtime_error("Unimplemented"); 24 | 25 | if (str.empty() || !is_numeric(str)) 26 | { 27 | throw std::invalid_argument("Invalid input string. Needs to be a valid Exact Real Radical"); 28 | } 29 | } 30 | 31 | // TODO: handle sign bit? 32 | std::string STH_ExactRadical::makeStringFromData(const data_t& data, const options_t& options, const TIVarFile* _ctx) 33 | { 34 | (void)options; 35 | (void)_ctx; 36 | 37 | if (data.size() != dataByteCount) 38 | { 39 | throw std::invalid_argument("Invalid data array. Needs to contain " + std::to_string(dataByteCount) + " bytes"); 40 | } 41 | 42 | std::string dataStr; 43 | for (size_t i = 0; i < dataByteCount; i++) 44 | { 45 | dataStr += dechex(data[i]); 46 | } 47 | 48 | const auto type = data[0] & ~0x80; // sign bit discarded 49 | if (type != TIVarType{"ExactRealRadical"}.getId() && type != TIVarType{"ExactComplexRadical"}.getId()) 50 | { 51 | throw std::invalid_argument("Invalid data bytes - invalid vartype: " + std::to_string(type)); 52 | } 53 | 54 | const auto variant = hexdec(dataStr.substr(2, 1)); 55 | if (variant > 3) 56 | { 57 | throw std::invalid_argument("Invalid data bytes - unknown type variant: " + std::to_string(variant)); 58 | } 59 | 60 | const std::vector parts = { 61 | (variant == 1 || variant == 3 ? "-" : "") + trimZeros(dataStr.substr(9, 3)), 62 | trimZeros(dataStr.substr(15, 3)), 63 | (variant == 2 || variant == 3 ? "-" : "+") + trimZeros(dataStr.substr(6, 3)), 64 | trimZeros(dataStr.substr(12, 3)), 65 | trimZeros(dataStr.substr(3, 3)) 66 | }; 67 | 68 | std::string str = "(" + parts[0] + "*√(" + parts[1] + ")" + parts[2] + "*√(" + parts[3] + "))/" + parts[4]; 69 | 70 | // Improve final display 71 | str = std::regex_replace(str, std::regex("\\+1\\*"), "+"); str = std::regex_replace(str, std::regex("\\(1\\*"), "("); 72 | str = std::regex_replace(str, std::regex("-1\\*"), "-"); str = std::regex_replace(str, std::regex("\\(-1\\*"), "(-"); 73 | str = std::regex_replace(str, std::regex("\\+-"), "-"); 74 | 75 | return str; 76 | } 77 | 78 | uint8_t STH_ExactRadical::getMinVersionFromData(const data_t& data) 79 | { 80 | // handled in TH_GenericXXX 81 | (void)data; 82 | return 0; 83 | } 84 | } -------------------------------------------------------------------------------- /src/TypeHandlers/STH_FP.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | // TODO : check if the models have different exponent offsets 9 | 10 | #include "TypeHandlers.h" 11 | 12 | #include 13 | 14 | static bool parseSign(std::string::const_iterator &i, const std::string::const_iterator &e) { 15 | bool sign = false; 16 | if (i != e && (*i == '+' || *i == '-')) { 17 | sign = *i++ == '-'; 18 | } 19 | if (i == e) { 20 | throw std::invalid_argument("Unexpected end of string."); 21 | } 22 | return sign; 23 | } 24 | 25 | namespace tivars::TypeHandlers 26 | { 27 | data_t STH_FP::makeDataFromString(const std::string& str, const options_t& options, const TIVarFile* _ctx) 28 | { 29 | (void)options; 30 | (void)_ctx; 31 | 32 | data_t data(dataByteCount); 33 | bool beforePoint = true, noDigits = true, zero = true; 34 | int exponent = 0x7F; 35 | unsigned index = 4; 36 | std::string::const_iterator i = str.begin(); 37 | const std::string::const_iterator e = str.end(); 38 | if (parseSign(i, e)) { 39 | data[0] = 1 << 7; 40 | } 41 | do { 42 | const char c = *i++; 43 | if (c == '.') { 44 | if (!beforePoint) { 45 | throw std::invalid_argument("Extra decimal points."); 46 | } 47 | beforePoint = false; 48 | } else if (c == '0') { 49 | noDigits = false; 50 | if (zero) { 51 | exponent -= !beforePoint; 52 | } else { 53 | exponent += beforePoint; 54 | index += index <= dataByteCount << 1; 55 | } 56 | } else if (c >= '1' && c <= '9') { 57 | noDigits = zero = false; 58 | exponent += beforePoint; 59 | if (index < dataByteCount << 1) { 60 | data[index >> 1] |= (c - '0') << ((~index & 1) << 2); 61 | index++; 62 | } else if (index == dataByteCount << 1) { 63 | if (c >= '5') { 64 | while (true) { 65 | if (--index < 4) { 66 | data[2] = 0x10; 67 | exponent++; 68 | break; 69 | } 70 | if ((data[index >> 1] >> ((~index & 1) << 2) & 0xF) < 9) { 71 | data[index >> 1] += 1 << ((~index & 1) << 2); 72 | break; 73 | } 74 | data[index >> 1] &= 0xF << ((index & 1) << 2); 75 | } 76 | } 77 | index = (dataByteCount << 1) + 1; 78 | } 79 | } else if (c == 'e') { 80 | const bool sign = parseSign(i, e); 81 | int offset = 0; 82 | do { 83 | const char cdigit = *i++; 84 | if (cdigit >= '0' && cdigit <= '9') { 85 | offset *= 10; 86 | offset += cdigit - '0'; 87 | } else { 88 | throw std::invalid_argument("Unexpected character."); 89 | } 90 | } while (i != e); 91 | exponent = sign ? exponent - offset : exponent + offset; 92 | } else { 93 | throw std::invalid_argument("Unexpected character."); 94 | } 95 | } while (i != e); 96 | if (noDigits) { 97 | throw std::invalid_argument("No digits found."); 98 | } 99 | if (exponent < 0x80 - 99 || exponent > 0x80 + 99) { 100 | throw std::invalid_argument("Exponent out of range."); 101 | } 102 | data[1] = zero ? 0x80 : exponent; // TI forces 0x80 as exp for zero values. 103 | return data; 104 | } 105 | 106 | std::string STH_FP::makeStringFromData(const data_t& data, const options_t& options, const TIVarFile* _ctx) 107 | { 108 | bool scientific = false; 109 | (void)options; 110 | (void)_ctx; 111 | 112 | if (data.size() != dataByteCount) 113 | { 114 | throw std::invalid_argument("Invalid data array. Needs to contain " + std::to_string(dataByteCount) + " bytes"); 115 | } 116 | 117 | const bool negative = ((data[0] & 0x80) == 0x80); 118 | if (!data[2]) { 119 | return scientific ? "0e0" : "0"; 120 | } 121 | int exponent = data[1] - 0x80; 122 | 123 | unsigned index = 4, point = 4; 124 | if (exponent < -3 || exponent > 9) { 125 | scientific = true; 126 | } else { 127 | if (exponent < 0) { 128 | index += exponent; 129 | } 130 | point += exponent; 131 | } 132 | char result[25], *i = result; 133 | if (negative) { 134 | *i++ = '-'; 135 | } 136 | do { 137 | const char digit = '0' + (index < 4 ? 0 : data[index >> 1] >> ((~index & 1) << 2) & 0xF); 138 | *i++ = digit <= '9' ? digit : '?'; 139 | if (index == point) { 140 | *i++ = '.'; 141 | } 142 | } while (++index < dataByteCount << 1); 143 | while (--i != result && *i == '0') { 144 | } 145 | if (*i == '.') { 146 | i--; 147 | } 148 | if (scientific) { 149 | *++i = 'e'; 150 | if (exponent < 0) { 151 | exponent = -exponent; 152 | *++i = '-'; 153 | } 154 | bool leading = true; 155 | for (int factor = 100; factor > 1; factor /= 10) { 156 | if (!leading || exponent >= factor) { 157 | *++i = '0' + exponent / factor; 158 | exponent = exponent % factor; 159 | leading = false; 160 | } 161 | } 162 | *++i = '0' + exponent; 163 | } 164 | *++i = '\0'; 165 | return std::string(result); 166 | } 167 | 168 | uint8_t STH_FP::getMinVersionFromData(const data_t& data) 169 | { 170 | // handled in TH_GenericXXX 171 | (void)data; 172 | return 0; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/TypeHandlers/STH_PythonAppVar.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "TypeHandlers.h" 9 | 10 | #include 11 | #include 12 | 13 | // TODO: handle filename stuff 14 | // TODO: handle UTF8 BOM stuff 15 | // TODO: handle weird chars stuff 16 | // TODO: handle full metadata format 17 | 18 | namespace tivars::TypeHandlers 19 | { 20 | const constexpr char STH_PythonAppVar::ID_CODE[]; 21 | const constexpr char STH_PythonAppVar::ID_SCRIPT[]; 22 | 23 | data_t STH_PythonAppVar::makeDataFromString(const std::string& str, const options_t& options, const TIVarFile* _ctx) 24 | { 25 | (void)options; 26 | (void)_ctx; 27 | 28 | const size_t length = str.size() + 4 + 1; 29 | 30 | if (length > 65490) // todo : compute actual max size 31 | { 32 | throw std::invalid_argument("Invalid input string. Too big?"); 33 | } 34 | 35 | data_t data(2 + length); 36 | data[0] = (uint8_t)(length & 0xFF); 37 | data[1] = (uint8_t)((length >> 8) & 0xFF); 38 | memcpy(&data[2], &ID_CODE, sizeof(ID_CODE)); 39 | memcpy(&data[2+sizeof(ID_CODE)], &str[0], str.size()); 40 | 41 | return data; 42 | } 43 | 44 | std::string STH_PythonAppVar::makeStringFromData(const data_t& data, const options_t& options, const TIVarFile* _ctx) 45 | { 46 | (void)options; 47 | (void)_ctx; 48 | 49 | const size_t byteCount = data.size(); 50 | const size_t lengthDat = byteCount - 2; 51 | 52 | if (byteCount < 2 + sizeof(ID_CODE)) 53 | { 54 | throw std::invalid_argument("Invalid data array. Need at least 6 bytes, got " + std::to_string(lengthDat)); 55 | } 56 | 57 | const size_t lengthExp = (size_t) ((data[0] & 0xFF) + ((data[1] & 0xFF) << 8)); 58 | 59 | if (lengthExp != lengthDat) 60 | { 61 | throw std::invalid_argument("Invalid data array. Expected " + std::to_string(lengthExp) + " bytes, got " + std::to_string(lengthDat)); 62 | } 63 | 64 | if (memcmp(ID_CODE, &(data[2]), strlen(ID_CODE)) != 0 65 | && memcmp(ID_SCRIPT, &(data[2]), strlen(ID_SCRIPT)) != 0) 66 | { 67 | throw std::invalid_argument("Invalid data array. Magic header 'PYCD' or 'PYSC' not found"); 68 | } 69 | 70 | // We skip the file name field if it's present 71 | // data[6] indicates its len. If it's > 0, there is an extra byte (always == 1 ?). 72 | const size_t scriptOffset = data[6] + 1u; 73 | 74 | return std::string(data.begin() + 6 + scriptOffset, data.end()); 75 | } 76 | 77 | uint8_t STH_PythonAppVar::getMinVersionFromData(const data_t& data) 78 | { 79 | (void)data; 80 | return 0; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/TypeHandlers/TH_GDB.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2023 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "TypeHandlers.h" 9 | #include "../tivarslib_utils.h" 10 | 11 | #include 12 | #include 13 | 14 | #if defined(TH_GDB_SUPPORT) || defined(__cpp_lib_variant) 15 | 16 | #include "../json.hpp" 17 | using json = nlohmann::ordered_json; 18 | 19 | #include 20 | #include 21 | 22 | namespace 23 | { 24 | u8enum(OSColor) { Blue = 1, Red, Black, Magenta, Green, Orange, Brown, Navy, LtBlue, Yellow, White, LtGray, MedGray, Gray, DarkGray, Off }; 25 | NLOHMANN_JSON_SERIALIZE_ENUM(OSColor, { { Blue, "Blue" }, { Red, "Red" }, { Black, "Black" }, { Magenta, "Magenta" }, { Green, "Green" }, { Orange, "Orange" }, 26 | { Brown, "Brown" }, { Navy, "Navy" }, { LtBlue, "LtBlue" }, { Yellow, "Yellow" }, { White, "White" }, { LtGray, "LtGray" }, 27 | { MedGray, "MedGray" }, { Gray, "Gray" }, { DarkGray, "DarkGray" }, { Off, "Off" } }) 28 | 29 | u8enum(GlobalLineStyle) { Thick, DotThick, Thin, DotThin }; 30 | NLOHMANN_JSON_SERIALIZE_ENUM(GlobalLineStyle, { { Thick, "Thick" }, { DotThick, "DotThick" }, { Thin, "Thin" }, { DotThin, "DotThin" } }) 31 | 32 | u8enum(GraphMode) { Function = 16, Polar = 32, Parametric = 64, Sequence = 128 }; 33 | NLOHMANN_JSON_SERIALIZE_ENUM(GraphMode, { { Function, "Function" }, { Polar, "Polar" }, { Parametric, "Parametric" }, { Sequence, "Sequence" } }) 34 | 35 | u8enum(GraphStyle) { SolidLine, ThickLine, ShadeAbove, ShadeBelow, Trace, Animate, DottedLine, DotThin_ }; 36 | NLOHMANN_JSON_SERIALIZE_ENUM(GraphStyle, { { SolidLine, "SolidLine" }, { ThickLine, "ThickLine" }, { ShadeAbove, "ShadeAbove" }, { ShadeBelow, "ShadeBelow" }, 37 | { Trace, "Trace" }, { Animate, "Animate" }, { DottedLine, "DottedLine" }, { DotThin_, "Dot-Thin" } }) 38 | 39 | struct FormatSettings 40 | { 41 | u8enum(Style) { Connected, Dot } style : 1 = Connected; 42 | u8enum(Tracing) { Sequential, Simul } tracing : 1 = Sequential; 43 | u8enum(Grid) { GridOff, GridOn } grid : 1 = GridOff; 44 | u8enum(CoordsType) { RectGC, PolarGC } coordsType : 1 = RectGC; 45 | u8enum(Coords) { CoordOn, CoordOff } coords : 1 = CoordOn; 46 | u8enum(Axes) { AxesOn, AxesOff } axes : 1 = AxesOn; 47 | u8enum(Label) { LabelOff, LabelOn } label : 1 = LabelOff; 48 | u8enum(GridType) { GridDot, GridLine } gridType : 1 = GridDot; 49 | }; 50 | static_assert(sizeof(FormatSettings) == 1); 51 | NLOHMANN_JSON_SERIALIZE_ENUM(FormatSettings::Style, { {FormatSettings::Connected, "Connected"}, {FormatSettings::Dot, "Dot"} }) 52 | NLOHMANN_JSON_SERIALIZE_ENUM(FormatSettings::Tracing, { {FormatSettings::Sequential, "Sequential"}, {FormatSettings::Simul, "Simul"} }) 53 | NLOHMANN_JSON_SERIALIZE_ENUM(FormatSettings::Grid, { {FormatSettings::GridOff, "GridOff"}, {FormatSettings::GridOn, "GridOn"} }) 54 | NLOHMANN_JSON_SERIALIZE_ENUM(FormatSettings::CoordsType, { {FormatSettings::RectGC, "RectGC"}, {FormatSettings::PolarGC, "PolarGC"} }) 55 | NLOHMANN_JSON_SERIALIZE_ENUM(FormatSettings::Coords, { {FormatSettings::CoordOn, "CoordOn"}, {FormatSettings::CoordOff, "CoordOff"} }) 56 | NLOHMANN_JSON_SERIALIZE_ENUM(FormatSettings::Axes, { {FormatSettings::AxesOn, "AxesOn"}, {FormatSettings::AxesOff, "AxesOff"} }) 57 | NLOHMANN_JSON_SERIALIZE_ENUM(FormatSettings::Label, { {FormatSettings::LabelOff, "LabelOff"}, {FormatSettings::LabelOn, "LabelOn"} }) 58 | NLOHMANN_JSON_SERIALIZE_ENUM(FormatSettings::GridType, { {FormatSettings::GridDot, "GridDot"}, {FormatSettings::GridLine, "GridLine"} }) 59 | 60 | struct SeqSettings 61 | { 62 | u8enum(Mode) { Time = 0, Web = 1, WebVert = 2, SeqUV = 4, SeqVW = 8, SeqUW = 16 } mode : 5 = Time; 63 | uint8_t _ : 3 = 0; // We're not sure what this is, and it's also unused on the CE apparently. Sometimes it's set to 0b001. 64 | }; 65 | static_assert(sizeof(SeqSettings) == 1); 66 | NLOHMANN_JSON_SERIALIZE_ENUM(SeqSettings::Mode, {{ SeqSettings::Time, "Time" }, { SeqSettings::Web, "Web" }, { SeqSettings::WebVert, "WebVert" }, { SeqSettings::SeqUV, "SeqUV" }, { SeqSettings::SeqVW, "SeqVW" }, { SeqSettings::SeqUW, "SeqUW" }}) 67 | 68 | struct ExtModeSettings 69 | { 70 | u8enum(Expr) { ExprOn, ExprOff } expr : 1 = ExprOn; 71 | u8enum(SeqMode) { SEQ_n, SEQ_np1, SEQ_np2 } seqMode : 2 = SEQ_n; 72 | uint8_t _ : 5 = 0; 73 | }; 74 | static_assert(sizeof(ExtModeSettings) == 1); 75 | NLOHMANN_JSON_SERIALIZE_ENUM(ExtModeSettings::SeqMode, {{ ExtModeSettings::SEQ_n, "SEQ(n)" }, { ExtModeSettings::SEQ_np1, "SEQ(n+1)" }, { ExtModeSettings::SEQ_np2, "SEQ(n+2)" } }) 76 | 77 | struct FuncFlags 78 | { 79 | uint8_t unk1 : 3 = 0b011; 80 | uint8_t unk2 : 2 = 0; 81 | bool selected : 1 = false; 82 | bool wasUsedForGraph : 1 = false; 83 | bool linkTransfer : 1 = false; 84 | }; 85 | static_assert(sizeof(FuncFlags) == 1); 86 | 87 | struct EquDefWrapper 88 | { 89 | FuncFlags flags{}; 90 | std::string expr; 91 | }; 92 | void to_json(json& j, const EquDefWrapper& fdw, const GraphStyle& graphStyle, const OSColor* color) { 93 | j["style"] = graphStyle; 94 | if (color) { 95 | j["color"] = *color; 96 | } 97 | j["flags"] = { 98 | { "selected", fdw.flags.selected }, 99 | { "wasUsedForGraph", fdw.flags.wasUsedForGraph }, 100 | { "linkTransfer", fdw.flags.linkTransfer } 101 | }; 102 | j["expr"] = fdw.expr; 103 | } 104 | void from_json(const json& j, EquDefWrapper& fdw, GraphStyle& graphStyle, OSColor* color) { 105 | try { graphStyle = j["style"]; } catch (...) {} 106 | try { if (color && j.contains("color")) { *color = j["color"]; } } catch (...) {} 107 | try { fdw.flags.selected = j["flags"]["selected"]; } catch (...) {} 108 | try { fdw.flags.selected = j["flags"]["selected"]; } catch (...) {} 109 | try { fdw.flags.wasUsedForGraph = j["flags"]["wasUsedForGraph"]; } catch (...) {} 110 | try { fdw.flags.linkTransfer = j["flags"]["linkTransfer"]; } catch (...) {} 111 | try { fdw.expr = j["expr"]; } catch (...) {} 112 | } 113 | 114 | class TIReal 115 | { 116 | public: 117 | TIReal() = default; 118 | ~TIReal() = default; 119 | TIReal(int i) : str(std::to_string(i)) {} 120 | TIReal(const char* s) : str(s) {} 121 | TIReal(std::string s) : str(std::move(s)) {} 122 | operator const std::string&() const { return str; } 123 | 124 | std::string str; // in order to keep the precision... in the json, can be a number for convenience if precision is enough. 125 | }; 126 | void to_json(json& j, const TIReal& real) { 127 | if (real.str.size() <= 6) { j = std::stod(real.str); } else { j = real.str; } 128 | } 129 | void from_json(const json& j, TIReal& real) { 130 | if (j.is_number()) 131 | { 132 | std::ostringstream out; 133 | out.precision(15); 134 | out << j.get(); 135 | real = std::move(out).str(); 136 | } else if (j.is_string()) { 137 | real = j.get(); 138 | } else { 139 | throw std::runtime_error("bad type, expected string or number"); 140 | } 141 | } 142 | 143 | struct GlobalWindowSettings 144 | { 145 | TIReal Xmin = -10, Xmax = 10, Xscl = 1; 146 | TIReal Ymin = -10, Ymax = 10, Yscl = 1; 147 | }; 148 | 149 | struct ExtSettings2 150 | { 151 | u8enum(DetectAsymptotes) { DetectAsymptotesOn, DetectAsymptotesOff } detectAsymptotes : 1 = DetectAsymptotesOn; 152 | uint8_t unk1 : 6 = 0; 153 | uint8_t unk2 : 1 = 0; // we have seen 1 here sometimes, but it doesn't appear to be used anywhere. 154 | }; 155 | static_assert(sizeof(ExtSettings2) == 1); 156 | 157 | struct Other84CGlobalSettings 158 | { 159 | OSColor gridColor = MedGray; 160 | OSColor axesColor = Black; 161 | GlobalLineStyle globalLineStyle = Thick; 162 | uint8_t borderColor : 3 = 1; // 1,2,3,4 ; corresponds approximately to: LightGray, Green, LightBlue, White 163 | ExtSettings2 extSettings2{}; 164 | }; 165 | static_assert(sizeof(Other84CGlobalSettings) == 5); 166 | 167 | #define STR_2_PI "6.283185307" 168 | #define STR_PI_1_24TH "0.13089969389957" 169 | 170 | struct FunctionData 171 | { 172 | std::pair settings[1] = { {"Xres",1} }; 173 | std::pair styles[10] = { {"Y1",{}}, {"Y2",{}}, {"Y3",{}}, {"Y4",{}}, {"Y5",{}}, 174 | {"Y6",{}}, {"Y7",{}}, {"Y8",{}}, {"Y9",{}}, {"Y0",{}} }; 175 | std::pair equations[10] = { {"Y1",{}}, {"Y2",{}}, {"Y3",{}}, {"Y4",{}}, {"Y5",{}}, 176 | {"Y6",{}}, {"Y7",{}}, {"Y8",{}}, {"Y9",{}}, {"Y0",{}} }; 177 | std::pair colors[10] = { {"Y1",Blue}, {"Y2",Red}, {"Y3",Black}, {"Y4",Magenta}, {"Y5",Green}, 178 | {"Y6",Orange}, {"Y7",Brown}, {"Y8",Blue}, {"Y9",Red}, {"Y0",Black} }; 179 | }; 180 | 181 | struct ParametricData 182 | { 183 | std::pair settings[3] = { {"Tmin",0}, {"Tmax",STR_2_PI}, {"Tstep",STR_PI_1_24TH} }; 184 | std::pair styles[6] = { {"X1T_Y1T",{}}, {"X2T_Y2T",{}}, {"X3T_Y3T",{}}, 185 | {"X4T_Y4T",{}}, {"X5T_Y5T",{}}, {"X6T_Y6T",{}} }; 186 | std::pair equations[12] = { {"X1T",{}}, {"Y1T",{}}, {"X2T",{}}, {"Y2T",{}}, {"X3T",{}}, {"Y3T",{}}, 187 | {"X4T",{}}, {"Y4T",{}}, {"X5T",{}}, {"Y5T",{}}, {"X6T",{}}, {"Y6T",{}} }; 188 | std::pair colors[6] = { {"X1T_Y1T",Blue}, {"X2T_Y2T",Red}, {"X3T_Y3T",Black}, 189 | {"X4T_Y4T",Magenta}, {"X5T_Y5T",Green}, {"X6T_Y6T",Orange} }; 190 | }; 191 | 192 | struct PolarData 193 | { 194 | std::pair settings[3] = { {"THmin",{}}, {"THmax",STR_2_PI}, {"THstep",STR_PI_1_24TH} }; 195 | std::pair styles[6] = { {"r1",{}}, {"r2",{}}, {"r3",{}}, {"r4",{}}, {"r5",{}}, {"r6",{}} }; 196 | std::pair equations[6] = { {"r1",{}}, {"r2",{}}, {"r3",{}}, {"r4",{}}, {"r5",{}}, {"r6",{}} }; 197 | std::pair colors[6] = { {"r1",Blue}, {"r2",Red}, {"r3",Black}, {"r4",Magenta}, {"r5",Green}, {"r6",Orange} }; 198 | }; 199 | 200 | struct SequenceData 201 | { 202 | std::pair settings[10] = { {"PlotStart",1}, {"nMax",10}, {"u(nMin)",0}, {"v(nMin)",0}, {"nMin",1}, 203 | {"u(nMin+1)",0}, {"v(nMin+1)",0}, {"w(nMin+1)",0}, {"PlotStep",1}, {"w(nMin)",0}}; 204 | std::pair styles[3] = { {"u",{}}, {"v",{}}, {"w",{}} }; 205 | std::pair equations[3] = { {"u",{}}, {"v",{}}, {"w",{}} }; 206 | std::pair colors[3] = { {"u",Blue}, {"v",Red}, {"w",Black} }; 207 | }; 208 | 209 | struct GDB 210 | { 211 | bool _has84CAndLaterData = false; 212 | GraphMode graphMode{}; 213 | FormatSettings formatSettings{}; 214 | SeqSettings seqSettings{}; 215 | ExtModeSettings extSettings{}; 216 | GlobalWindowSettings globalWindowSettings{}; 217 | std::variant specificData; 218 | Other84CGlobalSettings global84CSettings{}; 219 | }; 220 | 221 | void from_json(const json& j, GDB& gdb) 222 | { 223 | gdb._has84CAndLaterData = j.contains("global84CSettings") && j["global84CSettings"].is_object(); 224 | 225 | try { gdb.graphMode = j["graphMode"]; } catch(...) {} 226 | 227 | if (j.contains("formatSettings")) 228 | { 229 | if (!j["formatSettings"].is_array()) { 230 | throw std::runtime_error("bad type for formatSettings, expected array"); 231 | } 232 | const size_t size = j["formatSettings"].size(); 233 | if (size >= 1) try { gdb.formatSettings.style = j["formatSettings"][0]; } catch(...) {} 234 | if (size >= 2) try { gdb.formatSettings.tracing = j["formatSettings"][1]; } catch(...) {} 235 | if (size >= 3) try { gdb.formatSettings.grid = j["formatSettings"][2]; } catch(...) {} 236 | if (size >= 4) try { gdb.formatSettings.coordsType = j["formatSettings"][3]; } catch(...) {} 237 | if (size >= 5) try { gdb.formatSettings.coords = j["formatSettings"][4]; } catch(...) {} 238 | if (size >= 6) try { gdb.formatSettings.axes = j["formatSettings"][5]; } catch(...) {} 239 | if (size >= 7) try { gdb.formatSettings.label = j["formatSettings"][6]; } catch(...) {} 240 | if (size >= 8) try { gdb.formatSettings.gridType = j["formatSettings"][7]; } catch(...) {} 241 | } 242 | 243 | if (j.contains("seqSettings")) 244 | { 245 | if (!j["seqSettings"].is_object()) { 246 | throw std::runtime_error("bad type for seqSettings, expected object"); 247 | } 248 | try { gdb.seqSettings.mode = j["seqSettings"]["mode"]; } catch(...) {} 249 | } 250 | 251 | if (j.contains("extSettings")) 252 | { 253 | if (!j["extSettings"].is_object()) { 254 | throw std::runtime_error("bad type for extSettings, expected object"); 255 | } 256 | const auto& es = j["globalWindowSettings"]; 257 | if (es.contains("showExpr")) try { gdb.extSettings.expr = es["showExpr"].get() ? ExtModeSettings::ExprOn : ExtModeSettings::ExprOff; } catch(...) {} 258 | if (es.contains("seqMode")) try { gdb.extSettings.seqMode = es["seqMode"]; } catch(...) {} 259 | } 260 | 261 | if (j.contains("globalWindowSettings")) 262 | { 263 | if (!j["globalWindowSettings"].is_object()) { 264 | throw std::runtime_error("bad type for globalWindowSettings, expected object"); 265 | } 266 | const auto& gws = j["globalWindowSettings"]; 267 | if (gws.contains("Xmin")) try { from_json(gws["Xmin"], gdb.globalWindowSettings.Xmin); } catch(...) {} 268 | if (gws.contains("Xmax")) try { from_json(gws["Xmax"], gdb.globalWindowSettings.Xmax); } catch(...) {} 269 | if (gws.contains("Xscl")) try { from_json(gws["Xscl"], gdb.globalWindowSettings.Xscl); } catch(...) {} 270 | if (gws.contains("Ymin")) try { from_json(gws["Ymin"], gdb.globalWindowSettings.Ymin); } catch(...) {} 271 | if (gws.contains("Ymax")) try { from_json(gws["Ymax"], gdb.globalWindowSettings.Ymax); } catch(...) {} 272 | if (gws.contains("Yscl")) try { from_json(gws["Yscl"], gdb.globalWindowSettings.Yscl); } catch(...) {} 273 | } 274 | 275 | const auto getSpecificDataFromJSON = [&](const json& j, const GraphMode& graphMode, auto& specificData) -> void 276 | { 277 | for (auto& [name, value] : specificData.settings) 278 | { 279 | if (j["specificData"].contains("settings") && j["specificData"]["settings"].contains(name)) 280 | { 281 | from_json(j["specificData"]["settings"][name], value); 282 | } 283 | } 284 | 285 | if (j["specificData"].contains("equations")) 286 | { 287 | if (!j["specificData"]["equations"].is_object()) 288 | { 289 | throw std::runtime_error("need specificData.equations to be an object"); 290 | } 291 | 292 | uint8_t i = 0; 293 | for (auto& [name, equDefWrapper] : specificData.equations) 294 | { 295 | if (j["specificData"]["equations"].contains(name)) 296 | { 297 | auto& value = j["specificData"]["equations"][name]; 298 | if (value.is_object()) 299 | { 300 | const uint8_t idx = graphMode == Parametric ? (uint8_t)(i/2.0) : i; // Parametric is split in 2 eqs 301 | from_json(value, equDefWrapper, specificData.styles[idx].second, gdb._has84CAndLaterData ? &specificData.colors[idx].second : nullptr); 302 | } 303 | } 304 | i++; 305 | } 306 | } 307 | 308 | }; 309 | 310 | switch (gdb.graphMode) { 311 | case Function: gdb.specificData = FunctionData{}; getSpecificDataFromJSON(j, gdb.graphMode, std::get(gdb.specificData)); break; 312 | case Parametric: gdb.specificData = ParametricData{}; getSpecificDataFromJSON(j, gdb.graphMode, std::get(gdb.specificData)); break; 313 | case Polar: gdb.specificData = PolarData{}; getSpecificDataFromJSON(j, gdb.graphMode, std::get(gdb.specificData)); break; 314 | case Sequence: gdb.specificData = SequenceData{}; getSpecificDataFromJSON(j, gdb.graphMode, std::get(gdb.specificData)); break; 315 | default: 316 | throw std::runtime_error("Unknown graphMode value " + std::to_string(gdb.graphMode)); 317 | } 318 | 319 | if (gdb._has84CAndLaterData && j["global84CSettings"].is_object()) 320 | { 321 | const auto& g84cs = j["global84CSettings"]; 322 | if (g84cs.contains("colors")) 323 | { 324 | if (g84cs["colors"].contains("grid")) try { gdb.global84CSettings.gridColor = g84cs["colors"]["grid"]; } catch (...) {} 325 | if (g84cs["colors"].contains("axes")) try { gdb.global84CSettings.axesColor = g84cs["colors"]["axes"]; } catch (...) {} 326 | if (g84cs["colors"].contains("border")) try { gdb.global84CSettings.borderColor = g84cs["colors"]["border"]; } catch (...) {} 327 | } 328 | if (g84cs.contains("other")) 329 | { 330 | if (g84cs["other"].contains("globalLineStyle")) try { gdb.global84CSettings.globalLineStyle = g84cs["other"]["globalLineStyle"]; } catch (...) {} 331 | if (g84cs["other"].contains("detectAsymptotes")) try { gdb.global84CSettings.extSettings2.detectAsymptotes = g84cs["other"]["detectAsymptotes"].get() ? ExtSettings2::DetectAsymptotesOn : ExtSettings2::DetectAsymptotesOff; } catch(...) {} 332 | } 333 | } 334 | } 335 | 336 | void to_json(json& j, const GDB& gdb) { 337 | j = json{ 338 | { "graphMode", gdb.graphMode }, 339 | { "formatSettings", { 340 | gdb.formatSettings.style, 341 | gdb.formatSettings.tracing, 342 | gdb.formatSettings.grid, 343 | gdb.formatSettings.coordsType, 344 | gdb.formatSettings.coords, 345 | gdb.formatSettings.axes, 346 | gdb.formatSettings.label, 347 | gdb.formatSettings.gridType 348 | } }, 349 | { "seqSettings", { 350 | { "mode", gdb.seqSettings.mode } 351 | } }, 352 | { "extSettings", { 353 | { "showExpr", !((bool)gdb.extSettings.expr) }, 354 | { "seqMode", gdb.extSettings.seqMode }, 355 | } } 356 | }; 357 | 358 | to_json(j["globalWindowSettings"]["Xmin"], gdb.globalWindowSettings.Xmin); 359 | to_json(j["globalWindowSettings"]["Xmax"], gdb.globalWindowSettings.Xmax); 360 | to_json(j["globalWindowSettings"]["Xscl"], gdb.globalWindowSettings.Xscl); 361 | to_json(j["globalWindowSettings"]["Ymin"], gdb.globalWindowSettings.Ymin); 362 | to_json(j["globalWindowSettings"]["Ymax"], gdb.globalWindowSettings.Ymax); 363 | to_json(j["globalWindowSettings"]["Yscl"], gdb.globalWindowSettings.Yscl); 364 | 365 | std::visit([&](auto&& specificData) 366 | { 367 | for (const auto& [name, value] : specificData.settings) 368 | { 369 | to_json(j["specificData"]["settings"][name], value); 370 | } 371 | 372 | uint8_t i = 0; 373 | for (const auto& [name, equDefWrapper] : specificData.equations) 374 | { 375 | const uint8_t idx = (std::is_same_v, ParametricData>) ? (uint8_t)(i/2.) : i; 376 | const OSColor* oscolorPtr = gdb._has84CAndLaterData ? &specificData.colors[idx].second : nullptr; 377 | to_json(j["specificData"]["equations"][name], equDefWrapper, specificData.styles[idx].second, oscolorPtr); 378 | i++; 379 | } 380 | }, gdb.specificData); 381 | 382 | if (gdb._has84CAndLaterData) 383 | { 384 | j["global84CSettings"] = { 385 | { "colors", { 386 | { "grid", gdb.global84CSettings.gridColor }, 387 | { "axes", gdb.global84CSettings.axesColor }, 388 | { "border", gdb.global84CSettings.borderColor }, 389 | } }, 390 | { "other", { 391 | { "globalLineStyle", gdb.global84CSettings.globalLineStyle }, 392 | { "detectAsymptotes", gdb.global84CSettings.extSettings2.detectAsymptotes == ExtSettings2::DetectAsymptotesOn } 393 | } }, 394 | }; 395 | } 396 | } 397 | } 398 | 399 | /* 400 | * Some info from LogicalJoe: 401 | * For the Graph Mode byte it doesn't check for 0x10, 0x20, 0x40, and 0x80; 402 | * Instead, if bit 4 is set, then it is read as a Function GDB, else if bit 5 then Polar, else if bit 6 then Parametric, else Sequential. 403 | * i.e. Mode byte 0x60 is interpreted as Polar (0x20). 404 | * The calculator will (should) never return a GDB where this matters 405 | */ 406 | static GraphMode getGraphModeFromRawValue(uint8_t val) 407 | { 408 | const uint8_t m = (val & 0x70) | 0x80; 409 | return static_cast(m & -m); 410 | } 411 | 412 | namespace tivars::TypeHandlers 413 | { 414 | data_t TH_GDB::makeDataFromString(const std::string& str, const options_t& options, const TIVarFile* _ctx) 415 | { 416 | (void)options; 417 | (void)_ctx; 418 | 419 | GDB gdb{}; 420 | from_json(json::parse(str), gdb); 421 | 422 | data_t data(2); // reserve 2 bytes for size fields, filled later 423 | 424 | data.push_back(0); // always 0 425 | data.push_back(gdb.graphMode); 426 | data.push_back(*((uint8_t*)(&gdb.formatSettings))); 427 | data.push_back(*((uint8_t*)(&gdb.seqSettings))); 428 | data.push_back(*((uint8_t*)(&gdb.extSettings))); 429 | 430 | vector_append(data, STH_FP::makeDataFromString(gdb.globalWindowSettings.Xmin)); 431 | vector_append(data, STH_FP::makeDataFromString(gdb.globalWindowSettings.Xmax)); 432 | vector_append(data, STH_FP::makeDataFromString(gdb.globalWindowSettings.Xscl)); 433 | vector_append(data, STH_FP::makeDataFromString(gdb.globalWindowSettings.Ymin)); 434 | vector_append(data, STH_FP::makeDataFromString(gdb.globalWindowSettings.Ymax)); 435 | vector_append(data, STH_FP::makeDataFromString(gdb.globalWindowSettings.Yscl)); 436 | 437 | std::visit([&](auto&& specificData) 438 | { 439 | for (const auto& [_, value] : specificData.settings) 440 | { 441 | vector_append(data, STH_FP::makeDataFromString(value)); 442 | } 443 | 444 | for (const auto& [_, value] : specificData.styles) 445 | { 446 | data.push_back(value); 447 | } 448 | 449 | for (const auto& [_, equ] : specificData.equations) 450 | { 451 | data.push_back(*((uint8_t*)(&equ.flags))); 452 | vector_append(data, TH_Tokenized::makeDataFromString(equ.expr)); 453 | } 454 | 455 | if (gdb._has84CAndLaterData) 456 | { 457 | vector_append(data, data_t(magic84CAndLaterSectionMarker, magic84CAndLaterSectionMarker + strlen(magic84CAndLaterSectionMarker))); 458 | for (const auto& [_, value] : specificData.colors) 459 | { 460 | data.push_back(value); 461 | } 462 | } 463 | }, gdb.specificData); 464 | 465 | if (gdb._has84CAndLaterData) 466 | { 467 | data.push_back(gdb.global84CSettings.gridColor); 468 | data.push_back(gdb.global84CSettings.axesColor); 469 | data.push_back(gdb.global84CSettings.globalLineStyle); 470 | data.push_back(gdb.global84CSettings.borderColor); 471 | data.push_back(*((uint8_t*)(&gdb.global84CSettings.extSettings2))); 472 | } 473 | 474 | const size_t length = data.size() - 2; 475 | data[0] = (uint8_t)(length & 0xFF); 476 | data[1] = (uint8_t)((length >> 8) & 0xFF); 477 | 478 | return data; 479 | } 480 | 481 | std::string TH_GDB::makeStringFromData(const data_t& data, const options_t& options, const TIVarFile* _ctx) 482 | { 483 | (void)_ctx; 484 | 485 | const size_t dataSizeActual = data.size(); 486 | if (dataSizeActual < dataByteCountMinimum) 487 | { 488 | throw std::invalid_argument("Invalid data array. Needs to contain at least " + std::to_string(dataByteCountMinimum) + " bytes"); 489 | } 490 | 491 | const size_t dataSizeExpected = (data[0] & 0xFF) + ((data[1] & 0xFF) << 8); 492 | if (dataSizeActual - 2 != dataSizeExpected) 493 | { 494 | throw std::invalid_argument("Invalid data array. dataSizeActual-2 (" + std::to_string(dataSizeActual-2) + ") != dataSizeExpected (" + std::to_string(dataSizeExpected) + ")"); 495 | } 496 | 497 | GDB gdb = { 498 | .graphMode = getGraphModeFromRawValue(data[3]), 499 | .formatSettings = *((FormatSettings*)(&data[4])), 500 | .seqSettings = *((SeqSettings*)(&data[5])), 501 | .extSettings = *((ExtModeSettings*)(&data[6])), 502 | .globalWindowSettings = { 503 | .Xmin = STH_FP::makeStringFromData(data_t(data.begin()+7 + 0*STH_FP::dataByteCount, data.begin()+7 + 1*STH_FP::dataByteCount)), 504 | .Xmax = STH_FP::makeStringFromData(data_t(data.begin()+7 + 1*STH_FP::dataByteCount, data.begin()+7 + 2*STH_FP::dataByteCount)), 505 | .Xscl = STH_FP::makeStringFromData(data_t(data.begin()+7 + 2*STH_FP::dataByteCount, data.begin()+7 + 3*STH_FP::dataByteCount)), 506 | .Ymin = STH_FP::makeStringFromData(data_t(data.begin()+7 + 3*STH_FP::dataByteCount, data.begin()+7 + 4*STH_FP::dataByteCount)), 507 | .Ymax = STH_FP::makeStringFromData(data_t(data.begin()+7 + 4*STH_FP::dataByteCount, data.begin()+7 + 5*STH_FP::dataByteCount)), 508 | .Yscl = STH_FP::makeStringFromData(data_t(data.begin()+7 + 5*STH_FP::dataByteCount, data.begin()+7 + 6*STH_FP::dataByteCount)) 509 | } 510 | }; 511 | 512 | off_t tmpOff = 7 + 6*STH_FP::dataByteCount; 513 | 514 | const auto parseSpecificData = [&](auto& specificData) -> void 515 | { 516 | for (auto& [_, settingValue] : specificData.settings) 517 | { 518 | settingValue = STH_FP::makeStringFromData(data_t(data.begin() + tmpOff, data.begin() + tmpOff + STH_FP::dataByteCount)); 519 | tmpOff += STH_FP::dataByteCount; 520 | } 521 | 522 | for (auto& [_, style] : specificData.styles) 523 | { 524 | style = static_cast(data[tmpOff]); 525 | tmpOff++; 526 | } 527 | 528 | for (auto& [_, equ] : specificData.equations) 529 | { 530 | const uint16_t equLen = (data[tmpOff + 2] << 1) + data[tmpOff + 1]; 531 | equ.flags = *(FuncFlags*)(&(data[tmpOff])); 532 | equ.expr = TH_Tokenized::makeStringFromData(data_t(data.begin() + tmpOff + 3, data.begin() + tmpOff + 3 + equLen), {{"fromRawBytes", true}}); 533 | tmpOff += sizeof(equLen) + sizeof(equ.flags) + equLen; 534 | } 535 | 536 | if (std::memcmp(&data[tmpOff], magic84CAndLaterSectionMarker, strlen(magic84CAndLaterSectionMarker)) == 0) 537 | { 538 | tmpOff += strlen(magic84CAndLaterSectionMarker); 539 | gdb._has84CAndLaterData = true; 540 | for (auto& [_, color] : specificData.colors) 541 | { 542 | color = static_cast(data[tmpOff]); 543 | tmpOff++; 544 | } 545 | } 546 | }; 547 | 548 | switch (gdb.graphMode) { 549 | case Function: gdb.specificData = FunctionData{}; parseSpecificData(std::get(gdb.specificData)); break; 550 | case Parametric: gdb.specificData = ParametricData{}; parseSpecificData(std::get(gdb.specificData)); break; 551 | case Polar: gdb.specificData = PolarData{}; parseSpecificData(std::get(gdb.specificData)); break; 552 | case Sequence: gdb.specificData = SequenceData{}; parseSpecificData(std::get(gdb.specificData)); break; 553 | default: 554 | throw std::runtime_error("Unknown graphMode value " + std::to_string(gdb.graphMode)); 555 | } 556 | 557 | if (gdb._has84CAndLaterData) 558 | { 559 | gdb.global84CSettings.gridColor = static_cast(data[tmpOff++]); 560 | gdb.global84CSettings.axesColor = static_cast(data[tmpOff++]); 561 | gdb.global84CSettings.globalLineStyle = static_cast(data[tmpOff++]); 562 | gdb.global84CSettings.borderColor = data[tmpOff++]; 563 | gdb.global84CSettings.extSettings2 = *((ExtSettings2*)(&data[tmpOff++])); 564 | } 565 | 566 | const bool compactJSON = has_option(options, "compact") && options.at("compact") == 1; 567 | return json(gdb).dump(compactJSON ? -1 : 4); 568 | } 569 | 570 | uint8_t TH_GDB::getMinVersionFromData(const data_t& data) 571 | { 572 | (void)data; 573 | return 0; 574 | } 575 | } 576 | 577 | #else 578 | 579 | #warning "Compiler is too old to handle the GDB VarType support code so it will be disabled" 580 | 581 | namespace tivars::TypeHandlers 582 | { 583 | data_t TH_GDB::makeDataFromString(const std::string&, const options_t&, const TIVarFile* _ctx) 584 | { 585 | throw std::runtime_error("GDB support is not compiled in this tivars_lib_cpp version"); 586 | } 587 | 588 | std::string TH_GDB::makeStringFromData(const data_t&, const options_t&, const TIVarFile* _ctx) 589 | { 590 | throw std::runtime_error("GDB support is not compiled in this tivars_lib_cpp version"); 591 | } 592 | 593 | uint8_t TH_GDB::getMinVersionFromData(const data_t& data) 594 | { 595 | (void)data; 596 | return 0; 597 | } 598 | } 599 | 600 | #endif /* GDB_SUPPORT */ 601 | -------------------------------------------------------------------------------- /src/TypeHandlers/TH_GenericAppVar.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "TypeHandlers.h" 9 | 10 | #include 11 | #include 12 | 13 | namespace tivars::TypeHandlers 14 | { 15 | data_t TH_GenericAppVar::makeDataFromString(const std::string& str, const options_t& options, const TIVarFile* _ctx) 16 | { 17 | return STH_DataAppVar::makeDataFromString(str, options); 18 | } 19 | 20 | std::string TH_GenericAppVar::makeStringFromData(const data_t& data, const options_t& options, const TIVarFile* _ctx) 21 | { 22 | (void)_ctx; 23 | 24 | if (data.size() < 2) 25 | { 26 | throw std::invalid_argument("Invalid data array. Needs to contain at least 2 bytes"); 27 | } 28 | 29 | if (data.size() >= 2 + sizeof(STH_PythonAppVar::ID_CODE) 30 | && memcmp(STH_PythonAppVar::ID_CODE, &(data[2]), strlen(STH_PythonAppVar::ID_CODE)) == 0) 31 | { 32 | try { 33 | return STH_PythonAppVar::makeStringFromData(data, options); 34 | } catch (...) {} // "fallthrough" 35 | } 36 | 37 | return STH_DataAppVar::makeStringFromData(data, options); 38 | } 39 | 40 | uint8_t TH_GenericAppVar::getMinVersionFromData(const data_t& data) 41 | { 42 | (void)data; 43 | return 0; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/TypeHandlers/TH_GenericComplex.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "TypeHandlers.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace tivars::TypeHandlers 16 | { 17 | static const std::unordered_map type2handlers = { 18 | { 0x0C, SpecificHandlerTuple(STH_FP) }, 19 | { 0x1B, SpecificHandlerTuple(STH_ExactFraction) }, 20 | { 0x1D, SpecificHandlerTuple(STH_ExactRadical) }, 21 | { 0x1E, SpecificHandlerTuple(STH_ExactPi) }, 22 | { 0x1F, SpecificHandlerTuple(STH_ExactFractionPi) }, 23 | }; 24 | static const std::unordered_map type2patterns = { 25 | { 0x0C, STH_FP::validPattern }, 26 | { 0x1B, STH_ExactFraction::validPattern }, 27 | { 0x1D, STH_ExactRadical::validPattern }, 28 | { 0x1E, STH_ExactPi::validPattern }, 29 | { 0x1F, STH_ExactFractionPi::validPattern }, 30 | }; 31 | 32 | static bool checkValidStringAndGetMatches(const std::string& str, const char* typePattern, std::smatch& matches) 33 | { 34 | if (str.empty()) 35 | { 36 | return false; 37 | } 38 | // Handle real only, real+imag, imag only. 39 | const bool isValid = regex_match(str, matches, std::regex(std::string("^") + typePattern + "()$")) 40 | || regex_match(str, matches, std::regex(std::string("^") + typePattern + typePattern + "i$")) 41 | || regex_match(str, matches, std::regex(std::string("^()") + typePattern + "i$")); 42 | return isValid; 43 | } 44 | 45 | // For this, we're going to assume that both members are of the same type... 46 | // TODO: guess, by parsing, the type instead of reading it from the options 47 | data_t TH_GenericComplex::makeDataFromString(const std::string& str, const options_t& options, const TIVarFile* _ctx) 48 | { 49 | constexpr size_t bytesPerMember = 9; 50 | const auto& typeIter = options.find("_type"); 51 | if (typeIter == options.end()) 52 | { 53 | throw std::runtime_error("Needs _type in options for TH_GenericComplex::makeDataFromString"); 54 | } 55 | const uint8_t type = (uint8_t)typeIter->second; 56 | const auto& handlerIter = type2handlers.find(type); 57 | if (handlerIter == type2handlers.end()) 58 | { 59 | throw std::runtime_error("Unknown/Invalid type for this TH_GenericComplex: " + std::to_string(type)); 60 | } 61 | const auto& handler = std::get<0>(handlerIter->second); 62 | 63 | std::string newStr = str; 64 | newStr = std::regex_replace(newStr, std::regex(" "), ""); 65 | newStr = std::regex_replace(newStr, std::regex("\\+i"), "+1i"); 66 | newStr = std::regex_replace(newStr, std::regex("-i"), "-1i"); 67 | 68 | std::smatch matches; 69 | const bool isValid = checkValidStringAndGetMatches(newStr, type2patterns.at(type), matches); 70 | if (!isValid || matches.size() != 3) 71 | { 72 | throw std::invalid_argument("Invalid input string. Needs to be a valid complex subtype string"); 73 | } 74 | 75 | data_t data; 76 | for (int i=0; i<2; i++) 77 | { 78 | std::string coeff = matches[i+1]; 79 | if (coeff.empty()) 80 | { 81 | coeff = "0"; 82 | } 83 | 84 | const data_t& member_data = handler(coeff, options, _ctx); 85 | data.insert(data.end(), member_data.begin(), member_data.end()); 86 | 87 | uint8_t flags = 0; 88 | flags |= (atof(coeff.c_str()) < 0) ? (1 << 7) : 0; 89 | flags |= (1 << 2); // Because it's a complex number 90 | flags |= (1 << 3); // Because it's a complex number 91 | 92 | data[i * bytesPerMember] = flags; 93 | } 94 | 95 | return data; 96 | } 97 | 98 | std::string TH_GenericComplex::makeStringFromData(const data_t& data, const options_t& options, const TIVarFile* _ctx) 99 | { 100 | constexpr size_t bytesPerMember = 9; 101 | 102 | if (data.size() != 2*bytesPerMember) 103 | { 104 | throw std::invalid_argument("Invalid data array. Needs to contain 18 bytes"); 105 | } 106 | 107 | // 0x1F because we discard the flag bits (see above) 108 | const uint8_t typeR = (uint8_t)(data[0] & 0x1F); 109 | const uint8_t typeI = (uint8_t)(data[bytesPerMember] & 0x1F); 110 | 111 | const auto& handlerRIter = type2handlers.find(typeR); 112 | if (handlerRIter == type2handlers.end()) 113 | { 114 | throw std::runtime_error("Unknown/Invalid type for this TH_GenericComplex: " + std::to_string(typeR)); 115 | } 116 | const auto& handlerIIter = type2handlers.find(typeI); 117 | if (handlerIIter == type2handlers.end()) 118 | { 119 | throw std::runtime_error("Unknown/Invalid type for this TH_GenericComplex: " + std::to_string(typeI)); 120 | } 121 | 122 | const auto& handlerR = std::get<1>(handlerRIter->second); 123 | const auto& handlerI = std::get<1>(handlerIIter->second); 124 | 125 | const data_t::const_iterator mid = data.cbegin() + bytesPerMember; 126 | const std::string coeffR = handlerR(data_t(data.cbegin(), mid), options, _ctx); 127 | const std::string coeffI = handlerI(data_t(mid, data.cend()), options, _ctx); 128 | 129 | const bool coeffRZero = coeffR == "0"; 130 | const bool coeffIZero = coeffI == "0"; 131 | std::string str; 132 | str.reserve(coeffR.length() + 1 + coeffI.length() + 1); 133 | if (!coeffRZero || coeffIZero) { 134 | str += coeffR; 135 | } 136 | if (!coeffRZero && !coeffIZero && coeffI.front() != '-' && coeffI.front() != '+') { 137 | str += '+'; 138 | } 139 | if (!coeffIZero) { 140 | if (coeffI != "1") { 141 | str += coeffI; 142 | } 143 | str += 'i'; 144 | } 145 | return str; 146 | } 147 | 148 | uint8_t TH_GenericComplex::getMinVersionFromData(const data_t& data) 149 | { 150 | const uint8_t internalType = (uint8_t)(data[0] & 0x3F); 151 | if (internalType == 0x0C) { // Complex 152 | return 0x00; 153 | } else if (internalType == 0x1B) { // Fraction 154 | return 0x0B; 155 | } else { 156 | return 0x10; 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/TypeHandlers/TH_GenericList.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "TypeHandlers.h" 9 | #include "../tivarslib_utils.h" 10 | #include "../TIVarTypes.h" 11 | 12 | #include 13 | 14 | namespace tivars::TypeHandlers 15 | { 16 | // TODO: also make it detect the type correctly... 17 | data_t TH_GenericList::makeDataFromString(const std::string& str, const options_t& options, const TIVarFile* _ctx) 18 | { 19 | const auto& typeIter = options.find("_type"); 20 | if (typeIter == options.end()) 21 | { 22 | throw std::runtime_error("Needs _type in options for TH_GenericList::makeDataFromString"); 23 | } 24 | 25 | const auto type = typeIter->second; 26 | if (type != TIVarType{"Real"}.getId() && type != TIVarType{"Complex"}.getId()) 27 | { 28 | throw std::invalid_argument("Invalid type for given string"); 29 | } 30 | 31 | auto arr = explode(trim(str, "{}"), ','); 32 | const size_t numCount = arr.size(); 33 | 34 | for (auto& numStr : arr) 35 | { 36 | numStr = trim(numStr); 37 | } 38 | if (str.empty() || arr.empty() || numCount > 999) 39 | { 40 | throw std::invalid_argument("Invalid input string. Needs to be a valid real or complex list"); 41 | } 42 | 43 | const auto handler = (type == 0x00) ? &TH_GenericReal::makeDataFromString : &TH_GenericComplex::makeDataFromString; 44 | 45 | data_t data(2); // reserve 2 bytes for size fields 46 | 47 | data[0] = (uint8_t) (numCount & 0xFF); 48 | data[1] = (uint8_t) ((numCount >> 8) & 0xFF); 49 | 50 | for (const auto& numStr : arr) 51 | { 52 | const auto& tmp = handler(numStr, options, _ctx); 53 | data.insert(data.end(), tmp.begin(), tmp.end()); 54 | } 55 | 56 | return data; 57 | } 58 | 59 | std::string TH_GenericList::makeStringFromData(const data_t& data, const options_t& options, const TIVarFile* _ctx) 60 | { 61 | const size_t byteCount = data.size(); 62 | if (byteCount < 2) 63 | { 64 | throw std::invalid_argument("Invalid data array. Needs to contain at least 2 bytes"); 65 | } 66 | 67 | const size_t numCount = (size_t) ((data[0] & 0xFF) + ((data[1] & 0xFF) << 8)); 68 | 69 | const bool isRealList = (numCount == (size_t)((byteCount - 2) / TH_GenericReal::dataByteCount)); 70 | const bool isComplexList = (numCount == (size_t)((byteCount - 2) / TH_GenericComplex::dataByteCount)); 71 | 72 | if (!(isRealList ^ isComplexList)) 73 | { 74 | throw std::invalid_argument("Invalid data array. Needs to contain 2+9*n or 2+18*n bytes"); 75 | } 76 | 77 | const size_t typeByteCount = isRealList ? TH_GenericReal::dataByteCount : TH_GenericComplex::dataByteCount; 78 | 79 | if (byteCount < 2+typeByteCount || ((byteCount - 2) % typeByteCount != 0) || numCount > 999) 80 | { 81 | throw std::invalid_argument("Invalid data array. Needs to contain 2+" + std::to_string(typeByteCount) + "*n bytes"); 82 | } 83 | 84 | const auto handler = isRealList ? &TH_GenericReal::makeStringFromData : &TH_GenericComplex::makeStringFromData; 85 | 86 | std::string str = "{"; 87 | for (size_t i = 2, num = 0; i < byteCount; i += typeByteCount, num++) 88 | { 89 | str += handler(data_t(data.begin()+i, data.begin()+i+typeByteCount), options, _ctx); 90 | if (num < numCount - 1) // not last num 91 | { 92 | str += ','; 93 | } 94 | } 95 | str += "}"; 96 | 97 | return str; 98 | } 99 | 100 | uint8_t TH_GenericList::getMinVersionFromData(const data_t& data) 101 | { 102 | uint8_t version = 0; 103 | for (size_t offset = 2; offset < data.size(); offset += 9) { 104 | uint8_t internalType = data[offset] & 0x3F; 105 | if (internalType > 0x1B) { // exact complex frac 106 | version = 0x10; 107 | break; 108 | } else if (internalType == 0x1B) { // exact complex frac 109 | if (version < 0x0B) version = 0x0B; 110 | } else if (internalType == 0x18 || internalType == 0x19) { // real/mixed frac 111 | if (version < 0x06) version = 0x06; 112 | } 113 | } 114 | return version; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/TypeHandlers/TH_GenericReal.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "TypeHandlers.h" 9 | 10 | #include 11 | #include 12 | 13 | namespace tivars::TypeHandlers 14 | { 15 | static const std::unordered_map type2handlers = { 16 | { 0x00, SpecificHandlerTuple(STH_FP) }, 17 | { 0x18, SpecificHandlerTuple(STH_ExactFraction) }, 18 | { 0x1C, SpecificHandlerTuple(STH_ExactRadical) }, 19 | { 0x20, SpecificHandlerTuple(STH_ExactPi) }, 20 | { 0x21, SpecificHandlerTuple(STH_ExactFractionPi) }, 21 | }; 22 | 23 | // TODO: guess, by parsing, the type instead of reading it from the options 24 | data_t TH_GenericReal::makeDataFromString(const std::string& str, const options_t& options, const TIVarFile* _ctx) 25 | { 26 | const auto& typeIter = options.find("_type"); 27 | if (typeIter == options.end()) 28 | { 29 | throw std::runtime_error("Needs _type in options for TH_GenericReal::makeDataFromString"); 30 | } 31 | const uint8_t type = (uint8_t)typeIter->second; 32 | const auto& handlerIter = type2handlers.find(type); 33 | if (handlerIter == type2handlers.end()) 34 | { 35 | throw std::runtime_error("Unknown/Invalid type for this TH_GenericReal: " + std::to_string(type)); 36 | } 37 | return std::get<0>(handlerIter->second)(str, options, _ctx); 38 | } 39 | 40 | std::string TH_GenericReal::makeStringFromData(const data_t& data, const options_t& options, const TIVarFile* _ctx) 41 | { 42 | if (data.size() != dataByteCount) 43 | { 44 | throw std::invalid_argument("Invalid data array. Needs to contain " + std::to_string(dataByteCount) + " bytes"); 45 | } 46 | const uint8_t type = (uint8_t)(data[0] & 0x7F); 47 | const auto& handlerIter = type2handlers.find(type); 48 | if (handlerIter == type2handlers.end()) 49 | { 50 | throw std::runtime_error("Unknown/Invalid type in this TH_GenericReal data: " + std::to_string(type)); 51 | } 52 | return std::get<1>(handlerIter->second)(data, options, _ctx); 53 | } 54 | 55 | uint8_t TH_GenericReal::getMinVersionFromData(const data_t& data) 56 | { 57 | const uint8_t internalType = (uint8_t)(data[0] & 0x3F); 58 | return (internalType == 0) ? 0x00 : 0x06; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/TypeHandlers/TH_Matrix.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "TypeHandlers.h" 9 | #include "../tivarslib_utils.h" 10 | 11 | #include 12 | 13 | namespace tivars::TypeHandlers 14 | { 15 | 16 | data_t TH_Matrix::makeDataFromString(const std::string& str, const options_t& options, const TIVarFile* _ctx) 17 | { 18 | (void)options; 19 | 20 | data_t data(2); // reserve 2 bytes for size fields 21 | 22 | if (str.length() < 5 || str.substr(0, 2) != "[[" || str.substr(str.length()-2, 2) != "]]") 23 | { 24 | throw std::invalid_argument("Invalid input string. Needs to be a valid matrix"); 25 | } 26 | 27 | size_t rowCount, colCount; 28 | std::vector> matrix; 29 | std::vector rows; 30 | 31 | rows = explode(str.substr(2, str.length()-4), "]["); 32 | rowCount = rows.size(); 33 | matrix.resize(rowCount); 34 | 35 | colCount = count(rows[0].begin(), rows[0].end(), ',') + 1; 36 | 37 | if (colCount > 0xFF || rowCount > 0xFF) 38 | { 39 | throw std::invalid_argument("Invalid input string. Needs to be a valid matrix (max col/row = 255)"); 40 | } 41 | 42 | size_t counter = 0; 43 | for (const auto& row : rows) 44 | { 45 | auto tmp = explode(row, ","); 46 | for (auto& numStr : tmp) 47 | { 48 | numStr = trim(numStr); 49 | if (!is_numeric(numStr)) 50 | { 51 | throw std::invalid_argument("Invalid input string. Needs to be a valid matrix (real numbers inside)"); 52 | } 53 | } 54 | if (tmp.size() != colCount) 55 | { 56 | throw std::invalid_argument("Invalid input string. Needs to be a valid matrix (consistent column count)"); 57 | } 58 | matrix[counter++] = tmp; 59 | } 60 | 61 | data[0] = (uint8_t)(colCount & 0xFF); 62 | data[1] = (uint8_t)(rowCount & 0xFF); 63 | 64 | for (const std::vector& row : matrix) 65 | { 66 | for (const auto& numStr : row) 67 | { 68 | const auto& tmp = TH_GenericReal::makeDataFromString(numStr, { {"_type", 0x00} }); 69 | data.insert(data.end(), tmp.begin(), tmp.end()); 70 | } 71 | } 72 | 73 | return data; 74 | } 75 | 76 | std::string TH_Matrix::makeStringFromData(const data_t& data, const options_t& options, const TIVarFile* _ctx) 77 | { 78 | (void)options; // TODO: prettified option 79 | 80 | const size_t byteCount = data.size(); 81 | if (byteCount < 2) 82 | { 83 | throw std::invalid_argument("Invalid data array. Needs to contain at least 2 bytes"); 84 | } 85 | 86 | const size_t colCount = data[0]; 87 | const size_t rowCount = data[1]; 88 | 89 | if (data.size() < 2+TH_GenericReal::dataByteCount || colCount < 1 || rowCount < 1 90 | || ((byteCount - 2) % TH_GenericReal::dataByteCount != 0) || (colCount*rowCount != (byteCount - 2) / TH_GenericReal::dataByteCount)) 91 | { 92 | throw std::invalid_argument("Invalid data array. Needs to contain 1+1+" + std::to_string(TH_GenericReal::dataByteCount) + "*n bytes"); 93 | } 94 | 95 | std::string str = "["; 96 | 97 | for (size_t i = 2, num = 0; i < byteCount; i += TH_GenericReal::dataByteCount, num++) 98 | { 99 | if (num % colCount == 0) // first column 100 | { 101 | str += "["; 102 | } 103 | str += TH_GenericReal::makeStringFromData(data_t(data.begin()+i, data.begin()+i+TH_GenericReal::dataByteCount)); 104 | if (num % colCount < colCount - 1) // not last column 105 | { 106 | str += ","; 107 | } else { 108 | str += "]"; 109 | } 110 | } 111 | 112 | str += "]"; 113 | 114 | return str; 115 | } 116 | 117 | uint8_t TH_Matrix::getMinVersionFromData(const data_t& data) 118 | { 119 | uint8_t version = 0; 120 | for (size_t offset = 2; offset < data.size(); offset += 9) { 121 | uint8_t internalType = data[offset] & 0x3F; 122 | if (internalType > 0x1B) { // exact complex frac 123 | version = 0x10; 124 | break; 125 | } else if (internalType == 0x1B) { // exact complex frac 126 | if (version < 0x0B) version = 0x0B; 127 | } else if (internalType == 0x18 || internalType == 0x19) { // real/mixed frac 128 | if (version < 0x06) version = 0x06; 129 | } 130 | } 131 | return version; 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/TypeHandlers/TH_TempEqu.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "TypeHandlers.h" 9 | 10 | #include 11 | #include 12 | 13 | namespace tivars::TypeHandlers 14 | { 15 | data_t TH_TempEqu::makeDataFromString(const std::string& str, const options_t& options, const TIVarFile* _ctx) 16 | { 17 | (void)str; 18 | (void)options; 19 | (void)_ctx; 20 | 21 | throw std::runtime_error("Unimplemented"); 22 | } 23 | 24 | std::string TH_TempEqu::makeStringFromData(const data_t& data, const options_t& options, const TIVarFile* _ctx) 25 | { 26 | (void)options; 27 | (void)_ctx; 28 | 29 | if (data.size() < 2) 30 | { 31 | throw std::invalid_argument("Invalid data array. Missing the 2 initial bytes."); 32 | } 33 | 34 | const size_t writtenSize = (data[0] & 0xFF) + ((data[1] & 0xFF) << 8); 35 | const size_t dataSize = data.size() - 2; 36 | 37 | if (dataSize < 6) 38 | { 39 | throw std::invalid_argument("Invalid data array. Needs to contain at least 6 bytes (size fields)"); 40 | } 41 | 42 | if (writtenSize != dataSize) 43 | { 44 | throw std::invalid_argument("Invalid data array. Length field inconsistent with actual data"); 45 | } 46 | 47 | const uint8_t type = data[2]; 48 | const std::string typeStr = ((type == 5 || type == 6) ? "prgm" : ""); 49 | 50 | const uint8_t namelen = data[3]; 51 | if (namelen < 1 || namelen > 8 || namelen > dataSize-4) { // for the other fields 52 | throw std::invalid_argument("Invalid data array. namelen field impossible or inconsistent with actual data"); 53 | } 54 | 55 | std::string name(namelen, '\0'); 56 | memcpy(&name[0], &data[4], namelen); 57 | 58 | const uint16_t offset = (uint16_t) ((data[4 + namelen] & 0xFF) + ((data[4 + namelen + 1] & 0xFF) << 8)); 59 | 60 | data_t tokens(data.begin()+6+namelen-2, data.end()); // we take 2 bytes more at the beginning to overwrite them with length 61 | tokens[0] = (uint8_t) ((tokens.size()-2) & 0xFF); 62 | tokens[1] = (uint8_t) (((tokens.size()-2) >> 8) & 0xFF); 63 | const std::string detokenized = TH_Tokenized::makeStringFromData(tokens); 64 | 65 | return typeStr + name + ":" + std::to_string(offset) + ":" + detokenized; 66 | } 67 | 68 | uint8_t TH_TempEqu::getMinVersionFromData(const data_t& data) 69 | { 70 | (void)data; 71 | return 0; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/TypeHandlers/TH_Tokenized.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "TypeHandlers.h" 9 | #include "../tivarslib_utils.h" 10 | 11 | #ifndef CEMU_VERSION 12 | #include 13 | #else 14 | #include 15 | #endif 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | static size_t strlen_mb(const std::string& s) 25 | { 26 | size_t len = 0; 27 | for (const char* p = s.data(); *p != 0; ++p) 28 | len += (*p & 0xc0) != 0x80; 29 | return len; 30 | } 31 | 32 | namespace tivars::TypeHandlers 33 | { 34 | namespace 35 | { 36 | std::unordered_map> tokens_BytesToName; 37 | std::unordered_map tokens_NameToBytes; 38 | uint8_t lengthOfLongestTokenName; 39 | std::vector firstByteOfTwoByteTokens; 40 | constexpr uint16_t squishedASMTokens[] = { 0xBB6D, 0xEF69, 0xEF7B }; // 83+/84+, 84+CSE, CE 41 | const std::regex toPrettifyRX1(R"(\[\|([a-f])\])"); 42 | const std::regex toPrettifyRX2(R"(\[([prszt])\])"); 43 | const std::regex toPrettifyRX3(R"(\|([uvw]))"); 44 | } 45 | 46 | data_t TH_Tokenized::makeDataFromString(const std::string& str, const options_t& options, const TIVarFile* _ctx) 47 | { 48 | (void)_ctx; 49 | data_t data; 50 | 51 | const bool deindent = has_option(options, "deindent") && options.at("deindent") == 1; 52 | const bool detect_strings = !has_option(options, "detect_strings") || options.at("detect_strings") != 0; 53 | 54 | std::string str_new; 55 | if (deindent) 56 | { 57 | std::istringstream f{str}; 58 | std::string line; 59 | while (std::getline(f, line)) { 60 | str_new += ltrim(line) + "\n"; 61 | } 62 | str_new.pop_back(); 63 | } else { 64 | str_new = str; 65 | } 66 | 67 | // two bytes reserved for the size. Filled later 68 | data.push_back(0); data.push_back(0); 69 | 70 | bool isInCustomName = false; // after a "prgm" or ʟ token ( 71 | bool isWithinString = false; 72 | bool inEvaluatedString = false; // CE OS 5.2 added string interpolation with eval() for TI-Innovator commands 73 | uint16_t lastTokenBytes = 0; 74 | 75 | for (size_t strCursorPos = 0; strCursorPos < str_new.length(); strCursorPos++) 76 | { 77 | const std::string currChar = str_new.substr(strCursorPos, 1); 78 | if(detect_strings) 79 | { 80 | if((lastTokenBytes == 0x5F || lastTokenBytes == 0xEB)) { // prgm and ʟ 81 | isInCustomName = true; 82 | } else if(currChar == "\"") { 83 | isWithinString = !isWithinString; 84 | inEvaluatedString = isWithinString && lastTokenBytes == 0xE7; // Send( 85 | } else if(currChar == "\n" || (strCursorPos < str_new.length()-strlen("→") && memcmp(&str_new[strCursorPos], "→", strlen("→")) == 0)) { 86 | isInCustomName = false; 87 | isWithinString = false; 88 | inEvaluatedString = false; 89 | } else if(isInCustomName && !isalnum(currChar[0])) { 90 | isInCustomName = false; 91 | } 92 | } 93 | 94 | const uint8_t maxTokSearchLen = std::min(str_new.length() - strCursorPos, (size_t)lengthOfLongestTokenName); 95 | const bool needMinMunch = isInCustomName || (isWithinString && !inEvaluatedString); 96 | 97 | /* needMinMunch => minimum token length, otherwise maximal munch */ 98 | for (size_t currentLength = needMinMunch ? 1 : maxTokSearchLen; 99 | needMinMunch ? (currentLength <= maxTokSearchLen) : (currentLength > 0); 100 | currentLength += (needMinMunch ? 1 : -1)) 101 | { 102 | std::string currentSubString = str_new.substr(strCursorPos, currentLength); 103 | 104 | // We want to use true-lowercase alpha tokens in this case. 105 | if ((isWithinString && !inEvaluatedString) && currentLength == 1 && std::islower(currentSubString[0])) 106 | { 107 | // 0xBBB0 is 'a', etc. But we skip what would be 'l' at 0xBBBB which doesn't exist (prefix conflict) 108 | const char letter = currentSubString[0]; 109 | uint16_t tokenValue = 0xBBB0 + (letter - 'a') + (letter >= 'l' ? 1 : 0); 110 | data.push_back(tokenValue >> 8); 111 | data.push_back(tokenValue & 0xFF); 112 | lastTokenBytes = tokenValue; 113 | break; 114 | } 115 | 116 | if (tokens_NameToBytes.count(currentSubString)) 117 | { 118 | uint16_t tokenValue = tokens_NameToBytes[currentSubString]; 119 | if (tokenValue > 0xFF) 120 | { 121 | data.push_back((uint8_t)(tokenValue >> 8)); 122 | } 123 | data.push_back((uint8_t)(tokenValue & 0xFF)); 124 | strCursorPos += currentLength - 1; 125 | lastTokenBytes = tokenValue; 126 | break; 127 | } 128 | } 129 | } 130 | 131 | size_t actualDataLen = data.size() - 2; 132 | data[0] = (uint8_t)(actualDataLen & 0xFF); 133 | data[1] = (uint8_t)((actualDataLen >> 8) & 0xFF); 134 | return data; 135 | } 136 | 137 | std::string TH_Tokenized::makeStringFromData(const data_t& data, const options_t& options, const TIVarFile* _ctx) 138 | { 139 | (void)_ctx; 140 | const size_t dataSize = data.size(); 141 | 142 | const bool fromRawBytes = has_option(options, "fromRawBytes") && options.at("fromRawBytes") == 1; 143 | if (fromRawBytes && dataSize == 0) 144 | { 145 | return ""; 146 | } 147 | if (!fromRawBytes && dataSize < 2) 148 | { 149 | throw std::invalid_argument("Invalid data array. Needs to contain at least 2 bytes (size fields)"); 150 | } 151 | 152 | const uint8_t langIdx = (has_option(options, "lang") && options.at("lang") == LANG_FR) ? LANG_FR : LANG_EN; 153 | 154 | const size_t howManyBytes = fromRawBytes ? (int)data.size() : ((data[0] & 0xFF) + ((data[1] & 0xFF) << 8)); 155 | if (!fromRawBytes) 156 | { 157 | if (howManyBytes != dataSize - 2) 158 | { 159 | std::cerr << "[Warning] Byte count (" << (dataSize - 2) << ") and size field (" << howManyBytes << ") mismatch!" << std::endl; 160 | } 161 | } 162 | 163 | if (!fromRawBytes && howManyBytes >= 2 && dataSize >= 4) 164 | { 165 | const uint16_t twoFirstBytes = (uint16_t) ((data[3] & 0xFF) + ((data[2] & 0xFF) << 8)); 166 | if (std::find(std::begin(squishedASMTokens), std::end(squishedASMTokens), twoFirstBytes) != std::end(squishedASMTokens)) 167 | { 168 | return "[Error] This is a squished ASM program - cannot preview it!"; 169 | } 170 | } 171 | 172 | uint16_t errCount = 0; 173 | std::string str; 174 | for (size_t i = fromRawBytes ? 0 : 2; i < dataSize; i++) 175 | { 176 | const uint8_t currentToken = data[i]; 177 | uint8_t nextToken = (i < dataSize-1) ? data[i+1] : (uint8_t)-1; 178 | uint16_t bytesKey = currentToken; 179 | if (is_in_vector(firstByteOfTwoByteTokens, currentToken)) 180 | { 181 | if (nextToken == (uint8_t)-1) 182 | { 183 | std::cerr << "[Warning] Encountered an unfinished two-byte token! Setting the second byte to 0x00" << std::endl; 184 | nextToken = 0x00; 185 | } 186 | bytesKey = nextToken + (currentToken << 8); 187 | i++; 188 | } 189 | if (tokens_BytesToName.find(bytesKey) != tokens_BytesToName.end()) 190 | { 191 | str += tokens_BytesToName[bytesKey][langIdx]; 192 | } else { 193 | str += " [???] "; 194 | errCount++; 195 | } 196 | } 197 | 198 | if (errCount > 0) 199 | { 200 | std::cerr << "[Warning] " << errCount << " token(s) could not be detokenized (' [???] ' was used)!" << std::endl; 201 | } 202 | 203 | if (has_option(options, "prettify") && options.at("prettify") == 1) 204 | { 205 | str = std::regex_replace(str, toPrettifyRX1, "$1"); 206 | str = std::regex_replace(str, toPrettifyRX2, "$1"); 207 | str = std::regex_replace(str, toPrettifyRX3, "$1"); 208 | } 209 | 210 | if (has_option(options, "reindent") && options.at("reindent") == 1) 211 | { 212 | options_t indent_options{}; 213 | if (has_option(options, "indent_char")) 214 | indent_options["indent_char"] = options.at("indent_char"); 215 | if (has_option(options, "indent_n")) 216 | indent_options["indent_n"] = options.at("indent_n"); 217 | str = reindentCodeString(str, indent_options); 218 | } 219 | 220 | return str; 221 | } 222 | 223 | uint8_t TH_Tokenized::getMinVersionFromData(const data_t& data) 224 | { 225 | const size_t dataSize = data.size(); 226 | if (dataSize < 2) 227 | { 228 | throw std::invalid_argument("Invalid data array. Needs to contain at least 2 bytes (size fields)"); 229 | } 230 | 231 | bool usesRTC = false; 232 | int maxBB = -1; 233 | int maxEF = -1; 234 | uint16_t offset = 2; 235 | while (offset < dataSize) { 236 | const uint8_t firstByte = data[offset++]; 237 | if (is_in_vector(firstByteOfTwoByteTokens, firstByte)) { 238 | if (offset >= dataSize) { 239 | break; 240 | } 241 | uint8_t secondByte = data[offset++]; 242 | if (firstByte == 0xBB) { 243 | if (secondByte > maxBB) maxBB = secondByte; 244 | } else if (firstByte == 0xEF) { 245 | if (secondByte <= 0x10) usesRTC = true; 246 | if (secondByte > maxEF) maxEF = secondByte; 247 | } 248 | } 249 | } 250 | uint8_t version = usesRTC ? 0x20 : 0x00; 251 | if (maxBB > 0xF5 || maxEF > 0xA6) { 252 | version = 0xFF; 253 | } else if (maxEF > 0x98) { 254 | version |= 0x0C; 255 | } else if (maxEF > 0x75) { 256 | version |= 0x0B; 257 | } else if (maxEF > 0x40) { 258 | version |= 0x0A; 259 | } else if (maxEF > 0x3E) { 260 | version |= 0x07; 261 | } else if (maxEF > 0x16) { 262 | version |= 0x06; 263 | } else if (maxEF > 0x12) { 264 | version |= 0x05; 265 | } else if (maxEF > -1) { 266 | version |= 0x04; 267 | } else if (maxBB > 0xDA) { 268 | version |= 0x03; 269 | } else if (maxBB > 0xCE) { 270 | version |= 0x02; 271 | } else if (maxBB > 0x67) { 272 | version |= 0x01; 273 | } 274 | return version; 275 | } 276 | 277 | std::string TH_Tokenized::reindentCodeString(const std::string& str_orig, const options_t& options) 278 | { 279 | uint8_t lang; 280 | if (has_option(options, "lang")) 281 | { 282 | lang = options.at("lang"); 283 | } else if (str_orig.size() > 1 && str_orig[0] == '.' && (str_orig[1] == '.' || isalpha(str_orig[1]))) { 284 | lang = PRGMLANG_AXE; 285 | } else if (str_orig.substr(0, sizeof("\U0001D456") - 1) == "\U0001D456") { 286 | lang = PRGMLANG_ICE; 287 | } else { 288 | lang = PRGMLANG_BASIC; 289 | } 290 | 291 | char indent_char = INDENT_CHAR_SPACE; 292 | if (has_option(options, "indent_char") && options.at("indent_char") == INDENT_CHAR_TAB) 293 | { 294 | indent_char = INDENT_CHAR_TAB; 295 | } 296 | 297 | size_t indent_n = indent_char == INDENT_CHAR_SPACE ? 3 : 1; 298 | if (has_option(options, "indent_n")) 299 | { 300 | const size_t wanted_indent_n = options.at("indent_n"); 301 | if (wanted_indent_n != indent_n) 302 | { 303 | indent_n = wanted_indent_n; 304 | } 305 | } 306 | 307 | std::string str(str_orig); 308 | 309 | str = std::regex_replace(str, std::regex("([^\\s])(Del|Eff)Var "), "$1\n$2Var "); 310 | 311 | std::vector lines_tmp = explode(str, '\n'); 312 | 313 | // Inplace-replace the appropriate ":" by new-line chars (ie, by inserting the split string in the lines_tmp array) 314 | for (size_t idx = 0; idx < lines_tmp.size(); idx++) 315 | { 316 | const auto line = lines_tmp[idx]; 317 | bool isWithinString = false; 318 | for (size_t strIdx = 0; strIdx < line.size(); strIdx++) 319 | { 320 | const auto& currChar = line.substr(strIdx, 1); 321 | if (currChar == ":" && !isWithinString) 322 | { 323 | lines_tmp[idx] = line.substr(0, strIdx); // replace "old" line by lhs 324 | lines_tmp.insert(lines_tmp.begin() + idx + 1, line.substr(strIdx + 1)); // inserting rhs 325 | break; 326 | } else if (currChar == "\"") { 327 | isWithinString = !isWithinString; 328 | } else if (currChar == "\n" || (strIdx < line.length()-strlen("→") && memcmp(&line[strIdx], "→", strlen("→")) == 0)) { 329 | isWithinString = false; 330 | } 331 | } 332 | } 333 | 334 | // Take care of NBSP stuff 335 | for (auto& line : lines_tmp) 336 | { 337 | line = std::regex_replace(line, std::regex("^[\u00A0\uC2A0]*\\s*[\u00A0\uC2A0]*"), ""); 338 | } 339 | 340 | std::vector> lines(lines_tmp.size()); // indent, text 341 | for (const auto& line : lines_tmp) 342 | { 343 | lines.emplace_back(0, line); 344 | } 345 | 346 | std::vector increaseIndentAfter = { "If", "For", "While", "Repeat" }; 347 | std::vector decreaseIndentOfToken = { "Then", "Else", "End", "ElseIf", "EndIf", "End!If" }; 348 | std::vector closingTokens = { "End", "EndIf", "End!If" }; 349 | uint16_t nextIndent = 0; 350 | std::string oldFirstCommand, firstCommand; 351 | for (auto& line : lines) 352 | { 353 | oldFirstCommand = firstCommand; 354 | 355 | std::string trimmedLine = trim(line.second); 356 | if (!trimmedLine.empty()) { 357 | char* trimmedLine_c = (char*) trimmedLine.c_str(); 358 | firstCommand = strtok(trimmedLine_c, " "); 359 | firstCommand = trim(firstCommand); 360 | trimmedLine = std::string(trimmedLine_c); 361 | if (firstCommand == trimmedLine) 362 | { 363 | firstCommand = strtok((char*)trimmedLine.c_str(), "("); 364 | firstCommand = trim(firstCommand); 365 | } 366 | } else { 367 | firstCommand = ""; 368 | } 369 | 370 | line.first = nextIndent; 371 | 372 | if (is_in_vector(increaseIndentAfter, firstCommand)) 373 | { 374 | nextIndent++; 375 | } 376 | if (line.first > 0 && is_in_vector(decreaseIndentOfToken, firstCommand)) 377 | { 378 | line.first--; 379 | } 380 | if (nextIndent > 0 && (is_in_vector(closingTokens, firstCommand) || (oldFirstCommand == "If" && firstCommand != "Then" && lang != PRGMLANG_AXE && lang != PRGMLANG_ICE))) 381 | { 382 | nextIndent--; 383 | } 384 | } 385 | 386 | str = ""; 387 | for (const auto& line : lines) 388 | { 389 | str += std::string(line.first * indent_n, indent_char) + line.second + '\n'; 390 | } 391 | 392 | return ltrim(rtrim(str, "\t\n\r\f\v")); 393 | } 394 | 395 | std::string TH_Tokenized::tokenToString(const data_t& data, int *incr, const options_t& options) 396 | { 397 | const size_t dataSize = data.size(); 398 | 399 | const uint8_t currentToken = data[0]; 400 | const uint8_t nextToken = dataSize > 1 ? data[1] : (uint8_t)-1; 401 | uint16_t bytesKey = currentToken; 402 | const bool is2ByteTok = is_in_vector(firstByteOfTwoByteTokens, currentToken); 403 | 404 | if (incr) { 405 | *incr = is2ByteTok ? 2 : 1; 406 | } 407 | 408 | const uint8_t langIdx = (has_option(options, "lang") && options.at("lang") == LANG_FR) ? LANG_FR : LANG_EN; 409 | const bool fromPrettified = has_option(options, "prettify") && options.at("prettify") == 1; 410 | 411 | if (is2ByteTok) 412 | { 413 | if (nextToken == (uint8_t)-1) 414 | { 415 | std::cerr << "[Warning] Encountered an unfinished two-byte token!" << std::endl; 416 | return std::string(); 417 | } 418 | bytesKey = nextToken + (currentToken << 8); 419 | } 420 | 421 | std::string tokStr; 422 | if (tokens_BytesToName.find(bytesKey) != tokens_BytesToName.end()) 423 | { 424 | tokStr = tokens_BytesToName[bytesKey][langIdx]; 425 | } else { 426 | tokStr = " [???] "; 427 | } 428 | if (fromPrettified) 429 | { 430 | tokStr = std::regex_replace(tokStr, toPrettifyRX1, "$1"); 431 | tokStr = std::regex_replace(tokStr, toPrettifyRX2, "$1"); 432 | tokStr = std::regex_replace(tokStr, toPrettifyRX3, "$1"); 433 | } 434 | 435 | return tokStr; 436 | } 437 | 438 | std::string TH_Tokenized::oneTokenBytesToString(uint16_t tokenBytes) 439 | { 440 | if (tokenBytes < 0xFF && is_in_vector(firstByteOfTwoByteTokens, (uint8_t)(tokenBytes & 0xFF))) 441 | { 442 | std::cerr << "[Warning] Encountered an unfinished two-byte token!" << std::endl; 443 | return ""; 444 | } 445 | 446 | std::string tokStr; 447 | if (tokens_BytesToName.find(tokenBytes) != tokens_BytesToName.end()) 448 | { 449 | tokStr = tokens_BytesToName[tokenBytes][LANG_EN]; 450 | } else { 451 | tokStr = " [???] "; 452 | } 453 | 454 | tokStr = std::regex_replace(tokStr, toPrettifyRX1, "$1"); 455 | tokStr = std::regex_replace(tokStr, toPrettifyRX2, "$1"); 456 | tokStr = std::regex_replace(tokStr, toPrettifyRX3, "$1"); 457 | 458 | return tokStr; 459 | } 460 | 461 | TH_Tokenized::token_posinfo TH_Tokenized::getPosInfoAtOffset(const data_t& data, uint16_t byteOffset, const options_t& options) 462 | { 463 | const size_t dataSize = data.size(); 464 | 465 | if (byteOffset >= dataSize) 466 | { 467 | throw std::invalid_argument("byteOffset cannot be >= dataSize!"); 468 | } 469 | 470 | if (dataSize >= 2) 471 | { 472 | const uint16_t twoFirstBytes = (uint16_t) ((data[1] & 0xFF) + ((data[0] & 0xFF) << 8)); 473 | if (std::find(std::begin(squishedASMTokens), std::end(squishedASMTokens), twoFirstBytes) != std::end(squishedASMTokens)) 474 | { 475 | throw std::invalid_argument("This is a squished ASM program - cannot process it!"); 476 | } 477 | } 478 | 479 | token_posinfo posinfo = { 0, 0, 0 }; 480 | 481 | const uint8_t langIdx = (has_option(options, "lang") && options.at("lang") == LANG_FR) ? LANG_FR : LANG_EN; 482 | const bool fromPrettified = has_option(options, "prettify") && options.at("prettify") == 1; 483 | 484 | // Find line number 485 | uint16_t lastNewLineOffset = 0; 486 | for (uint16_t i = 0; i < byteOffset; i++) 487 | { 488 | if (data[i] == 0x3F) // newline token code 489 | { 490 | posinfo.line++; 491 | lastNewLineOffset = i; 492 | } 493 | } 494 | 495 | // Find column number and token length if byteOffset is reached 496 | for (uint16_t i = std::max(2, lastNewLineOffset+1); i <= byteOffset; i++) 497 | { 498 | const uint8_t currentToken = data[i]; 499 | uint8_t nextToken = (i < dataSize-1) ? data[i+1] : (uint8_t)-1; 500 | uint16_t bytesKey = currentToken; 501 | const bool is2ByteTok = is_in_vector(firstByteOfTwoByteTokens, currentToken); 502 | const uint16_t currIdx = i; 503 | 504 | if (is2ByteTok) 505 | { 506 | if (nextToken == (uint8_t)-1) 507 | { 508 | std::cerr << "[Warning] Encountered an unfinished two-byte token! Setting the second byte to 0x00" << std::endl; 509 | nextToken = 0x00; 510 | } 511 | bytesKey = nextToken + (currentToken << 8); 512 | i++; 513 | } 514 | 515 | std::string tokStr; 516 | if (tokens_BytesToName.find(bytesKey) != tokens_BytesToName.end()) 517 | { 518 | tokStr = tokens_BytesToName[bytesKey][langIdx]; 519 | } else { 520 | tokStr = " [???] "; 521 | } 522 | if (fromPrettified) 523 | { 524 | tokStr = std::regex_replace(tokStr, toPrettifyRX1, "$1"); 525 | tokStr = std::regex_replace(tokStr, toPrettifyRX2, "$1"); 526 | tokStr = std::regex_replace(tokStr, toPrettifyRX3, "$1"); 527 | } 528 | 529 | const size_t tokStrLen = strlen_mb(tokStr); 530 | posinfo.column += (uint16_t)tokStrLen; 531 | 532 | if (posinfo.len == 0 && ((currIdx == byteOffset && !is2ByteTok) || (currIdx >= byteOffset-1 && is2ByteTok))) 533 | { 534 | posinfo.len = (uint8_t)tokStrLen; 535 | posinfo.column -= posinfo.len; // column will be the beginning of the token 536 | } 537 | } 538 | 539 | return posinfo; 540 | } 541 | 542 | TH_Tokenized::token_posinfo TH_Tokenized::getPosInfoAtOffsetFromHexStr(const std::string& hexBytesStr, uint16_t byteOffset) 543 | { 544 | const size_t strLen = hexBytesStr.length(); 545 | if (strLen % 2 != 0 || strLen > 65500 * 2) // todo: actual len? 546 | { 547 | throw std::invalid_argument("invalid hexBytesStr length!"); 548 | } 549 | 550 | data_t data; 551 | for (size_t i = 0; i < strLen; i += 2) { 552 | data.push_back((char) strtol(hexBytesStr.substr(i, 2).c_str(), nullptr, 16)); 553 | } 554 | 555 | return getPosInfoAtOffset(data, byteOffset, { { "prettify", 1 } }); 556 | } 557 | 558 | void TH_Tokenized::initTokens() 559 | { 560 | #ifndef CEMU_VERSION 561 | initTokensFromCSVFilePath("programs_tokens.csv"); 562 | #else 563 | std::string csvFileStr; 564 | QFile inputFile(QStringLiteral(":/other/tivars_lib_cpp/programs_tokens.csv")); 565 | if (inputFile.open(QIODevice::ReadOnly)) 566 | { 567 | csvFileStr = inputFile.readAll().toStdString(); 568 | } 569 | 570 | initTokensFromCSVContent(csvFileStr); 571 | #endif 572 | } 573 | 574 | void TH_Tokenized::initTokensFromCSVFilePath(const std::string& csvFilePath) 575 | { 576 | std::ifstream t(csvFilePath); 577 | const std::string csvFileStr((std::istreambuf_iterator(t)), std::istreambuf_iterator()); 578 | initTokensFromCSVContent(csvFileStr); 579 | } 580 | 581 | void TH_Tokenized::initTokensFromCSVContent(const std::string& csvFileStr) 582 | { 583 | if (!csvFileStr.empty()) 584 | { 585 | std::vector> lines; 586 | ParseCSV(csvFileStr, lines); 587 | 588 | for (const auto& tokenInfo : lines) 589 | { 590 | uint16_t bytes; 591 | if (tokenInfo[6] == "2") // number of bytes for the token 592 | { 593 | if (!is_in_vector(firstByteOfTwoByteTokens, hexdec(tokenInfo[7]))) 594 | { 595 | firstByteOfTwoByteTokens.push_back(hexdec(tokenInfo[7])); 596 | } 597 | bytes = hexdec(tokenInfo[8]) + (hexdec(tokenInfo[7]) << 8); 598 | } else { 599 | bytes = hexdec(tokenInfo[7]); 600 | } 601 | tokens_BytesToName[bytes] = { tokenInfo[4], tokenInfo[5] }; // EN, FR 602 | tokens_NameToBytes[tokenInfo[4]] = bytes; // EN 603 | tokens_NameToBytes[tokenInfo[5]] = bytes; // FR 604 | const uint8_t maxLenName = (uint8_t) std::max(tokenInfo[4].length(), tokenInfo[5].length()); 605 | if (maxLenName > lengthOfLongestTokenName) 606 | { 607 | lengthOfLongestTokenName = maxLenName; 608 | } 609 | } 610 | } else { 611 | throw std::runtime_error("Could not open the tokens CSV file or read its data"); 612 | } 613 | } 614 | 615 | } 616 | 617 | #ifdef __EMSCRIPTEN__ 618 | #include 619 | using namespace emscripten; 620 | EMSCRIPTEN_BINDINGS(_thtokenized) { 621 | 622 | value_object("token_posinfo") 623 | .field("line", &tivars::TypeHandlers::TH_Tokenized::token_posinfo::line) 624 | .field("column", &tivars::TypeHandlers::TH_Tokenized::token_posinfo::column) 625 | .field("len", &tivars::TypeHandlers::TH_Tokenized::token_posinfo::len); 626 | 627 | function("TH_Tokenized_getPosInfoAtOffsetFromHexStr", &tivars::TypeHandlers::TH_Tokenized::getPosInfoAtOffsetFromHexStr); 628 | function("TH_Tokenized_oneTokenBytesToString" , &tivars::TypeHandlers::TH_Tokenized::oneTokenBytesToString); 629 | } 630 | #endif 631 | -------------------------------------------------------------------------------- /src/TypeHandlers/TypeHandlers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #ifndef TYPE_HANDLERS_H 9 | #define TYPE_HANDLERS_H 10 | 11 | #include "../CommonTypes.h" 12 | 13 | namespace tivars 14 | { 15 | class TIVarFile; 16 | } 17 | 18 | namespace tivars::TypeHandlers 19 | { 20 | #define th() static data_t makeDataFromString(const std::string& str, const options_t& options = options_t(), const TIVarFile* _ctx = nullptr); \ 21 | static std::string makeStringFromData(const data_t& data, const options_t& options = options_t(), const TIVarFile* _ctx = nullptr); \ 22 | static uint8_t getMinVersionFromData(const data_t& data); 23 | 24 | class DummyHandler 25 | { 26 | public: 27 | DummyHandler() = delete; 28 | DummyHandler(const DummyHandler&) = delete; 29 | DummyHandler& operator=(const DummyHandler) = delete; 30 | th(); 31 | }; 32 | 33 | using TypeHandlersTuple = std::tuple; 34 | #define SpecificHandlerTuple(which) TypeHandlersTuple{ &(which::makeDataFromString), &(which::makeStringFromData), &(which::getMinVersionFromData) } 35 | 36 | class TH_GenericReal : public DummyHandler 37 | { 38 | public: 39 | th(); 40 | static const constexpr size_t dataByteCount = 9; 41 | }; 42 | 43 | class STH_FP : public TH_GenericReal 44 | { 45 | public: 46 | th(); 47 | static const constexpr size_t dataByteCount = 9; 48 | static const constexpr char* validPattern = "([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]{1,2})?)"; 49 | }; 50 | 51 | class STH_ExactFraction : public TH_GenericReal 52 | { 53 | public: 54 | th(); 55 | static const constexpr size_t dataByteCount = 9; 56 | static const constexpr char* validPattern = "===UNIMPLEMENTED==="; // TODO 57 | }; 58 | 59 | class STH_ExactRadical : public TH_GenericReal 60 | { 61 | public: 62 | th(); 63 | static const constexpr size_t dataByteCount = 9; 64 | static const constexpr char* validPattern = "===UNIMPLEMENTED==="; // TODO 65 | }; 66 | 67 | class STH_ExactPi : public TH_GenericReal 68 | { 69 | public: 70 | th(); 71 | static const constexpr size_t dataByteCount = 9; 72 | static const constexpr char* validPattern = "===UNIMPLEMENTED==="; // TODO 73 | }; 74 | 75 | class STH_ExactFractionPi : public TH_GenericReal 76 | { 77 | public: 78 | th(); 79 | static const constexpr size_t dataByteCount = 9; 80 | static const constexpr char* validPattern = "===UNIMPLEMENTED==="; // TODO 81 | }; 82 | 83 | class TH_GenericComplex : public DummyHandler 84 | { 85 | public: 86 | th(); 87 | static const constexpr size_t dataByteCount = 2 * TH_GenericReal::dataByteCount; 88 | }; 89 | 90 | class TH_GenericList : public DummyHandler 91 | { 92 | public: 93 | th(); 94 | }; 95 | 96 | class TH_Matrix : public DummyHandler 97 | { 98 | public: 99 | th(); 100 | }; 101 | 102 | class TH_GenericAppVar : public DummyHandler 103 | { 104 | public: 105 | th(); 106 | }; 107 | 108 | class STH_DataAppVar : public TH_GenericAppVar 109 | { 110 | public: 111 | th(); 112 | }; 113 | 114 | class STH_PythonAppVar : public TH_GenericAppVar 115 | { 116 | public: 117 | th(); 118 | static const constexpr char ID_SCRIPT[] = "PYSC"; 119 | static const constexpr char ID_CODE[] = "PYCD"; 120 | }; 121 | 122 | class TH_GDB : public DummyHandler 123 | { 124 | public: 125 | th(); 126 | static const constexpr size_t dataByteCountMinimum = 100; 127 | static const constexpr char* magic84CAndLaterSectionMarker = "84C"; 128 | }; 129 | 130 | // Program, Protected Program, Y-Variable, String 131 | class TH_Tokenized : public DummyHandler 132 | { 133 | public: 134 | th(); 135 | enum lang { LANG_EN = 0, LANG_FR }; 136 | enum typelang { PRGMLANG_BASIC = 0, PRGMLANG_AXE, PRGMLANG_ICE }; 137 | enum indentchar : char { INDENT_CHAR_SPACE = ' ', INDENT_CHAR_TAB = '\t' }; 138 | struct token_posinfo { uint16_t line; uint16_t column; uint8_t len; }; 139 | static std::string reindentCodeString(const std::string& str_orig, const options_t& options = options_t()); 140 | static token_posinfo getPosInfoAtOffset(const data_t& data, uint16_t byteOffset, const options_t& options = options_t()); 141 | static token_posinfo getPosInfoAtOffsetFromHexStr(const std::string& hexBytesStr, uint16_t byteOffset); 142 | static std::string tokenToString(const data_t& data, int *incr, const options_t& options); 143 | static std::string oneTokenBytesToString(uint16_t tokenBytes); 144 | static void initTokens(); 145 | static void initTokensFromCSVFilePath(const std::string& csvFilePath); 146 | static void initTokensFromCSVContent(const std::string& csvFileStr); 147 | }; 148 | 149 | // Special temporary type that may appear as an equation, during basic program execution 150 | class TH_TempEqu : public DummyHandler 151 | { 152 | public: 153 | th(); 154 | }; 155 | 156 | #undef th 157 | 158 | } 159 | 160 | #endif 161 | -------------------------------------------------------------------------------- /src/main_emscripten.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #ifdef __EMSCRIPTEN__ 9 | 10 | #include "TIModels.h" 11 | #include "TIVarTypes.h" 12 | #include "TypeHandlers/TypeHandlers.h" 13 | 14 | #include 15 | #include 16 | 17 | using namespace tivars; 18 | 19 | extern "C" int EMSCRIPTEN_KEEPALIVE main(int, char**) 20 | { 21 | setlocale(LC_ALL, ".UTF-8"); 22 | 23 | TIModels::initTIModelsArray(); 24 | TIVarTypes::initTIVarTypesArray(); 25 | TypeHandlers::TH_Tokenized::initTokens(); 26 | 27 | puts("tivars_lib ready!"); 28 | 29 | return 0; 30 | } 31 | 32 | #endif -------------------------------------------------------------------------------- /src/tivarslib_utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #include "tivarslib_utils.h" 9 | #include 10 | #include 11 | 12 | namespace tivars 13 | { 14 | 15 | bool has_option(const options_t& m, const std::string& element) 16 | { 17 | return m.find(element) != m.end(); 18 | } 19 | 20 | unsigned char hexdec(const std::string& str) 21 | { 22 | return (unsigned char) stoul(str, nullptr, 16); 23 | } 24 | 25 | std::string dechex(unsigned char i, bool zeropad) 26 | { 27 | std::string str = "00"; 28 | snprintf(&str[0], 3, zeropad ? "%02X" : "%X", i); 29 | return str; 30 | } 31 | 32 | std::string strtoupper(const std::string& str) 33 | { 34 | std::string newStr(str); 35 | for (char& c : newStr) 36 | { 37 | c = (char) toupper(c); 38 | } 39 | return newStr; 40 | } 41 | 42 | std::vector explode(const std::string& str, const std::string& delim) 43 | { 44 | std::vector result; 45 | 46 | size_t last = 0; 47 | size_t next = 0; 48 | while ((next = str.find(delim, last)) != std::string::npos) 49 | { 50 | result.push_back(str.substr(last, next - last)); 51 | last = next + delim.length(); 52 | } 53 | result.push_back(str.substr(last)); 54 | 55 | return result; 56 | } 57 | 58 | std::vector explode(const std::string& str, char delim) 59 | { 60 | return explode(str, std::string(1, delim)); 61 | } 62 | 63 | // trim from start 64 | std::string ltrim(std::string s, const char* t) 65 | { 66 | s.erase(0, s.find_first_not_of(t)); 67 | return s; 68 | } 69 | 70 | // trim from end 71 | std::string rtrim(std::string s, const char* t) 72 | { 73 | s.erase(s.find_last_not_of(t) + 1); 74 | return s; 75 | } 76 | 77 | // trim from both ends 78 | std::string trim(const std::string& s, const char* t) 79 | { 80 | return ltrim(rtrim(s, t), t); 81 | } 82 | 83 | // From http://stackoverflow.com/a/2481126/378298 84 | void ParseCSV(const std::string& csvSource, std::vector>& lines) 85 | { 86 | bool inQuote(false); 87 | bool newLine(false); 88 | std::string field; 89 | lines.clear(); 90 | std::vector line; 91 | 92 | std::string::const_iterator aChar = csvSource.begin(); 93 | std::string::const_iterator tmp; 94 | 95 | while (aChar != csvSource.end()) 96 | { 97 | tmp = aChar; 98 | switch (*aChar) 99 | { 100 | case '"': 101 | newLine = false; 102 | // Handle weird escaping of quotes ("""" => ") 103 | if (*(tmp+1) == '"' && *(tmp+2) == '"' && *(tmp+3) == '"') { 104 | field.push_back(*aChar); 105 | aChar += 3; 106 | } else { 107 | inQuote = !inQuote; 108 | } 109 | break; 110 | 111 | case ',': 112 | newLine = false; 113 | if (inQuote) 114 | { 115 | field += *aChar; 116 | } 117 | else 118 | { 119 | line.push_back(field); 120 | field.clear(); 121 | } 122 | break; 123 | 124 | case '\n': 125 | case '\r': 126 | if (inQuote) 127 | { 128 | field += *aChar; 129 | } 130 | else 131 | { 132 | if (!newLine) 133 | { 134 | line.push_back(field); 135 | lines.push_back(line); 136 | field.clear(); 137 | line.clear(); 138 | newLine = true; 139 | } 140 | } 141 | break; 142 | 143 | default: 144 | newLine = false; 145 | field.push_back(*aChar); 146 | break; 147 | } 148 | 149 | ++aChar; 150 | } 151 | 152 | if (!field.empty()) 153 | line.push_back(field); 154 | 155 | if (!line.empty()) 156 | lines.push_back(line); 157 | } 158 | 159 | bool is_numeric(const std::string& str) 160 | { 161 | char* p; 162 | const double ignored = strtod(str.c_str(), &p); 163 | (void)ignored; 164 | return (bool)!*p; 165 | } 166 | 167 | bool file_exists(const std::string& path) { 168 | if (path.empty()) { 169 | return false; 170 | } 171 | if (FILE *file = fopen(path.c_str(), "r")) { 172 | fclose(file); 173 | return true; 174 | } else { 175 | return false; 176 | } 177 | } 178 | 179 | std::string str_pad(const std::string& str, unsigned long pad_length, std::string pad_string) 180 | { 181 | unsigned long i, j, x; 182 | const unsigned long str_size = str.size(); 183 | const unsigned long pad_size = pad_string.size(); 184 | 185 | if (pad_length <= str_size || pad_size < 1) 186 | { 187 | return str; 188 | } 189 | 190 | std::string o; 191 | o.reserve(pad_length); 192 | 193 | for (i = 0, x = str_size; i < x; i++) 194 | { 195 | o.push_back(str[i]); 196 | } 197 | for (i = str_size; i < pad_length;) 198 | { 199 | for (j = 0; j < pad_size && i < pad_length; j++, i++) 200 | { 201 | o.push_back(pad_string[j]); 202 | } 203 | } 204 | 205 | return o; 206 | } 207 | 208 | std::string multiple(int num, const std::string &var) { 209 | const std::string unit = var.empty() ? "1" : var; 210 | switch (num) { 211 | case 0: 212 | return "0"; 213 | case 1: 214 | return unit; 215 | case -1: 216 | return "-" + unit; 217 | default: 218 | return std::to_string(num) + var; 219 | } 220 | } 221 | 222 | // Adapted from http://stackoverflow.com/a/32903747/378298 223 | std::string dec2frac(double num, const std::string& var, double err) 224 | { 225 | if (err <= 0.0 || err >= 1.0) 226 | { 227 | err = 0.001; 228 | } 229 | 230 | const int sign = ( num > 0 ) ? 1 : ( ( num < 0 ) ? -1 : 0 ); 231 | 232 | if (sign == -1) 233 | { 234 | num = std::abs(num); 235 | } 236 | 237 | if (sign != 0) 238 | { 239 | // err is the maximum relative err; convert to absolute 240 | err *= num; 241 | } 242 | 243 | const int n = (int) std::floor(num); 244 | num -= n; 245 | 246 | if (num < err) 247 | { 248 | return multiple(sign * n, var); 249 | } 250 | 251 | if (1 - err < num) 252 | { 253 | return multiple(sign * (n + 1), var); 254 | } 255 | 256 | // The lower fraction is 0/1 257 | int lower_n = 0; 258 | int lower_d = 1; 259 | 260 | // The upper fraction is 1/1 261 | int upper_n = 1; 262 | int upper_d = 1; 263 | 264 | while (true) 265 | { 266 | // The middle fraction is (lower_n + upper_n) / (lower_d + upper_d) 267 | const int middle_n = lower_n + upper_n; 268 | const int middle_d = lower_d + upper_d; 269 | 270 | if (middle_d * (num + err) < middle_n) 271 | { 272 | // real + err < middle : middle is our new upper 273 | upper_n = middle_n; 274 | upper_d = middle_d; 275 | } 276 | else if (middle_n < (num - err) * middle_d) 277 | { 278 | // middle < real - err : middle is our new lower 279 | lower_n = middle_n; 280 | lower_d = middle_d; 281 | } else { 282 | // Middle is our best fraction 283 | return multiple((n * middle_d + middle_n) * sign, var) + "/" + std::to_string(middle_d); 284 | } 285 | } 286 | } 287 | 288 | std::string trimZeros(const std::string& str) 289 | { 290 | return std::to_string(std::stoi(str)); 291 | } 292 | 293 | } -------------------------------------------------------------------------------- /src/tivarslib_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of tivars_lib_cpp 3 | * (C) 2015-2021 Adrien "Adriweb" Bertrand 4 | * https://github.com/adriweb/tivars_lib_cpp 5 | * License: MIT 6 | */ 7 | 8 | #ifndef TIVARSLIB_UTILS_H 9 | #define TIVARSLIB_UTILS_H 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "CommonTypes.h" 16 | #include "TIModel.h" 17 | #include "TIVarType.h" 18 | 19 | namespace tivars 20 | { 21 | 22 | template 23 | bool is_in_vector(const std::vector& v, T element) 24 | { 25 | return std::find(v.begin(), v.end(), element) != v.end(); 26 | } 27 | 28 | template 29 | void vector_append(std::vector& vec, const std::vector& other) 30 | { 31 | vec.insert(vec.end(), other.begin(), other.end()); 32 | } 33 | 34 | bool has_option(const options_t& m, const std::string& element); 35 | 36 | unsigned char hexdec(const std::string& str); 37 | 38 | std::string dechex(unsigned char i, bool zeropad = true); 39 | 40 | std::string strtoupper(const std::string& str); 41 | 42 | std::vector explode(const std::string& str, const std::string& delim); 43 | std::vector explode(const std::string& str, char delim); 44 | 45 | std::string ltrim(std::string s, const char* t = " \t\n\r\f\v"); 46 | 47 | std::string rtrim(std::string s, const char* t = " \t\n\r\f\v"); 48 | 49 | std::string trim(const std::string& s, const char* t = " \t\n\r\f\v"); 50 | 51 | void ParseCSV(const std::string& csvSource, std::vector>& lines); 52 | 53 | bool is_numeric(const std::string& str); 54 | 55 | bool file_exists(const std::string& filePath); 56 | 57 | std::string str_pad(const std::string& str, unsigned long pad_length, std::string pad_string = " "); 58 | 59 | std::string multiple(int num, const std::string& var); 60 | 61 | std::string dec2frac(double num, const std::string& var = "", double err = 0.001); 62 | 63 | std::string trimZeros(const std::string& str); 64 | 65 | } 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /testData/ALLTOKS.8Xp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/ALLTOKS.8Xp -------------------------------------------------------------------------------- /testData/AppVar.8xv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/AppVar.8xv -------------------------------------------------------------------------------- /testData/Complex.8xc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/Complex.8xc -------------------------------------------------------------------------------- /testData/ComplexList.8xl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/ComplexList.8xl -------------------------------------------------------------------------------- /testData/Equation_X1T.8xy: -------------------------------------------------------------------------------- 1 | **TI83F* 2 | 3 | Created by TI Connect CE 5.1.0.68 ^ 2T -------------------------------------------------------------------------------- /testData/Equation_Y1.8xy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/Equation_Y1.8xy -------------------------------------------------------------------------------- /testData/Equation_Y1T.8xy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/Equation_Y1T.8xy -------------------------------------------------------------------------------- /testData/Equation_r1.8xy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/Equation_r1.8xy -------------------------------------------------------------------------------- /testData/Equation_u.8xy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/Equation_u.8xy -------------------------------------------------------------------------------- /testData/Exact_ComplexFrac.8xc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/Exact_ComplexFrac.8xc -------------------------------------------------------------------------------- /testData/Exact_ComplexPi.8xc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/Exact_ComplexPi.8xc -------------------------------------------------------------------------------- /testData/Exact_ComplexPiFrac.8xc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/Exact_ComplexPiFrac.8xc -------------------------------------------------------------------------------- /testData/Exact_ComplexRadical.8xc: -------------------------------------------------------------------------------- 1 | **TI83F* 2 | Created by TI Connect CE 5.1.0.68# G   u -------------------------------------------------------------------------------- /testData/Exact_RealPi.8xn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/Exact_RealPi.8xn -------------------------------------------------------------------------------- /testData/Exact_RealPiFrac.8xn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/Exact_RealPiFrac.8xn -------------------------------------------------------------------------------- /testData/Exact_RealRadical.8xn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/Exact_RealRadical.8xn -------------------------------------------------------------------------------- /testData/GDB_Parametric.json: -------------------------------------------------------------------------------- 1 | { 2 | "graphMode": "Parametric", 3 | "formatSettings": [ 4 | "Connected", 5 | "Sequential", 6 | "GridOff", 7 | "RectGC", 8 | "CoordOn", 9 | "AxesOn", 10 | "LabelOff" 11 | ], 12 | "extSettings": { 13 | "showExpr": true 14 | }, 15 | "globalWindowSettings": { 16 | "Xmin": -10.0, 17 | "Xmax": 10.0, 18 | "Xscl": 1.0, 19 | "Ymin": -10.0, 20 | "Ymax": 10.0, 21 | "Yscl": 1.0 22 | }, 23 | "specificData": { 24 | "settings": { 25 | "Tmin": 0.0, 26 | "Tmax": 6.283185307, 27 | "Tstep": 0.13089969389957 28 | }, 29 | "equations": { 30 | "X1T": { 31 | "style": "ThickLine", 32 | "color": "Blue", 33 | "flags": { 34 | "selected": false, 35 | "wasUsedForGraph": false, 36 | "linkTransfer": false 37 | }, 38 | "expr": "" 39 | }, 40 | "Y1T": { 41 | "style": "ThickLine", 42 | "color": "Blue", 43 | "flags": { 44 | "selected": false, 45 | "wasUsedForGraph": false, 46 | "linkTransfer": false 47 | }, 48 | "expr": "" 49 | }, 50 | "X2T": { 51 | "style": "ThickLine", 52 | "color": "Red", 53 | "flags": { 54 | "selected": false, 55 | "wasUsedForGraph": false, 56 | "linkTransfer": false 57 | }, 58 | "expr": "" 59 | }, 60 | "Y2T": { 61 | "style": "ThickLine", 62 | "color": "Red", 63 | "flags": { 64 | "selected": false, 65 | "wasUsedForGraph": false, 66 | "linkTransfer": false 67 | }, 68 | "expr": "" 69 | }, 70 | "X3T": { 71 | "style": "ThickLine", 72 | "color": "Black", 73 | "flags": { 74 | "selected": false, 75 | "wasUsedForGraph": false, 76 | "linkTransfer": false 77 | }, 78 | "expr": "" 79 | }, 80 | "Y3T": { 81 | "style": "ThickLine", 82 | "color": "Black", 83 | "flags": { 84 | "selected": false, 85 | "wasUsedForGraph": false, 86 | "linkTransfer": false 87 | }, 88 | "expr": "" 89 | }, 90 | "X4T": { 91 | "style": "ThickLine", 92 | "color": "Magenta", 93 | "flags": { 94 | "selected": false, 95 | "wasUsedForGraph": false, 96 | "linkTransfer": false 97 | }, 98 | "expr": "" 99 | }, 100 | "Y4T": { 101 | "style": "ThickLine", 102 | "color": "Magenta", 103 | "flags": { 104 | "selected": false, 105 | "wasUsedForGraph": false, 106 | "linkTransfer": false 107 | }, 108 | "expr": "" 109 | }, 110 | "X5T": { 111 | "style": "ThickLine", 112 | "color": "Green", 113 | "flags": { 114 | "selected": false, 115 | "wasUsedForGraph": false, 116 | "linkTransfer": false 117 | }, 118 | "expr": "" 119 | }, 120 | "Y5T": { 121 | "style": "ThickLine", 122 | "color": "Green", 123 | "flags": { 124 | "selected": false, 125 | "wasUsedForGraph": false, 126 | "linkTransfer": false 127 | }, 128 | "expr": "" 129 | }, 130 | "X6T": { 131 | "style": "ThickLine", 132 | "color": "Orange", 133 | "flags": { 134 | "selected": false, 135 | "wasUsedForGraph": false, 136 | "linkTransfer": false 137 | }, 138 | "expr": "" 139 | }, 140 | "Y6T": { 141 | "style": "ThickLine", 142 | "color": "Orange", 143 | "flags": { 144 | "selected": false, 145 | "wasUsedForGraph": false, 146 | "linkTransfer": false 147 | }, 148 | "expr": "" 149 | } 150 | } 151 | }, 152 | "global84CSettings": { 153 | "colors": { 154 | "grid": "MedGray", 155 | "axes": "Black", 156 | "border": 1 157 | }, 158 | "other": { 159 | "globalLineStyle": "Thick", 160 | "detectAsymptotes": true 161 | } 162 | } 163 | } -------------------------------------------------------------------------------- /testData/GraphDataBase_Func.8xd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/GraphDataBase_Func.8xd -------------------------------------------------------------------------------- /testData/GraphDataBase_Param.8xd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/GraphDataBase_Param.8xd -------------------------------------------------------------------------------- /testData/Group.8xg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/Group.8xg -------------------------------------------------------------------------------- /testData/Matrix_2x2_exact.8xm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/Matrix_2x2_exact.8xm -------------------------------------------------------------------------------- /testData/Matrix_3x3_standard.8xm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/Matrix_3x3_standard.8xm -------------------------------------------------------------------------------- /testData/Program.8xp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/Program.8xp -------------------------------------------------------------------------------- /testData/ProtectedProgram.8xp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/ProtectedProgram.8xp -------------------------------------------------------------------------------- /testData/ProtectedProgram_long.8xp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/ProtectedProgram_long.8xp -------------------------------------------------------------------------------- /testData/Real.8xn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/Real.8xn -------------------------------------------------------------------------------- /testData/RealList.8xl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/RealList.8xl -------------------------------------------------------------------------------- /testData/RecallWindow.8xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/RecallWindow.8xz -------------------------------------------------------------------------------- /testData/String.8xs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/String.8xs -------------------------------------------------------------------------------- /testData/TableRange.8xt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/TableRange.8xt -------------------------------------------------------------------------------- /testData/Window.8xw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/Window.8xw -------------------------------------------------------------------------------- /testData/clibs.8xg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/clibs.8xg -------------------------------------------------------------------------------- /testData/python_HELLO.8xv: -------------------------------------------------------------------------------- 1 | **TI83F* 2 | 3 | Created by TI-Smartview CE 5.3.0.3846 %HELLO%#PYCDimport sys 4 | print(sys.version) 5 | r -------------------------------------------------------------------------------- /testData/testPrgmQuotes.8xp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriweb/tivars_lib_cpp/ef98dd96249736bbe70856cd75c7a6d0a257c2b9/testData/testPrgmQuotes.8xp --------------------------------------------------------------------------------