mirror of
https://github.com/Start9Labs/registry.git
synced 2026-03-31 04:03:40 +00:00
conj normalization completely works now it seems
This commit is contained in:
@@ -53,6 +53,7 @@ dependencies:
|
|||||||
- process
|
- process
|
||||||
- protolude
|
- protolude
|
||||||
- rainbow
|
- rainbow
|
||||||
|
- semigroupoids
|
||||||
- shakespeare
|
- shakespeare
|
||||||
- template-haskell
|
- template-haskell
|
||||||
- terminal-progress-bar
|
- terminal-progress-bar
|
||||||
@@ -127,6 +128,7 @@ tests:
|
|||||||
ghc-options:
|
ghc-options:
|
||||||
- -Wall
|
- -Wall
|
||||||
- -fdefer-typed-holes
|
- -fdefer-typed-holes
|
||||||
|
- +RTS -N4 -RTS
|
||||||
dependencies:
|
dependencies:
|
||||||
- start9-registry
|
- start9-registry
|
||||||
- hspec
|
- hspec
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ module Lib.Types.Emver (
|
|||||||
satisfies,
|
satisfies,
|
||||||
(<||),
|
(<||),
|
||||||
(||>),
|
(||>),
|
||||||
-- we do not export 'None' because it is useful for its internal algebraic properties only
|
|
||||||
VersionRange (..),
|
VersionRange (..),
|
||||||
Version (..),
|
Version (..),
|
||||||
AnyRange (..),
|
AnyRange (..),
|
||||||
@@ -39,22 +38,32 @@ module Lib.Types.Emver (
|
|||||||
parseRange,
|
parseRange,
|
||||||
reduce,
|
reduce,
|
||||||
nodes,
|
nodes,
|
||||||
|
eq,
|
||||||
|
neq,
|
||||||
|
lt,
|
||||||
|
leq,
|
||||||
|
gt,
|
||||||
|
geq,
|
||||||
|
fl,
|
||||||
|
fr,
|
||||||
|
faces,
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Startlude (
|
import Startlude (
|
||||||
Alternative ((<|>)),
|
Alternative ((<|>)),
|
||||||
Applicative (liftA2, pure, (*>), (<*)),
|
Applicative (liftA2, pure, (*>), (<*)),
|
||||||
Bool (..),
|
Bool (..),
|
||||||
|
Bounded (maxBound),
|
||||||
Either (..),
|
Either (..),
|
||||||
Eq (..),
|
Eq (..),
|
||||||
Foldable (foldMap, length),
|
Foldable (length),
|
||||||
Hashable,
|
Hashable,
|
||||||
IsString (..),
|
IsString (..),
|
||||||
Monad ((>>=)),
|
Monad ((>>=)),
|
||||||
Monoid (mappend, mempty),
|
Monoid (mappend, mempty),
|
||||||
NFData (..),
|
NFData (..),
|
||||||
Num ((+)),
|
Num ((+)),
|
||||||
Ord (compare),
|
Ord (..),
|
||||||
Ordering (..),
|
Ordering (..),
|
||||||
Read,
|
Read,
|
||||||
Semigroup ((<>)),
|
Semigroup ((<>)),
|
||||||
@@ -63,10 +72,13 @@ import Startlude (
|
|||||||
Word,
|
Word,
|
||||||
Word64,
|
Word64,
|
||||||
either,
|
either,
|
||||||
|
empty,
|
||||||
flip,
|
flip,
|
||||||
fst,
|
fst,
|
||||||
id,
|
id,
|
||||||
isRight,
|
isRight,
|
||||||
|
maybe,
|
||||||
|
not,
|
||||||
on,
|
on,
|
||||||
otherwise,
|
otherwise,
|
||||||
seq,
|
seq,
|
||||||
@@ -86,9 +98,12 @@ import Startlude (
|
|||||||
import Control.Monad.Fail (fail)
|
import Control.Monad.Fail (fail)
|
||||||
import Data.Aeson (ToJSONKey)
|
import Data.Aeson (ToJSONKey)
|
||||||
import Data.Attoparsec.Text qualified as Atto
|
import Data.Attoparsec.Text qualified as Atto
|
||||||
|
import Data.List.NonEmpty qualified as NE
|
||||||
|
import Data.Semigroup.Foldable (Foldable1 (foldMap1))
|
||||||
import Data.String.Interpolate.IsString (i)
|
import Data.String.Interpolate.IsString (i)
|
||||||
import Data.Text qualified as T
|
import Data.Text qualified as T
|
||||||
import GHC.Base (Ord (..), error)
|
import Debug.Trace qualified
|
||||||
|
import GHC.Base (error)
|
||||||
import GHC.Read qualified as GHC (
|
import GHC.Read qualified as GHC (
|
||||||
readsPrec,
|
readsPrec,
|
||||||
)
|
)
|
||||||
@@ -139,6 +154,14 @@ revision :: Version -> Word
|
|||||||
revision (Version (_, _, _, q)) = q
|
revision (Version (_, _, _, q)) = q
|
||||||
|
|
||||||
|
|
||||||
|
adjacent :: Version -> Version -> Bool
|
||||||
|
adjacent a b =
|
||||||
|
major a == major b
|
||||||
|
&& minor a == minor b
|
||||||
|
&& patch a == patch b
|
||||||
|
&& (revision a == revision b + 1 && revision b /= maxBound || revision b == revision a + 1 && revision a /= maxBound)
|
||||||
|
|
||||||
|
|
||||||
-- | 'Operator' is the type that specifies how to compare against the target version. Right represents the ordering,
|
-- | 'Operator' is the type that specifies how to compare against the target version. Right represents the ordering,
|
||||||
-- Left negates it
|
-- Left negates it
|
||||||
type Operator = Either Ordering Ordering
|
type Operator = Either Ordering Ordering
|
||||||
@@ -228,6 +251,24 @@ data VersionRange
|
|||||||
deriving (Eq)
|
deriving (Eq)
|
||||||
|
|
||||||
|
|
||||||
|
instance Show VersionRange where
|
||||||
|
show (Anchor (Left EQ) v) = '!' : '=' : GHC.show v
|
||||||
|
show (Anchor (Right EQ) v) = '=' : GHC.show v
|
||||||
|
show (Anchor (Left LT) v) = '>' : '=' : GHC.show v
|
||||||
|
show (Anchor (Right LT) v) = '<' : GHC.show v
|
||||||
|
show (Anchor (Left GT) v) = '<' : '=' : GHC.show v
|
||||||
|
show (Anchor (Right GT) v) = '>' : GHC.show v
|
||||||
|
-- show (Conj a@(Disj _ _) b@(Disj _ _)) = paren (GHC.show a) <> (' ' : paren (GHC.show b))
|
||||||
|
-- show (Conj a@(Disj _ _) b) = paren (GHC.show a) <> (' ' : GHC.show b)
|
||||||
|
-- show (Conj a b@(Disj _ _)) = GHC.show a <> (' ' : paren (GHC.show b))
|
||||||
|
show (Conj a b) = paren (GHC.show a) <> (' ' : paren (GHC.show b))
|
||||||
|
show (Disj a b) = paren (GHC.show a) <> " || " <> paren (GHC.show b)
|
||||||
|
show Any = "*"
|
||||||
|
show None = "!"
|
||||||
|
instance Read VersionRange where
|
||||||
|
readsPrec _ s = case Atto.parseOnly parseRange (T.pack s) of
|
||||||
|
Left _ -> []
|
||||||
|
Right a -> [(a, "")]
|
||||||
instance IsString VersionRange where
|
instance IsString VersionRange where
|
||||||
fromString = either error id . Atto.parseOnly parseRange . toS
|
fromString = either error id . Atto.parseOnly parseRange . toS
|
||||||
|
|
||||||
@@ -256,22 +297,37 @@ disj :: VersionRange -> VersionRange -> VersionRange
|
|||||||
disj a b = reduce $ Disj a b
|
disj a b = reduce $ Disj a b
|
||||||
|
|
||||||
|
|
||||||
|
beforeAfter :: Show a => a -> a -> a
|
||||||
|
beforeAfter before after = Debug.Trace.trace [i|BEFORE: #{before} ===> After #{after}|] after
|
||||||
|
|
||||||
|
|
||||||
|
dbg :: Show a => a -> a
|
||||||
|
dbg = show >>= Debug.Trace.trace
|
||||||
|
|
||||||
|
|
||||||
reduce :: VersionRange -> VersionRange
|
reduce :: VersionRange -> VersionRange
|
||||||
-- atomic forms
|
-- atomic forms
|
||||||
reduce Any = Any
|
reduce Any = Any
|
||||||
reduce None = None
|
reduce None = None
|
||||||
|
reduce vr@(Anchor op v@(Version (0, 0, 0, 0))) = case op of
|
||||||
|
(Right LT) -> None
|
||||||
|
(Right EQ) -> vr
|
||||||
|
(Right GT) -> vr
|
||||||
|
(Left LT) -> Any
|
||||||
|
(Left EQ) -> Anchor gt v
|
||||||
|
(Left GT) -> Anchor eq v
|
||||||
reduce vr@(Anchor _ _) = vr
|
reduce vr@(Anchor _ _) = vr
|
||||||
reduce orig@(Conj x y) = case (reduce x, reduce y) of
|
reduce orig@(Conj x y) = case (reduce x, reduce y) of
|
||||||
-- conj units
|
-- conj units
|
||||||
(Any, vr) -> reduce vr
|
(Any, vr) -> vr
|
||||||
(vr, Any) -> reduce vr
|
(vr, Any) -> vr
|
||||||
-- conj annihilators
|
-- conj annihilators
|
||||||
(None, _) -> None
|
(None, _) -> None
|
||||||
(_, None) -> None
|
(_, None) -> None
|
||||||
-- primitive conjunction reduction
|
-- primitive conjunction reduction
|
||||||
(a@(Anchor op pt), b@(Anchor op' pt')) -> case compare pt pt' of
|
(a@(Anchor op pt), b@(Anchor op' pt')) -> case compare pt pt' of
|
||||||
-- conj commutes so we can make normalization order the points
|
-- conj commutes so we can make normalization order the points
|
||||||
GT -> reduce (Conj b a)
|
GT -> conj b a
|
||||||
-- trivial cases where the points are identical
|
-- trivial cases where the points are identical
|
||||||
EQ -> case (isRight op, isRight op == isRight op', primOrd op == primOrd op') of
|
EQ -> case (isRight op, isRight op == isRight op', primOrd op == primOrd op') of
|
||||||
-- the theorems found here will elucidate what is going on
|
-- the theorems found here will elucidate what is going on
|
||||||
@@ -307,78 +363,183 @@ reduce orig@(Conj x y) = case (reduce x, reduce y) of
|
|||||||
-- Right specific patterns: xo, <o, x<, <<
|
-- Right specific patterns: xo, <o, x<, <<
|
||||||
(True, False) -> b
|
(True, False) -> b
|
||||||
-- Irreducible patterns: <>, <x, x>, xx
|
-- Irreducible patterns: <>, <x, x>, xx
|
||||||
(True, True) -> orig
|
(True, True) ->
|
||||||
(a@(Anchor opa pta), Conj b@(Anchor opb ptb) c@(Anchor opc ptc)) ->
|
if adjacent pt pt'
|
||||||
case (compare pta ptb, compare pta ptc) of
|
then -- here we have some weird edge cases if versions are immediately adjacent
|
||||||
-- eq patterns reduce so prioritize conj'ing those
|
case (op, op') of
|
||||||
(EQ, LT) -> conj (conj a b) c
|
-- <>
|
||||||
(GT, EQ) -> conj b (conj a c)
|
(Right GT, Right LT) -> None
|
||||||
-- here we are to the right of an irreducible conj, so we try to reduce with the right side
|
(Left LT, Right LT) -> a
|
||||||
(GT, GT) -> case conj c a of
|
(Right GT, Left GT) -> b
|
||||||
-- cascade if successful
|
(Left LT, Left GT) -> Conj a b
|
||||||
cca@(Anchor _ _) -> conj b cca
|
-- x>
|
||||||
-- otherwise we move xs out right
|
(Left EQ, Right LT) -> Anchor lt pt
|
||||||
-- we know for sure at this point that the opc is x
|
(Left EQ, Left GT) -> Conj a b
|
||||||
_ -> case (fl opb, fr opa) of
|
-- <x
|
||||||
(True, True) -> Conj b (Conj c a) -- xxx: x (xx)
|
(Right GT, Left EQ) -> Anchor gt pt'
|
||||||
(True, False) -> Conj a (Conj b c) -- xx>: > (xx)
|
(Left LT, Left EQ) -> Conj a b
|
||||||
(False, True) -> Conj b (Conj c a) -- <xx: < (xx)
|
-- xx
|
||||||
(False, False) -> Conj (Conj b a) c -- <x>: (<>) x
|
(Left EQ, Left EQ) -> Conj a b
|
||||||
-- here we are to the left of an irreducible conj, so we try to reduce the left side
|
_ -> error [i|impossible reduction (anchor, anchor)|]
|
||||||
(LT, LT) -> case conj a b of
|
else Conj a b
|
||||||
-- cascade if successful
|
-- insert anchor into irreducible conj pair
|
||||||
cab@(Anchor _ _) -> conj cab c
|
(a@(Anchor opa pta), y'@(Conj b@(Anchor opb ptb) c@(Anchor opc ptc)))
|
||||||
-- otherwise we move xs out right
|
| opa == neq && opb == neq && opc == neq && pta < ptb && ptb < ptc -> Conj a y'
|
||||||
-- we know for sure that opb is x
|
| otherwise ->
|
||||||
_ -> case (fl opa, fr opc) of
|
case (compare pta ptb, compare pta ptc) of
|
||||||
(True, True) -> Conj a (Conj b c) -- xxx: x (xx)
|
-- eq patterns reduce so prioritize conj'ing those
|
||||||
(True, False) -> Conj c (Conj a b) -- xx>: > (xx)
|
(EQ, LT) -> conj (conj a b) c
|
||||||
(False, True) -> Conj a (Conj b c) -- <xx: < (xx)
|
(GT, EQ) -> conj b (conj a c)
|
||||||
(False, False) -> Conj (Conj a c) b -- <x>: (<>) x
|
-- here we are to the right of an irreducible conj, so we try to reduce with the right side
|
||||||
-- here we are in the middle of an irreducible conj
|
(GT, GT) -> case conj c a of
|
||||||
(GT, LT)
|
-- cascade if successful
|
||||||
-- <x> <xx xx> xxx all irreducible
|
None -> None
|
||||||
| opa == neq -> conj b (conj a c)
|
Any -> b
|
||||||
-- if there is a remaining left face component it will reduce with the right side
|
cca@(Anchor _ _) -> conj b cca
|
||||||
| fl opa -> conj b (conj a c)
|
-- otherwise we move xs out right
|
||||||
-- corollary
|
-- we know for sure at this point that the opc is x
|
||||||
| fr opa -> conj (conj b a) c
|
_ -> case (fl opb, fr opa) of
|
||||||
-- only remaining case is eq, which subsumes both sides
|
(True, True) -> Conj b (Conj c a) -- xxx: x (xx)
|
||||||
| otherwise -> a
|
(True, False) -> Conj a (Conj b c) -- xx>: > (xx)
|
||||||
-- impossible because all anchors of equal versions reduce
|
(False, True) -> Conj b (Conj c a) -- <xx: < (xx)
|
||||||
(EQ, EQ) -> error [i|bug in equal anchor version reduction: #{orig} -> #{a}, #{b}, #{c}|]
|
(False, False) -> Conj (Conj b a) c -- <x>: (<>) x
|
||||||
-- ordinarily we reorder things so the lesser point is on the left
|
-- here we are to the left of an irreducible conj, so we try to reduce the left side
|
||||||
-- the only exception to this is the accumulation of x's on the right
|
(LT, LT) -> case conj a b of
|
||||||
-- so these cases should be impossible
|
-- cascade if successful
|
||||||
_
|
None -> None
|
||||||
| opb == neq && opc == neq -> orig
|
Any -> c
|
||||||
| otherwise -> error $ [i|bug in anchor order normalization: #{orig} -> #{a}, #{b}, #{c}|]
|
cab@(Anchor _ _) -> conj cab c
|
||||||
(x'@(Conj (Anchor _ _) (Anchor _ _)), y'@(Anchor _ _)) -> conj y' x'
|
-- otherwise we move xs out right
|
||||||
|
-- we know for sure that opb is x
|
||||||
|
_ -> case (fl opa, fr opc) of
|
||||||
|
(True, True) -> Conj a (Conj b c) -- xxx: x (xx)
|
||||||
|
(True, False) -> Conj c (Conj a b) -- xx>: > (xx)
|
||||||
|
(False, True) -> Conj a (Conj b c) -- <xx: < (xx)
|
||||||
|
(False, False) -> Conj (Conj a c) b -- <x>: (<>) x
|
||||||
|
-- here we are in the middle of an irreducible conj
|
||||||
|
(GT, LT)
|
||||||
|
-- <x> <xx xx> xxx all irreducible
|
||||||
|
| opa == neq -> conj b (Conj a c)
|
||||||
|
-- if there is a remaining left face component it will reduce with the right side
|
||||||
|
| fl opa -> conj b (conj a c)
|
||||||
|
-- corollary
|
||||||
|
| fr opa -> conj (conj b a) c
|
||||||
|
-- only remaining case is eq, which subsumes both sides
|
||||||
|
| otherwise -> a
|
||||||
|
-- impossible because all anchors of equal versions reduce
|
||||||
|
(EQ, EQ) -> error [i|bug in equal anchor version reduction: #{orig} -> #{a}, #{b}, #{c}|]
|
||||||
|
-- ordinarily we reorder things so the lesser point is on the left
|
||||||
|
-- the only exception to this is the accumulation of x's on the right
|
||||||
|
-- so these cases should be impossible
|
||||||
|
_ -> error $ [i|bug in anchor order normalization: #{orig} -> #{a}, #{b}, #{c}|]
|
||||||
|
(x'@(Conj (Anchor opa pta) (Anchor opb ptb)), c@(Anchor opc ptc))
|
||||||
|
-- in this case it is a fully reduced conj form with lower and upper bounds and a single x
|
||||||
|
| opc == neq && opa /= neq && opb /= neq && ptc > pta && ptc < ptb -> Conj x' c
|
||||||
|
-- isn't in normal form and we will attempt to do so by swapping args and reducing to hit the case above
|
||||||
|
| otherwise -> conj c x'
|
||||||
|
(x'@(Conj _ _), y'@(Anchor _ _)) -> conj y' x'
|
||||||
|
-- insert anchor into conj tree with a single bounded conj tree or x tree
|
||||||
|
(a@(Anchor opa pta), y'@(Conj b@(Anchor opb ptb) at@(Conj _ _))) ->
|
||||||
|
case (opa == neq, opb == neq) of
|
||||||
|
-- this means that the conj tree is all x's
|
||||||
|
(True, True) ->
|
||||||
|
case compare pta ptb of
|
||||||
|
LT -> Conj a y'
|
||||||
|
EQ -> y'
|
||||||
|
GT -> Conj b (Conj a at)
|
||||||
|
-- this means that b is a bound and so we put a with the rest of the x's
|
||||||
|
(True, False) ->
|
||||||
|
if fl opb && pta < ptb || fr opb && pta > ptb
|
||||||
|
then conj b (conj a at)
|
||||||
|
else y'
|
||||||
|
-- this means that the conj tree is all neqs and so when a is a bound we give it the bound position
|
||||||
|
(False, True) -> case compare pta ptb of
|
||||||
|
LT -> if fr opa then Conj a y' else a
|
||||||
|
EQ -> conj (conj a b) at
|
||||||
|
GT ->
|
||||||
|
if fl opa
|
||||||
|
then case conj a at of
|
||||||
|
a'@(Anchor _ _) -> conj a' b
|
||||||
|
(Conj a' at') -> Conj a' (Conj b at')
|
||||||
|
r -> error [i|impossible reduction: #{r}|]
|
||||||
|
else conj a at
|
||||||
|
-- a and b are the bounds and we leave the x tree alone
|
||||||
|
(False, False) -> conj (conj a b) at
|
||||||
|
-- insert anchor into conj tree that has two bounds and a x tree
|
||||||
|
(a@(Anchor opa pta), y'@(Conj bounds@(Conj (Anchor _ ptb) (Anchor _ ptc)) neqs))
|
||||||
|
-- if the anchor is an x we put it into the x tree
|
||||||
|
| opa == neq -> if pta > ptb && pta < ptc then Conj bounds (conj a neqs) else y'
|
||||||
|
-- if not we smash it in with the bounds
|
||||||
|
| otherwise -> conj (conj a bounds) neqs
|
||||||
|
-- zip two irreducible conj pairs together
|
||||||
|
(x'@(Conj a@(Anchor opa pta) b@(Anchor opb ptb)), y'@(Conj c@(Anchor opc ptc) d@(Anchor opd ptd)))
|
||||||
|
-- we know for sure that a < b and c < d here
|
||||||
|
| opa /= neq && opb /= neq && opc == neq && opd == neq && ptc > pta && ptc < ptb && ptd < ptb -> Conj x' y'
|
||||||
|
| otherwise -> case (opa == neq, opb == neq, opc == neq, opd == neq) of
|
||||||
|
(False, False, False, False) -> conj (conj a c) (conj b d)
|
||||||
|
(False, _, False, _) -> conj (conj a c) (conj b d)
|
||||||
|
(_, False, _, False) -> conj (conj a c) (conj b d)
|
||||||
|
(True, True, False, False) -> conj y' x'
|
||||||
|
(False, True, True, False) -> conj (conj a d) (Conj b c)
|
||||||
|
(True, False, False, True) -> conj (conj b c) (Conj a d)
|
||||||
|
(False, False, True, True) -> case (ptc > pta && ptc < ptb, ptd > pta && ptd < ptb) of
|
||||||
|
(True, True) -> Conj x' y'
|
||||||
|
(True, False) -> Conj x' c
|
||||||
|
(False, True) -> Conj x' d
|
||||||
|
(False, False) -> x'
|
||||||
|
(True, True, True, False) -> Conj d (conj c x')
|
||||||
|
(True, True, False, True) -> Conj c (conj d x')
|
||||||
|
(True, False, True, True) -> Conj b (conj a y')
|
||||||
|
(False, True, True, True) -> Conj a (conj b y')
|
||||||
|
(True, True, True, True) -> conj a (conj b y')
|
||||||
|
-- insert irreducible conj pair into single bounded conj tree OR x tree
|
||||||
|
(x'@(Conj a@(Anchor opa pta) b@(Anchor opb ptb)), y'@(Conj c@(Anchor opc ptc) r))
|
||||||
|
| opa /= neq && opb /= neq && opc == neq ->
|
||||||
|
if ptc > pta && ptc < ptb
|
||||||
|
then conj c (conj x' r)
|
||||||
|
else conj x' r
|
||||||
|
| otherwise -> conj a (conj b y')
|
||||||
|
(x'@(Conj (Anchor _ _) _), y'@(Conj (Anchor _ _) (Anchor _ _))) -> conj y' x'
|
||||||
|
-- insert irreducible conj pair into double bounded conj tree
|
||||||
|
(Conj a@(Anchor _ _) b@(Anchor _ _), y'@(Conj (Conj (Anchor _ _) (Anchor _ _)) _)) -> conj a (conj b y')
|
||||||
|
(x'@(Conj (Conj (Anchor _ _) (Anchor _ _)) _), y'@(Conj (Anchor _ _) (Anchor _ _))) -> conj y' x'
|
||||||
|
-- zip two conj trees
|
||||||
|
(Conj xBounds@(Conj (Anchor _ _) (Anchor _ _)) xNeqs, Conj yBounds@(Conj (Anchor _ _) (Anchor _ _)) yNeqs) ->
|
||||||
|
conj (conj xBounds yBounds) (conj xNeqs yNeqs)
|
||||||
|
(Conj xBounds@(Anchor _ _) xNeqs@(Conj _ _), Conj yBounds@(Conj (Anchor _ _) (Anchor _ _)) yNeqs) ->
|
||||||
|
conj (conj xBounds yBounds) (conj xNeqs yNeqs)
|
||||||
|
(Conj xBounds@(Conj (Anchor _ _) (Anchor _ _)) xNeqs, Conj yBounds@(Anchor _ _) yNeqs@(Conj _ _)) ->
|
||||||
|
conj (conj xBounds yBounds) (conj xNeqs yNeqs)
|
||||||
|
(Conj xBounds@(Anchor _ _) xNeqs@(Conj _ _), Conj yBounds@(Anchor _ _) yNeqs@(Conj _ _)) ->
|
||||||
|
conj (conj xBounds yBounds) (conj xNeqs yNeqs)
|
||||||
-- distribute right
|
-- distribute right
|
||||||
(x', Disj p q) -> disj (conj x' p) (conj x' q)
|
(x', Disj p q) -> disj (conj x' p) (conj x' q)
|
||||||
-- distribute left
|
-- distribute left
|
||||||
(Disj p q, y') -> disj (conj p y') (conj q y')
|
(Disj p q, y') -> disj (conj p y') (conj q y')
|
||||||
-- insert anchor into accumulation tree
|
-- bugs
|
||||||
(a@(Anchor opa pta), Conj b@(Anchor opb ptb) at@(Conj atl atr)) -> error "TODO manage accumulation trees"
|
(Conj (Disj _ _) _, _) -> [i|bug in distributing conj over disj|]
|
||||||
-- reconcile free anchor with essential anchors in accumulation tree
|
(Conj _ (Disj _ _), _) -> [i|bug in distributing conj over disj|]
|
||||||
(a@(Anchor opa _), Conj (Conj b@(Anchor _ _) c@(Anchor _ _)) r) -> case (fl opa, fr opa) of
|
(_, Conj (Disj _ _) _) -> [i|bug in distributing conj over disj|]
|
||||||
(True, True) -> Conj (Conj b c) (conj a r)
|
(_, Conj _ (Disj _ _)) -> [i|bug in distributing conj over disj|]
|
||||||
(True, False) -> Conj (Conj b (conj a c)) r
|
(Conj Any _, _) -> [i|bug in conj any unit|]
|
||||||
(False, True) -> Conj (Conj (conj b a) c) r
|
(Conj _ Any, _) -> [i|bug in conj any unit|]
|
||||||
(False, False) -> a
|
(_, Conj Any _) -> [i|bug in conj any unit|]
|
||||||
(x'@(Conj (Conj _ _) (Anchor _ _)), y'@(Anchor _ _)) -> conj y' x'
|
(_, Conj _ Any) -> [i|bug in conj any unit|]
|
||||||
(Conj a@(Anchor _ _) b@(Anchor _ _), y'@(Conj _ _)) -> conj a (conj b y')
|
(Conj None _, _) -> [i|bug in conj none annihilation|]
|
||||||
|
(Conj _ None, _) -> [i|bug in conj none annihilation|]
|
||||||
|
(_, Conj None _) -> [i|bug in conj none annihilation|]
|
||||||
|
(_, Conj _ None) -> [i|bug in conj none annihilation|]
|
||||||
(x', y') -> error [i|missing conj case: #{orig} -> #{x'}, #{y'}|]
|
(x', y') -> error [i|missing conj case: #{orig} -> #{x'}, #{y'}|]
|
||||||
reduce orig@(Disj x y) = case (reduce x, reduce y) of
|
reduce orig@(Disj x y) = case (reduce x, reduce y) of
|
||||||
-- disj annihilators
|
-- disj annihilators
|
||||||
(Any, _) -> Any
|
(Any, _) -> Any
|
||||||
(_, Any) -> Any
|
(_, Any) -> Any
|
||||||
-- disj units
|
-- disj units
|
||||||
(None, vr) -> reduce vr
|
(None, vr) -> vr
|
||||||
(vr, None) -> reduce vr
|
(vr, None) -> vr
|
||||||
-- primitive disj reduction
|
-- primitive disj reduction
|
||||||
(a@(Anchor op pt), b@(Anchor op' pt')) -> case compare pt pt' of
|
(a@(Anchor op pt), b@(Anchor op' pt')) -> case compare pt pt' of
|
||||||
GT -> reduce (Disj b a)
|
GT -> disj b a
|
||||||
EQ -> case (isRight op, isRight op == isRight op', primOrd op == primOrd op') of
|
EQ -> case (isRight op, isRight op == isRight op', primOrd op == primOrd op') of
|
||||||
-- idempotence
|
-- idempotence
|
||||||
(_, True, True) -> a
|
(_, True, True) -> a
|
||||||
@@ -400,8 +561,8 @@ reduce orig@(Disj x y) = case (reduce x, reduce y) of
|
|||||||
-- Right general patterns: >x, ox, o>, >>
|
-- Right general patterns: >x, ox, o>, >>
|
||||||
(False, True) -> b
|
(False, True) -> b
|
||||||
-- Irreducible patterns: >< >o o< oo
|
-- Irreducible patterns: >< >o o< oo
|
||||||
(False, False) -> x
|
(False, False) -> Disj a b
|
||||||
(a@(Anchor opa pta), Disj b@(Anchor opb ptb) c@(Anchor opc ptc)) -> case (compare pta ptb, compare pta ptc) of
|
(a@(Anchor opa pta), y'@(Disj b@(Anchor opb ptb) c@(Anchor opc ptc))) -> case (compare pta ptb, compare pta ptc) of
|
||||||
-- eq patterns reduce so prioritize disj'ing those
|
-- eq patterns reduce so prioritize disj'ing those
|
||||||
(EQ, LT) -> disj (disj a b) c
|
(EQ, LT) -> disj (disj a b) c
|
||||||
(GT, EQ) -> disj b (disj a c)
|
(GT, EQ) -> disj b (disj a c)
|
||||||
@@ -430,7 +591,7 @@ reduce orig@(Disj x y) = case (reduce x, reduce y) of
|
|||||||
-- here we are in the middle of an irreducible conj
|
-- here we are in the middle of an irreducible conj
|
||||||
(GT, LT)
|
(GT, LT)
|
||||||
-- >o< >oo oo< ooo all irreducible
|
-- >o< >oo oo< ooo all irreducible
|
||||||
| opa == eq -> disj b (disj a c)
|
| opa == eq -> Disj b (Disj a c)
|
||||||
-- if there is a remaining left face component it will reduce with the left side
|
-- if there is a remaining left face component it will reduce with the left side
|
||||||
| fl opa -> disj (disj b a) c
|
| fl opa -> disj (disj b a) c
|
||||||
-- corollary
|
-- corollary
|
||||||
@@ -443,23 +604,46 @@ reduce orig@(Disj x y) = case (reduce x, reduce y) of
|
|||||||
-- the only exception to this is the accumulation of x's on the right
|
-- the only exception to this is the accumulation of x's on the right
|
||||||
-- so these cases should be impossible
|
-- so these cases should be impossible
|
||||||
_
|
_
|
||||||
| opb == eq && opc == eq -> orig
|
| opb == eq && opc == eq -> Disj a y'
|
||||||
| otherwise -> error $ [i|bug in anchor order normalization: #{orig} -> #{a}, #{b}, #{c}|]
|
| otherwise -> error $ [i|bug in anchor order normalization: #{orig} -> #{a}, #{b}, #{c}|]
|
||||||
(x'@(Disj (Anchor _ _) (Anchor _ _)), y'@(Anchor _ _)) -> disj y' x'
|
(x'@(Disj a@(Anchor _ pta) b@(Anchor _ ptb)), c@(Anchor _ ptc))
|
||||||
|
| ptc > pta && ptc > ptb -> disj a (disj b c)
|
||||||
|
| otherwise -> disj c x'
|
||||||
(a@(Anchor opa pta), y'@(Conj b@(Anchor opb ptb) c@(Anchor opc ptc))) ->
|
(a@(Anchor opa pta), y'@(Conj b@(Anchor opb ptb) c@(Anchor opc ptc))) ->
|
||||||
case (compare pta ptb, compare pta ptc) of
|
case (compare pta ptb, compare pta ptc) of
|
||||||
(GT, GT) -> if fl opa then a else Disj y' a
|
(GT, GT) -> case (fr opc, fl opa) of
|
||||||
(LT, LT) -> if fr opa then a else Disj a y'
|
-- <> / x> || o / <
|
||||||
|
(False, False) -> Disj y' a
|
||||||
|
-- <> / x> || x / >
|
||||||
|
(False, True) -> a
|
||||||
|
-- <x / xx || o / <
|
||||||
|
(True, False) -> y'
|
||||||
|
-- <x / xx || x / >
|
||||||
|
(True, True) -> Any
|
||||||
|
(LT, LT) -> case (fr opa, fl opb) of
|
||||||
|
-- o / > || <> / <x
|
||||||
|
(False, False) -> Disj a y'
|
||||||
|
-- o / > || x> / xx
|
||||||
|
(False, True) -> y'
|
||||||
|
-- < / x || <> / <x
|
||||||
|
(True, False) -> a
|
||||||
|
-- < / x || x> / xx
|
||||||
|
(True, True) -> Any
|
||||||
(GT, LT) -> case (fl opa, fr opa) of
|
(GT, LT) -> case (fl opa, fr opa) of
|
||||||
(True, True) -> Any
|
(True, True) -> Any
|
||||||
(True, False) -> c
|
(True, False) -> c
|
||||||
(False, True) -> b
|
(False, True) -> b
|
||||||
(False, False) -> y'
|
(False, False) -> y'
|
||||||
(EQ, EQ) -> error [i|bug in equal anchor version reduction: #{orig} -> #{a}, #{b}, #{c}|]
|
(EQ, EQ) -> error [i|bug in equal anchor version reduction: #{orig} -> #{a}, #{b}, #{c}|]
|
||||||
(EQ, _) -> error "TODO1"
|
(EQ, _) -> conj (conj a b) c
|
||||||
(_, EQ) -> error "TODO2"
|
(_, EQ) -> conj b (conj a c)
|
||||||
(LT, GT) -> error [i|bug in anchor order normalization: #{orig} -> #{a}, #{b}, #{c}|]
|
(LT, GT) -> error [i|bug in anchor order normalization: #{orig} -> #{a}, #{b}, #{c}|]
|
||||||
(x'@(Conj _ _), y'@(Anchor _ _)) -> disj y' x'
|
(x'@(Conj (Anchor _ _) (Anchor _ ptb)), c@(Anchor opc ptc))
|
||||||
|
| ptc > ptb && not (fl opc) -> Disj x' c
|
||||||
|
| otherwise -> disj c x'
|
||||||
|
(x'@(Disj a@(Anchor _ pta) b), c@(Anchor _ ptc)) -> case disj c a of
|
||||||
|
dca@(Anchor _ _) -> disj dca b
|
||||||
|
_ -> if ptc < pta then Disj c x' else disj a (disj c b)
|
||||||
(Conj _ _, Conj _ _) -> error "Disj (Conj _ _) (Conj _ _)"
|
(Conj _ _, Conj _ _) -> error "Disj (Conj _ _) (Conj _ _)"
|
||||||
(Conj _ _, _) -> error "Disj (Conj _ _) _"
|
(Conj _ _, _) -> error "Disj (Conj _ _) _"
|
||||||
(_, Conj _ _) -> error "Disj _ (Conj _ _)"
|
(_, Conj _ _) -> error "Disj _ (Conj _ _)"
|
||||||
@@ -471,26 +655,6 @@ exactly :: Version -> VersionRange
|
|||||||
exactly = Anchor (Right EQ)
|
exactly = Anchor (Right EQ)
|
||||||
|
|
||||||
|
|
||||||
instance Show VersionRange where
|
|
||||||
show (Anchor (Left EQ) v) = '!' : '=' : GHC.show v
|
|
||||||
show (Anchor (Right EQ) v) = '=' : GHC.show v
|
|
||||||
show (Anchor (Left LT) v) = '>' : '=' : GHC.show v
|
|
||||||
show (Anchor (Right LT) v) = '<' : GHC.show v
|
|
||||||
show (Anchor (Left GT) v) = '<' : '=' : GHC.show v
|
|
||||||
show (Anchor (Right GT) v) = '>' : GHC.show v
|
|
||||||
show (Conj a@(Disj _ _) b@(Disj _ _)) = paren (GHC.show a) <> (' ' : paren (GHC.show b))
|
|
||||||
show (Conj a@(Disj _ _) b) = paren (GHC.show a) <> (' ' : GHC.show b)
|
|
||||||
show (Conj a b@(Disj _ _)) = GHC.show a <> (' ' : paren (GHC.show b))
|
|
||||||
show (Conj a b) = GHC.show a <> (' ' : GHC.show b)
|
|
||||||
show (Disj a b) = GHC.show a <> " || " <> GHC.show b
|
|
||||||
show Any = "*"
|
|
||||||
show None = "!"
|
|
||||||
instance Read VersionRange where
|
|
||||||
readsPrec _ s = case Atto.parseOnly parseRange (T.pack s) of
|
|
||||||
Left _ -> []
|
|
||||||
Right a -> [(a, "")]
|
|
||||||
|
|
||||||
|
|
||||||
paren :: String -> String
|
paren :: String -> String
|
||||||
paren = mappend "(" . flip mappend ")"
|
paren = mappend "(" . flip mappend ")"
|
||||||
|
|
||||||
@@ -548,21 +712,25 @@ parseVersion = do
|
|||||||
|
|
||||||
|
|
||||||
-- >>> Atto.parseOnly parseRange "=2.3.4 1.2.3.4 - 2.3.4.5 (>3.0.0 || <3.4.5)"
|
-- >>> Atto.parseOnly parseRange "=2.3.4 1.2.3.4 - 2.3.4.5 (>3.0.0 || <3.4.5)"
|
||||||
-- Right =2.3.4 >=1.2.3.4 <=2.3.4.5 ((>3.0.0 || <3.4.5))
|
-- Right =2.3.4 >=1.2.3.4 <=2.3.4.5 (>3.0.0 * || <3.4.5 * || !) * || !
|
||||||
-- >>> Atto.parseOnly parseRange "0.2.6"
|
-- >>> Atto.parseOnly parseRange "0.2.6"
|
||||||
-- >>> Atto.parseOnly parseRange ">=2.14.1.1 <3.0.0"
|
-- >>> Atto.parseOnly parseRange ">=2.14.1.1 <3.0.0"
|
||||||
-- Right >=2.14.1.1 <3.0.0
|
-- Right =0.2.6
|
||||||
|
-- Right >=2.14.1.1 <3.0.0 * || !
|
||||||
parseRange :: Atto.Parser VersionRange
|
parseRange :: Atto.Parser VersionRange
|
||||||
parseRange = s <|> any <|> none <|> (Anchor (Right EQ) <$> parseVersion)
|
parseRange = s <|> any <|> none <|> (Anchor (Right EQ) <$> parseVersion)
|
||||||
where
|
where
|
||||||
any = Atto.char '*' *> pure Any
|
any = Atto.char '*' *> pure Any
|
||||||
none = Atto.char '!' *> pure None
|
none = Atto.char '!' *> pure None
|
||||||
sub = Atto.char '(' *> Atto.skipSpace *> parseRange <* Atto.skipSpace <* Atto.char ')'
|
sub = Atto.char '(' *> Atto.skipSpace *> parseRange <* Atto.skipSpace <* Atto.char ')'
|
||||||
s =
|
s = do
|
||||||
unAnyRange
|
exprs <- ((p <|> sub) `Atto.sepBy1` (Atto.skipSpace *> Atto.string "||" <* Atto.skipSpace))
|
||||||
. foldMap AnyRange
|
ne <- maybe empty pure $ NE.nonEmpty exprs
|
||||||
<$> ((p <|> sub) `Atto.sepBy1` (Atto.skipSpace *> Atto.string "||" <* Atto.skipSpace))
|
pure . unAnyRange . foldMap1 AnyRange $ ne
|
||||||
p = unAllRange . foldMap AllRange <$> ((a <|> sub) `Atto.sepBy1` Atto.space)
|
p = do
|
||||||
|
exprs <- ((a <|> sub) `Atto.sepBy1` Atto.space)
|
||||||
|
ne <- maybe empty pure $ NE.nonEmpty exprs
|
||||||
|
pure . unAllRange . foldMap1 AllRange $ ne
|
||||||
a = liftA2 Anchor parseOperator parseVersion <|> caret <|> tilde <|> wildcard <|> hyphen <|> any <|> none
|
a = liftA2 Anchor parseOperator parseVersion <|> caret <|> tilde <|> wildcard <|> hyphen <|> any <|> none
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ module Lib.Types.EmverProp where
|
|||||||
|
|
||||||
import Startlude hiding (
|
import Startlude hiding (
|
||||||
Any,
|
Any,
|
||||||
|
filter,
|
||||||
reduce,
|
reduce,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,8 +12,8 @@ import Data.Attoparsec.Text qualified as Atto
|
|||||||
import Hedgehog as Test
|
import Hedgehog as Test
|
||||||
import Hedgehog.Gen as Gen
|
import Hedgehog.Gen as Gen
|
||||||
import Hedgehog.Range
|
import Hedgehog.Range
|
||||||
import Lib.Types.Emver
|
import Lib.Types.Emver (Version (Version), VersionRange (..), fl, fr, geq, gt, leq, lt, neq, nodes, parseRange, reduce, satisfies, (<||))
|
||||||
import UnliftIO qualified
|
import System.Timeout (timeout)
|
||||||
|
|
||||||
|
|
||||||
versionGen :: MonadGen m => m Version
|
versionGen :: MonadGen m => m Version
|
||||||
@@ -25,7 +26,11 @@ versionGen = do
|
|||||||
|
|
||||||
|
|
||||||
rangeGen :: MonadGen m => m VersionRange
|
rangeGen :: MonadGen m => m VersionRange
|
||||||
rangeGen = choice [pure None, pure Any, anchorGen, disjGen, conjGen]
|
rangeGen = shrink rangeShrink $ choice [pure None, pure Any, anchorGen, disjGen, conjGen]
|
||||||
|
where
|
||||||
|
rangeShrink (Conj a b) = [a, b]
|
||||||
|
rangeShrink (Disj a b) = [a, b]
|
||||||
|
rangeShrink _ = []
|
||||||
|
|
||||||
|
|
||||||
anchorGen :: MonadGen m => m VersionRange
|
anchorGen :: MonadGen m => m VersionRange
|
||||||
@@ -39,6 +44,14 @@ conjGen :: MonadGen m => m VersionRange
|
|||||||
conjGen = liftA2 Conj rangeGen rangeGen
|
conjGen = liftA2 Conj rangeGen rangeGen
|
||||||
|
|
||||||
|
|
||||||
|
conjOnlyGen :: MonadGen m => m VersionRange
|
||||||
|
conjOnlyGen = shrink conjOnlyShrink . prune $ choice [anchorGen, liftA2 Conj conjOnlyGen conjOnlyGen]
|
||||||
|
where
|
||||||
|
conjOnlyShrink :: VersionRange -> [VersionRange]
|
||||||
|
conjOnlyShrink (Conj a b) = [a, b]
|
||||||
|
conjOnlyShrink _ = []
|
||||||
|
|
||||||
|
|
||||||
disjGen :: MonadGen m => m VersionRange
|
disjGen :: MonadGen m => m VersionRange
|
||||||
disjGen = liftA2 Disj rangeGen rangeGen
|
disjGen = liftA2 Disj rangeGen rangeGen
|
||||||
|
|
||||||
@@ -189,15 +202,61 @@ prop_reduceDisjAnchor = property $ do
|
|||||||
obs <|| reduce (Disj a b) === obs <|| Disj a b
|
obs <|| reduce (Disj a b) === obs <|| Disj a b
|
||||||
|
|
||||||
|
|
||||||
|
prop_reduceTerminates :: Property
|
||||||
|
prop_reduceTerminates = withTests 1000 . property $ do
|
||||||
|
a <- forAll $ filter ((<= 100) . nodes) rangeGen
|
||||||
|
b <- lift $ timeout 100_000 (pure $! reduce a)
|
||||||
|
case b of
|
||||||
|
Nothing -> failure
|
||||||
|
Just _ -> success
|
||||||
|
|
||||||
|
|
||||||
prop_reduceIdentity :: Property
|
prop_reduceIdentity :: Property
|
||||||
prop_reduceIdentity = withTests 2000 . withDiscards 90 $
|
prop_reduceIdentity = withTests 1000 . property $ do
|
||||||
property $ do
|
a <- forAll $ filter ((<= 100) . nodes) rangeGen
|
||||||
-- a <- forAll rangeGen
|
obs <- forAll versionGen
|
||||||
a <- forAll conjGen
|
b <- liftIO $ pure (reduce a) `catch` \e -> throwIO (e :: ErrorCall)
|
||||||
obs <- forAll versionGen
|
unless (b /= a) Test.discard
|
||||||
b <- liftIO $ pure (reduce a) `catch` \e -> throwIO (e :: ErrorCall)
|
obs <|| a === obs <|| b
|
||||||
unless (b /= a) Test.discard
|
|
||||||
obs <|| a === obs <|| b
|
|
||||||
|
prop_reduceConjTreeNormalForm :: Property
|
||||||
|
prop_reduceConjTreeNormalForm = withTests 1000 . property $ do
|
||||||
|
a <- forAll $ filter ((<= 100) . nodes) conjOnlyGen
|
||||||
|
let b = reduce a
|
||||||
|
annotateShow b
|
||||||
|
assert $ isConjNF b
|
||||||
|
|
||||||
|
|
||||||
|
isConjNF :: VersionRange -> Bool
|
||||||
|
isConjNF = \case
|
||||||
|
Any -> True
|
||||||
|
None -> True
|
||||||
|
Anchor _ _ -> True
|
||||||
|
Conj (Anchor _ pta) (Anchor _ ptb) -> pta < ptb
|
||||||
|
Conj (Conj (Anchor opa pta) (Anchor opb ptb)) (Anchor opc ptc) ->
|
||||||
|
pta < ptb
|
||||||
|
&& opa /= neq
|
||||||
|
&& opb /= neq
|
||||||
|
&& opc == neq
|
||||||
|
&& ptc > pta
|
||||||
|
&& ptc < ptb
|
||||||
|
Conj (Anchor opa pta) (Conj (Anchor opb ptb) (Anchor opc ptc)) ->
|
||||||
|
opb == neq
|
||||||
|
&& opc == neq
|
||||||
|
&& ptb < ptc
|
||||||
|
&& (opa /= neq || pta < ptb)
|
||||||
|
&& ((opa /= gt && opa /= geq) || pta < ptb)
|
||||||
|
&& ((opa /= lt && opa /= leq) || pta > ptc)
|
||||||
|
(Conj (Conj (Anchor opa pta) (Anchor opb ptb)) (Conj (Anchor opc ptc) (Anchor opd ptd))) ->
|
||||||
|
opc == neq && opd == neq && opa /= neq && opb /= neq && pta < ptb && ptc < ptd && ptc > pta && ptd < ptb
|
||||||
|
(Conj x@(Conj (Anchor opa pta) (Anchor opb ptb)) (Conj (Anchor opc ptc) r)) ->
|
||||||
|
pta < ptc && ptc < ptb && opa /= neq && opb /= neq && opc == neq && isConjNF (Conj x r)
|
||||||
|
(Conj a@(Anchor opa pta) y'@(Conj (Anchor opb ptb) r)) ->
|
||||||
|
(opa == neq && pta < ptb && isConjNF y')
|
||||||
|
|| (fr opa && pta < ptb && isConjNF y')
|
||||||
|
|| (fl opa && pta > ptb && isConjNF (Conj a r))
|
||||||
|
_ -> False
|
||||||
|
|
||||||
|
|
||||||
tests :: IO Bool
|
tests :: IO Bool
|
||||||
|
|||||||
Reference in New Issue
Block a user