# atdmbt

`atdmbt` generates MoonBit types and JSON readers and writers from ATD
files. For `foo.atd`, it creates `foo.mbt` in the current directory; `-o`
chooses another output file, and `-o -` prints the code.

These examples are executed by `moon cram test tests/cram`. With `moonx`,
use `moonx bobzhang/atd/cmd/atdmbt` in place of `atdmbt.exe`.

## Generating code

```mooncram
$ cat > shapes.atd <<'EOF' && atdmbt.exe shapes.atd
> type point = { x : float; ~y <mbt default="1.0"> : float }
> type shape = [ Dot | Circle of (point * float) ] <json repr="object">
> EOF
```

```mooncram
$ ls
shapes.atd
shapes.mbt
```

The generated code depends on the runtime library `bobzhang/atd_runtime`
(`moon add bobzhang/atd_runtime`), which the `moon.pkg` of the package
containing the code must import with the alias `atd_runtime`:

```mooncram
$ head -n 16 shapes.mbt
// Generated by atdmbt from type definitions in 'shapes.atd'.
//
// Type-safe translations from/to JSON.
//
// For each type 'foo', there are the following functions:
// - 'write_foo': convert a 'Foo' value into a JSON value;
// - 'read_foo': convert a JSON value into a 'Foo' value;
// - 'foo_of_json', 'foo_of_string' and 'string_of_foo': conveniences.
//
// This code requires the package "bobzhang/atd_runtime", imported with
// the alias 'atd_runtime' in moon.pkg:
//
//   import {
//     "bobzhang/atd_runtime" @atd_runtime,
//   }

```

Records become structs with a constructor taking labelled arguments, and
sum types become enums:

```mooncram
$ sed -n '/^pub(all) struct Point/,/^}/p;/^pub fn Point::new/,/^}/p;/^pub(all) enum Shape/,/^}/p' shapes.mbt
pub(all) struct Point {
  x : Double
  y : Double
} derive(Eq, Debug)
pub fn Point::new(
  x~ : Double,
  y? : Double = 1.0,
) -> Point {
  Point::{ x, y }
}
pub(all) enum Shape {
  Dot
  Circle(Point, Double)
} derive(Eq, Debug)
```

Each type has a JSON writer and reader, and convenience functions:

```mooncram
$ grep '^pub fn\|^pub impl' shapes.mbt
pub fn Point::new(
pub fn write_point(x : Point) -> Json {
pub fn read_point(x : Json, path : @atd_runtime.Path) -> Point raise @atd_runtime.JsonError {
pub fn point_of_json(x : Json) -> Point raise @atd_runtime.JsonError {
pub fn point_of_string(s : StringView) -> Point raise @atd_runtime.JsonError {
pub fn string_of_point(x : Point, indent? : Int = 0) -> String {
pub impl ToJson for Point with to_json(self) {
pub fn write_shape(x : Shape) -> Json {
pub fn read_shape(x : Json, path : @atd_runtime.Path) -> Shape raise @atd_runtime.JsonError {
pub fn shape_of_json(x : Json) -> Shape raise @atd_runtime.JsonError {
pub fn shape_of_string(s : StringView) -> Shape raise @atd_runtime.JsonError {
pub fn string_of_shape(x : Shape, indent? : Int = 0) -> String {
pub impl ToJson for Shape with to_json(self) {
```

The code follows the JSON conventions of ATD: here, the sum type uses
`<json repr="object">`, so `Circle` is written as `{"Circle": ...}`:

```mooncram
$ sed -n '/^pub fn write_shape/,/^}/p' shapes.mbt
pub fn write_shape(x : Shape) -> Json {
  match x {
    Shape::Dot => Json::string("Dot")
    Shape::Circle(v0, v1) => Json::object(Map::from_array([("Circle", Json::array([write_point(v0), @atd_runtime.write_float(v1)]))]))
  }
}
```

## MoonBit-specific annotations

`<mbt ...>` annotations tune the generated code: here, a map, 64-bit
integers, a renamed field and a custom type for a `wrap` construct.

```mooncram
$ cat > options.atd <<'EOF' && atdmbt.exe -o - options.atd | sed -n '/^pub(all) struct Account/,/^}/p'
> type date = string wrap <mbt t="Date" wrap="Date::parse" unwrap="Date::to_string">
> type account = {
>   id : int <mbt repr="int64">;
>   type_ <mbt name="kind"> : string;
>   created : date;
>   scores : (string * int) list <json repr="object"> <mbt repr="map">;
> }
> EOF
pub(all) struct Account {
  id : Int64
  kind : String
  created : Date
  scores : Map[String, Int]
} derive(Eq, Debug)
```

## Errors

Unsupported constructs are reported with their location:

```mooncram
$ echo 'type t = t list' > recursive.atd && atdmbt.exe recursive.atd 2>&1
File "recursive.atd", line 1, characters 0-15:
not implemented in atdmbt: recursive type alias 't'; use a record or a sum type to break the cycle
[1]
```

```mooncram
$ echo 'type t = { x : int <mbt bad> }' > bad.atd && atdmbt.exe bad.atd 2>&1
File "bad.atd", line 1, characters 24-27:
Invalid or misplaced annotation <mbt ... bad... >
[1]
```

Two input files can't be written to the same output file, and no file is
written if one of the inputs is invalid:

```mooncram
$ mkdir -p a b && echo 'type t = int' > a/Foo.atd && echo 'type u = string' > b/foo.atd && atdmbt.exe a/Foo.atd b/foo.atd 2>&1
atdmbt: 'a/Foo.atd' and 'b/foo.atd' would both be written to 'foo.mbt'.
[2]
```

```mooncram
$ echo 'type v = x' > invalid.atd && atdmbt.exe a/Foo.atd invalid.atd 2>&1; for f in foo.mbt invalid.mbt; do test -e $f && echo "$f was written" || echo "$f was not written"; done
File "invalid.atd", line 1, characters 8-10:
Undefined type x
foo.mbt was not written
invalid.mbt was not written
```
