Morph for an ArraySized element lengthRange
Morph.OneToOne from an Array
import ArraySized
import Array
Array.fromList [ 0, 1, 2, 3 ]
|> Morph.mapTo ArraySized.Morph.fromArray
--: ArraySized (Min (Up0 x_)) number_Morph.OneToOne from a List
import ArraySized
import Morph
[ 0, 1, 2, 3 ]
|> Morph.mapTo ArraySized.Morph.list
--: ArraySized (Min (Up0 x_)) number_Morph.OneToOne from a stack
import ArraySized
import Morph
import Stack
Stack.topBelow 0 [ 1, 2, 3, 4 ]
|> Morph.mapTo ArraySized.Morph.stack
--: ArraySized (Min (Up1 x_)) number_Morph.OneToOne from a String to ArraySized ... Char
import ArraySized
import Morph
"0123"
|> Morph.mapTo ArraySized.Morph.string
--: ArraySized (Min (Up0 x_)) number_Match a value a given number of times
and return them as an ArraySized
ℹ️ Equivalent regular expression:
{n}
import Morph
import Char.Morph
import String.Morph
import AToZ exposing (AToZ(..))
import AToZ.Morph
import N exposing (n3)
import ArraySized
import List.Morph
-- we want `exactly 3` letters
"abc"
|> Morph.toNarrow
(exactly n3 (AToZ.Morph.lowerChar |> Morph.one)
|> Morph.rowFinish
|> Morph.over List.Morph.string
)
|> Result.map ArraySized.toList
--> Ok [ A, B, C ]
-- not 2 or 4, we want 3
"ab"
|> Morph.toNarrow
(exactly n3 (AToZ.Morph.lowerChar |> Morph.one)
|> Morph.rowFinish
|> Morph.over List.Morph.string
)
|> Result.toMaybe
--> NothingDepending on the number parsed by a given morph, morph exactly that amount of elements.
To for example morph string bits where the first 16 bits represent the amount of code points that follow:
ArraySized.Morph.exactlyWith
(N.Morph.natural |> Morph.overRow (Natural.Morph.bits n16 Bytes.BE))
Char.Morph.bitsThe name is pretty horrible, so suggestions are welcome!
If you need to parse a format like
[ length ] [ other necessary data you want to grab ] [ elements ]Please open an issue and I'll add a version for it. This isn't exposed currently because the API for this would be jAnKy and the use-case kinda obscure.
using :
(complete -> toDecide)
-> MorphRow toDecide broadElement
-> (toDecide -> MorphRow complete broadElement)
-> MorphRow complete broadElementThis might look like the happy "andThen" that you love but it's more evil than that!
For example, you'd typically use "andThen" for versioning etc. – not with morphs.
Use Morph.choice where each possibility expects a specific number.
using is only unavoidable
when certain data that tells you how to proceed can have infinite possibilities.
Here's some ugly consequences if we use using instead of a less generic exactlyWith:
Its description can barely tell users anything,
as our main morph is hidden behind a function :(
Adding Morph.named to the whole using morph helps but the quality is still notably lower.
Instead of recursively using using, you should use
untilNext, untilLast, untilNextFold, untilLastFold
to not blow the stack and get a nicer description
Its harder to reason about
It's less composable / less "independent"
It's easier than ever to get your toBroad and toNarrow
functions out of sync
Match a value at least a given number of times
and return them as an ArraySized.
ℹ️ Equivalent regular expression:
{min,}
import Morph
import List.Morph
import AToZ.Morph
import AToZ
import ArraySized
import N exposing (n3)
-- we want at least three letters, we are okay with more than three
"abcdef"
|> Morph.toNarrow
(ArraySized.Morph.atLeast n3 (AToZ.Morph.lowerChar |> Morph.one)
|> Morph.rowFinish
|> Morph.over List.Morph.string
)
|> Result.map ArraySized.toList
--> Ok [ A, B, C, D, E, F ]
-- but not two, that's sacrilegious
"ab"
|> Morph.toNarrow
(ArraySized.Morph.atLeast n3 (AToZ.Morph.lowerChar |> Morph.one)
|> Morph.rowFinish
|> Morph.over List.Morph.string
)
|> Result.toMaybe
--> NothingatLeast n0ℹ️ Equivalent regular expression:
*
import Morph
import List.Morph
import AToZ.Morph
import AToZ exposing (AToZ(..))
import ArraySized
import N exposing (n0)
-- We want as many letters as there are.
"abc"
|> Morph.toNarrow
(ArraySized.Morph.atLeast n0 (AToZ.Morph.lowerChar |> Morph.one)
|> Morph.rowFinish
|> Morph.over List.Morph.string
)
|> Result.map ArraySized.toList
--> Ok [ A, B, C ]
-- even zero letters is okay
""
|> Morph.toNarrow
(ArraySized.Morph.atLeast n0 (AToZ.Morph.lowerChar |> Morph.one)
|> Morph.rowFinish
|> Morph.over List.Morph.string
)
|> Result.map ArraySized.toList
--> Ok []As you can see, atLeast n0 will never fail.
If you want a to morph a List instead of an ArraySized ... (Min (On N0)),
you might as well use Morph.whilePossible instead of
ArraySized.toList
|> Morph.overRow (ArraySized.Morph.atLeast n0)atLeast n1ℹ️ Equivalent regular expression:
+
import Morph
import List.Morph
import AToZ.Morph
import AToZ exposing (AToZ(..))
import ArraySized
import N exposing (n1)
-- we want as many letters as there are
"abc"
|> Morph.toNarrow
(ArraySized.Morph.atLeast n1 (AToZ.Morph.lowerChar |> Morph.one)
|> Morph.rowFinish
|> Morph.over List.Morph.string
)
|> Result.map ArraySized.toList
--> Ok [ A, B, C ]
-- but we want at least one
""
|> Morph.toNarrow
(ArraySized.Morph.atLeast n1 (AToZ.Morph.lowerChar |> Morph.one)
|> Morph.rowFinish
|> Morph.over List.Morph.string
)
|> Result.toMaybe
--> Nothingimport Stack
import Morph
import ArraySized.Morph exposing (atLeast)
import List.Morph
import AToZ.Morph
import AToZ exposing (AToZ(..))
import N exposing (n0)
tag =
atLeast n1 (AToZ.Morph.lowerChar |> Morph.one)
tags =
Morph.narrow Stack.onTopLay
|> grab Stack.top tag
|> grab Stack.removeTop
(List.Morph.arraySized
|> Morph.overRow
(atLeast n0
(Morph.narrow (\tag -> tag)
|> match separator
|> grab (\tag -> tag) tag
)
)
)
"a,bc,def"
|> Morph.toNarrow (tags |> Morph.rowFinish |> Morph.over List.Morph.string)
|> Result.map (Stack.map (\_ -> ArraySized.toList))
--→ Ok
--→ (Stack.topBelow [ A ]
--→ [ [ B, C ]
--→ , [ D, E, F ]
--→ ]
--→ )Morph.narrow ...
|> grab ... (atLeast n0 (Morph.keep |> Morph.one))
|> grab ...would only parse the first part until the end
because it always narrows.
Nothing after would ever be parsed, making the whole thing fail.
The maximum of the lower limit argument enables what's shown in the following example: "match any number of spaces and broaden to 1"
broad (ArraySized.one ())
|> Morph.overRow (atLeast n0 (String.Morph.only " "))In this case, the minimum of the given "seed" before broadening ArraySized.one () is 1,
whereas the narrow result of atLeast n0 will have a minimum length of 0.
The maximum of the lower limit argument is a type-level proof that the "seed" minimum is greater or equal to the resulting narrow minimum length.
↓ for example will lead to a compile time error:
broad ArraySized.empty
|> Morph.overRow (atLeast n1 (String.Morph.only " "))The argument to
|>is of type:... ArraySized () (In #(On N0)# (Up0 maxX_)) ...But it needs to be:
... ArraySized () (In #(On N1)# (Up0 maxX_)) ...
Match a value between a minimum and maximum number of times
and return them as an ArraySized.
ℹ️ Equivalent regular expression:
{min,max}
import Morph
import AToZ.Morph
import AToZ exposing (AToZ(..))
import List.Morph
import ArraySized
import N exposing (n2, n4)
-- we want between two and four letters
"abc"
|> Morph.toNarrow
(in_ ( n2, n4 ) (AToZ.Morph.lowerChar |> Morph.one)
|> Morph.rowFinish
|> Morph.over List.Morph.string
)
|> Result.map ArraySized.toList
--> Ok [ A, B, C ]
"a"
|> Morph.toNarrow
(in_ ( n2, n4 ) (AToZ.Morph.lowerChar |> Morph.one)
|> Morph.rowFinish
|> Morph.over List.Morph.string
)
|> Result.toMaybe
--> Nothingin_ ( n0, n1 )Alternative to Maybe.Morph.row which instead returns an ArraySized.
ℹ️ Equivalent regular expression:
?
ℹ️ Equivalent regular expression:
{0,max}
import Morph
import AToZ exposing (AToZ(..))
import AToZ.Morph
import List.Morph
import ArraySized
import N exposing (n0, n3)
-- we want a maximum of three letters
"ab"
|> Morph.toNarrow
(in_ ( n0, n3 ) (AToZ.Morph.lowerChar |> Morph.one)
|> Morph.rowFinish
|> Morph.over List.Morph.string
)
|> Result.map ArraySized.toList
--> Ok [ A, B ]
-- even zero letters are fine
""
|> Morph.toNarrow
(in_ ( n0, n3 ) (AToZ.Morph.lowerChar |> Morph.one)
|> Morph.rowFinish
|> Morph.over List.Morph.string
)
|> Result.map ArraySized.toList
--> Ok []From the elements in a given ArraySized,
create MorphRows
that will be run in the same order, one after the other.
More details → List.Morph.sequenceMap
Match broad MorphRows
(those that can always produce the same broad value)
based on given input elements in sequence.
More details → List.Morph.broadSequenceMap
Morph each element in an
ArraySized