stim-parser 0.4.0.0 → 0.4.1.0
raw patch · 7 files changed
+67/−41 lines, 7 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ StimParser.DEM.Expr: TargetSeparator :: DEMTarget
Files
- CHANGELOG.md +13/−0
- src/StimParser/DEM/Expr.hs +3/−1
- src/StimParser/DEM/Parse.hs +2/−1
- src/StimParser/Parse.hs +3/−3
- stim-parser.cabal +1/−1
- test/Test/DEM/Parse.hs +17/−35
- test/Test/Parse.hs +28/−0
CHANGELOG.md view
@@ -1,5 +1,18 @@ # Revision history for stim-parser +## 0.4.1.0 -- 2026-07-20++* Parse DEM separator targets (`^`) inside `error(...)` instructions.+ Added `TargetSeparator` constructor to `DEMTarget`. This is a non-breaking+ API addition; downstream code pattern-matching on `DEMTarget` should add a+ wildcard or handle the new constructor.+* Require at least one target for gate and measurement instructions.+ `parseGate` and `parseMeasure` now use `some parseQ` instead of+ `parseExhaust parseQ`. This prevents single-letter gate/measure names such+ as `R`, `S`, `H`, `I`, and `M` from consuming the leading character of+ keywords like `REPEAT` and `SHIFT_COORDS` and producing misleading parse+ errors.+ ## 0.4.0.0 -- 2026-07-20 * Add Pauli-target support for `OBSERVABLE_INCLUDE` and `DETECTOR`
src/StimParser/DEM/Expr.hs view
@@ -11,10 +11,12 @@ newtype ObservableId = ObservableId { unObservableId :: Int } deriving (Show, Eq, Ord) --- | Targets of an error instruction: detectors and/or observables.+-- | Targets of an error instruction: detectors, observables, or the+-- separator target (^) used to suggest a decomposition. data DEMTarget = TargetDetector !DetectorId | TargetObservable !ObservableId+ | TargetSeparator deriving (Show, Eq) -- | A single error instruction: error(p) D0 D1 L0
src/StimParser/DEM/Parse.hs view
@@ -31,11 +31,12 @@ targets <- many parseDEMTarget return $ DEMError p targets --- | Parse a single target: D0 or L0+-- | Parse a single target: D0, L0, or ^ parseDEMTarget :: Parser DEMTarget parseDEMTarget = TargetDetector . DetectorId <$> (lstring "D" *> parseInt) <|> TargetObservable . ObservableId <$> (lstring "L" *> parseInt)+ <|> TargetSeparator <$ lstring "^" -- | Parse a detector declaration: detector(0, 0) D0 parseDEMDetector :: Parser DEMDetector
src/StimParser/Parse.hs view
@@ -103,7 +103,7 @@ parseGate = do gty <- parseGateTy tag <- optional parseTag- qs <- parseExhaust parseQ+ qs <- some parseQ return $ Gate gty tag qs parseMeasureTy :: Parser MeasureTy@@ -124,13 +124,13 @@ mty <- parseMeasureTy tag <- optional parseTag ph <- parsePh- qs <- parseExhaust parseQ+ qs <- some parseQ return $ Measure mty tag (Just ph) qs -- parser for case: M[tag] 0 pm2 = do mty <- parseMeasureTy tag <- optional parseTag- qs <- parseExhaust parseQ+ qs <- some parseQ return $ Measure mty tag Nothing qs -- the order is tricky - try phase version first try pm1 <|> pm2
stim-parser.cabal view
@@ -20,7 +20,7 @@ -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change-version: 0.4.0.0+version: 0.4.1.0 -- A short (one-line) description of the package. synopsis: A parser combinator library for STIM quantum circuit files
test/Test/DEM/Parse.hs view
@@ -283,50 +283,32 @@ DEM [] ~=? run parseDEM "" ] --- | Caret (^) rejection — '^' indicates unflattened DEM decompositions,--- which are not part of the stable DEM format. We reject them loudly--- instead of silently discarding them.+-- | Caret (^) separator targets inside error(...) instructions. testParseDEMCaret :: Test testParseDEMCaret = TestList- [ "single caret fails" ~: TestCase $ do- let result = runParser parseDEM "" "error(0.01) D0 ^ D1 L0"- case result of- Left _ -> return ()- Right _ -> assertFailure "parser should fail on unflattened DEM with ^"-- , "multiple carets fails" ~: TestCase $ do- let result = runParser parseDEM "" "error(0.01) D0 ^ D1 ^ D2 L0"- case result of- Left _ -> return ()- Right _ -> assertFailure "parser should fail on unflattened DEM with ^"+ [ "single caret parses" ~:+ DEM [DEMInstrError (DEMError 0.02 [TargetDetector (DetectorId 2), TargetObservable (ObservableId 0), TargetSeparator, TargetDetector (DetectorId 5), TargetDetector (DetectorId 6)])]+ ~=? run parseDEM "error(0.02) D2 L0 ^ D5 D6\n" - , "caret between detectors fails" ~: TestCase $ do- let result = runParser parseDEM "" "error(0.01) D0 ^ D1 ^ D2"- case result of- Left _ -> return ()- Right _ -> assertFailure "parser should fail on unflattened DEM with ^"+ , "multiple carets parse" ~:+ DEM [DEMInstrError (DEMError 0.01 [TargetDetector (DetectorId 0), TargetSeparator, TargetDetector (DetectorId 1), TargetSeparator, TargetObservable (ObservableId 0)])]+ ~=? run parseDEM "error(0.01) D0 ^ D1 ^ L0\n" - , "caret with observables fails" ~: TestCase $ do- let result = runParser parseDEM "" "error(0.01) D0 ^ L0 ^ D1"- case result of- Left _ -> return ()- Right _ -> assertFailure "parser should fail on unflattened DEM with ^"+ , "caret between detectors parses" ~:+ DEM [DEMInstrError (DEMError 0.01 [TargetDetector (DetectorId 0), TargetSeparator, TargetDetector (DetectorId 1), TargetSeparator, TargetDetector (DetectorId 2)])]+ ~=? run parseDEM "error(0.01) D0 ^ D1 ^ D2\n" - , "caret at start fails" ~: TestCase $ do- let result = runParser parseDEM "" "error(0.01) ^ D0 D1"- case result of- Left _ -> return ()- Right _ -> assertFailure "parser should fail on unflattened DEM with ^"+ , "caret at start parses" ~:+ DEM [DEMInstrError (DEMError 0.01 [TargetSeparator, TargetDetector (DetectorId 0), TargetDetector (DetectorId 1)])]+ ~=? run parseDEM "error(0.01) ^ D0 D1\n" - , "caret at end fails" ~: TestCase $ do- let result = runParser parseDEM "" "error(0.01) D0 D1 ^"- case result of- Left _ -> return ()- Right _ -> assertFailure "parser should fail on unflattened DEM with ^"+ , "caret at end parses" ~:+ DEM [DEMInstrError (DEMError 0.01 [TargetDetector (DetectorId 0), TargetDetector (DetectorId 1), TargetSeparator])]+ ~=? run parseDEM "error(0.01) D0 D1 ^\n" , "no-caret error still parses" ~: DEM [DEMInstrError (DEMError 0.01 [TargetDetector (DetectorId 0), TargetDetector (DetectorId 1), TargetObservable (ObservableId 0)])]- ~=? run parseDEM "error(0.01) D0 D1 L0"+ ~=? run parseDEM "error(0.01) D0 D1 L0\n" ] -- | EOF enforcement — parseDEM must consume all input
test/Test/Parse.hs view
@@ -1,6 +1,7 @@ module Test.Parse where import Test.HUnit+import Text.Megaparsec (runParser) import StimParser.Parse import StimParser.ParseUtils (run) import StimParser.Expr@@ -25,6 +26,7 @@ , testParseFInd , testParseAnn , testParseStimNoiseOrder+ , testParseZeroTargetRejection ] -- Helper to compare using show (since types don't derive Eq)@@ -310,3 +312,29 @@ (StimList [StimG (Gate H Nothing [Q 0, Q 1, Q 2])]) (run parseStim "!!!Start\nH 0 1 2\n") ]++-- | Zero-target gates/measures must fail, and keywords that share prefixes with+-- gate/measure names must not be misparsed.+testParseZeroTargetRejection :: Test+testParseZeroTargetRejection = TestList+ [ "parseGate H with no target fails" ~: TestCase $ do+ let result = runParser parseGate "" "H"+ case result of+ Left _ -> return ()+ Right _ -> assertFailure "gate with zero targets should fail"++ , "parseMeasure M with no target fails" ~: TestCase $ do+ let result = runParser parseMeasure "" "M"+ case result of+ Left _ -> return ()+ Right _ -> assertFailure "measure with zero targets should fail"++ , assertShowEqual "SHIFT_COORDS after TICK parses"+ (StimList [StimAnn (Ann TICK Nothing [] []), StimAnn (Ann SHIFT_COORDS Nothing [In 0, In 0, In 1] [])])+ (run parseStim "!!!Start\nTICK\nSHIFT_COORDS(0, 0, 1)\n")++ , assertShowEqual "REPEAT after DETECTOR parses"+ (StimList [StimAnn (Ann DETECTOR Nothing [In 0, In 0, In 0] [AnnRec (Rec (-1))]), StimRepeat 10 (StimList [StimAnn (Ann TICK Nothing [] [])])])+ (run parseStim "!!!Start\nDETECTOR(0, 0, 0) rec[-1]\nREPEAT 10 {\nTICK\n}\n")+ ]+