ascii85x (empty) → 0.2.4.1
raw patch · 17 files changed
+2398/−0 lines, 17 filesdep +JuicyPixelsdep +arraydep +ascii85xsetup-changed
Dependencies added: JuicyPixels, array, ascii85x, attoparsec, base, bytestring, hedgehog, optparse-applicative, text, vector
Files
- ChangeLog.md +37/−0
- LICENSE +30/−0
- README.md +106/−0
- Setup.hs +2/−0
- app/Main.hs +131/−0
- ascii85x.cabal +97/−0
- src/Data/TI85.hs +41/−0
- src/Data/TI85/Encoding.hs +43/−0
- src/Data/TI85/File.hs +56/−0
- src/Data/TI85/File/Backup.hs +50/−0
- src/Data/TI85/File/Variable.hs +227/−0
- src/Data/TI85/IO.hs +90/−0
- src/Data/TI85/Parsers.hs +539/−0
- src/Data/TI85/Token.hs +348/−0
- src/Data/TI85/Var.hs +463/−0
- src/Data/TI85/Var/Pic.hs +101/−0
- test/Spec.hs +37/−0
+ ChangeLog.md view
@@ -0,0 +1,37 @@+# Changelog for ascii85x++## 0.2.4.1++- Dependency updates++## 0.2.4.0++- Handle GDB variables++## 0.2.3.0++- Handle window settings++## 0.2.2.0++- Handle ZRCL settings++## 0.2.1.0++- Handle picture variables++## 0.2.0.0++- Also parse Backup files+- Significant refactor++## 0.1.2.1++- Internal change: Added testing++## 0.1.2.0++- Added complete set of variable IDs+- Make real/comlex a type parameter for some variable types++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Nigel Stepp (c) 2021++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Nigel Stepp nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,106 @@+# ascii85x++## Introduction++This module is meant to display information from+TI-85 variable files in a human readable way.++There were a few reasons for making this in 2021:++1. ascii85p, an ancient program for displaying+ program text, does not come with source code+2. TokenIDE, a more modern and very featureful+ tool, is written in C#, and I didn't want to+ use mono.+3. TokenIDE also does not come with source code.+4. Just let me have some fun.++## Background++Perhaps you, like many technically minded folks who+went through school with their trusty TI calculator,+have a catalog of TI-BASIC programs that you wrote+for both fun and utility.++If I look through my backup directory and see `RND.85p`,+I might wonder what's in there and try `vim RND.85p`:++```+**TI85**^Z^L^@Group file dated Sun Sep 26 16:12:47 202^@^@^L^A^G^@^A^A^R^CRND^A^Aÿ^@D0^@^K3AoD0^@^K3BoD0^@^K3CoD0^@^K3DoD0^@^K3SoD0^@^K=^DoD100^@^K=^Eo¡D35^@^K=^FoD35^@^K=^GoD25^@^K=^BoD5^@^K=^Coìo<91>o<83>oÛ^P^PCUD22^@^Q@^P3CSD500^@^Q^QoØ^P^P¥^PApD2^@^Q^QPD1^@^QoÙo3A`D1^@^K3AoÚo3B`D1^@^K3BoÞo3C`D1^@^K3Co3S`^P3Aa3B^Q^K3So<98>^P3CqD5^@/3Aa3B^Qo<98>^P3CqD5^@/3Sq3C^QoÞoë^PD2^@/D1^@/3Aoë^PD3^@/D1^@/3Boë^PD4^@/D1^@/3Sq3Cv?+```++Not so nice. As mentioned above, there are some options for decoding these files, but they didn't work for me, so I made `ascii85x`.++```+$ ascii85x RND.85p++Program "RND":+0→A+0→B+0→C+0→D+0→S+0→xMin+100→xMax+-35→yMin+35→yMax+25→xScl+5→yScl+ClLCD+FnOff +ClDrw+While ((getKy≠22) and (C<=500))+If ((int(rand*2))==1)+Then+A+1→A+Else+B+1→B+End+C+1→C+S+(A-B)→S+PtOn(C/5,A-B)+PtOn(C/5,S/C)+End+Outpt(2,1,A+Outpt(3,1,B+Outpt(4,1,S/C+```++Much better!++## Usage++`ascii85x` will read and display any variable or group file as output by the TI-85 link backup utility.+The variable types supported are:++| Type | Extension |+|------------|-----------|+|RealValue | 85n |+|ComplexValue| 85c |+|Vector | 85v |+|List | 85l |+|Matrix | 85m |+|Constant | 85k |+|Equation | 85y |+|String | 85s |+|Program | 85p |+|Picture | 85i |+|Settings | 85? |+|GDB | 85d |++A group file (`85g`) may contain many of these types of variables.++```+$ ascii85x --help+Usage: ascii85x [-i|--info] [-D|--debug] [-v|--verbose] VARFILE [-V|--version]+ Convert TI-85 variable files to text++Available options:+ -i,--info Show file info only+ -D,--debug Show extra variable details+ -v,--verbose Show variable file summary+ VARFILE 85x variable file+ -V,--version Show version only+ -h,--help Show this help text+```+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,131 @@+module Main where++import Prelude hiding (putStrLn,take,drop)+import Control.Monad+import Data.Text (pack,unpack)+import Data.Text.IO (putStrLn)+import Data.ByteString (foldr',take,drop)+import Data.Word+import Data.Version+import Numeric (showHex)+import System.Exit (exitSuccess)++import Data.Attoparsec.ByteString (parseOnly)+import Options.Applicative++import Data.TI85+import Paths_ascii85x (version)++data Config = Config {+ showInfo :: Bool,+ debug :: Bool,+ verbose :: Bool,+ imgOut :: Maybe FilePath,+ filePath :: FilePath+ }++versionOpt = infoOption (showVersion version) (+ long "version"+ <> short 'V'+ <> help "Show version only"+ )++argParser :: Parser Config+argParser = Config+ <$> switch (+ long "info"+ <> short 'i'+ <> help "Show file info only"+ )+ <*> switch (+ long "debug"+ <> short 'D'+ <> help "Show extra variable details"+ )+ <*> switch (+ long "verbose"+ <> short 'v'+ <> help "Show file summary"+ )+ <*> optional (strOption (+ long "image-dir"+ <> short 'I'+ <> metavar "DIR"+ <> help "Output PNG versions of PIC variables to this directory"+ ))+ <*> strArgument (+ metavar "FILE"+ <> help "85x file"+ )++argInfo = info (argParser <**> versionOpt <**> helper) (+ fullDesc <> progDesc "Convert TI-85 variable files to text"+ )++processVarData :: Config -> TIVarData -> IO ()+processVarData config (TIVarData tiVars) = do+ let vars = map readVariable tiVars+ when (debug config) $ print vars+ let names = map (tiDecode.varName) tiVars+ let types = map (showType.idToType.varId) tiVars+ forM_ (zip3 names vars types) $ \(name,var,varType) -> do+ putStrLn $ "\n" <> varType <> " \"" <> name <> "\":"+ printVariable var++ -- Check for a picture variable, and write+ -- to an image if configured to do so+ let pic = case var of+ TIPicture bitmap -> Just bitmap+ _ -> Nothing++ -- The following completes only when the variable is+ -- a picture and when the image path option is set+ sequence_ $ do+ bitmap <- pic+ imgPath <- imgOut config+ return $ do+ let imgFile = imgPath <> "/" <> unpack name <> ".png"+ writePicPng imgFile bitmap+ putStrLn $ "Saved " <> name <> " to " <> pack imgFile++processBackupData :: Config -> TIBackupData -> IO ()+processBackupData config tiBackup = do+ let backupHdr = backupHeader tiBackup+ let data2Addr = hdrData2Addr backupHdr+ let dataDisplay = if verbose config+ then print . foldr' hexify ""+ else print+ putStrLn $ pack $ "Data Section 1 (" <> show (data1Len tiBackup) <> "):"+ dataDisplay (data1 tiBackup)+ putStrLn $ pack $ "Data Section 2 (" <> show (data2Len tiBackup) <> "):"+ dataDisplay (data2 tiBackup)+ putStrLn $ pack $ "Data 2 Address: " <> showHex data2Addr "\n"+ putStrLn $ pack $ "Variable Table (" <> show (varTableLen tiBackup) <> "):"+ printVariableTable data2Addr (varTable tiBackup)++ when (verbose config) $ do+ extractVariableTable data2Addr tiBackup++ where+ hexify :: Word8 -> ShowS+ hexify w =+ let pad = if w < 0xf then "0" else ""+ in \s' -> pad <> showHex w s'++main :: IO ()+main = do+ config <- execParser argInfo++ tiFile <- readTIFile (filePath config)++ when (showInfo config) $ do+ printFileSummary tiFile+ exitSuccess++ when (verbose config) $ do+ printFileSummary tiFile++ case tiData tiFile of+ BackupData backup -> processBackupData config backup+ VariableData vars -> processVarData config vars+
+ ascii85x.cabal view
@@ -0,0 +1,97 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.37.0.+--+-- see: https://github.com/sol/hpack++name: ascii85x+version: 0.2.4.1+synopsis: Displays TI-85 variable files as text+description: Please see the README on GitHub at <https://github.com/nstepp/ascii85x#readme>+category: Data+homepage: https://github.com/nstepp/ascii85x#readme+bug-reports: https://github.com/nstepp/ascii85x/issues+author: Nigel Stepp+maintainer: stepp@atistar.net+copyright: 2021 Nigel Stepp+license: BSD3+license-file: LICENSE+build-type: Simple+extra-source-files:+ README.md+ ChangeLog.md++source-repository head+ type: git+ location: https://github.com/nstepp/ascii85x++library+ exposed-modules:+ Data.TI85+ Data.TI85.Encoding+ Data.TI85.File+ Data.TI85.File.Backup+ Data.TI85.File.Variable+ Data.TI85.IO+ Data.TI85.Parsers+ Data.TI85.Token+ Data.TI85.Var+ Data.TI85.Var.Pic+ other-modules:+ Paths_ascii85x+ hs-source-dirs:+ src+ default-extensions:+ OverloadedStrings+ build-depends:+ JuicyPixels >=3.3 && <4+ , array ==0.5.*+ , attoparsec >=0.13 && <0.15+ , base >=4.7 && <5+ , bytestring >=0.10 && <0.12+ , text >=1.2 && <3+ , vector >=0.12 && <1+ default-language: Haskell2010++executable ascii85x+ main-is: Main.hs+ other-modules:+ Paths_ascii85x+ hs-source-dirs:+ app+ default-extensions:+ OverloadedStrings+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ JuicyPixels >=3.3 && <4+ , array ==0.5.*+ , ascii85x+ , attoparsec >=0.13 && <0.15+ , base >=4.7 && <5+ , bytestring >=0.10 && <0.12+ , optparse-applicative >=0.18.1 && <0.19+ , text >=1.2 && <3+ , vector >=0.12 && <1+ default-language: Haskell2010++test-suite ascii85x-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ Paths_ascii85x+ hs-source-dirs:+ test+ default-extensions:+ OverloadedStrings+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ JuicyPixels >=3.3 && <4+ , array ==0.5.*+ , ascii85x+ , attoparsec >=0.13 && <0.15+ , base >=4.7 && <5+ , bytestring >=0.10 && <0.12+ , hedgehog+ , text >=1.2 && <3+ , vector >=0.12 && <1+ default-language: Haskell2010
+ src/Data/TI85.hs view
@@ -0,0 +1,41 @@+{-|+Module : Data.TI85+Description : TI-85 variable file utilities.+Copyright : (c) Nigel Stepp, 2021+License : BSD3+Maintainer : stepp@atistar.net+Stability : experimental+Portability : POSIX++This module is meant to display information from+TI-85 variable files in a human readable way.++There were a few reasons for making this in 2021:++1. ascii85p, an ancient program for displaying+ program text, does not come with source code+2. TokenIDE, a more modern and very featureful+ tool, is written in C#, and I didn't want to+ use mono.+3. TokenIDE also does not come with source code.+4. Just let me have some fun.++-}+module Data.TI85 (+ module Data.TI85.Encoding,+ module Data.TI85.Token,+ module Data.TI85.Parsers,+ module Data.TI85.Var,+ module Data.TI85.Var.Pic,+ module Data.TI85.File,+ module Data.TI85.IO+ ) where++import Data.TI85.Encoding+import Data.TI85.Token+import Data.TI85.Parsers+import Data.TI85.Var+import Data.TI85.Var.Pic+import Data.TI85.File+import Data.TI85.IO+
+ src/Data/TI85/Encoding.hs view
@@ -0,0 +1,43 @@++-- | Text data in a variable file is encoded using a+-- special purpose code page. This module supports+-- decoding such text, using an approximate projection+-- into unicode.+module Data.TI85.Encoding (+ tiDecode+ ) where+++import Data.Word+import Data.Array (Array, listArray, (!))+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import Data.Text (Text)+import qualified Data.Text as T++-- | An approximation of the TI-86 code page as unocide.+tiCodePage :: Array Word8 Text+tiCodePage = listArray (0x00,0xff) [+ "■","b","o","d","h","▸","⬆","⬇","∫","⨉","A","B","C","D","E","F",+ "√","⁻¹","²","∠","°","ʳ","ᵀ","≤","≠","≥","⁻","ᴇ","→","₁₀","↑","↓",+ " ","!","\"","#","$","%","&","'","(",")","*","+",",","-",".","/",+ "0","1","2","3","4","5","6","7","8","9",":",";","<","=",">","?",+ "@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O",+ "P","Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_",+ "`","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o",+ "p","q","r","s","t","u","v","w","x","y","z","{","|","}","~","=",+ "₀","₁","₂","₃","₄","₅","₆","₇","₈","₉","Á","À","Â","Ä","á","à",+ "â","ä","É","È","Ê","Ë","é","è","ê","ë","Í","Ì","Î","Ï","í","ì",+ "î","ï","Ó","Ò","Ô","Ö","ó","ò","ô","ö","Ú","Ù","Û","Ü","ú","ù",+ "û","ü","Ç","ç","Ñ","ñ","´","`","¨","¿","¡","α","β","γ","Δ","δ",+ "ε","θ","λ","μ","π","ρ","Σ","σ","τ","φ","Ω","x̅","y̅","ˣ","…","◂",+ "■","≀","-","²","°","³","\n","➡","╲","╲","◥","◣","⊸","∘","⋱","█",+ "⇧","A","a","_","↥","A̲","a̲","▩","▫","₊",".","⁴","=","■","■","■",+ "■","■","■","■","■","■","■","■","■","■","■","■","■","■","■","■"]+++-- |Decode a bytestring into text using the +-- TI-86 screen code page.+tiDecode :: ByteString -> Text+tiDecode = T.concat . map (tiCodePage!) . BS.unpack+
+ src/Data/TI85/File.hs view
@@ -0,0 +1,56 @@++-- | A collection of types and utilities for dealing with+-- TI Link backup files.+module Data.TI85.File (+ -- * Re-exported modules+ module Data.TI85.File.Variable,+ module Data.TI85.File.Backup,+ -- * Types+ TIHeader(..),+ TIFileData(..),+ TIFile(..)+ ) where++import Data.ByteString+import Data.Word++import Data.TI85.File.Variable+import Data.TI85.File.Backup++-- | The TI-85 header is common between backup files+-- and variable files.+--+-- +--------+---+---------------------------------++-- | 8 Byte | 3 | 42 Byte |+-- +========+===+=================================++-- |**TI85**|xyz| Comment |+-- +--------+---+---------------------------------++--+-- where @xyz@ is always @0x1a,0x0c,0x00@.+data TIHeader = TIHeader {+ hdrSig :: ByteString, -- 8 bytes+ hdrSig2 :: ByteString, -- 3 bytes+ hdrComment :: ByteString, -- 42 bytes+ hdrDataLen :: Word16+ } deriving Show++-- | There are two possible file formats+--+-- - Variable files contain one or more variables,+-- encoded in a variable-specific way.+-- - Backup files contain memory dumps, which also+-- include variable data in a raw form.+data TIFileData = BackupData TIBackupData+ | VariableData TIVarData+ deriving Show++-- | All TI Link files have a common header and+-- checksum, with a data section that differs+-- between backup and variable types.+-- See `TIFileData`.+data TIFile = TIFile {+ tiHeader :: TIHeader,+ tiData :: TIFileData,+ tiChecksum :: Word16+ } deriving Show+
+ src/Data/TI85/File/Backup.hs view
@@ -0,0 +1,50 @@++-- | This module defines the structure of a TI-85 backup file.+-- A backup file contains system memory (data section 1), user+-- memory (section 2), and a variable table that maps where+-- user variables are located in data section 2.+module Data.TI85.File.Backup where++import Data.ByteString (ByteString)+import Data.Text (Text)+import Data.Word (Word8,Word16)++import Data.TI85.File.Variable (VarType)++-- | An entry in the variable table.+-- Note: the variable table is stored in+-- reverse byte order (including 2-byte+-- words!).+data VarTableEntry = VarTableEntry {+ entryId :: Word8,+ entryAddr :: Word16,+ entryNameLen :: Word8,+ entryName :: ByteString+ } deriving Show++-- | A list of variable table entries+type VarTable = [VarTableEntry]++-- | Backup-specific header. This header comes after+-- the more general `Data.TI85.File.TIHeader`.+data TIBackupHeader = TIBackupHeader {+ hdrDataLenOffset :: Word16, -- Always 9+ hdrData1Len :: Word16,+ hdrTypeID :: Word8, -- Always 0x1D+ hdrData2Len :: Word16,+ hdrData3Len :: Word16,+ hdrData2Addr :: Word16+ } deriving Show++-- | The top-level structure of a backup+-- file.+data TIBackupData = TIBackupData {+ backupHeader :: TIBackupHeader,+ data1Len :: Word16,+ data1 :: ByteString,+ data2Len :: Word16,+ data2 :: ByteString,+ varTableLen :: Word16,+ varTable :: VarTable+ } deriving Show+
+ src/Data/TI85/File/Variable.hs view
@@ -0,0 +1,227 @@++-- | This module defines the structure of a TI-85 variable file.+module Data.TI85.File.Variable (+ -- * Types+ TIVar(..),+ TIVarData(..),+ VarField(..),+ VarType(..),+ -- * Utilities+ idToType,+ typeToId,+ showType+ ) where++import Data.ByteString (ByteString)+import Data.Text (Text)+import Data.Word (Word8,Word16)++-- | The structure of a single variable.+-- For the meaning of variable IDs, see `idToType`.+data TIVar = TIVar {+ varOffset :: Word16,+ varLen :: Word16,+ varId :: Word8,+ varNameLen :: Word8,+ varName :: ByteString,+ varDataLen :: Word16,+ varData :: ByteString+ } deriving Show++-- | The contents of a variable file (minus standard+-- header and checksum).+newtype TIVarData = TIVarData {+ varsData :: [TIVar]+ } deriving Show++-- | Scalar values can either be real or complex.+-- Likewise, vectors, lists, etc can contain values+-- of either.+data VarField = VarReal | VarComplex deriving Show++-- | Possible variable types.+-- See also `Data.TI85.Var.Variable`.+data VarType = VarUnknown+ | VarValue VarField+ | VarVector VarField+ | VarList VarField+ | VarMatrix VarField+ | VarConstant VarField+ | VarEquation+ | VarString+ | VarGDBFunc+ | VarGDBPolar+ | VarGDBParam+ | VarGDBDiff+ | VarPicture+ | VarProgram+ | VarDirectory+ | VarSettingsFunc+ | VarSettingsPolar+ | VarSettingsParam+ | VarSettingsDiff+ | VarSavedWinSize+ | VarMemory+ deriving Show++-- | Convert the variable ID word from+-- a variable file into its type.+-- From https://www.ticalc.org/pub/text/calcinfo/ti86prot.txt:+--+-- +---------+-------------------++-- | Type ID | Description |+-- +=========+===================++-- | 00 | Real Number |+-- +---------+-------------------++-- | 01 | Complex Number |+-- +---------+-------------------++-- | 02 | Real Vector |+-- +---------+-------------------++-- | 03 | Complex Vector |+-- +---------+-------------------++-- | 04 | Real List |+-- +---------+-------------------++-- | 05 | Complex List |+-- +---------+-------------------++-- | 06 | Real Matrix |+-- +---------+-------------------++-- | 07 | Complex Matrix |+-- +---------+-------------------++-- | 08 | Real Constant |+-- +---------+-------------------++-- | 09 | Complex Constant |+-- +---------+-------------------++-- | 0A | Equation |+-- +---------+-------------------++-- | 0C | String |+-- +---------+-------------------++-- | 0D | Function GDB |+-- +---------+-------------------++-- | 0E | Polar GDB |+-- +---------+-------------------++-- | 0F | Parametric GDB |+-- +---------+-------------------++-- | 10 | Differential |+-- | | Equation GDB |+-- +---------+-------------------++-- | 11 | Picture |+-- +---------+-------------------++-- | 12 | Program |+-- +---------+-------------------++-- | 15 | Directory (only |+-- | | used when |+-- | | requesting dir) |+-- +---------+-------------------++-- | 17 | Function Window |+-- | | Settings |+-- +---------+-------------------++-- | 18 | Polar Window |+-- | | Settings |+-- +---------+-------------------++-- | 19 | Parametric Window |+-- | | Settings |+-- +---------+-------------------++-- | 1A | Differential |+-- | | Equation Window |+-- | | Settings |+-- +---------+-------------------++-- | 1B | Saved Window Size |+-- | | (ZRCL) |+-- +---------+-------------------++-- | 1D | Memory backup |+-- +---------+-------------------++-- | 1E | Unknown (only used|+-- | | when requesting |+-- | | var) |+-- +---------+-------------------++--+idToType :: Word8 -> VarType+idToType 0x00 = VarValue VarReal+idToType 0x01 = VarValue VarComplex+idToType 0x02 = VarVector VarReal+idToType 0x03 = VarVector VarComplex+idToType 0x04 = VarList VarReal+idToType 0x05 = VarList VarComplex+idToType 0x06 = VarMatrix VarReal+idToType 0x07 = VarMatrix VarComplex+idToType 0x08 = VarConstant VarReal+idToType 0x09 = VarConstant VarComplex+idToType 0x0a = VarEquation+idToType 0x0c = VarString+idToType 0x0d = VarGDBFunc+idToType 0x0e = VarGDBPolar+idToType 0x0f = VarGDBParam+idToType 0x10 = VarGDBDiff+idToType 0x11 = VarPicture+idToType 0x12 = VarProgram+idToType 0x15 = VarDirectory+idToType 0x17 = VarSettingsFunc+idToType 0x18 = VarSettingsPolar+idToType 0x19 = VarSettingsParam+idToType 0x1a = VarSettingsDiff+idToType 0x1b = VarSavedWinSize+idToType 0x1d = VarMemory+idToType 0x1e = VarUnknown+idToType _ = VarUnknown++-- | Convert the variable type into+-- its ID. See `idToType`.+typeToId :: VarType -> Word8+typeToId (VarValue VarReal) = 0x00+typeToId (VarValue VarComplex) = 0x01+typeToId (VarVector VarReal) = 0x02+typeToId (VarVector VarComplex) = 0x03+typeToId (VarList VarReal) = 0x04+typeToId (VarList VarComplex) = 0x05+typeToId (VarMatrix VarReal) = 0x06+typeToId (VarMatrix VarComplex) = 0x07+typeToId (VarConstant VarReal) = 0x08+typeToId (VarConstant VarComplex) = 0x09+typeToId VarEquation = 0x0a+typeToId VarString = 0x0c+typeToId VarGDBFunc = 0x0d+typeToId VarGDBPolar = 0x0e+typeToId VarGDBParam = 0x0f+typeToId VarGDBDiff = 0x10+typeToId VarPicture = 0x11+typeToId VarProgram = 0x12+typeToId VarDirectory = 0x15+typeToId VarSettingsFunc = 0x17+typeToId VarSettingsPolar = 0x18+typeToId VarSettingsParam = 0x19+typeToId VarSettingsDiff = 0x1a+typeToId VarSavedWinSize = 0x1b+typeToId VarMemory = 0x1d+typeToId VarUnknown = 0x1e+++-- | Convert a variable type to+-- its textual representation.+showType :: VarType -> Text+showType VarUnknown = "Unknown"+showType (VarValue VarReal) = "Real Value"+showType (VarValue VarComplex) = "Complex Value"+showType (VarVector VarReal) = "Real Vector"+showType (VarVector VarComplex) = "Complex Vector"+showType (VarList VarReal) = "Real List"+showType (VarList VarComplex) = "Complex List"+showType (VarMatrix VarReal) = "Real Matrix"+showType (VarMatrix VarComplex) = "Complex Matrix"+showType (VarConstant VarReal) = "Real Constant"+showType (VarConstant VarComplex) = "Complex Constant"+showType VarEquation = "Equation"+showType VarString = "String"+showType VarGDBFunc = "Function GDB"+showType VarGDBPolar = "Polar GDB"+showType VarGDBParam = "Parametric GDB"+showType VarGDBDiff = "Differential Equation GDB"+showType VarPicture = "Picture"+showType VarProgram = "Program"+showType VarDirectory = "Directory"+showType VarSettingsFunc = "Function Settings"+showType VarSettingsPolar = "Polar Settings"+showType VarSettingsParam = "Parametric Settings"+showType VarSettingsDiff = "Differential Equation Settings"+showType VarSavedWinSize = "Saved Window Size"+showType VarMemory = "Memory Backup"+
+ src/Data/TI85/IO.hs view
@@ -0,0 +1,90 @@+-- | Various utilities for printing file and+-- variable data.+module Data.TI85.IO where++import Prelude hiding (putStrLn)+import Control.Monad+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import Data.Text (Text, intercalate, pack)+import Data.Text.Encoding (decodeLatin1)+import Data.Text.IO (putStrLn)+import Data.Word+import Numeric (showHex)++import Data.TI85.Encoding+import Data.TI85.File+import Data.TI85.Parsers+import Data.TI85.Var++-- | A backup file contains a variable table that lists+-- all user data, and indexes into the user memory area.+printVariableTable :: Word16 -> VarTable -> IO ()+printVariableTable baseAddr vars = do+ forM_ vars printEntry+ where+ printEntry (VarTableEntry idNum addr _ name) = do+ let idName = (showType.idToType) idNum+ let offset = addr - baseAddr + putStrLn $ "Name: " <> tiDecode name <> "\n" <>+ "Type: " <> idName <> "\n" <>+ "Addr: " <> pack (showHex addr "") <> " (offset " <> (pack.show) offset <> ")\n"++-- | User the variable table to lookup variable data+-- in user memory and display it.+extractVariableTable :: Word16 -> TIBackupData -> IO ()+extractVariableTable baseAddr tiBackup = do+ let table = varTable tiBackup+ let userData = data2 tiBackup+ let vars = readUserMem baseAddr table userData+ forM_ table $ \entry@(VarTableEntry idNum addr _nameLen name) -> do+ let var = readVarMem baseAddr entry userData+ putStrLn $ "\n" <> (showType . idToType ) idNum <> " \"" <> tiDecode name <> "\""+ <> " at 0x" <> hexify addr <> " (0x" <> hexify (addr-baseAddr) <> "):\n"+ printVariable var+ where+ hexify :: Word16 -> Text+ hexify w = pack (showHex w "")++-- | Print file metadata.+printFileSummary :: TIFile -> IO ()+printFileSummary tiFile =+ let hdr = tiHeader tiFile+ check = tiChecksum tiFile+ sig = decodeLatin1 $ hdrSig hdr+ comment = decodeLatin1 $ hdrComment hdr+ fileType = case tiData tiFile of+ BackupData _ -> "Backup"+ VariableData _ -> "Variable"+ in do+ putStrLn $+ "\nTI " <> fileType <> " File <" <> sig <> ">\n" <>+ "\"" <> comment <> "\"\n\n"+ case tiData tiFile of+ BackupData backupData -> printBackupSummary backupData+ VariableData variableData -> printVariableSummary variableData++-- | Print backup-specific top-level iniformation.+printBackupSummary :: TIBackupData -> IO ()+printBackupSummary tiBackup = do+ let backupHdr = backupHeader tiBackup+ let data2Addr = hdrData2Addr backupHdr+ putStrLn $ pack $ "Data Section 1 (" <> show (data1Len tiBackup) <> "):"+ putStrLn $ pack $ "Data Section 2 (" <> show (data2Len tiBackup) <> "):"+ putStrLn $ pack $ "Data 2 Address: " <> showHex data2Addr "\n"++-- | Print variable-specific metadata.+printVariableSummary :: TIVarData -> IO ()+printVariableSummary (TIVarData vars) =+ putStrLn $+ "Variables:\n" <>+ intercalate "\n" (map varSummary vars)+ where+ varSummary :: TIVar -> Text+ varSummary var =+ let varIdStr = (pack.show.fromEnum) (varId var)+ varIdType = (showType.idToType) (varId var)+ in "\tName: " <> tiDecode (varName var) <> "\n" <>+ "\tType: " <> varIdType <> " (" <> varIdStr <> ")\n" <>+ "\tLength : " <> (pack.show.fromEnum) (varDataLen var) <> "\n"+
+ src/Data/TI85/Parsers.hs view
@@ -0,0 +1,539 @@+{-# LANGUAGE DataKinds #-}++-- | This module contains the highest-level user-facing functions.+-- Typically, code using this library will want to call `readTIFile`+-- on a file name.+module Data.TI85.Parsers (+ -- * General TI Files+ readTIFile,+ parseTIHeader,+ parseTIFile,+ -- * Backup Files+ -- ** High-level Parsers+ parseTIBackupHeader,+ readUserMem,+ -- ** Lower-level Parsers+ readVarMem,+ extractVar,+ -- * Variable Files+ -- ** High-level Parsers+ readVariable,+ parseVariable,+ -- ** Lower-level Parsers+ parseProgram,+ parseTINumber,+ parseToken+ ) where++import Prelude hiding (take,takeWhile,putStrLn)+import Data.Char+import Data.Bits+import Data.Word+import Data.Array.Unboxed (Array, UArray, array, listArray, (!))+import Data.ByteString (ByteString, foldr')+import qualified Data.ByteString as BS+import Data.Text (Text)+import qualified Data.Text as T+import Data.Text.IO (putStrLn)+import Data.Attoparsec.ByteString+import Data.Attoparsec.Combinator (lookAhead)+import Control.Applicative+import Control.Monad (guard, when, forM)++import Data.TI85.Var+import Data.TI85.Var.Pic+import Data.TI85.Encoding+import Data.TI85.Token+import Data.TI85.File++bytes2Int :: ByteString -> Int+bytes2Int = BS.foldr' (\w x -> 256*x + fromEnum w) 0++anyWord16 :: Parser Word16+anyWord16 = do+ bytes <- take 2+ let val = bytes2Int bytes+ return (toEnum val)++-- | Parse a general TI file, which might be+-- a variable file or backup file.+parseTIFile :: Parser TIFile+parseTIFile = do+ header <- parseTIHeader+ (checksum, calcSum) <- lookAhead (calculateSum $ hdrDataLen header)++ when (calcSum /= checksum) (error "Checkum check failed")++ contents <- + BackupData <$> parseTIBackupData+ <|> VariableData <$> parseTIVarData++ return $ TIFile {+ tiHeader = header,+ tiData = contents,+ tiChecksum = checksum+ }+ where+ calculateSum :: Word16 -> Parser (Word16,Word16)+ calculateSum len = do+ bytes <- take (fromEnum len)+ let sum = foldr' (\w s -> fromEnum w + s) (0::Int) bytes+ checksum <- anyWord16+ return (checksum, toEnum $ sum .&. 0xffff)++-- | Read a general TI file, which might be+-- a variable file or backup file.+readTIFile :: FilePath -> IO TIFile+readTIFile fileName = do+ contents <- BS.readFile fileName+ let tiFile = parseOnly parseTIFile contents+ either error return tiFile++-- | The backup header is a second header after+-- the top-level file header (`TIFile`).+-- It contains sizes of the three data sections+-- and the location of user memory in RAM (which+-- can use used with the variable table to look+-- up variable data).+parseTIBackupHeader :: Parser TIBackupHeader+parseTIBackupHeader = do+ dataOffset <- string "\x09\x00"+ len1 <- anyWord16+ typeId <- word8 0x1d+ len2 <- anyWord16+ len3 <- anyWord16+ addr <- anyWord16+ return $ TIBackupHeader {+ hdrDataLenOffset = 0x9,+ hdrData1Len = len1,+ hdrTypeID = typeId,+ hdrData2Len = len2,+ hdrData3Len = len2,+ hdrData2Addr = addr+ }++parseVarTableEntry :: Parser VarTableEntry+parseVarTableEntry = do+ entId <- anyWord8+ entAddr <- anyWord16+ len <- anyWord8+ name <- take (fromEnum len)+ return $ VarTableEntry {+ entryId = entId,+ entryAddr = entAddr,+ entryNameLen = len,+ entryName = name+ }++parseTIBackupData :: Parser TIBackupData+parseTIBackupData = do+ backupHdr <- parseTIBackupHeader+ len1 <- anyWord16+ data1 <- take $ fromEnum len1+ len2 <- anyWord16+ data2 <- take $ fromEnum len2+ len3 <- anyWord16+ data3 <- take $ fromEnum len3+ let vars = parseOnly (many' parseVarTableEntry) (BS.reverse data3)+ return $ TIBackupData {+ backupHeader = backupHdr,+ data1Len = len1,+ data1 = data1,+ data2Len = len2,+ data2 = data2,+ varTableLen = len3,+ varTable = either error id vars+ }++-- |The TI-85 header is common between backup files+-- and variable files.+--+-- +--------+---+---------------------------------++-- | 8 Byte | 3 | 42 Byte |+-- +========+===+=================================++-- |**TI85**|xyz| Comment |+-- +--------+---+---------------------------------++--+-- where @xyz@ is always @0x1a,0x0c,0x00@.+parseTIHeader :: Parser TIHeader+parseTIHeader = do+ string "**TI85**"+ string "\x1a\x0c\x00"+ rawComment <- take 42+ dataLen <- anyWord16+ return $ TIHeader {+ hdrSig = "**TI85**",+ hdrSig2 = "\x1a\x0c\x00",+ hdrComment = BS.takeWhile (/= 0x0) rawComment,+ hdrDataLen = dataLen+ }++parseTIVar :: Parser TIVar+parseTIVar = do+ offset <- anyWord16+ len <- anyWord16+ varType <- anyWord8+ nameLen <- anyWord8+ name <- take (fromEnum nameLen)+ dataLen <- anyWord16+ var <- take (fromEnum dataLen)+ return $ TIVar {+ varOffset = offset,+ varLen = len,+ varId = varType,+ varNameLen = nameLen,+ varName = name,+ varDataLen = dataLen,+ varData = var+ }++parseTIVarData :: Parser TIVarData+parseTIVarData = TIVarData <$> many1' parseTIVar++-- |Programs are either plain text, ot encoded in a+-- tokenized format. See "Data.TI85.Token" for the +-- mapping.+parseProgram :: Parser Program+parseProgram = do+ len <- fromEnum <$> anyWord16+ encoding <- peekWord8'+ case encoding of+ 0x00 -> do+ anyWord8+ PlainText <$> parsePlaintext (len-1)+ _ -> Tokenized <$> parseTokenized len++parsePlaintext :: Int -> Parser Text+parsePlaintext len = do+ bytes <- take len+ return $ tiDecode bytes++parseTokenized :: Int -> Parser [Token]+parseTokenized len = do+ bytes <- take len+ let tokenResult = parseOnly (many' parseToken) bytes+ either error return tokenResult+ +-- |Interpret data as a token (or token stream)+parseToken :: Parser Token+parseToken = do+ tokenByte <- anyWord8+ let token = tokenTable ! tokenByte+ tokenText <- case token of+ Invalid -> return T.empty+ Fixed t -> return t+ QuoteText -> do+ t <- zeroTerminated+ return $ '"' `T.cons` t `T.snoc` '"'+ NameLength -> varLength+ FixedLength n -> tiDecode <$> take n+ Extended -> do+ extToken <- anyWord8+ case tokenTableExtended ! extToken of+ Fixed ext -> return ext+ _ -> return T.empty+ Conversion -> do+ unit1 <- varLength+ unit2 <- varLength+ return $ T.intercalate "->" [unit1,unit2]+ Literal -> zeroTerminated+ Label -> do+ label <- zeroTerminated+ return $ "Lbl " <> label+ Goto -> do+ mystery <- anyWord16+ label <- zeroTerminated+ return $ "Goto " <> label+ return $ Token token tokenText+ where+ zeroTerminated = do+ bytes <- takeWhile (/= 0x0)+ zero <- word8 0x0+ return $ tiDecode bytes+ varLength = do+ len <- anyWord8+ bytes <- take (fromEnum len)+ return $ tiDecode bytes++bcd :: ByteString -> Double+bcd bytes = snd $ BS.foldl' accDecimal (0,0.0) bytes+ where+ accDecimal :: (Int, Double) -> Word8 -> (Int, Double)+ accDecimal (place, value) byte =+ let (high,low) = toNibbles byte+ highVal = fromIntegral high * 10.0 ** fromIntegral place+ lowVal = fromIntegral low * 10.0 ** fromIntegral (place-1)+ in (place-2, value+highVal+lowVal)+ toNibbles :: Word8 -> (Word8,Word8)+ toNibbles byte =+ let low = byte .&. 0x0f+ high = shiftR byte 4 .&. 0x0f+ in (high,low)++-- |Numeric values can either be real or complex, and most variable+-- types can store either.+parseTINumber :: Parser TINumber+parseTINumber = do+ (realFlags, realPart) <- parseReal+ let isComplex = testBit realFlags 0+ if isComplex+ then do+ (_imagFlags, imagPart) <- parseReal+ return $ TIComplex realPart imagPart+ else return $ TIReal realPart+ where+ parseReal :: Parser (Word8, Double)+ parseReal = do+ flags <- anyWord8+ expRaw <- anyWord16+ let exp = fromEnum expRaw - 0xFC00+ mantissa <- bcd <$> take 7+ let sign = testBit flags 7+ let value = mantissa * 10 ** fromIntegral exp+ let signedValue = if sign+ then -1.0 * value+ else value+ return (flags,signedValue)++parsePicture :: Parser TIBitmap+parsePicture = do+ word8 0xf0+ word8 0x03+ pic <- take 1008+ case fromBytes pic of+ Just tiPic -> return tiPic+ Nothing -> fail "Wrong size of picture bitmap"++-- |Parser for the elements contained a variable file+-- that has possibly more than one +parseVariable :: VarType -> Parser Variable+parseVariable (VarValue _) = TIScalar <$> parseTINumber+parseVariable (VarVector _) = do+ alwaysOne <- anyWord8+ len <- fromEnum <$> anyWord8+ vals <- count len parseTINumber+ return $ TIVector vals+parseVariable (VarList _) = do+ len <- fromEnum <$> anyWord16+ vals <- count len parseTINumber+ return $ TIList vals+parseVariable (VarMatrix _) = do+ numCols <- fromEnum <$> anyWord8+ numRows <- fromEnum <$> anyWord8+ vals <- count numRows (count numCols parseTINumber)+ return $ TIMatrix vals+parseVariable (VarConstant _) = TIConstant <$> parseTINumber+parseVariable VarEquation = do+ len <- fromEnum <$> anyWord16+ tokens <- parseTokenized len+ let tokenText = map (\(Token _ t) -> t) tokens+ let eqText = T.concat tokenText+ return $ TIEquation eqText+parseVariable VarString = do+ len <- fromEnum <$> anyWord16+ TIString <$> parsePlaintext len+parseVariable VarProgram = TIProgram <$> parseProgram+parseVariable VarPicture = TIPicture <$> parsePicture+parseVariable VarSettingsFunc = TIFuncSettings <$> parseFuncSettings+parseVariable VarSettingsPolar = TIPolarSettings <$> parsePolarSettings+parseVariable VarSettingsParam = TIParamSettings <$> parseParamSettings+parseVariable VarSettingsDiff = TIDiffEqSettings <$> parseDiffEqSettings+parseVariable VarSavedWinSize = TIZRCL <$> parseWinSettings+parseVariable VarGDBFunc = TIFuncGDB <$> parseFuncGDB+parseVariable VarUnknown = return $ TIString "?"+parseVariable _ = return $ TIString "(not implemented)"++parseModeSettings :: Parser ModeSettings+parseModeSettings = do+ modeByte <- anyWord8+ let drawDot = testBit modeByte 0+ simulG = testBit modeByte 1+ gridOn = testBit modeByte 2+ polarGC = testBit modeByte 3+ coordOff = testBit modeByte 4+ axesOff = testBit modeByte 5+ labelOb = testBit modeByte 6+ return $ ModeSettings {+ modeDrawDot = drawDot,+ modeSimulG = simulG,+ modeGridOn = gridOn,+ modePolarGC = polarGC,+ modeCoordOff = coordOff,+ modeAxesOff = axesOff,+ modeLabelOn = labelOb+ }++parseFuncGDB :: Parser (GDB Func)+parseFuncGDB = do+ len <- anyWord16+ mode <- parseModeSettings+ settings <- parseBareFuncSettings+ numFunc <- fromEnum <$> satisfy (<=99)+ lib <- count numFunc parseFuncEntry+ return $ GDB {+ gdbMode = mode,+ gdbSettings = settings,+ gdbLib = lib+ }++parseFuncEntry :: Parser (GDBLibEntry Func)+parseFuncEntry = do+ idByte <- anyWord8+ let selected = testBit idByte 7+ funcId = idByte .&. 0x7f+ (TIEquation eqn) <- parseVariable VarEquation+ return $ GDBLibEntry {+ libId = fromEnum funcId,+ libSelected = selected,+ libEqn = eqn+ }+{-+0 2 bytes Length, in bytes, of GDB, minus two.+2 1 byte Mode settings (see mode setting table below)+3 10 bytes A real number: xMin+13 (Dh) 10 bytes A real number: xMax+23 (17h) 10 bytes A real number: xScl+33 (21h) 10 bytes A real number: yMin+43 (2Bh) 10 bytes A real number: yMax+53 (35h) 10 bytes A real number: yScl+63 (3Fh) 10 bytes A real number: xRes+73 (49h) 1 byte Number of functions defined (up to 99)+74 (4Ah) n bytes Function table - several function definitions one after another (see function table below)+-}++parseBareFuncSettings :: Parser FuncSettings+parseBareFuncSettings = do+ FuncSettings+ <$> parseTINumber -- fXMin+ <*> parseTINumber -- fXMax+ <*> parseTINumber -- fXScl+ <*> parseTINumber -- fYMin+ <*> parseTINumber -- fYMax+ <*> parseTINumber -- fYScl++parseFuncSettings :: Parser FuncSettings+parseFuncSettings = do+ word8 0x51+ word8 0x00+ anyWord8+ val <- parseBareFuncSettings+ take 20+ return val++parsePolarSettings :: Parser PolarSettings+parsePolarSettings = do+ word8 0x5b+ word8 0x00+ anyWord8+ PolarSettings+ <$> parseTINumber -- polThetaMin+ <*> parseTINumber -- polThetaMax+ <*> parseTINumber -- polThetaStep+ <*> parseTINumber -- polXMin+ <*> parseTINumber -- polXMax+ <*> parseTINumber -- polXScl+ <*> parseTINumber -- polYMin+ <*> parseTINumber -- polYMax+ <*> parseTINumber -- polYScl++parseParamSettings :: Parser ParamSettings+parseParamSettings = do+ word8 0x5b+ word8 0x00+ anyWord8+ ParamSettings+ <$> parseTINumber -- parTMin+ <*> parseTINumber -- parTMax+ <*> parseTINumber -- parTStep+ <*> parseTINumber -- parXMin+ <*> parseTINumber -- parXMax+ <*> parseTINumber -- parXScl+ <*> parseTINumber -- parYMin+ <*> parseTINumber -- parYMax+ <*> parseTINumber -- parYScl++parseDiffEqSettings :: Parser DiffEqSettings+parseDiffEqSettings = do+ word8 0x71+ word8 0x00+ anyWord8+ DiffEqSettings+ <$> parseTINumber -- diffTol+ <*> parseTINumber -- diffTPlot+ <*> parseTINumber -- diffTMin+ <*> parseTINumber -- diffTMax+ <*> parseTINumber -- diffTStep+ <*> parseTINumber -- diffXMin+ <*> parseTINumber -- diffXMax+ <*> parseTINumber -- diffXScl+ <*> parseTINumber -- diffYMin+ <*> parseTINumber -- diffYMax+ <*> parseTINumber -- diffYScl+ <*> parseAxis -- diffXAxis+ <*> parseAxis -- diffYAxis+ where+ parseAxis :: Parser DiffEqAxis+ parseAxis = do+ axisCode <- anyWord8+ return $ case axisCode of+ 0x0 -> AxisT+ w | w >= 0x10 && w <= 0x19 ->+ AxisQ (toEnum (fromEnum w - 0x10))+ w | w >= 0x20 && w <= 0x29 ->+ AxisQ' (toEnum (fromEnum w - 0x20))+ _ -> error "Invalid axis code"++parseWinSettings :: Parser SavedWinSettings+parseWinSettings = do+ word8 0x82+ word8 0x00+ SavedWinSettings+ <$> parseTINumber -- zThetaMin+ <*> parseTINumber -- zThetaMax+ <*> parseTINumber -- zThetaStep+ <*> parseTINumber -- ztPlot+ <*> parseTINumber -- ztMin+ <*> parseTINumber -- ztMax+ <*> parseTINumber -- ztStep+ <*> parseTINumber -- zxMin+ <*> parseTINumber -- zxMax+ <*> parseTINumber -- zxScl+ <*> parseTINumber -- zyMin+ <*> parseTINumber -- zyMax+ <*> parseTINumber -- zyScl++parseUserMem :: Word16 -> VarTable -> Parser [Variable]+parseUserMem baseAddr vars = do+ forM vars (extractVar baseAddr)++-- | Parse a variable out of user memory, without+-- consuming any input (hence "extract"). This allows+-- many calls on the same memory. The base address+-- is required for converting the variable addresses+-- in the table to offsets into the user memory backup.+extractVar :: Word16 -> VarTableEntry -> Parser Variable+extractVar baseAddr (VarTableEntry idNum addr _ name) = do+ let offset = fromEnum $ addr - baseAddr+ lookAhead $ take offset *> parseVariable (idToType idNum)++-- | Given a bytestring of user memory, look up the variable+-- table entry and return a `Variable`.+readVarMem :: Word16 -> VarTableEntry -> ByteString -> Variable+readVarMem baseAddr entry userData =+ let var = parseOnly (extractVar baseAddr entry) userData+ in either error id var++-- | Read all ofthe variables contained in a user memory+-- backup, according to a variable table.+readUserMem :: Word16 -> VarTable -> ByteString -> [Variable]+readUserMem baseAddr table userData =+ let vars = parseOnly (parseUserMem baseAddr table) userData+ in either error id vars++-- |Convert raw variable data into a Variable.+readVariable :: TIVar -> Variable+readVariable var =+ let varType = idToType (varId var)+ parsedVar = parseOnly (parseVariable varType) (varData var)+ in either error id parsedVar+
+ src/Data/TI85/Token.hs view
@@ -0,0 +1,348 @@++-- | As a kind of object file or byte code, TI-85 programs can be+-- represented as list of tokens.+--+-- The contents of this module describe the possible tokens, and a way+-- to convert from byte to token.+module Data.TI85.Token where++import Data.Word+import Data.Array.Unboxed (Array, array)+import Data.Text (Text)++-- | There are several types of tokens. Most come+-- with some instructions for what do to with the+-- bytes following the token.+data TokenDef = Invalid+ | Fixed Text -- ^ Representation of specific text+ | QuoteText -- ^ Quoted string+ | NameLength -- ^ A string specified by length+ | FixedLength Int -- ^ A fixed-length string+ | Extended -- ^ Look up the next token in the extended token table+ | Conversion -- ^ Unit conversion+ | Literal -- ^ Literal value, represented as text+ | Label -- ^ Lbl, with a text label+ | Goto -- ^ Goto, with a text label+ deriving Show++-- | Mapping from byte to TokenDef.+tokenTable :: Array Word8 TokenDef+tokenTable = array (0x00,0xff) [+ (0x00, Invalid),+ (0x01, Fixed "▸Rec"),+ (0x02, Fixed "▸Pol"),+ (0x03, Fixed "▸Cyl"),+ (0x04, Fixed "▸Sph"),+ (0x05, Fixed "▸DMS"),+ (0x06, Fixed "▸Bin"),+ (0x07, Fixed "▸Hex"),+ (0x08, Fixed "▸Oct"),+ (0x09, Fixed "▸Dec"),+ (0x0A, Fixed "▸Frac"),+ (0x0B, Fixed "→"),+ (0x0C, Fixed "["),+ (0x0D, Fixed "]"),+ (0x0E, Fixed "{"),+ (0x0F, Fixed "}"),+ (0x10, Fixed "("),+ (0x11, Fixed ")"),+ (0x12, Fixed "round"),+ (0x13, Fixed "max"),+ (0x14, Fixed "min"),+ (0x15, Fixed "mod"),+ (0x16, Fixed "cross"),+ (0x17, Fixed "dot"),+ (0x18, Fixed "aug"),+ (0x19, Fixed "rSwap"),+ (0x1A, Fixed "rAdd"),+ (0x1B, Fixed "multR"),+ (0x1C, Fixed "mRAdd"),+ (0x1D, Fixed "sub"),+ (0x1E, Fixed "lcm"),+ (0x1F, Fixed "gcd"),+ (0x20, Fixed "simult"),+ (0x21, Fixed "inter"),+ (0x22, Fixed "pEval"),+ (0x23, Fixed "randM"),+ (0x24, Fixed "seq"),+ (0x25, Fixed "evalF"),+ (0x26, Fixed "fnInt"),+ (0x27, Fixed "arc"),+ (0x28, Fixed "fMin"),+ (0x29, Fixed "fMax"),+ (0x2A, Fixed "der1"),+ (0x2B, Fixed "der2"),+ (0x2C, Fixed "nDer"),+ (0x2D, QuoteText),+ (0x2E, Fixed "∠"),+ (0x2F, Fixed ","),+ (0x30, Fixed " or "),+ (0x31, Fixed " xor "),+ (0x32, NameLength),+ (0x33, FixedLength 1),+ (0x34, FixedLength 2),+ (0x35, FixedLength 3),+ (0x36, FixedLength 4),+ (0x37, FixedLength 5),+ (0x38, FixedLength 6),+ (0x39, FixedLength 7),+ (0x3A, FixedLength 8),+ (0x3B, NameLength),+ (0x3C, NameLength),+ (0x3D, Extended),+ (0x3E, Conversion),+ (0x3F, Fixed "="),+ (0x40, Fixed " and "),+ (0x41, Fixed "rand"),+ (0x42, Fixed "π"),+ (0x43, Fixed "getKy"),+ (0x44, Literal),+ (0x45, Fixed "%"),+ (0x46, Fixed "!"),+ (0x47, Fixed "ʳ"),+ (0x48, Fixed "°"),+ (0x49, Fixed "⁻¹"),+ (0x4A, Fixed "²"),+ (0x4B, Fixed "ᵀ"),+ (0x4C, Fixed "Menu"),+ (0x4D, Fixed "P2Reg"),+ (0x4E, Fixed "P3Reg"),+ (0x4F, Fixed "P4Reg"),+ (0x50, Fixed "=="),+ (0x51, Fixed "<"),+ (0x52, Fixed ">"),+ (0x53, Fixed "<="),+ (0x54, Fixed ">="),+ (0x55, Fixed "≠"),+ (0x56, Fixed "Radian"),+ (0x57, Fixed "Degree"),+ (0x58, Fixed "Normal"),+ (0x59, Fixed "Sci"),+ (0x5A, Fixed "Eng"),+ (0x5B, Fixed "Float"),+ (0x5C, Fixed "Fix"),+ (0x5D, Fixed "RectV"),+ (0x5E, Fixed "CylV"),+ (0x5F, Fixed "SphereV"),+ (0x60, Fixed "+"),+ (0x61, Fixed "-"),+ (0x62, Fixed "Func"),+ (0x63, Fixed "Param"),+ (0x64, Fixed "Pol"),+ (0x65, Fixed "DifEq"),+ (0x66, Fixed "Bin"),+ (0x67, Fixed "Oct"),+ (0x68, Fixed "Hex"),+ (0x69, Fixed "Dec"),+ (0x6A, Fixed "RectC"),+ (0x6B, Fixed "PolarC"),+ (0x6C, Fixed "dxDer1"),+ (0x6D, Fixed "dxNDer"),+ (0x6E, Fixed ":"),+ (0x6F, Fixed "\n"),+ (0x70, Fixed "*"),+ (0x71, Fixed "/"),+ (0x72, Fixed "SeqG"),+ (0x73, Fixed "SimulG"),+ (0x74, Fixed "PolarGC"),+ (0x75, Fixed "RectGC"),+ (0x76, Fixed "CoordOn"),+ (0x77, Fixed "CoordOff"),+ (0x78, Fixed "DrawLine"),+ (0x79, Fixed "DrawDot"),+ (0x7A, Fixed "AxesOn"),+ (0x7B, Fixed "AxesOff"),+ (0x7C, Fixed "GridOn"),+ (0x7D, Fixed "GridOff"),+ (0x7E, Fixed "LabelOn"),+ (0x7F, Fixed "LabelOff"),+ (0x80, Fixed "nPr"),+ (0x81, Fixed "nCr"),+ (0x82, Fixed "Trace"),+ (0x83, Fixed "ClDrw"),+ (0x84, Fixed "ZStd"),+ (0x85, Fixed "ZTrig"),+ (0x86, Fixed "ZFit"),+ (0x87, Fixed "ZIn"),+ (0x88, Fixed "ZOut"),+ (0x89, Fixed "ZSqr"),+ (0x8A, Fixed "ZInt"),+ (0x8B, Fixed "ZPrev"),+ (0x8C, Fixed "ZDecm"),+ (0x8D, Fixed "ZRcl"),+ (0x8E, Fixed "PrtScrn"),+ (0x8F, Fixed "DrawF"),+ (0x90, Fixed "FnOn "),+ (0x91, Fixed "FnOff "),+ (0x92, Fixed "StPic"),+ (0x93, Fixed "RcPic"),+ (0x94, Fixed "StGDB"),+ (0x95, Fixed "RcGDB"),+ (0x96, Fixed "Line"),+ (0x97, Fixed "Vert"),+ (0x98, Fixed "PtOn"),+ (0x99, Fixed "PtOff"),+ (0x9A, Fixed "PtChg"),+ (0x9B, Fixed "Shade"),+ (0x9C, Fixed "Circl"),+ (0x9D, Fixed "Axes"),+ (0x9E, Fixed "TanLn"),+ (0x9F, Fixed "DrInv"),+ (0xA0, Fixed "√"),+ (0xA1, Fixed "-"),+ (0xA2, Fixed "abs"),+ (0xA3, Fixed "iPart"),+ (0xA4, Fixed "fPart"),+ (0xA5, Fixed "int"),+ (0xA6, Fixed "ln"),+ (0xA7, Fixed "e^"),+ (0xA8, Fixed "log"),+ (0xA9, Fixed "10^"),+ (0xAA, Fixed "sin "),+ (0xAB, Fixed "sin⁻¹ "),+ (0xAC, Fixed "cos "),+ (0xAD, Fixed "cos⁻¹ "),+ (0xAE, Fixed "tan "),+ (0xAF, Fixed "tan⁻¹ "),+ (0xB0, Fixed "sinh "),+ (0xB1, Fixed "sinh⁻¹ "),+ (0xB2, Fixed "cosh "),+ (0xB3, Fixed "cosh⁻¹ "),+ (0xB4, Fixed "tanh "),+ (0xB5, Fixed "tanh⁻¹ "),+ (0xB6, Fixed "sign "),+ (0xB7, Fixed "det "),+ (0xB8, Fixed "ident"),+ (0xB9, Fixed "unitV"),+ (0xBA, Fixed "norm"),+ (0xBB, Fixed "rnorm"),+ (0xBC, Fixed "cnorm"),+ (0xBD, Fixed "ref"),+ (0xBE, Fixed "rref"),+ (0xBF, Fixed "dim"),+ (0xC0, Fixed "dimL"),+ (0xC1, Fixed "sum"),+ (0xC2, Fixed "prod"),+ (0xC3, Fixed "sortA"),+ (0xC4, Fixed "sortD"),+ (0xC5, Fixed "li▸vc"),+ (0xC6, Fixed "vc▸li"),+ (0xC7, Fixed "lngth"),+ (0xC8, Fixed "conj"),+ (0xC9, Fixed "real"),+ (0xCA, Fixed "imag"),+ (0xCB, Fixed "angle"),+ (0xCC, Fixed "not"),+ (0xCD, Fixed "rotR"),+ (0xCE, Fixed "rotL"),+ (0xCF, Fixed "shftR"),+ (0xD0, Fixed "shftL"),+ (0xD1, Fixed "eigVl"),+ (0xD2, Fixed "eigVc"),+ (0xD3, Fixed "cond"),+ (0xD4, Fixed "poly"),+ (0xD5, Fixed "fcstx"),+ (0xD6, Fixed "fcsty"),+ (0xD7, Fixed "eval "),+ (0xD8, Fixed "If "),+ (0xD9, Fixed "Then"),+ (0xDA, Fixed "Else"),+ (0xDB, Fixed "While "),+ (0xDC, Fixed "Repeat "),+ (0xDD, Fixed "For"),+ (0xDE, Fixed "End"),+ (0xDF, Fixed "Return"),+ (0xE0, Label),+ (0xE1, Goto),+ (0xE2, Fixed "Pause"),+ (0xE3, Fixed "Stop"),+ (0xE4, Fixed "IS>"),+ (0xE5, Fixed "DS<"),+ (0xE6, Fixed "Input "),+ (0xE7, Fixed "Prompt "),+ (0xE8, Fixed "InpSt "),+ (0xE9, Fixed "Disp "),+ (0xEA, Fixed "DispG"),+ (0xEB, Fixed "Outpt"),+ (0xEC, Fixed "ClLCD"),+ (0xED, Fixed "Eq▸St"),+ (0xEE, Fixed "St▸Eq"),+ (0xEF, Fixed "Fill"),+ (0xF0, Fixed "^"),+ (0xF1, Fixed "ˣ√"),+ (0xF2, Fixed "Solver"),+ (0xF3, Fixed "OneVar"),+ (0xF4, Fixed "LinR"),+ (0xF5, Fixed "ExpR"),+ (0xF6, Fixed "LnR"),+ (0xF7, Fixed "PwrR"),+ (0xF8, Fixed "ShwSt"),+ (0xF9, Fixed "Hist"),+ (0xFA, Fixed "xyLine"),+ (0xFB, Fixed "Scatter"),+ (0xFC, Fixed "SortX"),+ (0xFD, Fixed "SortY"),+ (0xFE, Fixed "LU"),+ (0xFF, Invalid)+ ]++-- | Extended table of tokens.+-- When the token is 'Extended' this table+-- is used to look up the final token.+tokenTableExtended :: Array Word8 TokenDef+tokenTableExtended = array (0x00,0x35) [+ (0x00, Fixed "zxScl"),+ (0x01, Fixed "zyScl"),+ (0x02, Fixed "xScl"),+ (0x03, Fixed "yScl"),+ (0x04, Fixed "xMin"),+ (0x05, Fixed "xMax"),+ (0x06, Fixed "yMin"),+ (0x07, Fixed "yMax"),+ (0x08, Fixed "tMin"),+ (0x09, Fixed "tMax"),+ (0x0a, Fixed "tStep"),+ (0x0b, Fixed "θStep"),+ (0x0c, Fixed "ztStep"),+ (0x0d, Fixed "zθStep"),+ (0x0e, Fixed "tPlot"),+ (0x0f, Fixed "θMin"),+ (0x10, Fixed "θMax"),+ (0x11, Fixed "zxMin"),+ (0x12, Fixed "zxMax"),+ (0x13, Fixed "zyMin"),+ (0x14, Fixed "zyMax"),+ (0x15, Fixed "ztPlot"),+ (0x16, Fixed "zθMin"),+ (0x17, Fixed "zθMax"),+ (0x18, Fixed "ztMin"),+ (0x19, Fixed "ztMax"),+ (0x1a, Fixed "lower"),+ (0x1b, Fixed "upper"),+ (0x1c, Fixed "Δx"),+ (0x1d, Fixed "Δy"),+ (0x1e, Fixed "xFact"),+ (0x1f, Fixed "yFact"),+ (0x20, Fixed "difTol"),+ (0x21, Fixed "tol"),+ (0x22, Fixed "δ"),+ (0x23, Fixed "Na"),+ (0x24, Fixed "k"),+ (0x25, Fixed "Cc"),+ (0x26, Fixed "ec"),+ (0x27, Fixed "Rc"),+ (0x28, Fixed "Gc"),+ (0x29, Fixed "g"),+ (0x2a, Fixed "Me"),+ (0x2b, Fixed "Mp"),+ (0x2c, Fixed "Mn"),+ (0x2d, Fixed "µ0"),+ (0x2e, Fixed "ε0"),+ (0x2f, Fixed "h"),+ (0x30, Fixed "c"),+ (0x31, Fixed "u"),+ (0x32, Fixed "e"),+ (0x33, Fixed "xStat"),+ (0x34, Fixed "yStat"),+ (0x35, Fixed "Plot")+ ]
+ src/Data/TI85/Var.hs view
@@ -0,0 +1,463 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}++-- | Specifics of the TI-85 variables themselves+-- (i.e. not their representation in the file).+module Data.TI85.Var (+ -- * Types+ Variable(..),+ TINumber(..),+ -- ** Program+ Program(..),+ Token(..),+ -- ** Window Settings+ FuncSettings(..),+ PolarSettings(..),+ ParamSettings(..),+ SavedWinSettings(..),+ -- *** Differential Equations+ DiffEqSettings(..),+ DiffEqAxis(..),+ AxisInd(..),+ -- ** Graph Database+ ModeSettings(..),+ GraphMode(..),+ FuncEqn(..),+ ParamEqn(..),+ DiffEqEqn(..),+ GDBLibEntry(..),+ GDBEqn(..),+ GDBSettings(..),+ GDB(..),+ HasGDB,++ -- * Text Conversion+ showVariable,+ -- ** Variable-specific+ showNumber,+ showProgram,+ showFuncSettings,+ showPolarSettings,+ showParamSettings,+ showDiffEqSettings,+ showWinSettings,+ showGDB,+ showGDBMode,++ -- * IO+ printVariable+ ) where++import Prelude hiding (concat, putStrLn)+import Data.Text (Text, concat, pack, intercalate, unpack)+import Data.Text.IO (putStrLn)+import Data.TI85.Token (TokenDef)+import Data.TI85.Var.Pic++-- * Types++-- | An instance of a token from the `TokenDef`+-- table. This will include actual text that goes+-- along with a token, when it is not a fixed-text+-- token.+data Token = Token TokenDef Text deriving Show++-- | A program is either stored as plaintext+-- (in the TI-86 codepage; see `Data.TI85.Encoding.tiDecode`)+-- or a list of tokens. The two are represented here to+-- maintain that information.+data Program = PlainText Text+ | Tokenized [Token]+ deriving Show++-- | Numerical variables are either Real or Complex.+data TINumber = TIReal Double | TIComplex Double Double deriving Show++-- ** Window Settings++-- | Function window settings+data FuncSettings = FuncSettings {+ fXMin :: TINumber,+ fXMax :: TINumber,+ fXScl :: TINumber,+ fYMin :: TINumber,+ fYMax :: TINumber,+ fYScl :: TINumber+ }+ deriving Show++-- | Polar window settings+data PolarSettings = PolarSettings {+ polThetaMin :: TINumber,+ polThetaMax :: TINumber,+ polThetaStep :: TINumber,+ polXMin :: TINumber,+ polXMax :: TINumber,+ polXScl :: TINumber,+ polYMin :: TINumber,+ polYMax :: TINumber,+ polYScl :: TINumber+ }+ deriving Show++-- | Parametric window settings+data ParamSettings = ParamSettings {+ parTMin :: TINumber,+ parTMax :: TINumber,+ parTStep :: TINumber,+ parXMin :: TINumber,+ parXMax :: TINumber,+ parXScl :: TINumber,+ parYMin :: TINumber,+ parYMax :: TINumber,+ parYScl :: TINumber+ }+ deriving Show++-- | Differential equation axes can come+-- with an index (e.g. Q1-Q9).+data AxisInd = Axis0+ | Axis1+ | Axis2+ | Axis3+ | Axis4+ | Axis5+ | Axis6+ | Axis7+ | Axis8+ | Axis9+ deriving (Show,Eq,Enum)++-- | Differential equation axis type+data DiffEqAxis = AxisT+ | AxisQ AxisInd+ | AxisQ' AxisInd+ deriving Show++-- | Differential equation window settings+data DiffEqSettings = DiffEqSettings {+ diffTol :: TINumber,+ diffTPlot :: TINumber,+ diffTMin :: TINumber,+ diffTMax :: TINumber,+ diffTStep :: TINumber,+ diffXMin :: TINumber,+ diffXMax :: TINumber,+ diffXScl :: TINumber,+ diffYMin :: TINumber,+ diffYMax :: TINumber,+ diffYScl :: TINumber,+ diffXAxis :: DiffEqAxis,+ diffYAxis :: DiffEqAxis+ }+ deriving Show++-- | Saved window settings, used for ZRCL.+data SavedWinSettings = SavedWinSettings {+ zThetaMin :: TINumber,+ zThetaMax :: TINumber,+ zThetaStep :: TINumber,+ ztPlot :: TINumber,+ ztMin :: TINumber,+ ztMax :: TINumber,+ ztStep :: TINumber,+ zxMin :: TINumber,+ zxMax :: TINumber,+ zxScl :: TINumber,+ zyMin :: TINumber,+ zyMax :: TINumber,+ zyScl :: TINumber+ }+ deriving Show++data ModeSettings = ModeSettings {+ modeDrawDot :: Bool,+ modeSimulG :: Bool,+ modeGridOn :: Bool,+ modePolarGC :: Bool,+ modeCoordOff :: Bool,+ modeAxesOff :: Bool,+ modeLabelOn :: Bool+ }+ deriving Show++-- | There are four graph modes, each with+-- its own set of window ranges and equation+-- types.+data GraphMode = Func | Polar | Param | DiffEq++-- | Plain functions and Polar functions both use+-- a single equation.+type FuncEqn = Text++-- | Parametric functions use a pair of equations+data ParamEqn = ParamEqn {+ xEqn :: Text,+ yEqn :: Text+ } deriving Show++-- | Differential equations have a single equation+-- paired with an initial condition.+data DiffEqEqn = DiffEqEqn {+ diffEqn :: Text,+ diffIC :: Double+ } deriving Show++class HasGDB (a :: GraphMode) where+ type GDBEqn a :: *+ type GDBSettings a :: *++ showGDBSettings :: GDB a -> Text+ showGDBHeader :: GDB a -> Text++ showGDBLib :: GDB a -> Text+ showGDBLib gdb = intercalate "\n" (map showGDBEntry (gdbLib gdb))++ showGDBEntry :: GDBLibEntry a -> Text++instance HasGDB Func where+ type GDBEqn Func = FuncEqn+ type GDBSettings Func = FuncSettings++ showGDBSettings gdb = showFuncSettings (gdbSettings gdb)+ showGDBHeader _ = "ID\tSelected\tEquation\n"+ showGDBEntry (GDBLibEntry entryId selected eqn) =+ showText entryId <> "\t"+ <> showText selected <> "\t"+ <> eqn++instance HasGDB Polar where+ type GDBEqn Polar = FuncEqn+ type GDBSettings Polar = PolarSettings+ showGDBSettings gdb = showPolarSettings (gdbSettings gdb)+ showGDBHeader _ = "ID\tSelected\tEquation\n"+ showGDBEntry (GDBLibEntry entryId selected eqn) =+ showText entryId <> "\t"+ <> showText selected <> "\t"+ <> eqn++instance HasGDB Param where+ type GDBEqn Param = ParamEqn+ type GDBSettings Param = ParamSettings+ showGDBSettings gdb = showParamSettings (gdbSettings gdb)+ showGDBHeader _ = "ID\tSelected\tx-Equation\ty-Equation\n"+ showGDBEntry (GDBLibEntry entryId selected eqn) =+ showText entryId <> "\t"+ <> showText selected <> "\t"+ <> xEqn eqn <> "\t"+ <> yEqn eqn++instance HasGDB DiffEq where+ type GDBEqn DiffEq = DiffEqEqn+ type GDBSettings DiffEq = DiffEqSettings+ showGDBSettings gdb = showDiffEqSettings (gdbSettings gdb)+ showGDBHeader _ = "ID\tSelected\tEquation\tInitial Condition\n"+ showGDBEntry (GDBLibEntry entryId selected eqn) =+ showText entryId <> "\t"+ <> showText selected <> "\t"+ <> diffEqn eqn <> "\t"+ <> showText (diffIC eqn)+++-- | A graph database entry, containing a+-- function ID, whether or not it is currently+-- selected, and the equations that define the+-- function.+data GDBLibEntry (a :: GraphMode) = GDBLibEntry {+ libId :: Int,+ libSelected :: Bool,+ libEqn :: GDBEqn a+ }++-- | A graph database contains mode settings, window+-- settings, and a library of functions. The latter two+-- depend on the graphcs mode.+data GDB (a :: GraphMode) = GDB {+ gdbMode :: ModeSettings,+ gdbSettings :: GDBSettings a,+ gdbLib :: [GDBLibEntry a]+ }++instance HasGDB a => Show (GDB (a :: GraphMode)) where+ show gdb = unpack $ showGDB gdb+++-- | Variables have a type and type-specific data.+-- See also `Data.TI85.File.Variable.VarType`.+data Variable =+ TIScalar TINumber+ | TIVector [TINumber]+ | TIList [TINumber]+ | TIMatrix [[TINumber]]+ | TIConstant TINumber+ | TIEquation Text+ | TIString Text+ | TIProgram Program+ | TIPicture TIBitmap+ | TIFuncSettings FuncSettings+ | TIPolarSettings PolarSettings+ | TIParamSettings ParamSettings+ | TIDiffEqSettings DiffEqSettings+ | TIZRCL SavedWinSettings+ | TIFuncGDB (GDB Func)+ | TIPolarGDB (GDB Polar)+ | TIParamGDB (GDB Param)+ | TIDiffEqGDB (GDB DiffEq)+ deriving Show++-- * Text Conversion++-- | Utility for converting a showable+-- to Text.+showText :: Show a => a -> Text+showText = pack.show++-- | Convert a TINumber to Text.+showNumber :: TINumber -> Text+showNumber (TIReal x) = showText x+showNumber (TIComplex x y) = showText x <> "+" <> showText y <> "i"++-- | Convert a Program to Text.+showProgram :: Program -> Text+showProgram (PlainText progText) = progText+showProgram (Tokenized tokens) =+ foldMap (\(Token _ t) -> t) tokens++-- ** Window Settings++-- | Function window settings.+showFuncSettings :: FuncSettings -> Text+showFuncSettings settings =+ "\nxMin: " <> showNumber (fXMin settings)+ <> "\nxMax: " <> showNumber (fXMax settings)+ <> "\nxScl: " <> showNumber (fXScl settings)+ <> "\nyMin: " <> showNumber (fYMin settings)+ <> "\nyMax: " <> showNumber (fYMax settings)+ <> "\nyScl: " <> showNumber (fYScl settings)++-- | Polar window settings.+showPolarSettings :: PolarSettings -> Text+showPolarSettings settings =+ "\nθMin: " <> showNumber (polThetaMin settings)+ <> "\nθMax: " <> showNumber (polThetaMax settings)+ <> "\nθStep: " <> showNumber (polThetaStep settings)+ <> "\nxMin: " <> showNumber (polXMin settings)+ <> "\nxMax: " <> showNumber (polXMax settings)+ <> "\nxScl: " <> showNumber (polXScl settings)+ <> "\nyMin: " <> showNumber (polYMin settings)+ <> "\nyMax: " <> showNumber (polYMax settings)+ <> "\nyScl: " <> showNumber (polYScl settings)++-- | Parametric window settings.+showParamSettings :: ParamSettings -> Text+showParamSettings settings =+ "\ntMin: " <> showNumber (parTMin settings)+ <> "\ntMax: " <> showNumber (parTMax settings)+ <> "\ntStep: " <> showNumber (parTStep settings)+ <> "\nxMin: " <> showNumber (parXMin settings)+ <> "\nxMax: " <> showNumber (parXMax settings)+ <> "\nxScl: " <> showNumber (parXScl settings)+ <> "\nyMin: " <> showNumber (parYMin settings)+ <> "\nyMax: " <> showNumber (parYMax settings)+ <> "\nyScl: " <> showNumber (parYScl settings)++-- | DiffEq window settings.+showDiffEqSettings :: DiffEqSettings -> Text+showDiffEqSettings settings =+ "\ndiffTol: " <> showNumber (diffTol settings)+ <> "\ntPlot: " <> showNumber (diffTPlot settings)+ <> "\ntMin: " <> showNumber (diffTMin settings)+ <> "\ntMax: " <> showNumber (diffTMax settings)+ <> "\ntStep: " <> showNumber (diffTStep settings)+ <> "\nxMin: " <> showNumber (diffXMin settings)+ <> "\nxMax: " <> showNumber (diffXMax settings)+ <> "\nxScl: " <> showNumber (diffXScl settings)+ <> "\nyMin: " <> showNumber (diffYMin settings)+ <> "\nyMax: " <> showNumber (diffYMax settings)+ <> "\nyScl: " <> showNumber (diffYScl settings)+ <> "\nxAxis: " <> showAxis (diffXAxis settings)+ <> "\nyAxis: " <> showAxis (diffYAxis settings)+ where+ showAxis :: DiffEqAxis -> Text+ showAxis AxisT = "t"+ showAxis (AxisQ ai) =+ let ind = fromEnum ai+ in if ind == 0+ then "Q"+ else "Q" <> showText ind+ showAxis (AxisQ' ai) =+ let ind = fromEnum ai+ in if ind == 0+ then "Q'"+ else "Q'" <> showText ind++-- | Saved window settings.+showWinSettings :: SavedWinSettings -> Text+showWinSettings settings =+ "\nzθMin: " <> showNumber (zThetaMin settings)+ <> "\nzθMax: " <> showNumber (zThetaMax settings)+ <> "\nzθStep: " <> showNumber (zThetaStep settings)+ <> "\nztPlot: " <> showNumber (ztPlot settings)+ <> "\nztMin: " <> showNumber (ztMin settings)+ <> "\nztMax: " <> showNumber (ztMax settings)+ <> "\nztStep: " <> showNumber (ztStep settings)+ <> "\nzxMin: " <> showNumber (zxMin settings)+ <> "\nzxMax: " <> showNumber (zxMax settings)+ <> "\nzxScl: " <> showNumber (zxScl settings)+ <> "\nzyMin: " <> showNumber (zyMin settings)+ <> "\nzyMax: " <> showNumber (zyMax settings)+ <> "\nzyScl: " <> showNumber (zyScl settings)++showGDBMode :: ModeSettings -> Text+showGDBMode mode =+ "Dot/Line: " <> if modeDrawDot mode then "Dot" else "Line" <> "\n"+ <> "Simul/Seq: " <> if modeSimulG mode then "Simul" else "Seq" <> "\n"+ <> "Grid: " <> showText (modeGridOn mode) <> "\n"+ <> "Polar: " <> showText (modePolarGC mode) <> "\n"+ <> "Coord: " <> showText (not $ modeCoordOff mode) <> "\n"+ <> "Axes: " <> showText (not $ modeAxesOff mode) <> "\n"+ <> "Label: " <> showText (modeLabelOn mode) <> "\n"++showGDB :: HasGDB (a :: GraphMode) => GDB (a :: GraphMode) -> Text+showGDB gdb =+ "Mode Settings:\n"+ <> showGDBMode (gdbMode gdb) <> "\n"+ <> "Window Settings:"+ <> showGDBSettings gdb <> "\n\n"+ <> "Library:\n"+ <> showGDBHeader gdb+ <> showGDBLib gdb++-- | Convert a Variable to Text+showVariable :: Variable -> Text+showVariable (TIScalar tn) = showNumber tn+showVariable (TIVector tns) =+ let nums = map showNumber tns+ in "<" <> intercalate "," nums <> ">"+showVariable (TIList tns) =+ let nums = map showNumber tns+ in "[" <> intercalate "," nums <> "]"+showVariable (TIMatrix tnss) =+ let rows = [ intercalate "," $ map showNumber row | row <- tnss ]+ in "<<" <> intercalate "\n " rows <> ">>"+showVariable (TIConstant tn) = showNumber tn+showVariable (TIEquation txt) = txt+showVariable (TIString txt) = txt+showVariable (TIProgram pro) = showProgram pro+showVariable (TIPicture pic) = showAsciiArt pic+showVariable (TIFuncSettings settings) = showFuncSettings settings+showVariable (TIPolarSettings settings) = showPolarSettings settings+showVariable (TIParamSettings settings) = showParamSettings settings+showVariable (TIDiffEqSettings settings) = showDiffEqSettings settings+showVariable (TIZRCL settings) = showWinSettings settings+showVariable (TIFuncGDB gdb) = showGDB gdb+showVariable (TIPolarGDB gdb) = showGDB gdb+showVariable (TIParamGDB gdb) = showGDB gdb+showVariable (TIDiffEqGDB gdb) = showGDB gdb++-- * IO++-- | Print a textual representation of a Variable.+printVariable :: Variable -> IO ()+printVariable = putStrLn.showVariable+
+ src/Data/TI85/Var/Pic.hs view
@@ -0,0 +1,101 @@+-- | Picture variable types and utilities.+module Data.TI85.Var.Pic (+ -- * TI Bitmap+ TIBitmap,+ emptyBitmap,+ fromBytes,+ toBytes,+ -- * Display utilities+ showAsciiArt,+ writePicPng+ ) where++import Prelude hiding (take, drop)+import Data.Text (Text, pack, intercalate, take, drop)+import Data.Text.Encoding (decodeLatin1)+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import Codec.Picture+import Data.Bits+import Data.Word+import Data.Vector.Storable hiding (map,take,drop)+import Numeric (showHex)++import Data.TI85.Encoding (tiDecode)++-- | A TI Picture variable is encoded as a packed bitmap,+-- and is always 128x63 binary pixels.+newtype TIBitmap = TIBitmap ByteString deriving Show++_bmCols = 128+_bmRows = 63+_bmSizeBytes = 1008++-- | A blank (all zero) bitmap+emptyBitmap :: TIBitmap+emptyBitmap = TIBitmap (BS.replicate _bmSizeBytes 0x0)++-- | Create a bitmap from a packed `ByteString`. If the data+-- is the wrong size, returns `Nothing`.+fromBytes :: ByteString -> Maybe TIBitmap+fromBytes bytes = if BS.length bytes == 1008+ then Just (TIBitmap bytes)+ else Nothing++-- | Extract the raw `ByteString` from a bitmap.+toBytes :: TIBitmap -> ByteString+toBytes (TIBitmap bytes) = bytes++-- Expands an 8-bit word into an 8-element ByteString+explodeWord :: Word8 -> ByteString+explodeWord w =+ let (bits,_) = BS.unfoldrN 8 (\x -> Just (x .&. 1, shiftR x 1)) w+ in BS.reverse bits++-- Very approximate calculator-like colors+screenColorMap :: Word8 -> Pixel8+screenColorMap 0x0 = 0xc6+screenColorMap _ = 0x39++-- Convert a `ByteString`, where each bit represents a pixel into+-- a vector of pixels suitable for use in a `DynamicImage`.+-- Bits are mapped to pixels using a vaguely calculator-like+-- color map (light gray background, dark gray foreground).+bytesToPixels :: ByteString -> Vector Pixel8+bytesToPixels = fromList . map screenColorMap . BS.unpack . BS.concatMap explodeWord++-- Create an image out of a packed `ByteString`.+-- See `bytesToPixels`+bytesToImage :: ByteString -> Int -> Int -> DynamicImage+bytesToImage bytes width height =+ let pixels = bytesToPixels bytes+ in ImageY8 (Image width height pixels)++-- | Create a text-based representation of a picture.+-- Background pixels are rendered as spaces, while+-- foreground are rendered as full block glyphs (█).+showAsciiArt :: TIBitmap -> Text+showAsciiArt (TIBitmap bytes) =+ let bits = BS.concatMap explodeWord bytes+ --hexChars = pack $ BS.foldr' showHex "" bits+ --imgChars = chunk _bmCols hexChars []+ glyphs = tiDecode $ BS.map toGlyph bits+ imgChars = chunk _bmCols glyphs []+ in intercalate "\n" imgChars+ where+ toGlyph :: Word8 -> Word8+ toGlyph 0x0 = 0x20+ toGlyph _ = 0xdf++ chunk :: Int -> Text -> [Text] -> [Text]+ chunk n "" chunks = chunks+ chunk n xs chunks = take n xs : chunk n (drop n xs) chunks++-- | Save a bitmap as a PNG image.+-- Bits are mapped to pixels using a vaguely calculator-like+-- color map (light gray background, dark gray foreground).+writePicPng :: FilePath -> TIBitmap -> IO ()+writePicPng path (TIBitmap bitmap) =+ let img = bytesToImage bitmap _bmCols _bmRows+ in savePngImage path img+
+ test/Spec.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE OverloadedStrings #-}+import Hedgehog+import qualified Hedgehog.Gen as Gen+import qualified Hedgehog.Range as Range++import Data.Word++import Data.TI85++invalidID = [0x0b,0x13,0x14,0x16,0x1c]++validTypeID :: Word8 -> Bool+validTypeID val+ | val >= 0x00 && val <= 0x1e = val `notElem` invalidID+ | otherwise = False++-- | Generate a valid variable type ID+genTypeID :: Gen Word8+genTypeID = do+ Gen.filter validTypeID $ Gen.word8 (Range.linear 0x00 0x1e)+++-- | Check ID mapping syncrhonization+idMapping :: Property+idMapping = property $ do+ idVal <- forAll genTypeID+ (typeToId.idToType) idVal === idVal++tests :: IO Bool+tests = checkParallel $ Group "Test.TI85" [+ ("idMapping", idMapping)+ ]++main :: IO ()+main = do+ tests+ return ()