packages feed

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 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 ()