diff --git a/.gitignore b/.gitignore index e1945d9..a4eb337 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ examples/RandomNumbers/main examples/CommandLineArgs/main examples/Tasks/main examples/Tuples/main +examples/EncodeDecode/main roc_nightly/ # macOS directory attributes diff --git a/ci_scripts/all_tests.sh b/ci_scripts/all_tests.sh index bb2d5f8..fa7b403 100755 --- a/ci_scripts/all_tests.sh +++ b/ci_scripts/all_tests.sh @@ -66,6 +66,9 @@ $ROC test ./examples/BasicDict/BasicDict.roc $ROC build ./examples/MultipleRocFiles/main.roc expect ci_scripts/expect_scripts/MultipleRocFiles.exp +$ROC build ./examples/EncodeDecode/main.roc +expect ci_scripts/expect_scripts/EncodeDecode.exp + $ROC build --lib ./examples/GoPlatform/main.roc --output examples/GoPlatform/platform/libapp.so go build -C examples/GoPlatform/platform -buildmode=pie -o dynhost $ROC preprocess-host ./examples/GoPlatform/main.roc diff --git a/ci_scripts/expect_scripts/EncodeDecode.exp b/ci_scripts/expect_scripts/EncodeDecode.exp new file mode 100644 index 0000000..e2fad50 --- /dev/null +++ b/ci_scripts/expect_scripts/EncodeDecode.exp @@ -0,0 +1,16 @@ +#!/usr/bin/expect + +# uncomment line below for debugging +# exp_internal 1 + +set timeout 7 + +spawn ./examples/EncodeDecode/main + +expect "(@ItemKind Text)\r\n(@ItemKind Method)\r\n(@ItemKind Function)\r\n(@ItemKind Constructor)\r\n(@ItemKind Field)\r\n(@ItemKind Variable)\r\n(@ItemKind Class)\r\n(@ItemKind Interface)\r\n(@ItemKind Module)\r\n" { + expect eof + exit 0 +} + +puts stderr "\nError: output was different from expected value." +exit 1 \ No newline at end of file diff --git a/examples/EncodeDecode/README.md b/examples/EncodeDecode/README.md new file mode 100644 index 0000000..0ad9617 --- /dev/null +++ b/examples/EncodeDecode/README.md @@ -0,0 +1,34 @@ +# Encoding & Decoding Abilities + +An example for how to implement the builtin `Encoding` and `Decoding` abilities for an opaque type. + +Implementing these abilites for an opaque type like `ItemKind`, enables it to be used seamlessly within other data structures. This is useful when you would like to provide a custom mapping, such as in this example, between an integer and a tag union. + +## Implementation +```roc +file:main.roc:snippet:impl +``` + +## Demo +```roc +file:main.roc:snippet:demo +``` + +## Output + +Run this from the directory that has `main.roc` in it: + +``` +$ roc dev +(@ItemKind Text) +(@ItemKind Method) +(@ItemKind Function) +(@ItemKind Constructor) +(@ItemKind Field) +(@ItemKind Variable) +(@ItemKind Class) +(@ItemKind Interface) +(@ItemKind Module) +``` + +You can also use `roc test` to run the tests. diff --git a/examples/EncodeDecode/main.roc b/examples/EncodeDecode/main.roc new file mode 100644 index 0000000..7d7bd52 --- /dev/null +++ b/examples/EncodeDecode/main.roc @@ -0,0 +1,111 @@ +app "example" + packages { + pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br", + json: "https://github.com/lukewilliamboswell/roc-json/releases/download/0.6.3/_2Dh4Eju2v_tFtZeMq8aZ9qw2outG04NbkmKpFhXS_4.tar.br", + } + imports [pf.Stdout.{ line }, json.Core.{ json }] + provides [main] to pf + +### start snippet impl + +ItemKind := [ + Text, + Method, + Function, + Constructor, + Field, + Variable, + Class, + Interface, + Module, +] + implements [ + Decoding { decoder: decodeItems }, + Encoding { toEncoder: encodeItems }, + Inspect, + Eq, + ] + +decodeItems : Decoder ItemKind fmt +decodeItems = Decode.custom \bytes, _ -> + + # helper to convert our tag to a DecodeResult + ok : _, List U8 -> DecodeResult ItemKind + ok = \tag, rest -> { result: Ok (@ItemKind tag), rest } + + when bytes is + ['1', .. as rest] -> ok Text rest + ['2', .. as rest] -> ok Method rest + ['3', .. as rest] -> ok Function rest + ['4', .. as rest] -> ok Constructor rest + ['5', .. as rest] -> ok Field rest + ['6', .. as rest] -> ok Variable rest + ['7', .. as rest] -> ok Class rest + ['8', .. as rest] -> ok Interface rest + ['9', .. as rest] -> ok Module rest + _ -> { result: Err TooShort, rest: bytes } + +encodeItems : ItemKind -> Encoder fmt +encodeItems = \@ItemKind tag -> + + Encode.custom \bytes, _ -> + + # helper to append our encoded byte + append : U8 -> List U8 + append = \u8 -> List.append bytes u8 + + when tag is + Text -> append '1' + Method -> append '2' + Function -> append '3' + Constructor -> append '4' + Field -> append '5' + Variable -> append '6' + Class -> append '7' + Interface -> append '8' + Module -> append '9' + +### end snippet impl + +### start snippet demo + +# make a list of ItemKind's +originalList : List ItemKind +originalList = [ + @ItemKind Text, + @ItemKind Method, + @ItemKind Function, + @ItemKind Constructor, + @ItemKind Field, + @ItemKind Variable, + @ItemKind Class, + @ItemKind Interface, + @ItemKind Module, +] + +# encode them into JSON +encodedBytes : List U8 +encodedBytes = Encode.toBytes originalList json + +# test we have encoded correctly +expect encodedBytes == originalBytes + +# take a JSON encoded list +originalBytes : List U8 +originalBytes = "[1,2,3,4,5,6,7,8,9]" |> Str.toUtf8 + +# decode into a list of ItemKind's +decodedList : List ItemKind +decodedList = Decode.fromBytes originalBytes json |> Result.withDefault [] + +# test we have decoded correctly +expect decodedList == originalList + +main = + # debug print decoded items to stdio + decodedList + |> List.map Inspect.toStr + |> Str.joinWith "\n" + |> Stdout.line + +### end snippet demo diff --git a/examples/index.md b/examples/index.md index 8e8b28c..41d5519 100644 --- a/examples/index.md +++ b/examples/index.md @@ -25,6 +25,7 @@ You can find the source code for all of these at [github.com/roc-lang/examples]( - [Multi-line Comments](/MultiLineComments/README.html) - [Go Platform](/GoPlatform/README.html) - [.NET Platform](/DotNetPlatform/README.html) +- [Encoding & Decoding Abilities](/EncodeDecode/README.html) ## External examples