test_gen: replace stdlib Json with inline implementation

Towards the goal of removing Json from stdlib, this change replaces
usage of TotallyNotJson in test_gen/gen_abilities with a simple usable
inline implementation of Encoder/DecoderFormatting.

Similarly, the use of TotallyNotJson in test_reporting is not necessary
at all and is replaced with a Decoder that wouldn't actually work, but
which does compile.
This commit is contained in:
shua 2024-06-28 15:20:29 +02:00
parent 05ab018380
commit 0faa1d5f20
No known key found for this signature in database
GPG key ID: 73387DA37055770F
8 changed files with 679 additions and 278 deletions

1
Cargo.lock generated
View file

@ -3846,6 +3846,7 @@ dependencies = [
"roc_solve", "roc_solve",
"roc_std", "roc_std",
"roc_target", "roc_target",
"roc_test_utils",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"roc_wasm_interp", "roc_wasm_interp",

View file

@ -11401,10 +11401,50 @@ In roc, functions are always written as a lambda, like{}
r#" r#"
app "test" imports [] provides [main] to "./platform" app "test" imports [] provides [main] to "./platform"
import TotallyNotJson ErrDecoder := {} implements [DecoderFormatting {
u8: decodeU8,
u16: decodeU16,
u32: decodeU32,
u64: decodeU64,
u128: decodeU128,
i8: decodeI8,
i16: decodeI16,
i32: decodeI32,
i64: decodeI64,
i128: decodeI128,
f32: decodeF32,
f64: decodeF64,
dec: decodeDec,
bool: decodeBool,
string: decodeString,
list: decodeList,
record: decodeRecord,
tuple: decodeTuple,
}]
decodeU8 = Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
decodeU16 = Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
decodeU32 = Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
decodeU64 = Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
decodeU128 = Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
decodeI8 = Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
decodeI16 = Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
decodeI32 = Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
decodeI64 = Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
decodeI128 = Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
decodeF32 = Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
decodeF64 = Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
decodeDec = Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
decodeBool = Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
decodeString = Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
decodeList : Decoder elem (ErrDecoder) -> Decoder (List elem) (ErrDecoder)
decodeList = \_ -> Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
decodeRecord : state, (state, Str -> [Keep (Decoder state (ErrDecoder)), Skip]), (state, (ErrDecoder) -> Result val DecodeError) -> Decoder val (ErrDecoder)
decodeRecord =\_, _, _ -> Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
decodeTuple : state, (state, U64 -> [Next (Decoder state (ErrDecoder)), TooLong]), (state -> Result val DecodeError) -> Decoder val (ErrDecoder)
decodeTuple = \_, _, _ -> Decode.custom \rest, @ErrDecoder {} -> {result: Err TooShort, rest}
main = main =
decoded = Str.toUtf8 "{\"first\":\"ab\",\"second\":\"cd\"}" |> Decode.fromBytes TotallyNotJson.json decoded = Str.toUtf8 "{\"first\":\"ab\",\"second\":\"cd\"}" |> Decode.fromBytes (@ErrDecoder {})
when decoded is when decoded is
Ok rcd -> rcd.first rcd.second Ok rcd -> rcd.first rcd.second
_ -> "something went wrong" _ -> "something went wrong"
@ -11415,7 +11455,7 @@ In roc, functions are always written as a lambda, like{}
This expression has a type that does not implement the abilities it's expected to: This expression has a type that does not implement the abilities it's expected to:
8 Ok rcd -> rcd.first rcd.second 48 Ok rcd -> rcd.first rcd.second
^^^^^^^^^ ^^^^^^^^^
I can't generate an implementation of the `Decoding` ability for I can't generate an implementation of the `Decoding` ability for

View file

@ -45,6 +45,7 @@ roc_reporting = { path = "../../reporting" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_std = { path = "../../roc_std" } roc_std = { path = "../../roc_std" }
roc_target = { path = "../roc_target" } roc_target = { path = "../roc_target" }
roc_test_utils = { path = "../../test_utils"}
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_unify = { path = "../unify" } roc_unify = { path = "../unify" }
roc_wasm_interp = { path = "../../wasm_interp" } roc_wasm_interp = { path = "../../wasm_interp" }

File diff suppressed because it is too large Load diff

View file

@ -1,18 +0,0 @@
app "test"
imports [TotallyNotJson]
provides [main] to "./platform"
HelloWorld := {} implements [Encoding {toEncoder}]
toEncoder = \@HelloWorld {} ->
Encode.custom \bytes, fmt ->
bytes
|> Encode.appendWith (Encode.string "Hello, World!\n") fmt
f =
when Str.fromUtf8 (Encode.toBytes (@HelloWorld {}) TotallyNotJson.json) is
Ok s -> s
_ -> "<bad>"
main = f
# ^ Str

View file

@ -0,0 +1,66 @@
app "test"
imports []
provides [main] to "./platform"
OnlyStrEncoder := {} implements [Encode.EncoderFormatting {
u8: encodeU8,
u16: encodeU16,
u32: encodeU32,
u64: encodeU64,
u128: encodeU128,
i8: encodeI8,
i16: encodeI16,
i32: encodeI32,
i64: encodeI64,
i128: encodeI128,
f32: encodeF32,
f64: encodeF64,
dec: encodeDec,
bool: encodeBool,
string: encodeString,
list: encodeList,
record: encodeRecord,
tuple: encodeTuple,
tag: encodeTag,
}]
encodeNothing = Encode.custom \bytes, @OnlyStrEncoder {} -> bytes
encodeU8 = \_n -> encodeNothing
encodeU16 = \_n -> encodeNothing
encodeU32 = \_n -> encodeNothing
encodeU64 = \_n -> encodeNothing
encodeU128 = \_n -> encodeNothing
encodeI8 = \_n -> encodeNothing
encodeI16 = \_n -> encodeNothing
encodeI32 = \_n -> encodeNothing
encodeI64 = \_n -> encodeNothing
encodeI128 = \_n -> encodeNothing
encodeF32 = \_n -> encodeNothing
encodeF64 = \_n -> encodeNothing
encodeDec = \_n -> encodeNothing
encodeBool = \_b -> encodeNothing
encodeString = \str -> Encode.custom \bytes, @OnlyStrEncoder {} -> List.concat bytes (Str.toUtf8 str)
encodeList : List elem, (elem -> Encoder OnlyStrEncoder) -> Encoder OnlyStrEncoder
encodeList = \_lst, _encodeElem -> encodeNothing
encodeRecord : List {key: Str, value: Encoder OnlyStrEncoder} -> Encoder OnlyStrEncoder
encodeRecord = \_fields -> encodeNothing
encodeTuple : List (Encoder OnlyStrEncoder) -> Encoder OnlyStrEncoder
encodeTuple = \_elems -> encodeNothing
encodeTag : Str, List (Encoder OnlyStrEncoder) -> Encoder OnlyStrEncoder
encodeTag = \_name, _payload -> encodeNothing
HelloWorld := {} implements [Encoding {toEncoder}]
toEncoder = \@HelloWorld {} ->
Encode.custom \bytes, fmt ->
bytes
|> Encode.appendWith (Encode.string "Hello, World!\n") fmt
f =
when Str.fromUtf8 (Encode.toBytes (@HelloWorld {}) (@OnlyStrEncoder {})) is
Ok s -> s
_ -> "<bad>"
main = f
# ^ Str

View file

@ -0,0 +1,261 @@
# a simple encoder/decoder format
#
# HACK: since this file is inlined into test code, it can't be a proper module
#
# the original author found it useful to leave this header here as a comment, to
# make it easy to switch back and forth between editing it as a proper roc module
# (so things like language server and `roc test` work), and inlining it into test
# code.
#
# module [
# TagLenFmt,
# tagLenFmt,
# ]
TagLenFmt := {}
implements [
EncoderFormatting {
u8: encodeU8,
u16: encodeU16,
u32: encodeU32,
u64: encodeU64,
u128: encodeU128,
i8: encodeI8,
i16: encodeI16,
i32: encodeI32,
i64: encodeI64,
i128: encodeI128,
f32: encodeF32,
f64: encodeF64,
dec: encodeDec,
bool: encodeBool,
string: encodeString,
list: encodeList,
record: encodeRecord,
tuple: encodeTuple,
tag: encodeTag,
},
DecoderFormatting {
u8: decodeU8,
u16: decodeU16,
u32: decodeU32,
u64: decodeU64,
u128: decodeU128,
i8: decodeI8,
i16: decodeI16,
i32: decodeI32,
i64: decodeI64,
i128: decodeI128,
f32: decodeF32,
f64: decodeF64,
dec: decodeDec,
bool: decodeBool,
string: decodeString,
list: decodeList,
record: decodeRecord,
tuple: decodeTuple,
},
]
tagLenFmt = @TagLenFmt {}
# ENCODE
appendPreLen = \bytes, pre, len ->
List.append bytes (Num.toU8 pre)
|> List.concat (Num.toStr len |> Str.toUtf8)
|> List.append ' '
encodeNum = \n -> Encode.custom \bytes, @TagLenFmt {} -> appendPreLen bytes 'n' n
encodeU8 = encodeNum
encodeU16 = encodeNum
encodeU32 = encodeNum
encodeU64 = encodeNum
encodeU128 = encodeNum
encodeI8 = encodeNum
encodeI16 = encodeNum
encodeI32 = encodeNum
encodeI64 = encodeNum
encodeI128 = encodeNum
encodeF32 = encodeNum
encodeF64 = encodeNum
encodeDec = encodeNum
encodeBool = \b -> encodeU8 (if b then 1 else 0)
expect
actual = Encode.toBytes 1 tagLenFmt
actual == (Str.toUtf8 "n1 ")
expect
actual = Encode.toBytes 1.3dec tagLenFmt
actual == (Str.toUtf8 "n1.3 ")
expect
actual = Encode.toBytes Bool.true tagLenFmt
actual == (Str.toUtf8 "n1 ")
encodeString = \str -> Encode.custom \bytes, @TagLenFmt {} ->
appendPreLen bytes 's' (Str.countUtf8Bytes str)
|> List.concat (Str.toUtf8 str)
|> List.append ' '
expect
actual = Encode.toBytes "hey" tagLenFmt
actual == (Str.toUtf8 "s3 hey ")
encodeList = \lst, encodeElem -> Encode.custom \bytes, @TagLenFmt {} ->
bytesPre = appendPreLen bytes 'l' (List.len lst)
List.walk lst bytesPre \buf, elem ->
Encode.appendWith buf (encodeElem elem) (@TagLenFmt {})
expect
actual = Encode.toBytes [1, 2, 3] tagLenFmt
actual == (Str.toUtf8 "l3 n1 n2 n3 ")
encodeRecord = \fields -> Encode.custom \bytes, @TagLenFmt {} ->
bytesPre =
appendPreLen bytes 'r' (List.len fields)
List.walk fields bytesPre \buf, { key, value } ->
Encode.appendWith buf (encodeString key) (@TagLenFmt {})
|> Encode.appendWith value (@TagLenFmt {})
expect
actual = Encode.toBytes { foo: "foo", bar: Bool.true } tagLenFmt
actual == Str.toUtf8 "r2 s3 bar n1 s3 foo s3 foo "
encodeTuple = \elems -> encodeList elems (\e -> e)
encodeTag = \name, payload -> encodeTuple (List.prepend payload (encodeString name))
expect
actual = Encode.toBytes (1, "foo", {}) tagLenFmt
actual == (Str.toUtf8 "l3 n1 s3 foo r0 ")
# DECODE
splitAtSpace = \bytes ->
when List.splitFirst bytes ' ' is
Ok { before, after } -> { taken: before, rest: after }
Err _ -> { taken: [], rest: bytes }
decodeNumPre = \bytes, pre, toNum ->
when List.split bytes 1 is
{ before: [b], others } if b == pre ->
{ taken, rest } = splitAtSpace others
str = taken |> Str.fromUtf8 |> Result.mapErr \_ -> TooShort
result = Result.try str \s -> (toNum s |> Result.mapErr \_ -> TooShort)
when result is
Ok _ -> { result, rest }
Err _ -> { result, rest: others }
_ -> { result: Err TooShort, rest: bytes }
decodeNum = \toNum -> Decode.custom \bytes, @TagLenFmt {} -> decodeNumPre bytes 'n' toNum
decodeU8 = decodeNum Str.toU8
decodeU16 = decodeNum Str.toU16
decodeU32 = decodeNum Str.toU32
decodeU64 = decodeNum Str.toU64
decodeU128 = decodeNum Str.toU128
decodeI8 = decodeNum Str.toI8
decodeI16 = decodeNum Str.toI16
decodeI32 = decodeNum Str.toI32
decodeI64 = decodeNum Str.toI64
decodeI128 = decodeNum Str.toI128
decodeF32 = decodeNum Str.toF32
decodeF64 = decodeNum Str.toF64
decodeDec = decodeNum Str.toDec
decodeBool = Decode.custom \bytes, @TagLenFmt {} ->
{ result: numResult, rest } = Decode.decodeWith bytes decodeU8 (@TagLenFmt {})
when numResult is
Ok 1 -> { result: Ok Bool.true, rest }
Ok 0 -> { result: Ok Bool.false, rest }
_ -> { result: Err TooShort, rest: bytes }
expect
actual = Decode.fromBytes (Str.toUtf8 "n1 ") tagLenFmt
actual == Ok (Num.toU8 1)
expect
actual = Decode.fromBytes (Str.toUtf8 "n1 ") tagLenFmt
actual == Ok Bool.true
decodeLenPre = \bytes, pre -> decodeNumPre bytes pre Str.toU64
decodeTry = \{ result, rest }, map ->
when result is
Ok a -> map a rest
Err e -> { result: Err e, rest }
decodeString = Decode.custom \bytes, @TagLenFmt {} ->
decodeLenPre bytes 's'
|> decodeTry \len, lenRest ->
{ before, others } = List.split lenRest len
result = Str.fromUtf8 before |> Result.mapErr \_ -> TooShort
when List.split others 1 is
{ before: [' '], others: rest } -> { result, rest }
_ -> { result: Err TooShort, rest: others }
expect
actual = Decode.fromBytes (Str.toUtf8 "s3 foo ") tagLenFmt
actual == Ok "foo"
repeatDecode : U8, List U8, state, (state -> Decode.Decoder state TagLenFmt) -> DecodeResult state
repeatDecode = \pre, bytes, state, stepState ->
run = \end, bs ->
List.range { start: At 0, end: Before end }
|> List.walk { result: Ok state, rest: bs } \res, _i ->
decodeTry res \s, rest ->
Decode.decodeWith rest (stepState s) (@TagLenFmt {})
decodeLenPre bytes pre |> decodeTry run
decodeList = \elemDecoder -> Decode.custom \bytes, @TagLenFmt {} ->
step = \lst -> Decode.custom \sbytes, @TagLenFmt {} ->
Decode.decodeWith sbytes elemDecoder (@TagLenFmt {})
|> Decode.mapResult \elem -> List.append lst elem
repeatDecode 'l' bytes [] step
expect
actual = Decode.fromBytes (Str.toUtf8 "l3 n1 n2 n3 ") tagLenFmt
actual == Ok [1, 2, 3]
decodeRecord = \initState, stepField, finalizer -> Decode.custom \bytes, @TagLenFmt {} ->
flattenFieldRes = \next, rest ->
when next is
Keep valueDecoder -> { result: Ok valueDecoder, rest }
Skip -> { result: Err TooShort, rest }
step = \state -> Decode.custom \sbytes, @TagLenFmt {} ->
Decode.decodeWith sbytes decodeString (@TagLenFmt {})
|> decodeTry \key, bs ->
flattenFieldRes (stepField state key) bs
|> decodeTry \valueDecoder, bs ->
Decode.decodeWith bs valueDecoder (@TagLenFmt {})
repeatDecode 'r' bytes initState step
|> decodeTry \state, rest -> { result: finalizer state (@TagLenFmt {}), rest }
expect
actual = Decode.fromBytes (Str.toUtf8 "r2 s3 bar n1 s3 foo s3 foo ") tagLenFmt
actual == Ok ({ foo: "foo", bar: Bool.true })
decodeTuple = \initialState, stepElem, finalizer -> Decode.custom \bytes, @TagLenFmt {} ->
flattenFieldRes = \next, rest ->
when next is
Next dec -> { result: Ok dec, rest }
TooLong -> { result: Err TooShort, rest }
step = \{ state, i } -> Decode.custom \sbytes, @TagLenFmt {} ->
flattenFieldRes (stepElem state i) sbytes
|> decodeTry \dec, rest -> Decode.decodeWith rest dec (@TagLenFmt {})
|> Decode.mapResult \s -> { state: s, i: i + 1 }
repeatDecode 'l' bytes { state: initialState, i: 0 } step
|> decodeTry \s, rest -> { result: finalizer s.state, rest }
expect
actual = Decode.fromBytes (Str.toUtf8 "l3 n1 s3 abc l1 n0 ") tagLenFmt
actual == Ok (1, "abc", [Bool.false])
expect
input = { foo: (1, "abc", [Bool.false, Bool.true]), bar: { baz: 0.32 } }
encoded = Encode.toBytes input tagLenFmt
decoded = Decode.fromBytes encoded tagLenFmt
decoded == Ok input

View file

@ -18,3 +18,11 @@ macro_rules! assert_multiline_str_eq {
$crate::_pretty_assert_eq!($crate::DebugAsDisplay($a), $crate::DebugAsDisplay($b)) $crate::_pretty_assert_eq!($crate::DebugAsDisplay($a), $crate::DebugAsDisplay($b))
}; };
} }
/// a very simple implementation of En/DecoderFormatting to be embedded in roc source under test
///
/// - numbers and bools are encoded as 'n' <num> ' '
/// - strings are encoded as 's' <count utf-8 bytes> ' ' <str> ' '
/// - records are encoded as 'r' <number of fields> ' ' [<key><value>]*
/// - lists and tuples are encoded as 'l' <len> ' ' [<elem>]*
pub const TAG_LEN_ENCODER_FMT: &str = include_str!("TagLenEncoderFmt.roc");