From a00236565117e6e6bed377d433ce92ac6c4c5839 Mon Sep 17 00:00:00 2001 From: James Eversole Date: Thu, 7 May 2026 12:28:14 -0500 Subject: [PATCH] Add Arborix section directory byte readers --- lib/arborix.tri | 55 ++++++++++++++++++++++ test/Spec.hs | 122 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) diff --git a/lib/arborix.tri b/lib/arborix.tri index df567bd..7ce6026 100644 --- a/lib/arborix.tri +++ b/lib/arborix.tri @@ -20,3 +20,58 @@ readArborixHeader = (bs : (pair majorVersion (pair minorVersion sectionCount)) afterSectionCount))))) + +readSectionRecord = (bs : + bindResult (readBytes 2 bs) + (sectionId afterSectionId : + bindResult (readBytes 4 afterSectionId) + (offset afterOffset : + bindResult (readBytes 4 afterOffset) + (length afterLength : + ok + (pair sectionId + (pair offset length)) + afterLength)))) + +readSectionDirectory_ = y (self sectionCount i bs acc : + matchBool + (ok (reverse acc) bs) + (bindResult (readSectionRecord bs) + (sectionRecord afterSectionRecord : + self sectionCount (succ i) afterSectionRecord (pair sectionRecord acc))) + (equal? i sectionCount)) + +readSectionDirectory = (sectionCount bs : readSectionDirectory_ sectionCount 0 bs t) + +sectionRecordId = (sectionRecord : + matchPair + (sectionId _ : sectionId) + sectionRecord) + +sectionRecordOffset = (sectionRecord : + matchPair + (_ payload : + matchPair + (offset _ : offset) + payload) + sectionRecord) + +sectionRecordLength = (sectionRecord : + matchPair + (_ payload : + matchPair + (_ length : length) + payload) + sectionRecord) + +lookupSectionRecord = y (self sectionId directory : + matchList + nothing + (sectionRecord rest : + matchBool + (just sectionRecord) + (self sectionId rest) + (bytesEq? sectionId (sectionRecordId sectionRecord))) + directory) + +byteSlice = (offset length bytes : bytesTake length (bytesDrop offset bytes)) diff --git a/test/Spec.hs b/test/Spec.hs index a45cd4d..fe813ba 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -1554,4 +1554,126 @@ binaryReaderTests = testGroup "Binary Reader Tests" library <- evaluateFile "./lib/arborix.tri" let env = evalTricu library (parseTricu input) result env @?= errT eofT (bytesT [65,82]) + + -- ------------------------------------------------------------------------ + -- Arborix section directory record parsing + -- ------------------------------------------------------------------------ + + , testCase "readSectionRecord: parses raw section id offset and length" $ do + let input = "readSectionRecord [(0) (2) (0) (0) (0) (16) (0) (0) (0) (32)]" + library <- evaluateFile "./lib/arborix.tri" + let env = evalTricu library (parseTricu input) + result env @?= okT + (pairT (bytesT [0,2]) + (pairT (bytesT [0,0,0,16]) + (bytesT [0,0,0,32]))) + (bytesT []) + + , testCase "readSectionRecord: preserves trailing bytes" $ do + let input = "readSectionRecord [(0) (2) (0) (0) (0) (16) (0) (0) (0) (32) (9) (8)]" + library <- evaluateFile "./lib/arborix.tri" + let env = evalTricu library (parseTricu input) + result env @?= okT + (pairT (bytesT [0,2]) + (pairT (bytesT [0,0,0,16]) + (bytesT [0,0,0,32]))) + (bytesT [9,8]) + + , testCase "readSectionRecord: empty input returns EOF" $ do + let input = "readSectionRecord []" + library <- evaluateFile "./lib/arborix.tri" + let env = evalTricu library (parseTricu input) + result env @?= errT eofT (bytesT []) + + , testCase "readSectionRecord: short section id returns EOF preserving input" $ do + let input = "readSectionRecord [(0)]" + library <- evaluateFile "./lib/arborix.tri" + let env = evalTricu library (parseTricu input) + result env @?= errT eofT (bytesT [0]) + + , testCase "readSectionRecord: missing offset returns EOF preserving unread offset bytes" $ do + let input = "readSectionRecord [(0) (2)]" + library <- evaluateFile "./lib/arborix.tri" + let env = evalTricu library (parseTricu input) + result env @?= errT eofT (bytesT []) + + , testCase "readSectionRecord: short offset returns EOF preserving unread offset bytes" $ do + let input = "readSectionRecord [(0) (2) (0) (0) (0)]" + library <- evaluateFile "./lib/arborix.tri" + let env = evalTricu library (parseTricu input) + result env @?= errT eofT (bytesT [0,0,0]) + + , testCase "readSectionRecord: missing length returns EOF preserving unread length bytes" $ do + let input = "readSectionRecord [(0) (2) (0) (0) (0) (16)]" + library <- evaluateFile "./lib/arborix.tri" + let env = evalTricu library (parseTricu input) + result env @?= errT eofT (bytesT []) + + , testCase "readSectionRecord: short length returns EOF preserving unread length bytes" $ do + let input = "readSectionRecord [(0) (2) (0) (0) (0) (16) (0) (0) (0)]" + library <- evaluateFile "./lib/arborix.tri" + let env = evalTricu library (parseTricu input) + result env @?= errT eofT (bytesT [0,0,0]) + + -- ------------------------------------------------------------------------ + -- Arborix section directory parsing + -- ------------------------------------------------------------------------ + + , testCase "readSectionDirectory: zero records preserves input" $ do + let input = "readSectionDirectory 0 [(9) (8)]" + library <- evaluateFile "./lib/arborix.tri" + let env = evalTricu library (parseTricu input) + result env @?= okT (ofList []) (bytesT [9,8]) + + , testCase "readSectionDirectory: reads requested records and preserves trailing bytes" $ do + let input = "readSectionDirectory 2 [(0) (1) (0) (0) (0) (10) (0) (0) (0) (20) (0) (2) (0) (0) (0) (30) (0) (0) (0) (40) (9)]" + library <- evaluateFile "./lib/arborix.tri" + let env = evalTricu library (parseTricu input) + result env @?= okT + (ofList + [ pairT (bytesT [0,1]) + (pairT (bytesT [0,0,0,10]) + (bytesT [0,0,0,20])) + , pairT (bytesT [0,2]) + (pairT (bytesT [0,0,0,30]) + (bytesT [0,0,0,40])) + ]) + (bytesT [9]) + + , testCase "readSectionDirectory: truncated record returns EOF" $ do + let input = "readSectionDirectory 2 [(0) (1) (0) (0) (0) (10) (0) (0) (0) (20) (0) (2) (0) (0)]" + library <- evaluateFile "./lib/arborix.tri" + let env = evalTricu library (parseTricu input) + result env @?= errT eofT (bytesT [0,0]) + + -- ------------------------------------------------------------------------ + -- Arborix section lookup and raw byte slicing + -- ------------------------------------------------------------------------ + + , testCase "lookupSectionRecord: finds record by raw section id" $ do + let input = "lookupSectionRecord [(0) (2)] [(pair [(0) (1)] (pair [(0) (0) (0) (10)] [(0) (0) (0) (20)])) (pair [(0) (2)] (pair [(0) (0) (0) (30)] [(0) (0) (0) (40)]))]" + library <- evaluateFile "./lib/arborix.tri" + let env = evalTricu library (parseTricu input) + result env @?= justT + (pairT (bytesT [0,2]) + (pairT (bytesT [0,0,0,30]) + (bytesT [0,0,0,40]))) + + , testCase "lookupSectionRecord: missing section id returns nothing" $ do + let input = "lookupSectionRecord [(0) (3)] [(pair [(0) (1)] (pair [(0) (0) (0) (10)] [(0) (0) (0) (20)])) (pair [(0) (2)] (pair [(0) (0) (0) (30)] [(0) (0) (0) (40)]))]" + library <- evaluateFile "./lib/arborix.tri" + let env = evalTricu library (parseTricu input) + result env @?= nothingT + + , testCase "byteSlice: extracts requested byte range" $ do + let input = "byteSlice 2 3 [(10) (11) (12) (13) (14) (15)]" + library <- evaluateFile "./lib/arborix.tri" + let env = evalTricu library (parseTricu input) + result env @?= bytesT [12,13,14] + + , testCase "byteSlice: overlong length returns remaining bytes" $ do + let input = "byteSlice 4 9 [(10) (11) (12) (13) (14) (15)]" + library <- evaluateFile "./lib/arborix.tri" + let env = evalTricu library (parseTricu input) + result env @?= bytesT [14,15] ]