├── src ├── tutorial-data │ ├── untypable-json │ │ ├── untypable.atd │ │ ├── untypable_v2.atd │ │ ├── untypable_v1.atd │ │ └── input.json │ ├── modularity │ │ ├── part1.atd │ │ ├── part3.atd │ │ ├── main.ml │ │ ├── part2.atd │ │ └── demo.sh │ ├── hello │ │ ├── hello.atd │ │ ├── hello.ml │ │ └── demo.sh │ ├── Makefile │ ├── pretty-json │ │ ├── single.json │ │ ├── stream.json │ │ ├── prettify.ml │ │ └── demo.sh │ ├── inspect-biniou │ │ ├── tree.atd │ │ ├── demo.sh │ │ └── tree.ml │ ├── config-file │ │ ├── bad-config1.json │ │ ├── sample-config.json │ │ ├── bad-config2.json │ │ ├── config.atd │ │ ├── demo.sh │ │ └── config.ml │ └── validate │ │ ├── resume.atd │ │ ├── demo.sh │ │ ├── resume.ml │ │ └── resume_util.ml ├── atdgen.css ├── index.md ├── Makefile ├── biniou-format.txt ├── atd-syntax.md ├── tutorial.md └── atdgen.md ├── .gitignore ├── README.md ├── Makefile.master ├── Makefile ├── Makefile.gh-pages └── docs ├── atdgen.css ├── index.html ├── biniou-format.txt └── atd-syntax.html /src/tutorial-data/untypable-json/untypable.atd: -------------------------------------------------------------------------------- 1 | untypable_v2.atd -------------------------------------------------------------------------------- /src/tutorial-data/modularity/part1.atd: -------------------------------------------------------------------------------- 1 | type t = { x : int; y : int } 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | html 3 | man1 4 | *.cm[ioxa] 5 | *.cmx[as] 6 | *.[oa] 7 | *.annot 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository is retired. Development continues https://github.com/mjambon/atd 2 | -------------------------------------------------------------------------------- /src/tutorial-data/hello/hello.atd: -------------------------------------------------------------------------------- 1 | type date = { 2 | year : int; 3 | month : int; 4 | day : int; 5 | } 6 | -------------------------------------------------------------------------------- /src/tutorial-data/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | rm -f */*~ 4 | rm -f */*.cm[ioxa] */*.cmx[as] */*.[oa] */*_[tjv].mli */*_[tjv].ml 5 | -------------------------------------------------------------------------------- /Makefile.master: -------------------------------------------------------------------------------- 1 | # -*- makefile -*- 2 | .PHONY: default clean 3 | 4 | default: 5 | $(MAKE) -C src 6 | 7 | clean: 8 | $(MAKE) -C src clean 9 | -------------------------------------------------------------------------------- /src/tutorial-data/modularity/part3.atd: -------------------------------------------------------------------------------- 1 | type t2 = abstract 2 | 3 | type t3 = { 4 | name : string; 5 | ?data : t2 option; 6 | } 7 | -------------------------------------------------------------------------------- /src/tutorial-data/pretty-json/single.json: -------------------------------------------------------------------------------- 1 | [1234,"abcde",{"start_date":{"year":1970,"month":1,"day":1}, 2 | "end_date":{"year":1980,"month":1,"day":1}}] 3 | -------------------------------------------------------------------------------- /src/tutorial-data/hello/hello.ml: -------------------------------------------------------------------------------- 1 | open Hello_t 2 | let () = 3 | let date = { year = 1970; month = 1; day = 1 } in 4 | print_endline (Hello_j.string_of_date date) 5 | -------------------------------------------------------------------------------- /src/tutorial-data/pretty-json/stream.json: -------------------------------------------------------------------------------- 1 | [1234,"abcde",{"start_date":{"year":1970,"month":1,"day":1}, 2 | "end_date":{"year":1980,"month":1,"day":1}}] 3 | [1,"a",{}] 4 | -------------------------------------------------------------------------------- /src/tutorial-data/inspect-biniou/tree.atd: -------------------------------------------------------------------------------- 1 | (* This a binary tree. Just for the purpose of pretty-printing trees. *) 2 | type tree = 3 | [ Empty 4 | | Node of (tree * int * tree) ] 5 | 6 | -------------------------------------------------------------------------------- /src/tutorial-data/pretty-json/prettify.ml: -------------------------------------------------------------------------------- 1 | let json = 2 | "[1234,\"abcde\",{\"start_date\":{\"year\":1970,\"month\":1,\"day\":1}, 3 | \"end_date\":{\"year\":1980,\"month\":1,\"day\":1}}]" 4 | 5 | let () = print_endline (Yojson.Safe.prettify json) 6 | -------------------------------------------------------------------------------- /src/tutorial-data/pretty-json/demo.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh -e 2 | 3 | set -x 4 | cat single.json 5 | ydump single.json 6 | cat stream.json 7 | ydump -s stream.json 8 | 9 | cat prettify.ml 10 | ocamlfind ocamlopt -o prettify prettify.ml -package atdgen -linkpkg 11 | ./prettify 12 | -------------------------------------------------------------------------------- /src/tutorial-data/config-file/bad-config1.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Example", 3 | "credentials": [ 4 | { 5 | "name": 0, 6 | "key": "db7c0877bdef3016" 7 | }, 8 | { 9 | "name": "tester", 10 | "key": "09871ff387ac2b10" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/tutorial-data/modularity/main.ml: -------------------------------------------------------------------------------- 1 | let v = { 2 | Part3_t.name = "foo"; 3 | data = Some [ 4 | { Part1_t.x = 1; y = 2 }; 5 | { Part1_t.x = 3; y = 4 }; 6 | ] 7 | } 8 | 9 | let () = 10 | Ag_util.Json.to_channel Part3_j.write_t3 stdout v; 11 | print_newline () 12 | -------------------------------------------------------------------------------- /src/tutorial-data/config-file/sample-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Example", 3 | "credentials": [ 4 | { 5 | "name": "joeuser", 6 | "key": "db7c0877bdef3016" 7 | }, 8 | { 9 | "name": "tester", 10 | "key": "09871ff387ac2b10" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/tutorial-data/config-file/bad-config2.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Example", 3 | "tiemout": 20, 4 | "credentials": [ 5 | { 6 | "name": "joeuser", 7 | "key": "db7c0877bdef3016" 8 | }, 9 | { 10 | "name": "tester", 11 | "key": "09871ff387ac2b10" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/tutorial-data/modularity/part2.atd: -------------------------------------------------------------------------------- 1 | type t1 = abstract 2 | (* 3 | Imports type t defined in file part1.atd. 4 | The local name is t1. Because the local name (t1) is different from the 5 | original name (t), we must specify the original name using t=. 6 | *) 7 | 8 | type t2 = t1 list 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: default clean 2 | 3 | PAGES = \ 4 | atd-syntax.html \ 5 | atdgen.css \ 6 | atdgen.html \ 7 | biniou-format.txt \ 8 | index.html \ 9 | tutorial.html 10 | 11 | default: 12 | $(MAKE) -C src 13 | mkdir -p docs 14 | cd html && cp $(PAGES) ../docs 15 | 16 | clean: 17 | rm -f *~ 18 | $(MAKE) -C src clean 19 | -------------------------------------------------------------------------------- /Makefile.gh-pages: -------------------------------------------------------------------------------- 1 | # -*- makefile -*- 2 | .PHONY: default dev pub clean 3 | 4 | PAGES = \ 5 | atd-syntax.html \ 6 | atdgen.css \ 7 | atdgen.html \ 8 | biniou-format.txt \ 9 | index.html \ 10 | tutorial.html 11 | 12 | default: 13 | $(MAKE) -C src 14 | cd html && cp $(PAGES) .. 15 | 16 | clean: 17 | $(MAKE) -C src clean 18 | -------------------------------------------------------------------------------- /src/tutorial-data/untypable-json/untypable_v2.atd: -------------------------------------------------------------------------------- 1 | type raw_json = abstract 2 | (* uses type Yojson.Safe.json, 3 | with the functions Yojson.Safe.write_json 4 | and Yojson.Safe.read_json *) 5 | 6 | type obj_list = obj list 7 | 8 | type obj = { 9 | ?label: string option; 10 | ?labels: string list option; 11 | value: raw_json 12 | } 13 | -------------------------------------------------------------------------------- /src/tutorial-data/inspect-biniou/demo.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh -e 2 | 3 | set -x 4 | 5 | cat tree.atd 6 | cat tree.ml 7 | 8 | atdgen -t tree.atd 9 | atdgen -b tree.atd 10 | ocamlfind ocamlopt -o tree \ 11 | tree_t.mli tree_t.ml tree_b.mli tree_b.ml tree.ml \ 12 | -package atdgen -linkpkg 13 | ./tree 14 | 15 | ls -l tree.dat 16 | bdump tree.dat 17 | bdump -w Empty,Node tree.dat 18 | bdump tree.dat 19 | -------------------------------------------------------------------------------- /src/tutorial-data/untypable-json/untypable_v1.atd: -------------------------------------------------------------------------------- 1 | (* File untypable.atd *) 2 | 3 | type json = abstract 4 | (* uses type Yojson.Safe.json, 5 | with the functions Yojson.Safe.write_json 6 | and Yojson.Safe.read_json *) 7 | 8 | type obj_list = obj list 9 | 10 | type obj = { 11 | ?label: string option; 12 | ?labels: string list option; 13 | value: json 14 | } 15 | -------------------------------------------------------------------------------- /src/tutorial-data/untypable-json/input.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "label": "flower", 4 | "value": { 5 | "petals": [12, 45, 83.5555], 6 | "water": "a340bcf02e" 7 | } 8 | }, 9 | { 10 | "label": "flower", 11 | "value": { 12 | "petals": "undefined", 13 | "fold": null, 14 | "water": 0 15 | } 16 | }, 17 | { "labels": ["fork", "scissors"], 18 | "value": [ 8, 8 ] 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /src/tutorial-data/validate/resume.atd: -------------------------------------------------------------------------------- 1 | type text = string 2 | 3 | type date = { 4 | year : int; 5 | month : int; 6 | day : int; 7 | } 8 | 9 | type job = { 10 | company : text; 11 | title : text; 12 | start_date : date; 13 | ?end_date : date option; 14 | } 15 | 16 | type work_experience = job list 17 | -------------------------------------------------------------------------------- /src/tutorial-data/config-file/config.atd: -------------------------------------------------------------------------------- 1 | type config = { 2 | title : string; 3 | ?description : string option; 4 | ~timeout : int; 5 | ~credentials : param list 6 | ; 8 | } 9 | 10 | type param = { 11 | name : string 12 | ; 13 | key : string 14 | ; 15 | } 16 | -------------------------------------------------------------------------------- /src/tutorial-data/hello/demo.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh -e 2 | 3 | set -x 4 | cat hello.atd 5 | atdgen -t hello.atd 6 | atdgen -j hello.atd 7 | ls 8 | ocamlfind ocamlc -c hello_t.mli -package atdgen 9 | ocamlfind ocamlc -c hello_j.mli -package atdgen 10 | ocamlfind ocamlopt -c hello_t.ml -package atdgen 11 | ocamlfind ocamlopt -c hello_j.ml -package atdgen 12 | ocamlfind ocamlopt -c hello.ml -package atdgen 13 | ocamlfind ocamlopt -o hello hello_t.cmx hello_j.cmx hello.cmx \ 14 | -package atdgen -linkpkg 15 | ./hello 16 | -------------------------------------------------------------------------------- /src/tutorial-data/modularity/demo.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh -e 2 | 3 | set -x 4 | cat part1.atd 5 | cat part2.atd 6 | cat part3.atd 7 | for x in part1 part2 part3; do 8 | atdgen -t $x.atd 9 | atdgen -j $x.atd 10 | ocamlfind ocamlc -c ${x}_t.mli -package atdgen 11 | ocamlfind ocamlc -c ${x}_j.mli -package atdgen 12 | ocamlfind ocamlopt -c ${x}_t.ml -package atdgen 13 | ocamlfind ocamlopt -c ${x}_j.ml -package atdgen 14 | done 15 | ocamlfind ocamlopt -c main.ml -package atdgen 16 | 17 | ocamlfind ocamlopt -o test_modularity \ 18 | part1_t.cmx part1_j.cmx \ 19 | part2_t.cmx part2_j.cmx \ 20 | part3_t.cmx part3_j.cmx \ 21 | main.cmx \ 22 | -package atdgen -linkpkg 23 | ./test_modularity 24 | -------------------------------------------------------------------------------- /src/atdgen.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 3em; 3 | margin-left: auto; 4 | margin-right: auto; 5 | margin-bottom: 7em; 6 | width: 45em; 7 | text-align: left; 8 | 9 | font-family: Arial, sans-serif; 10 | } 11 | 12 | a, a:active { 13 | color: #1a0dab; 14 | } 15 | 16 | a:visited { 17 | color: #609; 18 | } 19 | 20 | h1 a, h1 a:active, h1 a:visited, 21 | h2 a, h2 a:active, h2 a:visited, 22 | h3 a, h3 a:active, h3 a:visited, 23 | h4 a, h4 a:active, h4 a:visited, 24 | h5 a, h5 a:active, h5 a:visited 25 | { 26 | color: black; 27 | text-decoration: none; 28 | } 29 | 30 | table code { 31 | color: rgb(51, 51, 51); 32 | background-color: rgba(0, 0, 0, 0.039); 33 | border-radius: 3px; 34 | padding: 2.72px 0px 2.72px 0px; 35 | } 36 | -------------------------------------------------------------------------------- /docs/atdgen.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 3em; 3 | margin-left: auto; 4 | margin-right: auto; 5 | margin-bottom: 7em; 6 | width: 45em; 7 | text-align: left; 8 | 9 | font-family: Arial, sans-serif; 10 | } 11 | 12 | a, a:active { 13 | color: #1a0dab; 14 | } 15 | 16 | a:visited { 17 | color: #609; 18 | } 19 | 20 | h1 a, h1 a:active, h1 a:visited, 21 | h2 a, h2 a:active, h2 a:visited, 22 | h3 a, h3 a:active, h3 a:visited, 23 | h4 a, h4 a:active, h4 a:visited, 24 | h5 a, h5 a:active, h5 a:visited 25 | { 26 | color: black; 27 | text-decoration: none; 28 | } 29 | 30 | table code { 31 | color: rgb(51, 51, 51); 32 | background-color: rgba(0, 0, 0, 0.039); 33 | border-radius: 3px; 34 | padding: 2.72px 0px 2.72px 0px; 35 | } 36 | -------------------------------------------------------------------------------- /src/tutorial-data/validate/demo.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh -e 2 | 3 | set -x 4 | cat resume.atd 5 | atdgen -t resume.atd 6 | atdgen -j resume.atd 7 | atdgen -v resume.atd 8 | ls 9 | ocamlfind ocamlc -c resume_t.mli -package atdgen 10 | ocamlfind ocamlc -c resume_v.mli -package atdgen 11 | ocamlfind ocamlc -c resume_j.mli -package atdgen 12 | ocamlfind ocamlopt -c resume_t.ml -package atdgen 13 | ocamlfind ocamlopt -c resume_util.ml -package atdgen 14 | ocamlfind ocamlopt -c resume_v.ml -package atdgen 15 | ocamlfind ocamlopt -c resume_j.ml -package atdgen 16 | ocamlfind ocamlopt -c resume.ml -package atdgen 17 | ocamlfind ocamlopt -o test_resume \ 18 | resume_t.cmx resume_util.cmx resume_v.cmx resume_j.cmx resume.cmx \ 19 | -package atdgen -linkpkg 20 | ./test_resume 21 | -------------------------------------------------------------------------------- /src/tutorial-data/inspect-biniou/tree.ml: -------------------------------------------------------------------------------- 1 | open Printf 2 | 3 | (* sample value *) 4 | let tree : Tree_t.tree = 5 | `Node ( 6 | `Node (`Empty, 1, `Empty), 7 | 2, 8 | `Node ( 9 | `Node (`Empty, 3, `Empty), 10 | 4, 11 | `Node (`Empty, 5, `Empty) 12 | ) 13 | ) 14 | 15 | let () = 16 | (* write sample value to file *) 17 | let fname = "tree.dat" in 18 | Ag_util.Biniou.to_file Tree_b.write_tree fname tree; 19 | 20 | (* write sample value to string *) 21 | let s = Tree_b.string_of_tree tree in 22 | printf "raw value (saved as %s):\n%S\n" fname s; 23 | printf "length: %i\n" (String.length s); 24 | 25 | printf "pretty-printed value (without dictionary):\n"; 26 | print_endline (Bi_io.view s); 27 | 28 | printf "pretty-printed value (with dictionary):\n"; 29 | let unhash = Bi_io.make_unhash ["Empty"; "Node"; "foo"; "bar" ] in 30 | print_endline (Bi_io.view ~unhash s) 31 | -------------------------------------------------------------------------------- /src/index.md: -------------------------------------------------------------------------------- 1 | % atdgen documentation 2 | 3 | Atdgen and atdj are code generators that derive JSON serializers and 4 | deserializers from type definitions. Atdgen produces OCaml code 5 | while atdj is for Java. 6 | 7 | Documentation: 8 | 9 | * [Adjustable type definitions for data 10 | exchange](http://mjambon.com/atd-biniou-intro.html): 11 | original motivation for the atdgen project 12 | * [atdgen tutorial](tutorial) 13 | * [atdgen reference manual](atdgen) 14 | * [atd syntax reference](atd-syntax) 15 | * [atdj project](https://github.com/esperco/atdj) 16 | 17 | Development and community: 18 | 19 | * [atdgen on GitHub](https://github.com/mjambon/atdgen) 20 | * [atd on GitHub](https://github.com/mjambon/atd) 21 | * [yojson on GitHub](https://github.com/mjambon/yojson) 22 | * [biniou on GitHub](https://github.com/mjambon/biniou) 23 | * [atdgen-make on GitHub](https://github.com/mjambon/atdgen-make) 24 | * [atdgen-omake on GitHub](https://github.com/mjambon/atdgen-omake) 25 | * [documentation sources](https://github.com/mjambon/atdgen-doc) 26 | -------------------------------------------------------------------------------- /src/tutorial-data/config-file/demo.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh -e 2 | 3 | set -x 4 | 5 | # Embed the contents of the .atd file into our OCaml program 6 | echo 'let contents = "\' > config_atd.ml 7 | sed -e 's/\([\\"]\)/\\\1/g' config.atd >> config_atd.ml 8 | echo '"' >> config_atd.ml 9 | 10 | # Derive OCaml type definitions from .atd file 11 | atdgen -t config.atd 12 | 13 | # Derive JSON-related functions from .atd file 14 | atdgen -j -j-defaults -j-strict-fields config.atd 15 | 16 | # Derive validator from .atd file 17 | atdgen -v config.atd 18 | 19 | # Compile the OCaml program 20 | ocamlfind ocamlopt -o config \ 21 | config_t.mli config_t.ml config_j.mli config_j.ml config_v.mli config_v.ml \ 22 | config_atd.ml config.ml -package atdgen -linkpkg 23 | 24 | # Output a sample config 25 | ./config -template 26 | 27 | # Print the original type definitions 28 | ./config -format 29 | 30 | # Fail to validate an invalid config file 31 | ./config -validate bad-config1.json || : 32 | 33 | # Fail to validate another invalid config file (using custom validators) 34 | ./config -validate bad-config3.json || : 35 | 36 | # Validate, inject missing defaults and pretty-print 37 | ./config -validate sample-config.json 38 | -------------------------------------------------------------------------------- /src/tutorial-data/validate/resume.ml: -------------------------------------------------------------------------------- 1 | let check_experience x = 2 | (match Resume_v.validate_work_experience [] x with 3 | None -> 4 | Printf.printf "VALID:\n" 5 | | Some error -> 6 | Printf.printf "INVALID: %s\n" 7 | (Ag_util.Validation.string_of_error error) 8 | ); 9 | Printf.printf "%s\n" 10 | (Yojson.Safe.prettify (Resume_j.string_of_work_experience x)) 11 | 12 | let () = 13 | (* one valid date *) 14 | let valid = { Resume_t.year = 2000; month = 2; day = 29 } in 15 | (* one invalid date *) 16 | let invalid = { Resume_t.year = 2010; month = 0; day = 0 } in 17 | (* two more valid dates, created with Resume_v.create_date *) 18 | let date1 = { Resume_t.year = 2005; month = 8; day = 1 } in 19 | let date2 = { Resume_t.year = 2006; month = 3; day = 22 } in 20 | 21 | let job = { 22 | Resume_t.company = "Acme Corp."; 23 | title = "Tester"; 24 | start_date = date1; 25 | end_date = Some date2; 26 | } 27 | in 28 | let valid_job = { job with Resume_t.start_date = valid } in 29 | let invalid_job = { job with Resume_t.end_date = Some invalid } in 30 | let valid_experience = [ job; valid_job ] in 31 | let invalid_experience = [ job; invalid_job ] in 32 | check_experience valid_experience; 33 | check_experience invalid_experience 34 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | MANDIR = ../man1 2 | HTMLDIR = ../html 3 | 4 | .PHONY: default all man html clean 5 | default: all 6 | all: man html 7 | 8 | man: \ 9 | $(MANDIR)/atd.1 \ 10 | $(MANDIR)/atdgen.1 11 | 12 | html: \ 13 | $(HTMLDIR)/atd-syntax.html \ 14 | $(HTMLDIR)/atdgen.html \ 15 | $(HTMLDIR)/biniou-format.txt \ 16 | $(HTMLDIR)/index.html \ 17 | $(HTMLDIR)/tutorial.html 18 | 19 | $(MANDIR)/atd.1: atd-syntax.md 20 | mkdir -p $(MANDIR) 21 | pandoc $< -o $@ -t man -s 22 | 23 | $(MANDIR)/atdgen.1: atdgen.md 24 | mkdir -p $(MANDIR) 25 | pandoc $< -o $@ -t man -s 26 | 27 | $(HTMLDIR)/atd-syntax.html: atd-syntax.md atdgen.css 28 | mkdir -p $(HTMLDIR) 29 | cp atdgen.css $(HTMLDIR) 30 | pandoc $< -o $@ -t html5 -s --toc -c atdgen.css 31 | 32 | $(HTMLDIR)/atdgen.html: atdgen.md atdgen.css 33 | mkdir -p $(HTMLDIR) 34 | cp atdgen.css $(HTMLDIR) 35 | pandoc $< -o $@ -t html5 -s --toc -c atdgen.css 36 | 37 | $(HTMLDIR)/tutorial.html: tutorial.md atdgen.css 38 | mkdir -p $(HTMLDIR) 39 | cp atdgen.css $(HTMLDIR) 40 | pandoc $< -o $@ -t html5 -s --toc -c atdgen.css 41 | 42 | $(HTMLDIR)/index.html: index.md atdgen.css 43 | mkdir -p $(HTMLDIR) 44 | cp atdgen.css $(HTMLDIR) 45 | pandoc $< -o $@ -t html5 -s --toc -c atdgen.css 46 | 47 | $(HTMLDIR)/biniou-format.txt: biniou-format.txt 48 | cp $< $@ 49 | 50 | clean: 51 | rm -f *~ 52 | rm -rf $(MANDIR) $(HTMLDIR) 53 | -------------------------------------------------------------------------------- /src/tutorial-data/validate/resume_util.ml: -------------------------------------------------------------------------------- 1 | open Resume_t 2 | 3 | let ascii_printable c = 4 | let n = Char.code c in 5 | n >= 32 && n <= 127 6 | 7 | (* 8 | Check that string is not empty and contains only ASCII printable 9 | characters (for the sake of the example; we use UTF-8 these days) 10 | *) 11 | let validate_some_text s = 12 | s <> "" && 13 | try 14 | String.iter (fun c -> if not (ascii_printable c) then raise Exit) s; 15 | true 16 | with Exit -> 17 | false 18 | 19 | (* 20 | Check that the combination of year, month and day exists in the 21 | Gregorian calendar. 22 | *) 23 | let validate_date x = 24 | let y = x.year in 25 | let m = x.month in 26 | let d = x.day in 27 | m >= 1 && m <= 12 && d >= 1 && 28 | (let dmax = 29 | match m with 30 | 2 -> 31 | if y mod 4 = 0 && not (y mod 100 = 0) || y mod 400 = 0 then 29 32 | else 28 33 | | 1 | 3 | 5 | 7 | 8 | 10 | 12 -> 31 34 | | _ -> 30 35 | in 36 | d <= dmax) 37 | 38 | (* Compare dates chronologically *) 39 | let compare_date a b = 40 | let c = compare a.year b.year in 41 | if c <> 0 then c 42 | else 43 | let c = compare a.month b.month in 44 | if c <> 0 then c 45 | else compare a.day b.day 46 | 47 | (* Check that the end_date, when defined, is not earlier than the start_date *) 48 | let validate_job x = 49 | match x.end_date with 50 | None -> None 51 | | Some end_date -> 52 | compare_date x.start_date end_date <= 0 53 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | atdgen documentation 8 | 9 | 10 | 13 | 14 | 15 |
16 |

atdgen documentation

17 |
18 |

Atdgen and atdj are code generators that derive JSON serializers and deserializers from type definitions. Atdgen produces OCaml code while atdj is for Java.

19 |

Documentation:

20 | 27 |

Development and community:

28 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/tutorial-data/config-file/config.ml: -------------------------------------------------------------------------------- 1 | open Printf 2 | 3 | let param_template = 4 | (* Sample item used to populate the template config file *) 5 | { 6 | Config_v.name = "foo"; 7 | key = "0123456789abcdef" 8 | } 9 | 10 | let config_template = 11 | (* 12 | Records can be conveniently created using functions generated by 13 | "atdgen -v". 14 | Here we use Config_v.create_config to create a record of type 15 | Config_t.config. The big advantage over creating the record 16 | directly using the record notation {...} is that we don't have to 17 | specify default values (such as timeout in this example). 18 | *) 19 | Config_v.create_config ~title:"" ~credentials: [param_template] () 20 | 21 | let make_json_template () = 22 | (* Thanks to the -j-defaults flag passed to atdgen, even default 23 | fields will be printed out *) 24 | let compact_json = Config_j.string_of_config config_template in 25 | Yojson.Safe.prettify compact_json 26 | 27 | let print_template () = 28 | print_endline (make_json_template ()) 29 | 30 | let print_format () = 31 | print_string Config_atd.contents 32 | 33 | let validate fname = 34 | let x = 35 | try 36 | (* Read config data structure from JSON file *) 37 | let x = Ag_util.Json.from_file Config_j.read_config fname in 38 | (* Call the validators specified by *) 39 | if not (Config_v.validate_config x) then 40 | failwith "Some fields are invalid" 41 | else 42 | x 43 | with e -> 44 | (* Print decent error message and exit *) 45 | let msg = 46 | match e with 47 | Failure s 48 | | Yojson.Json_error s -> s 49 | | e -> Printexc.to_string e 50 | in 51 | eprintf "Error: %s\n%!" msg; 52 | exit 1 53 | in 54 | (* Convert config to compact JSON and pretty-print it. 55 | ~std:true means that the output will not use extended syntax for 56 | variants and tuples but only standard JSON. *) 57 | let json = Yojson.Safe.prettify ~std:true (Config_j.string_of_config x) in 58 | print_endline json 59 | 60 | type action = Template | Format | Validate of string 61 | 62 | let main () = 63 | let action = ref Template in 64 | let options = [ 65 | "-template", Arg.Unit (fun () -> action := Template), 66 | " 67 | prints a sample configuration file"; 68 | 69 | "-format", Arg.Unit (fun () -> action := Format), 70 | " 71 | prints the format specification of the config files (atd format)"; 72 | 73 | "-validate", Arg.String (fun s -> action := Validate s), 74 | " 75 | reads a config file, validates it, adds default values 76 | and prints the config nicely to stdout"; 77 | ] 78 | in 79 | let usage_msg = sprintf "\ 80 | Usage: %s [-template|-format|-validate ...] 81 | Demonstration of how to manage JSON configuration files with atdgen. 82 | " 83 | Sys.argv.(0) 84 | in 85 | let anon_fun s = eprintf "Invalid command parameter %S\n%!" s; exit 1 in 86 | Arg.parse options anon_fun usage_msg; 87 | 88 | match !action with 89 | Template -> print_template () 90 | | Format -> print_format () 91 | | Validate s -> validate s 92 | 93 | let () = main () 94 | -------------------------------------------------------------------------------- /docs/biniou-format.txt: -------------------------------------------------------------------------------- 1 | The Biniou format 2 | ----------------- 3 | 4 | Contents: 5 | 6 | 1. Grammar 7 | 2. Tags 8 | 3. Fixed-length types 9 | 4. Vints 10 | 5. Field and variant name hashing 11 | 6. Numeric variants 12 | 13 | 14 | 15 | 1. Grammar 16 | 17 | 18 | TAGVAL ::= TAG VAL // A biniou value with its matching tag 19 | 20 | VAL ::= ATOM 21 | | ARRAY 22 | | TUPLE 23 | | RECORD 24 | | NUM_VARIANT 25 | | VARIANT 26 | | TABLE 27 | | SHARED 28 | 29 | ATOM ::= unit // 0, using one byte 30 | | bool // 0 for false, 1 for true, using one byte 31 | | int8 // 1 arbitrary byte 32 | | int16 // 2 arbitrary bytes 33 | | int32 // 4 arbitrary bytes 34 | | int64 // 8 arbitrary bytes 35 | | float64 // IEEE-754 binary64 36 | | uvint // unsigned variable-length int 37 | | svint // signed variable-length int 38 | | string // sequence of any number of bytes 39 | 40 | ARRAY ::= LENGTH (TAG VAL* )? 41 | NUM_VARIANT ::= NUM_VARIANT_TAG TAGVAL? 42 | VARIANT ::= VARIANT_TAG TAGVAL? 43 | TUPLE ::= LENGTH TAGVAL* 44 | RECORD ::= LENGTH (FIELD_TAG TAGVAL)* 45 | TABLE ::= LENGTH (LENGTH (FIELD_TAG TAG)* (VAL* )* )? // list of records 46 | 47 | SHARED ::= OFFSET TAGVAL? // Value given iff the offset is 0. 48 | // Otherwise, the offset indicates the 49 | // relative position to the left of a SHARED 50 | // to which we are redirected. 51 | 52 | TAG ::= int8 // identifies a type of node 53 | LENGTH ::= uvint 54 | OFFSET ::= uvint 55 | NUM_VARIANT_TAG ::= int8 // 0-127 if no argument, 128-255 if has argument 56 | VARIANT_TAG ::= int32 // first bit indicates argument, then 31-bit hash 57 | FIELD_TAG ::= int32 // 31-bit hash (first bit always 1) 58 | 59 | 60 | 61 | 2. Tags 62 | 63 | 64 | Tags indicate the shallow structure of any biniou value. 65 | 66 | The biniou format is such that the tag of any value is known 67 | from the input data. This allows decoding biniou data as a tree 68 | where each node represents a biniou value, without requiring external 69 | type information. 70 | 71 | The tag values for the various kinds of biniou values are: 72 | 73 | Type of value Tag 74 | --------------------------- 75 | bool 0 76 | int8 1 77 | int16 2 78 | int32 3 79 | int64 4 80 | float64 12 81 | uvint 16 82 | svint 17 83 | string 18 84 | ARRAY 19 85 | TUPLE 20 86 | RECORD 21 87 | NUM_VARIANT 22 88 | VARIANT 23 89 | unit 24 90 | TABLE 25 91 | SHARED 26 92 | 93 | 94 | 95 | 3. Fixed-length types 96 | 97 | 98 | Atomic values of type unit, bool, int8, int16, int32, int64 and float64 99 | represent arbitrary sequences of 1, 2, 4 or 8 bytes. 100 | 101 | In order to make the visualization of data easier, 102 | the default interpretation of these values shall be used: 103 | 104 | Length 105 | in bytes Type of value Default interpretation 106 | --------------------------------------------------------------------- 107 | 1 unit 0 represents the unit value 108 | 1 bool 0 represents false, 1 represents true 109 | 1 int8 unsigned 8-bit int 110 | 2 int16 big endian unsigned 16-bit int 111 | 4 int32 big endian unsigned 32-bit int 112 | 8 int64 big endian unsigned 64-bit int 113 | 8 float64 big endian IEEE-754 binary64 (double) 114 | 115 | 116 | 117 | 4. Vints 118 | 119 | 120 | Vints are a variable-length, byte-aligned representation of 121 | positive integers. 122 | 123 | A vint is represented by a sequence of bytes from least significant 124 | to most significant. In all the bytes except the last one, the 125 | high bit is set to 1 and indicates that more bytes follow. 126 | The high bit of the last byte is set to 0. 127 | The remaining 7 bits in each byte represent data. 128 | 129 | Here is the representation of some sample values: 130 | 131 | 0xxxxxxx 132 | 0 00000000 133 | 1 00000001 134 | 2 00000010 135 | 127 01111111 136 | 137 | 1xxxxxxx 0xxxxxxx 138 | 128 10000000 00000001 139 | 129 10000001 00000001 140 | 255 11111111 00000001 141 | 256 11111111 00000010 142 | 16383 11111111 01111111 143 | 144 | 1xxxxxxx 1xxxxxxx 0xxxxxxx 145 | 16384 10000000 10000000 00000001 146 | 16385 10000001 10000000 00000001 147 | 148 | 149 | Positive integers can be represented by standard vints. 150 | We call this representation unsigned vint or uvint. 151 | 152 | Arbitrary integers can also be represented using vints, after mapping 153 | to positive integers. We call this representation signed vint or svint. 154 | Positive numbers and 0 are mapped to even numbers and negative numbers 155 | are mapped to odd positive numbers. Here is the mapping for 156 | small numbers: 157 | 158 | vint unsigned signed 159 | representation interpretation interpretation 160 | (uvint) (svint) 161 | 0xxxxxx0 162 | 00000000 0 0 163 | 00000010 2 1 164 | 00000100 4 2 165 | 00000110 6 3 166 | 167 | 0xxxxxx1 168 | 00000001 1 -1 169 | 00000011 3 -2 170 | 00000101 5 -3 171 | 172 | 173 | 174 | 5. Field and variant name hashing 175 | 176 | 177 | Record field names and variant names are represented by a 178 | 31-bit tag which must be a hash of the name. The following 179 | hash function must be used: 180 | 181 | hash(s): 182 | h <- 0 183 | for i = 0 to length(s) - 1 do 184 | h <- 223 * h + s[i] 185 | done 186 | h <- h mod 2^31 187 | return h 188 | 189 | For example, hash("Hello") is 0x37eea2f2. 190 | 191 | A full field tag or variant tag is made of 32 bits. 192 | The first bit is 0 for variants without an argument, and 1 for 193 | variants with an argument or record fields. 194 | The remaining 31 bits are the hash of field or variant name described above. 195 | 196 | 197 | 198 | 6. Numeric variants 199 | 200 | 201 | Numeric variants are a more compact alternative to variants using 202 | 32-bit hash-based tags since the tag of numeric variants 203 | takes only one byte. 204 | 205 | The most common use of numeric variants is for an option type. 206 | A value of type option is either None or Some value, 207 | e.g. None, Some 123 or Some 0. 208 | This allows to represent undefined values without 209 | reserving a special value called null or undefined. 210 | -------------------------------------------------------------------------------- /src/biniou-format.txt: -------------------------------------------------------------------------------- 1 | The Biniou format 2 | ----------------- 3 | 4 | Contents: 5 | 6 | 1. Grammar 7 | 2. Tags 8 | 3. Fixed-length types 9 | 4. Vints 10 | 5. Field and variant name hashing 11 | 6. Numeric variants 12 | 13 | 14 | 15 | 1. Grammar 16 | 17 | 18 | TAGVAL ::= TAG VAL // A biniou value with its matching tag 19 | 20 | VAL ::= ATOM 21 | | ARRAY 22 | | TUPLE 23 | | RECORD 24 | | NUM_VARIANT 25 | | VARIANT 26 | | TABLE 27 | | SHARED 28 | 29 | ATOM ::= unit // 0, using one byte 30 | | bool // 0 for false, 1 for true, using one byte 31 | | int8 // 1 arbitrary byte 32 | | int16 // 2 arbitrary bytes 33 | | int32 // 4 arbitrary bytes 34 | | int64 // 8 arbitrary bytes 35 | | float64 // IEEE-754 binary64 36 | | uvint // unsigned variable-length int 37 | | svint // signed variable-length int 38 | | string // sequence of any number of bytes 39 | 40 | ARRAY ::= LENGTH (TAG VAL* )? 41 | NUM_VARIANT ::= NUM_VARIANT_TAG TAGVAL? 42 | VARIANT ::= VARIANT_TAG TAGVAL? 43 | TUPLE ::= LENGTH TAGVAL* 44 | RECORD ::= LENGTH (FIELD_TAG TAGVAL)* 45 | TABLE ::= LENGTH (LENGTH (FIELD_TAG TAG)* (VAL* )* )? // list of records 46 | 47 | SHARED ::= OFFSET TAGVAL? // Value given iff the offset is 0. 48 | // Otherwise, the offset indicates the 49 | // relative position to the left of a SHARED 50 | // to which we are redirected. 51 | 52 | TAG ::= int8 // identifies a type of node 53 | LENGTH ::= uvint 54 | OFFSET ::= uvint 55 | NUM_VARIANT_TAG ::= int8 // 0-127 if no argument, 128-255 if has argument 56 | VARIANT_TAG ::= int32 // first bit indicates argument, then 31-bit hash 57 | FIELD_TAG ::= int32 // 31-bit hash (first bit always 1) 58 | 59 | 60 | 61 | 2. Tags 62 | 63 | 64 | Tags indicate the shallow structure of any biniou value. 65 | 66 | The biniou format is such that the tag of any value is known 67 | from the input data. This allows decoding biniou data as a tree 68 | where each node represents a biniou value, without requiring external 69 | type information. 70 | 71 | The tag values for the various kinds of biniou values are: 72 | 73 | Type of value Tag 74 | --------------------------- 75 | bool 0 76 | int8 1 77 | int16 2 78 | int32 3 79 | int64 4 80 | float64 12 81 | uvint 16 82 | svint 17 83 | string 18 84 | ARRAY 19 85 | TUPLE 20 86 | RECORD 21 87 | NUM_VARIANT 22 88 | VARIANT 23 89 | unit 24 90 | TABLE 25 91 | SHARED 26 92 | 93 | 94 | 95 | 3. Fixed-length types 96 | 97 | 98 | Atomic values of type unit, bool, int8, int16, int32, int64 and float64 99 | represent arbitrary sequences of 1, 2, 4 or 8 bytes. 100 | 101 | In order to make the visualization of data easier, 102 | the default interpretation of these values shall be used: 103 | 104 | Length 105 | in bytes Type of value Default interpretation 106 | --------------------------------------------------------------------- 107 | 1 unit 0 represents the unit value 108 | 1 bool 0 represents false, 1 represents true 109 | 1 int8 unsigned 8-bit int 110 | 2 int16 big endian unsigned 16-bit int 111 | 4 int32 big endian unsigned 32-bit int 112 | 8 int64 big endian unsigned 64-bit int 113 | 8 float64 big endian IEEE-754 binary64 (double) 114 | 115 | 116 | 117 | 4. Vints 118 | 119 | 120 | Vints are a variable-length, byte-aligned representation of 121 | positive integers. 122 | 123 | A vint is represented by a sequence of bytes from least significant 124 | to most significant. In all the bytes except the last one, the 125 | high bit is set to 1 and indicates that more bytes follow. 126 | The high bit of the last byte is set to 0. 127 | The remaining 7 bits in each byte represent data. 128 | 129 | Here is the representation of some sample values: 130 | 131 | 0xxxxxxx 132 | 0 00000000 133 | 1 00000001 134 | 2 00000010 135 | 127 01111111 136 | 137 | 1xxxxxxx 0xxxxxxx 138 | 128 10000000 00000001 139 | 129 10000001 00000001 140 | 255 11111111 00000001 141 | 256 11111111 00000010 142 | 16383 11111111 01111111 143 | 144 | 1xxxxxxx 1xxxxxxx 0xxxxxxx 145 | 16384 10000000 10000000 00000001 146 | 16385 10000001 10000000 00000001 147 | 148 | 149 | Positive integers can be represented by standard vints. 150 | We call this representation unsigned vint or uvint. 151 | 152 | Arbitrary integers can also be represented using vints, after mapping 153 | to positive integers. We call this representation signed vint or svint. 154 | Positive numbers and 0 are mapped to even numbers and negative numbers 155 | are mapped to odd positive numbers. Here is the mapping for 156 | small numbers: 157 | 158 | vint unsigned signed 159 | representation interpretation interpretation 160 | (uvint) (svint) 161 | 0xxxxxx0 162 | 00000000 0 0 163 | 00000010 2 1 164 | 00000100 4 2 165 | 00000110 6 3 166 | 167 | 0xxxxxx1 168 | 00000001 1 -1 169 | 00000011 3 -2 170 | 00000101 5 -3 171 | 172 | 173 | 174 | 5. Field and variant name hashing 175 | 176 | 177 | Record field names and variant names are represented by a 178 | 31-bit tag which must be a hash of the name. The following 179 | hash function must be used: 180 | 181 | hash(s): 182 | h <- 0 183 | for i = 0 to length(s) - 1 do 184 | h <- 223 * h + s[i] 185 | done 186 | h <- h mod 2^31 187 | return h 188 | 189 | For example, hash("Hello") is 0x37eea2f2. 190 | 191 | A full field tag or variant tag is made of 32 bits. 192 | The first bit is 0 for variants without an argument, and 1 for 193 | variants with an argument or record fields. 194 | The remaining 31 bits are the hash of field or variant name described above. 195 | 196 | 197 | 198 | 6. Numeric variants 199 | 200 | 201 | Numeric variants are a more compact alternative to variants using 202 | 32-bit hash-based tags since the tag of numeric variants 203 | takes only one byte. 204 | 205 | The most common use of numeric variants is for an option type. 206 | A value of type option is either None or Some value, 207 | e.g. None, Some 123 or Some 0. 208 | This allows to represent undefined values without 209 | reserving a special value called null or undefined. 210 | -------------------------------------------------------------------------------- /src/atd-syntax.md: -------------------------------------------------------------------------------- 1 | % atd(1) syntax reference 2 | % December 14, 2014 3 | 4 | [Home](https://mjambon.github.io/atdgen-doc/) 5 | 6 | Name 7 | ==== 8 | 9 | atd - syntax for cross-language **a**djustable **t**ype **d**efinitions 10 | 11 | Synopsis 12 | ======== 13 | 14 | atdcat [_infile_**.atd**] [_options_...] 15 | 16 | atdcat **-version** 17 | 18 | atdcat **-help** 19 | 20 | Introduction 21 | ============ 22 | 23 | ATD stands for Adjustable Type Definitions. 24 | 25 | ```ocaml 26 | (* This is a sample ATD file *) 27 | 28 | type profile = { 29 | id : string; 30 | email : string; 31 | ~email_validated : bool; 32 | name : string; 33 | ?real_name : string option; 34 | ~about_me : string list; 35 | ?gender : gender option; 36 | ?date_of_birth : date option; 37 | } 38 | 39 | type gender = [ Female | Male ] 40 | 41 | type date = { 42 | year : int; 43 | month : int; 44 | day : int; 45 | } 46 | ``` 47 | 48 | ATD is a language for defining data types across multiple programming 49 | languages and multiple data formats. 50 | That's it. 51 | 52 | We provide an OCaml library that provides a parser and a collection of 53 | tools that make it easy to write data validators and code generators 54 | based on ATD definitions. 55 | 56 | Unlike big frameworks that provide everything in one 57 | monolithic package, we split the problem of data exchange into logical 58 | modules and ATD is one of them. 59 | In particular, we acknowledge that the following pieces have 60 | little in common and should be defined and implemented separately: 61 | 62 | 63 | * data type specifications 64 | * transport protocols 65 | * serialization formats 66 | 67 | 68 | Ideally we want just one single language for defining data types and 69 | it should accomodate all programming languages and data formats. ATD 70 | can play this role, but its OCaml implementation makes it 71 | particularly easy to translate ATD specifications into other interface 72 | definition languages if needed. 73 | 74 | It is however much harder to imagine that a single transport protocol and 75 | a single serialization format would ever become the only ones used. 76 | A reader from the future might wonder why we are even considering 77 | defining a transport protocol and a serialization format together. 78 | This has been a widespread practice at least until the beginning of 79 | the 21st century (ONC RPC, ICE, Thrift, etc.). For mysterious reasons, 80 | people somehow became convinced that calls to remote services should 81 | be made to mimic internal function calls, pretending that nothing 82 | really bad could happen on the way between the caller and the remote 83 | service. Well, I don't let my 3-old daughter go to school by herself 84 | because the definition of the external world is precisely that it is 85 | unsafe. 86 | 87 | Data input is by definition unsafe. A program whose internal data is 88 | corrupted should abort but a failed attempt to read external data 89 | should not cause a program to abort. On the contrary, a program should 90 | be very resistent to all forms of data corruption and attacks and 91 | provide the best diagnosis possible when problems with external data 92 | occur. 93 | 94 | Because data exchange is critical and involves multiple partners, we 95 | depart from magic programming language-centric or company-centric 96 | approaches. We define ATD, a data type definition language 97 | designed for maximum expressivity, compatibility across languages and 98 | static type checking of programs using such data. 99 | 100 | 101 | Scope 102 | ----- 103 | 104 | 105 | ATD offers a core syntax for type definitions, i.e. an idealized view 106 | of the structure of data. Types are mapped to each programming 107 | language or data format using language-specific conventions. 108 | Annotations can complete the type definitions in 109 | order to specify options for a particular language. 110 | Annotations are placed in angle brackets after the element they refer to: 111 | 112 | ```ocaml 113 | type profile = { 114 | id : int ; 115 | (* 116 | An int here will map to an OCaml int64 instead of 117 | OCaml's default int type. 118 | Other languages than OCaml will use their default int type. 119 | *) 120 | 121 | age : int; 122 | (* No annotation here, the default int type will be used. *) 123 | } 124 | ``` 125 | 126 | ATD supports: 127 | 128 | * the following atomic types: bool, int, float, string and unit; 129 | * built-in list and option types; 130 | * records aka structs with a syntax for optional fields with or 131 | without default; 132 | * tuples; 133 | * sum types aka variant types, algebraic data types or tagged unions; 134 | * parametrized types; 135 | * inheritance for both records and sum types; 136 | * abstract types; 137 | * arbitrary annotations. 138 | 139 | 140 | ATD by design does not support: 141 | 142 | * function types, function signatures or method signatures; 143 | * a syntax to represent values; 144 | * a syntax for submodules. 145 | 146 | 147 | 148 | Language overview 149 | ----------------- 150 | 151 | ATD was strongly inspired by the type system of ML and OCaml. Such a 152 | type system allows static type checking and type inference, properties 153 | which contribute to the safety and conciseness of the language. 154 | 155 | Unlike mainstream languages like Java, C++, C# or Python to name a 156 | few, languages such as Haskell or OCaml offer sum types, 157 | also known as algebraic data types or variant types. These allow to 158 | specify that an object is of one kind or another without ever 159 | performing dynamic casts. 160 | 161 | ```ocaml 162 | (* Example of a sum type in ATD. The vertical bar reads `or'. *) 163 | type shape = [ 164 | Square of float (* argument: side length *) 165 | | Rectangle of (float * float) (* argument: width and height *) 166 | | Circle of float (* argument: radius *) 167 | | Dot (* no argument *) 168 | ] 169 | ``` 170 | 171 | A notable example of sum types is the predefined option type. 172 | An object of an option type contains either one value of a given type 173 | or nothing. We could define our own `int_option` type as follows: 174 | 175 | ```ocaml 176 | type int_option = [ None | Some of int ] 177 | ``` 178 | 179 | ATD supports parametrized types also known as generics in Java or 180 | templates in C++. We could define our own generic option type as 181 | follows: 182 | 183 | ```ocaml 184 | type 'a opt = [ None | Some of 'a ] 185 | (* 'a denotes a type parameter. *) 186 | 187 | type opt_int = int opt 188 | (* equivalent to int_option defined in the previous example *) 189 | 190 | type opt_string = string opt 191 | (* same with string instead of int *) 192 | ``` 193 | 194 | In practice we shall use the predefined option type. 195 | The option type is fundamentally different from nullable objects since 196 | the latter don't allow values that would have type `'a option option`. 197 | 198 | ATD also support product types. They come in two forms: tuples and 199 | records: 200 | 201 | ```ocaml 202 | type tuple_example = (string * int) 203 | 204 | type record_example = { 205 | name : string; 206 | age : int; 207 | } 208 | ``` 209 | 210 | Although tuples in theory are not more expressive than 211 | records, they are much more concise and languages that support them 212 | natively usually do not require type definitions. 213 | 214 | Finally, ATD supports multiple inheritance which is a simple mechanism 215 | for adding fields to records or variants to sum types: 216 | 217 | ```ocaml 218 | type builtin_color = [ 219 | Red | Green | Blue | Yellow 220 | | Purple | Black | White 221 | ] 222 | 223 | type rgb = (float * float * float) 224 | type cmyk = (float * float * float * float) 225 | 226 | (* Inheritance of variants *) 227 | type color = [ 228 | inherit builtin_color 229 | | Rgb of rgb 230 | | Cmyk of cmyk 231 | ] 232 | ``` 233 | 234 | ```ocaml 235 | type basic_profile = { 236 | id : string; 237 | name : string; 238 | } 239 | 240 | (* Inheritance of record fields *) 241 | type full_profile = { 242 | inherit basic_profile; 243 | date_of_birth : (int * int * int) option; 244 | street_address1 : string option; 245 | street_address2 : string option; 246 | city : string option; 247 | zip_code : string option; 248 | state : string option; 249 | } 250 | ``` 251 | 252 | 253 | Editing and validating ATD files 254 | -------------------------------- 255 | 256 | The extension for ATD files is `.atd`. Editing ATD files is best 257 | achieved using an OCaml-friendly editor since the ATD syntax is vastly 258 | compatible with OCaml and uses a subset of OCaml's keywords. 259 | 260 | Emacs users can use caml-mode or tuareg-mode to edit ATD files. 261 | Adding the following line to the `~/.emacs` file will 262 | automatically use tuareg-mode when opening a file with a `.atd` 263 | extension: 264 | 265 | ```commonlisp 266 | (add-to-list 'auto-mode-alist '("\\.atd\\'" . tuareg-mode)) 267 | ``` 268 | 269 | The syntax of an ATD file can be checked with the program 270 | `atdcat` provided with the OCaml library `atd`. 271 | `atdcat` pretty-prints its input data, optionally after some 272 | transformations such as monomorphization or inheritance. 273 | Here is the output of `atdcat -help`: 274 | 275 | ``` 276 | Usage: atdcat FILE 277 | -x 278 | make type expressions monomorphic 279 | -xk 280 | keep parametrized type definitions and imply -x. 281 | Default is to return only monomorphic type definitions 282 | -xd 283 | debug mode implying -x 284 | -i 285 | expand all `inherit' statements 286 | -if 287 | expand `inherit' statements in records 288 | -iv 289 | expand `inherit' statements in sum types 290 | -ml 291 | output the ocaml code of the ATD abstract syntax tree 292 | -html-doc 293 | replace directly by (*html ... *) 294 | or replace by (*html ... *) 295 | where the contents are formatted as HTML 296 | using

, and

.
297 |           This is suitable input for "caml2html -ext html:cat"
298 |           which converts ATD files into HTML.
299 |   -strip NAME1[,NAME2,...]
300 |           remove all annotations of the form ,
301 |           , etc.
302 |   -strip-all 
303 |           remove all annotations
304 |   -version 
305 |           print the version of atd and exit
306 |   -help  Display this list of options
307 |   --help  Display this list of options
308 | ```
309 | 
310 | 
311 | ATD language
312 | ============
313 | 
314 | This is a precise description of the syntax of the ATD language, not a
315 | tutorial.
316 | 
317 | Notations
318 | ----------
319 | 
320 | Lexical and grammatical rules are expressed using a BNF-like syntax.
321 | Graphical terminal symbols use `unquoted strings in typewriter font`.
322 | Non-graphical characters use their official uppercase ASCII name such
323 | as LF for the newline character or SPACE for the space character.
324 | Non-terminal symbols use the regular font and link to their
325 | definition.  Parentheses are used for grouping.
326 | 
327 | The following postfix operators are used to specify repeats:
328 | 
329 | ------ ---------------------------------
330 | x*     0, 1 or more occurrences of x
331 | x?     0 or 1 occurrence of x
332 | x+     1 or more occurrences of x
333 | ------ ---------------------------------
334 | 
335 | 
336 | Lexical rules
337 | -------------
338 | 
339 | ATD does not enforce a particular character encoding other than ASCII
340 | compatibility. Non-ASCII text and data found in annotations and
341 | in comments may contain arbitrary bytes in the non-ASCII range 128-255
342 | without escaping. The UTF-8 encoding is however strongly recommended
343 | for all text. The use of hexadecimal or decimal escape sequences is
344 | recommended for binary data.
345 | 
346 | An ATD lexer splits its input into a stream of tokens,
347 | discarding whitespace and comments.
348 | 
349 | ---------------- ---------------------------------------- --------------------
350 |        token ::= keyword
351 | 
352 |                | lident
353 | 
354 |                | uident
355 | 
356 |                | tident
357 | 
358 |                | string
359 | 
360 |    ignorable ::= space                                    _discarded_
361 | 
362 |                | comment
363 | 
364 |        space ::= SPACE | TAB | CR | LF
365 | 
366 |        blank ::= SPACE | TAB
367 | 
368 |      comment ::= `(*` (comment | string | byte)* `*)`
369 | 
370 |       lident ::= (lower | `_` identchar) identchar*       _lowercase
371 |                                                           identifier_
372 | 
373 |       uident ::= upper identchar*                         _uppercase
374 |                                                           identifier_
375 | 
376 |       tident ::= `'` lident                               _type parameter_
377 | 
378 |        lower ::= `a`...`z`
379 | 
380 |        upper ::= `A`...`Z`
381 | 
382 |    identchar ::= upper | lower | digit | `_` | `'`
383 | 
384 |       string ::= `"` substring* `"`                       _string literal,
385 |                                                           used in annotations_
386 | 
387 |    substring ::= `\\`                                     _single backslash_
388 | 
389 |                | `\"`                                     _double quote_
390 | 
391 |                | `\x` hex hex                             _single byte
392 |                                                           in hexadecimal
393 |                                                           notation_
394 | 
395 |                | `\` digit digit digit                    _single byte
396 |                                                           in decimal
397 |                                                           notation_
398 | 
399 |                | `\n`                                     _LF_
400 | 
401 |                | `\r`                                     _CR_
402 | 
403 |                | `\t`                                     _TAB_
404 | 
405 |                | `\b`                                     _BS_
406 | 
407 |                | `\` CR? LF blank*                        _discarded_
408 | 
409 |                | not-backslash                            _any byte
410 |                                                           except `\`
411 |                                                           or `"`_
412 | 
413 |        digit ::= `0`...`9`
414 | 
415 |          hex ::= `0`...`9` | `a`...`f` | `A`...`F`
416 | 
417 |      keyword ::= `(` | `)` | `[`                          _all keywords_
418 |                  | `]` | \str{\{} | `\`}
419 |                  | `<` | `>` &
420 |                  | `;` | `,` | `:` | `*`
421 |                  | `|` | `=` | `?` | `~`
422 |                  | `type` | `of` | `inherit`
423 | ---------------- ---------------------------------------- --------------------
424 | 
425 | 
426 | Grammar
427 | -------
428 | 
429 | ---------------- ---------------------------------------- --------------------
430 |       module ::= annot* typedef*                          _entry point_
431 | 
432 |        annot ::= `<` lident annot-field* `>`              _annotation_
433 | 
434 |  annot-field ::= (lident (`=` string)?)
435 | 
436 |      typedef ::= `type` params? lident annot              _type definition_
437 |                  `=` expr
438 | 
439 |       params ::= tident                                   _one parameter_
440 | 
441 |                | `(` tident (`,` tident)+ `)`             _two or more
442 |                                                           parameters_
443 | 
444 |         expr ::= expr-body annot*                         _type expression_
445 | 
446 |                | tident
447 | 
448 |    expr-body ::= args? lident
449 | 
450 |                | `(`                                      _tuple type_
451 |                  (cell (`*` cell)*)?
452 |                  `)`
453 | 
454 |                | `{`                                      _record type_
455 |                  ((field (`;` field)*) `;`?)?
456 |                  `}`
457 | 
458 |                | `[`                                      _sum type_
459 |                  (`|`? variant (`|` variant)*)?
460 |                  `]`
461 | 
462 |         args ::= expr                                     _one argument_
463 | 
464 |                | `(` expr (`,` expr)+ `)`                 _two or more
465 |                                                           arguments_
466 | 
467 |         cell ::= (annot+ `:`)? expr
468 | 
469 |        field ::= (`?` | `~`)? lident `=` expr
470 | 
471 |                | `inherit` expr
472 | 
473 |      variant ::= uident annot* `of` expr
474 | 
475 |                | uident annot*
476 | 
477 |                | `inherit` expr
478 | ------------------------------------------------------------------------------
479 | 
480 | 
481 | 
482 | Predefined type names
483 | ---------------------
484 | 
485 | The following types are considered predefined and may not be
486 | redefined.
487 | 
488 | ------------------------------------------------------------------------------
489 | Type name          Intended use
490 | ------------------ -----------------------------------------------------------
491 | `unit`             Type of just one value, useful with parametrized types
492 | 
493 | `bool`             Boolean
494 | 
495 | `int`              Integer
496 | 
497 | `float`            Floating-point number
498 | 
499 | `string`           Sequence of bytes or characters
500 | 
501 | `'a option`        Container of zero or one element of type `'a`.
502 |                    See also `'a nullable`.
503 | 
504 | `'a list`          Collection or sequence of elements of type `'a`
505 | 
506 | `'a nullable`      Extend type `'a` with an extra conventional value,
507 |                    typically called "null". The operation is idempotent,
508 |                    i.e. `'a nullable` is equivalent to
509 |                    `'a nullable nullable`.
510 | 
511 | `'a shared`        Values of type `'a` for which sharing must be preserved
512 | 
513 | `'a wrap`          Values on which a custom, reversible transformation
514 |                    may be applied, as specified by
515 |                    language-specific annotations.
516 | 
517 | `abstract`         Type defined elsewhere
518 | ------------------------------------------------------------------------------
519 | 
520 | 
521 | Shared values
522 | -------------
523 | 
524 | ATD supports a special type $x$ `shared` where $x$ can be
525 | any monomorphic type expression.
526 | It allows notably to represent cyclic values and to enforce that cycles
527 | are preserved during transformations such as serialization.
528 | 
529 | ```ocaml
530 | (* Example of a simple graph type *)
531 | type shared_node = node shared (* sharing point *)
532 | type graph = shared_node list
533 | type node = {
534 |   label : string;
535 |   neighbors : shared_node list;
536 | }
537 | ```
538 | 
539 | Two shared values that are physically identical must remain physically
540 | identical after any translation from one data format to another.
541 | 
542 | Each occurrence of a `shared` type expression in the ATD
543 | source definition defines its own sharing point.
544 | Therefore the following attempt at defining a graph type will not
545 | preserve cycles because two sharing points are defined:
546 | 
547 | ```ocaml
548 | (* Incorrect definition of a graph type *)
549 | type node = {
550 |   label : string;
551 |   neighbors : node shared (* sharing point 1 *) list;
552 | }
553 | 
554 | (* Second occurrence of "shared", won't preserve cycles! *)
555 | type graph = node shared (* sharing point 2 *) list
556 | ```
557 | 
558 | There is actually a way of having multiple `shared`
559 | type expressions using the same sharing point
560 | but this feature is designed for code generators and
561 | should not be used in handwritten ATD definitions.
562 | The technique consists in providing an annotation of the form
563 | `` where _x_ is any string identifying the sharing
564 | point.
565 | The graph example can be rewritten correctly as:
566 | 
567 | ```ocaml
568 | type node = {
569 |   label : string;
570 |   neighbors : node shared  list;
571 | }
572 | 
573 | type graph = node shared  list
574 | ```
575 | 
576 | 
577 | See also
578 | ========
579 | 
580 | [atdgen](atdgen)(1)
581 | 


--------------------------------------------------------------------------------
/docs/atd-syntax.html:
--------------------------------------------------------------------------------
  1 | 
  2 | 
  3 | 
  4 |   
  5 |   
  6 |   
  7 |   
  8 |   atd(1) syntax reference
  9 |   
 10 |   
 47 |   
 48 |   
 51 | 
 52 | 
 53 | 
54 |

atd(1) syntax reference

55 |

December 14, 2014

56 |
57 | 76 |

Home

77 |

Name

78 |

atd - syntax for cross-language adjustable type definitions

79 |

Synopsis

80 |

atdcat [infile.atd] [options...]

81 |

atdcat -version

82 |

atdcat -help

83 |

Introduction

84 |

ATD stands for Adjustable Type Definitions.

85 |
(* This is a sample ATD file *)
 86 | 
 87 | type profile = {
 88 |   id : string;
 89 |   email : string;
 90 |   ~email_validated : bool;
 91 |   name : string;
 92 |   ?real_name : string option;
 93 |   ~about_me : string list;
 94 |   ?gender : gender option;
 95 |   ?date_of_birth : date option;
 96 | }
 97 | 
 98 | type gender = [ Female | Male ]
 99 | 
100 | type date = {
101 |   year : int;
102 |   month : int;
103 |   day : int;
104 | }
105 |

ATD is a language for defining data types across multiple programming languages and multiple data formats. That's it.

106 |

We provide an OCaml library that provides a parser and a collection of tools that make it easy to write data validators and code generators based on ATD definitions.

107 |

Unlike big frameworks that provide everything in one monolithic package, we split the problem of data exchange into logical modules and ATD is one of them. In particular, we acknowledge that the following pieces have little in common and should be defined and implemented separately:

108 |
    109 |
  • data type specifications
  • 110 |
  • transport protocols
  • 111 |
  • serialization formats
  • 112 |
113 |

Ideally we want just one single language for defining data types and it should accomodate all programming languages and data formats. ATD can play this role, but its OCaml implementation makes it particularly easy to translate ATD specifications into other interface definition languages if needed.

114 |

It is however much harder to imagine that a single transport protocol and a single serialization format would ever become the only ones used. A reader from the future might wonder why we are even considering defining a transport protocol and a serialization format together. This has been a widespread practice at least until the beginning of the 21st century (ONC RPC, ICE, Thrift, etc.). For mysterious reasons, people somehow became convinced that calls to remote services should be made to mimic internal function calls, pretending that nothing really bad could happen on the way between the caller and the remote service. Well, I don't let my 3-old daughter go to school by herself because the definition of the external world is precisely that it is unsafe.

115 |

Data input is by definition unsafe. A program whose internal data is corrupted should abort but a failed attempt to read external data should not cause a program to abort. On the contrary, a program should be very resistent to all forms of data corruption and attacks and provide the best diagnosis possible when problems with external data occur.

116 |

Because data exchange is critical and involves multiple partners, we depart from magic programming language-centric or company-centric approaches. We define ATD, a data type definition language designed for maximum expressivity, compatibility across languages and static type checking of programs using such data.

117 |

Scope

118 |

ATD offers a core syntax for type definitions, i.e. an idealized view of the structure of data. Types are mapped to each programming language or data format using language-specific conventions. Annotations can complete the type definitions in order to specify options for a particular language. Annotations are placed in angle brackets after the element they refer to:

119 |
type profile = {
120 |   id : int <ocaml repr="int64">;
121 |     (*
122 |        An int here will map to an OCaml int64 instead of
123 |        OCaml's default int type.
124 |        Other languages than OCaml will use their default int type.
125 |     *)
126 | 
127 |   age : int;
128 |     (* No annotation here, the default int type will be used. *)
129 | }
130 |

ATD supports:

131 |
    132 |
  • the following atomic types: bool, int, float, string and unit;
  • 133 |
  • built-in list and option types;
  • 134 |
  • records aka structs with a syntax for optional fields with or without default;
  • 135 |
  • tuples;
  • 136 |
  • sum types aka variant types, algebraic data types or tagged unions;
  • 137 |
  • parametrized types;
  • 138 |
  • inheritance for both records and sum types;
  • 139 |
  • abstract types;
  • 140 |
  • arbitrary annotations.
  • 141 |
142 |

ATD by design does not support:

143 |
    144 |
  • function types, function signatures or method signatures;
  • 145 |
  • a syntax to represent values;
  • 146 |
  • a syntax for submodules.
  • 147 |
148 |

Language overview

149 |

ATD was strongly inspired by the type system of ML and OCaml. Such a type system allows static type checking and type inference, properties which contribute to the safety and conciseness of the language.

150 |

Unlike mainstream languages like Java, C++, C# or Python to name a few, languages such as Haskell or OCaml offer sum types, also known as algebraic data types or variant types. These allow to specify that an object is of one kind or another without ever performing dynamic casts.

151 |
(* Example of a sum type in ATD. The vertical bar reads `or'. *)
152 | type shape = [
153 |     Square of float               (* argument: side length *)
154 |   | Rectangle of (float * float)  (* argument: width and height *)
155 |   | Circle of float               (* argument: radius *)
156 |   | Dot                           (* no argument *)
157 | ]
158 |

A notable example of sum types is the predefined option type. An object of an option type contains either one value of a given type or nothing. We could define our own int_option type as follows:

159 |
type int_option = [ None | Some of int ]
160 |

ATD supports parametrized types also known as generics in Java or templates in C++. We could define our own generic option type as follows:

161 |
type 'a opt = [ None | Some of 'a ]
162 |   (* 'a denotes a type parameter. *)
163 | 
164 | type opt_int = int opt
165 |   (* equivalent to int_option defined in the previous example *)
166 | 
167 | type opt_string = string opt
168 |   (* same with string instead of int *)
169 |

In practice we shall use the predefined option type. The option type is fundamentally different from nullable objects since the latter don't allow values that would have type 'a option option.

170 |

ATD also support product types. They come in two forms: tuples and records:

171 |
type tuple_example = (string * int)
172 | 
173 | type record_example = {
174 |   name : string;
175 |   age : int;
176 | }
177 |

Although tuples in theory are not more expressive than records, they are much more concise and languages that support them natively usually do not require type definitions.

178 |

Finally, ATD supports multiple inheritance which is a simple mechanism for adding fields to records or variants to sum types:

179 |
type builtin_color = [
180 |     Red | Green | Blue | Yellow
181 |   | Purple | Black | White
182 | ]
183 | 
184 | type rgb = (float * float * float)
185 | type cmyk = (float * float * float * float)
186 | 
187 | (* Inheritance of variants *)
188 | type color = [
189 |     inherit builtin_color
190 |   | Rgb of rgb
191 |   | Cmyk of cmyk
192 | ]
193 |
type basic_profile = {
194 |   id : string;
195 |   name : string;
196 | }
197 | 
198 | (* Inheritance of record fields *)
199 | type full_profile = {
200 |   inherit basic_profile;
201 |   date_of_birth : (int * int * int) option;
202 |   street_address1 : string option;
203 |   street_address2 : string option;
204 |   city : string option;
205 |   zip_code : string option;
206 |   state : string option;
207 | }
208 |

Editing and validating ATD files

209 |

The extension for ATD files is .atd. Editing ATD files is best achieved using an OCaml-friendly editor since the ATD syntax is vastly compatible with OCaml and uses a subset of OCaml's keywords.

210 |

Emacs users can use caml-mode or tuareg-mode to edit ATD files. Adding the following line to the ~/.emacs file will automatically use tuareg-mode when opening a file with a .atd extension:

211 |
(add-to-list 'auto-mode-alist '("\\.atd\\'" . tuareg-mode))
212 |

The syntax of an ATD file can be checked with the program atdcat provided with the OCaml library atd. atdcat pretty-prints its input data, optionally after some transformations such as monomorphization or inheritance. Here is the output of atdcat -help:

213 |
Usage: atdcat FILE
214 |   -x 
215 |           make type expressions monomorphic
216 |   -xk 
217 |           keep parametrized type definitions and imply -x.
218 |           Default is to return only monomorphic type definitions
219 |   -xd 
220 |           debug mode implying -x
221 |   -i 
222 |           expand all `inherit' statements
223 |   -if 
224 |           expand `inherit' statements in records
225 |   -iv 
226 |           expand `inherit' statements in sum types
227 |   -ml <name>
228 |           output the ocaml code of the ATD abstract syntax tree
229 |   -html-doc 
230 |           replace directly <doc html="..."> by (*html ... *)
231 |           or replace <doc text="..."> by (*html ... *)
232 |           where the contents are formatted as HTML
233 |           using <p>, <code> and <pre>.
234 |           This is suitable input for "caml2html -ext html:cat"
235 |           which converts ATD files into HTML.
236 |   -strip NAME1[,NAME2,...]
237 |           remove all annotations of the form <NAME1 ...>,
238 |           <NAME2 ...>, etc.
239 |   -strip-all 
240 |           remove all annotations
241 |   -version 
242 |           print the version of atd and exit
243 |   -help  Display this list of options
244 |   --help  Display this list of options
245 |

ATD language

246 |

This is a precise description of the syntax of the ATD language, not a tutorial.

247 |

Notations

248 |

Lexical and grammatical rules are expressed using a BNF-like syntax. Graphical terminal symbols use unquoted strings in typewriter font. Non-graphical characters use their official uppercase ASCII name such as LF for the newline character or SPACE for the space character. Non-terminal symbols use the regular font and link to their definition. Parentheses are used for grouping.

249 |

The following postfix operators are used to specify repeats:

250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 |
x*0, 1 or more occurrences of x
x?0 or 1 occurrence of x
x+1 or more occurrences of x
266 |

Lexical rules

267 |

ATD does not enforce a particular character encoding other than ASCII compatibility. Non-ASCII text and data found in annotations and in comments may contain arbitrary bytes in the non-ASCII range 128-255 without escaping. The UTF-8 encoding is however strongly recommended for all text. The use of hexadecimal or decimal escape sequences is recommended for binary data.

268 |

An ATD lexer splits its input into a stream of tokens, discarding whitespace and comments.

269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 |
token ::=keyword
|lident
|uident
|tident
|string
ignorable ::=spacediscarded
|comment
space ::=SPACE | TAB | CR | LF
blank ::=SPACE | TAB
comment ::=(* (comment | string | byte)* *)
lident ::=(lower | _ identchar) identchar*lowercase identifier
uident ::=upper identchar*uppercase identifier
tident ::=' lidenttype parameter
lower ::=a...z
upper ::=A...Z
identchar ::=upper | lower | digit | _ | '
string ::=" substring* "string literal, used in annotations
substring ::=\\single backslash
|\"double quote
|\x hex hexsingle byte in hexadecimal notation
|\ digit digit digitsingle byte in decimal notation
|\nLF
|\rCR
|\tTAB
|\bBS
|\ CR? LF blank*discarded
|not-backslashany byte except \ or "
digit ::=0...9
hex ::=0...9 | a...f | A...F
keyword ::=( | ) | [ | ] | | \} | < | > & | ; | , | : | * | | | = | ? | ~ | type | of | inheritall keywords
428 |

Grammar

429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 |
module ::=annot* typedef*entry point
annot ::=< lident annot-field* >annotation
annot-field ::=(lident (= string)?)
typedef ::=type params? lident annot = exprtype definition
params ::=tidentone parameter
|( tident (, tident)+ )two or more parameters
expr ::=expr-body annot*type expression
|tident
expr-body ::=args? lident
|( (cell (* cell)*)? )tuple type
|{ ((field (; field)*) ;?)? }record type
|[ (|? variant (| variant)*)? ]sum type
args ::=exprone argument
|( expr (, expr)+ )two or more arguments
cell ::=(annot+ :)? expr
field ::=(? | ~)? lident = expr
|inherit expr
variant ::=uident annot* of expr
|uident annot*
|inherit expr
538 |

Predefined type names

539 |

The following types are considered predefined and may not be redefined.

540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 |
Type nameIntended use
unitType of just one value, useful with parametrized types
boolBoolean
intInteger
floatFloating-point number
stringSequence of bytes or characters
'a optionContainer of zero or one element of type 'a. See also 'a nullable.
'a listCollection or sequence of elements of type 'a
'a nullableExtend type 'a with an extra conventional value, typically called "null". The operation is idempotent, i.e. 'a nullable is equivalent to 'a nullable nullable.
'a sharedValues of type 'a for which sharing must be preserved
'a wrapValues on which a custom, reversible transformation may be applied, as specified by language-specific annotations.
abstractType defined elsewhere
598 |

Shared values

599 |

ATD supports a special type x shared where x can be any monomorphic type expression. It allows notably to represent cyclic values and to enforce that cycles are preserved during transformations such as serialization.

600 |
(* Example of a simple graph type *)
601 | type shared_node = node shared (* sharing point *)
602 | type graph = shared_node list
603 | type node = {
604 |   label : string;
605 |   neighbors : shared_node list;
606 | }
607 |

Two shared values that are physically identical must remain physically identical after any translation from one data format to another.

608 |

Each occurrence of a shared type expression in the ATD source definition defines its own sharing point. Therefore the following attempt at defining a graph type will not preserve cycles because two sharing points are defined:

609 |
(* Incorrect definition of a graph type *)
610 | type node = {
611 |   label : string;
612 |   neighbors : node shared (* sharing point 1 *) list;
613 | }
614 | 
615 | (* Second occurrence of "shared", won't preserve cycles! *)
616 | type graph = node shared (* sharing point 2 *) list
617 |

There is actually a way of having multiple shared type expressions using the same sharing point but this feature is designed for code generators and should not be used in handwritten ATD definitions. The technique consists in providing an annotation of the form <share id=x> where x is any string identifying the sharing point. The graph example can be rewritten correctly as:

618 |
type node = {
619 |   label : string;
620 |   neighbors : node shared <share id="1"> list;
621 | }
622 | 
623 | type graph = node shared <share id="1"> list
624 |

See also

625 |

atdgen(1)

626 | 627 | 628 | -------------------------------------------------------------------------------- /src/tutorial.md: -------------------------------------------------------------------------------- 1 | % atdgen tutorial 2 | 3 | [Home](https://mjambon.github.io/atdgen-doc/) 4 | 5 | What is atdgen? 6 | =============== 7 | 8 | Atdgen is a tool that derives OCaml boilerplate code from type definitions. 9 | Currently it provides support for: 10 | 11 | 12 | * [JSON](http://json.org/) serialization and deserialization. 13 | * [Biniou](http://mjambon.com/biniou-format.txt) 14 | serialization and deserialization. 15 | Biniou is a binary format extensible like JSON but more compact 16 | and faster to process. 17 | * Convenience functions for creating and validating OCaml data. 18 | 19 | 20 | What are the advantages of atdgen? 21 | ======== 22 | 23 | Atdgen has a number of advantages over its predecessor json-static 24 | which was based on Camlp4: 25 | 26 | * produces explicit interfaces which describe what is available to 27 | the user (`.mli` files). 28 | * produces readable OCaml code that can be easily reviewed 29 | (`.ml` files). 30 | * produces fast code, 3x faster than json-static. 31 | * runs fast, keeping build times low. 32 | * same ATD definitions can be used to generate code other than 33 | OCaml. See for instance 34 | [atdj](https://github.com/esperco/atdj) 35 | which generates Java classes for JSON IO. 36 | Auto-generating GUI widgets from type definitions is another 37 | popular use of annotated type definitions. The implementation of 38 | such code generators is facilitated by the 39 | [`atd`](https://github.com/mjambon/atd) library. 40 | 41 | 42 | 43 | Prerequisites 44 | ======== 45 | 46 | This tutorial assumes that you are using atdgen version 1.5.0 or above. 47 | The following command tells you which version you are using: 48 | 49 | ``` 50 | $ atdgen -version 51 | 1.5.0 52 | ``` 53 | 54 | The recommended way of installing atdgen and all its dependencies is with 55 | [opam](http://opam.ocamlpro.com/): 56 | 57 | ``` 58 | $ opam install atdgen 59 | ``` 60 | 61 | 62 | Getting started 63 | ======== 64 | 65 | From now on we assume that atdgen 1.5.0 or above is installed properly. 66 | 67 | ``` 68 | $ atdgen -version 69 | 1.5.0 70 | ``` 71 | 72 | Type definitions are placed in a `.atd` file (`hello.atd`): 73 | 74 | ```ocaml 75 | type date = { 76 | year : int; 77 | month : int; 78 | day : int; 79 | } 80 | ``` 81 | 82 | Our handwritten OCaml program is `hello.ml`: 83 | 84 | ```ocaml 85 | open Hello_t 86 | let () = 87 | let date = { year = 1970; month = 1; day = 1 } in 88 | print_endline (Hello_j.string_of_date date) 89 | ``` 90 | 91 | We produce OCaml code from the type definitions using `atdgen`: 92 | 93 | ``` 94 | $ atdgen -t hello.atd # produces OCaml type definitions 95 | $ atdgen -j hello.atd # produces OCaml code dealing with JSON 96 | ``` 97 | 98 | We now have `_t` and `_j` files produced by `atdgen -t` and `atdgen -j` 99 | respectively: 100 | 101 | ``` 102 | $ ls 103 | hello.atd hello.ml hello_j.ml hello_j.mli hello_t.ml hello_t.mli 104 | ``` 105 | 106 | We compile all `.mli` and `.ml` files: 107 | 108 | ``` 109 | $ ocamlfind ocamlc -c hello_t.mli -package atdgen 110 | $ ocamlfind ocamlc -c hello_j.mli -package atdgen 111 | $ ocamlfind ocamlopt -c hello_t.ml -package atdgen 112 | $ ocamlfind ocamlopt -c hello_j.ml -package atdgen 113 | $ ocamlfind ocamlopt -c hello.ml -package atdgen 114 | $ ocamlfind ocamlopt -o hello hello_t.cmx hello_j.cmx hello.cmx \ 115 | -package atdgen -linkpkg 116 | ``` 117 | 118 | And finally we run our `hello` program: 119 | 120 | ``` 121 | $ ./hello 122 | {"year":1970,"month":1,"day":1} 123 | ``` 124 | 125 | [Source code for this section](https://github.com/mjambon/atdgen-doc/src/tutorial-data/hello) 126 | 127 | Inspecting and pretty-printing JSON 128 | ======== 129 | 130 | Input JSON data: 131 | 132 | ``` 133 | $ cat single.json 134 | [1234,"abcde",{"start_date":{"year":1970,"month":1,"day":1}, 135 | "end_date":{"year":1980,"month":1,"day":1}}] 136 | ``` 137 | 138 | Pretty-printed JSON can be produced with the `ydump` command: 139 | 140 | ``` 141 | $ ydump single.json 142 | [ 143 | 1234, 144 | "abcde", 145 | { 146 | "start_date": { "year": 1970, "month": 1, "day": 1 }, 147 | "end_date": { "year": 1980, "month": 1, "day": 1 } 148 | } 149 | ] 150 | ``` 151 | 152 | Multiple JSON objects separated by whitespace, typically one JSON object 153 | per line, can also be pretty-printed with `ydump`. Input: 154 | 155 | ``` 156 | $ cat stream.json 157 | [1234,"abcde",{"start_date":{"year":1970,"month":1,"day":1}, 158 | "end_date":{"year":1980,"month":1,"day":1}}] 159 | [1,"a",{}] 160 | ``` 161 | 162 | In this case the `-s` option is required: 163 | 164 | ``` 165 | $ ydump -s stream.json 166 | [ 167 | 1234, 168 | "abcde", 169 | { 170 | "start_date": { "year": 1970, "month": 1, "day": 1 }, 171 | "end_date": { "year": 1980, "month": 1, "day": 1 } 172 | } 173 | ] 174 | [ 1, "a", {} ] 175 | ``` 176 | 177 | From an OCaml program, pretty-printing can be done with 178 | `Yojson.Safe.prettify` 179 | which has the following signature: 180 | 181 | ```ocaml 182 | val prettify : string -> string 183 | ``` 184 | 185 | We wrote a tiny program that simply calls the `prettify` function on 186 | some predefined JSON data (file `prettify.ml`): 187 | 188 | ```ocaml 189 | let json = 190 | "[1234,\"abcde\",{\"start_date\":{\"year\":1970,\"month\":1,\"day\":1}, 191 | \"end_date\":{\"year\":1980,\"month\":1,\"day\":1}}]" 192 | 193 | let () = print_endline (Yojson.Safe.prettify json) 194 | ``` 195 | 196 | We now compile and run prettify.ml: 197 | 198 | ``` 199 | $ ocamlfind ocamlopt -o prettify prettify.ml -package atdgen -linkpkg 200 | $ ./prettify 201 | [ 202 | 1234, 203 | "abcde", 204 | { 205 | "start_date": { "year": 1970, "month": 1, "day": 1 }, 206 | "end_date": { "year": 1980, "month": 1, "day": 1 } 207 | } 208 | ] 209 | ``` 210 | 211 | [Source code for this section](https://github.com/mjambon/atdgen-doc/src/tutorial-data/pretty-json) 212 | 213 | 214 | 215 | Inspecting biniou data 216 | ======== 217 | 218 | Biniou is a binary format that can be displayed as text using a generic 219 | command called `bdump`. The only practical difficulty is to recover 220 | the original field names and variant names which are stored as 31-bit hashes. 221 | Unhashing them is done by consulting a dictionary (list of words) 222 | maintained by the user. 223 | 224 | Let's first produce a sample data file `tree.dat` containing the 225 | biniou representation of a binary tree. In the same program 226 | we will also demonstrate how to render biniou data into text from an 227 | OCaml program. 228 | 229 | Here is the ATD file defining our tree type (file `tree.atd`): 230 | 231 | ```ocaml 232 | type tree = [ 233 | | Empty 234 | | Node of (tree * int * tree) 235 | ] 236 | ``` 237 | 238 | This is our OCaml program (file `tree.ml`): 239 | 240 | ```ocaml 241 | open Printf 242 | 243 | (* sample value *) 244 | let tree : Tree_t.tree = 245 | `Node ( 246 | `Node (`Empty, 1, `Empty), 247 | 2, 248 | `Node ( 249 | `Node (`Empty, 3, `Empty), 250 | 4, 251 | `Node (`Empty, 5, `Empty) 252 | ) 253 | ) 254 | 255 | let () = 256 | (* write sample value to file *) 257 | let fname = "tree.dat" in 258 | Ag_util.Biniou.to_file Tree_b.write_tree fname tree; 259 | 260 | (* write sample value to string *) 261 | let s = Tree_b.string_of_tree tree in 262 | printf "raw value (saved as %s):\n%S\n" fname s; 263 | printf "length: %i\n" (String.length s); 264 | 265 | printf "pretty-printed value (without dictionary):\n"; 266 | print_endline (Bi_io.view s); 267 | 268 | printf "pretty-printed value (with dictionary):\n"; 269 | let unhash = Bi_io.make_unhash ["Empty"; "Node"; "foo"; "bar" ] in 270 | print_endline (Bi_io.view ~unhash s) 271 | ``` 272 | 273 | Compilation: 274 | 275 | ``` 276 | $ atdgen -t tree.atd 277 | $ atdgen -b tree.atd 278 | $ ocamlfind ocamlopt -o tree \ 279 | tree_t.mli tree_t.ml tree_b.mli tree_b.ml tree.ml \ 280 | -package atdgen -linkpkg 281 | ``` 282 | 283 | Running the program: 284 | 285 | ``` 286 | $ ./tree 287 | raw value (saved as tree.dat): 288 | "\023\179\2276\"\020\003\023\179\2276\"\020\003\023\003\007\170m\017\002\023\003\007\170m\017\004\023\179\2276\"\020\003\023\179\2276\"\020\003\023\003\007\170m\017\006\023\003\007\170m\017\b\023\179\2276\"\020\003\023\003\007\170m\017\n\023\003\007\170m" 289 | length: 75 290 | pretty-printed value (without dictionary): 291 | <#33e33622: 292 | (<#33e33622: (<#0307aa6d>, 1, <#0307aa6d>)>, 293 | 2, 294 | <#33e33622: 295 | (<#33e33622: (<#0307aa6d>, 3, <#0307aa6d>)>, 296 | 4, 297 | <#33e33622: (<#0307aa6d>, 5, <#0307aa6d>)>)>)> 298 | pretty-printed value (with dictionary): 299 | <"Node": 300 | (<"Node": (<"Empty">, 1, <"Empty">)>, 301 | 2, 302 | <"Node": 303 | (<"Node": (<"Empty">, 3, <"Empty">)>, 304 | 4, 305 | <"Node": (<"Empty">, 5, <"Empty">)>)>)> 306 | ``` 307 | 308 | Now let's see how to pretty-print any biniou data from the command line. 309 | Our sample data are now in file `tree.dat`: 310 | 311 | ``` 312 | $ ls -l tree.dat 313 | -rw-r--r-- 1 martin martin 75 Apr 17 01:46 tree.dat 314 | ``` 315 | 316 | We use the command `bdump` to render our sample biniou data as text: 317 | 318 | ``` 319 | $ bdump tree.dat 320 | <#33e33622: 321 | (<#33e33622: (<#0307aa6d>, 1, <#0307aa6d>)>, 322 | 2, 323 | <#33e33622: 324 | (<#33e33622: (<#0307aa6d>, 3, <#0307aa6d>)>, 325 | 4, 326 | <#33e33622: (<#0307aa6d>, 5, <#0307aa6d>)>)>)> 327 | ``` 328 | 329 | We got hashes for the variant names `Empty` and `Node`. 330 | Let's add them to the dictionary: 331 | 332 | ``` 333 | $ bdump -w Empty,Node tree.dat 334 | <"Node": 335 | (<"Node": (<"Empty">, 1, <"Empty">)>, 336 | 2, 337 | <"Node": 338 | (<"Node": (<"Empty">, 3, <"Empty">)>, 339 | 4, 340 | <"Node": (<"Empty">, 5, <"Empty">)>)>)> 341 | ``` 342 | 343 | `bdump` remembers the dictionary so we don't have to pass the 344 | `-w` option anymore (for this user on this machine). 345 | The following now works: 346 | 347 | ``` 348 | $ bdump tree.dat 349 | <"Node": 350 | (<"Node": (<"Empty">, 1, <"Empty">)>, 351 | 2, 352 | <"Node": 353 | (<"Node": (<"Empty">, 3, <"Empty">)>, 354 | 4, 355 | <"Node": (<"Empty">, 5, <"Empty">)>)>)> 356 | ``` 357 | 358 | [Source code for this section](https://github.com/mjambon/atdgen-doc/src/tutorial-data/inspect-biniou) 359 | 360 | Optional fields and default values 361 | ======== 362 | 363 | Although OCaml records do not support optional fields, both the JSON 364 | and biniou formats make it possible to omit certain fields on a 365 | per-record basis. 366 | 367 | For example the JSON record `{ "x": 0, "y": 0 }` can be more 368 | compactly written as `{}` if the reader knows the default values for 369 | the missing fields `x` and `y`. Here is the corresponding type 370 | definition: 371 | 372 | ```ocaml 373 | type vector_v1 = { ~x: int; ~y: int } 374 | ``` 375 | 376 | `~x` means that field `x` supports a default 377 | value. Since we do not 378 | specify the default value ourselves, the built-in default is used, 379 | which is 0. 380 | 381 | If we want the default to be something else than 0, we just have to 382 | specify it as follows: 383 | 384 | ```ocaml 385 | type vector_v2 = { 386 | ~x : int; (* default x is 1 *) 387 | ~y: int; (* default y is 0 *) 388 | } 389 | ``` 390 | 391 | It is also possible to specify optional fields without a default 392 | value. For example, let's add an optional `z` field: 393 | 394 | ```ocaml 395 | type vector_v3 = { 396 | ~x: int; 397 | ~y: int; 398 | ?z: int option; 399 | } 400 | ``` 401 | 402 | The following two examples are valid JSON representations of data of 403 | type `vector_v3`: 404 | 405 | ``` 406 | { "x": 2, "y": 2, "z": 3 } // OCaml: { x = 2; y = 2; z = Some 3 } 407 | ``` 408 | 409 | ``` 410 | { "x": 2, "y": 2 } // OCaml: { x = 2; y = 2; z = None } 411 | ``` 412 | 413 | For a variety of good reasons JSON's `null` value may not be used to 414 | indicate that a field is undefined. 415 | Therefore the following JSON data cannot be read as a record of type 416 | `vector_v3`: 417 | 418 | ``` 419 | { "x": 2, "y": 2, "z": null } // invalid value for field z 420 | ``` 421 | 422 | 423 | Note also the difference between `?z: int option` and 424 | `~z: int option`: 425 | 426 | ```ocaml 427 | type vector_v4 = { 428 | ~x: int; 429 | ~y: int; 430 | ~z: int option; (* no unwrapping of the JSON field value! *) 431 | } 432 | ``` 433 | 434 | Here are valid values of type `vector_v4`, showing that it is usually 435 | not what is intended: 436 | 437 | ``` 438 | { "x": 2, "y": 2, "z": [ "Some", 3 ] } 439 | ``` 440 | 441 | ``` 442 | { "x": 2, "y": 2, "z": "None" } 443 | ``` 444 | 445 | ``` 446 | { "x": 2, "y": 2 } 447 | ``` 448 | 449 | 450 | Smooth protocol upgrades 451 | ======== 452 | 453 | Problem: you have a production system that uses a specific 454 | JSON or biniou format. It may be data files or a client-server 455 | pair. You now want to add a field to a record type or to add a case to 456 | a variant type. 457 | 458 | Both JSON and biniou allow extra record fields. If the 459 | consumer does not know how to deal with the extra field, the default 460 | behavior is to happily ignore it. 461 | 462 | 463 | Adding or removing an optional record field 464 | --------- 465 | 466 | ```ocaml 467 | type t = { 468 | x: int; 469 | y: int; 470 | } 471 | ``` 472 | 473 | Same `.atd` source file, edited: 474 | 475 | ```ocaml 476 | type t = { 477 | x: int; 478 | y: int; 479 | ~z: int; (* new field *) 480 | } 481 | ``` 482 | 483 | 484 | * Upgrade producers and consumers in any order 485 | * Converting old data is not required nor useful 486 | 487 | 488 | Adding a required record field 489 | --------- 490 | 491 | ```ocaml 492 | type t = { 493 | x: int; 494 | y: int; 495 | } 496 | ``` 497 | 498 | Same `.atd` source file, edited: 499 | 500 | ```ocaml 501 | type t = { 502 | x: int; 503 | y: int; 504 | z: int; (* new field *) 505 | } 506 | ``` 507 | 508 | 509 | * Upgrade all producers before the consumers 510 | * Converting old data requires special-purpose hand-written code 511 | 512 | 513 | Removing a required record field 514 | --------- 515 | 516 | 517 | * Upgrade all consumers before the producers 518 | * Converting old data is not required but may save some storage space 519 | (just read and re-write each record using the new type) 520 | 521 | 522 | Adding a variant case 523 | --------- 524 | 525 | ```ocaml 526 | type t = [ A | B ] 527 | ``` 528 | 529 | Same `.atd` source file, edited: 530 | 531 | ```ocaml 532 | type t = [ A | B | C ] 533 | ``` 534 | 535 | 536 | * Upgrade all consumers before the producers 537 | * Converting old data is not required and would have no effect 538 | 539 | 540 | Removing a variant case 541 | --------- 542 | 543 | 544 | * Upgrade all producers before the consumers 545 | * Converting old data requires special-purpose hand-written code 546 | 547 | 548 | Avoiding future problems 549 | --------- 550 | 551 | 552 | * In doubt, use records rather than tuples because it makes it 553 | possible to add or remove any field or to reorder them. 554 | * Do not hesitate to create variant types with only one case or 555 | records with only one field if you think they might be extended 556 | later. 557 | 558 | 559 | 560 | Data validation 561 | ======== 562 | 563 | Atdgen can be used to produce data validators for all types defined 564 | in an ATD file, 565 | based on user-given validators specified only for certain types. 566 | A simple example is: 567 | 568 | ```ocaml 569 | type t = string option 570 | ``` 571 | 572 | `atdgen -v` will produce something equivalent to the following 573 | implementation: 574 | 575 | ```ocaml 576 | let validate_t x = 577 | match x with 578 | None -> true 579 | | Some x -> (fun s -> String.length s >= 8) x 580 | ``` 581 | 582 | Let's now consider a more realistic example with complex validators defined 583 | in a separate `.ml` file. We created the following 3 source files: 584 | 585 | 586 | * `resume.atd`: contains the type definitions with annotations 587 | * `resume_util.ml`: contains our handwritten validators 588 | * `resume.ml`: is our main program that creates data and 589 | calls the validators 590 | 591 | 592 | In terms of OCaml modules we have: 593 | 594 | 595 | * `Resume_t`: produced by `atdgen -t resume.atd`, 596 | provides OCaml type definitions 597 | * `Resume_util`: depends on `Resume_t`, provides 598 | validators mentioned in `resume.atd` 599 | * `Resume_v`: produced by `atdgen -v resume.atd`, 600 | depends on `Resume_util`, provides a validator for each type 601 | * `Resume`: depends on `Resume_v`, uses the validators 602 | 603 | 604 | Type definitions are placed in `resume.atd`: 605 | 606 | ```ocaml 607 | type text = string 608 | 609 | type date = { 610 | year : int; 611 | month : int; 612 | day : int; 613 | } 614 | 615 | type job = { 616 | company : text; 617 | title : text; 618 | start_date : date; 619 | ?end_date : date option; 620 | } 621 | 622 | type work_experience = job list 623 | ``` 624 | 625 | `resume_util.ml` contains our handwritten validators: 626 | 627 | ```ocaml 628 | open Resume_t 629 | 630 | let ascii_printable c = 631 | let n = Char.code c in 632 | n >= 32 && n <= 127 633 | 634 | (* 635 | Check that string is not empty and contains only ASCII printable 636 | characters (for the sake of the example; we use UTF-8 these days) 637 | *) 638 | let validate_some_text s = 639 | s <> "" && 640 | try 641 | String.iter (fun c -> if not (ascii_printable c) then raise Exit) s; 642 | true 643 | with Exit -> 644 | false 645 | 646 | (* 647 | Check that the combination of year, month and day exists in the 648 | Gregorian calendar. 649 | *) 650 | let validate_date x = 651 | let y = x.year in 652 | let m = x.month in 653 | let d = x.day in 654 | m >= 1 && m <= 12 && d >= 1 && 655 | (let dmax = 656 | match m with 657 | 2 -> 658 | if y mod 4 = 0 && not (y mod 100 = 0) || y mod 400 = 0 then 29 659 | else 28 660 | | 1 | 3 | 5 | 7 | 8 | 10 | 12 -> 31 661 | | _ -> 30 662 | in 663 | d <= dmax) 664 | 665 | (* Compare dates chronologically *) 666 | let compare_date a b = 667 | let c = compare a.year b.year in 668 | if c <> 0 then c 669 | else 670 | let c = compare a.month b.month in 671 | if c <> 0 then c 672 | else compare a.day b.day 673 | 674 | (* Check that the end_date, when defined, is not earlier than the start_date *) 675 | let validate_job x = 676 | match x.end_date with 677 | None -> true 678 | | Some end_date -> 679 | compare_date x.start_date end_date <= 0 680 | ``` 681 | 682 | `resume.ml` uses the `validate_work_experience` 683 | function provided by the `Resume_v` module: 684 | 685 | ```ocaml 686 | let check_experience x = 687 | let is_valid = Resume_v.validate_work_experience x in 688 | Printf.printf "%s:\n%s\n" 689 | (if is_valid then "VALID" else "INVALID") 690 | (Yojson.Safe.prettify (Resume_j.string_of_work_experience x)) 691 | 692 | let () = 693 | (* one valid date *) 694 | let valid = { Resume_t.year = 2000; month = 2; day = 29 } in 695 | (* one invalid date *) 696 | let invalid = { Resume_t.year = 1900; month = 0; day = 0 } in 697 | (* two more valid dates, created with Resume_v.create_date *) 698 | let date1 = { Resume_t.year = 2005; month = 8; day = 1 } in 699 | let date2 = { Resume_t.year = 2006; month = 3; day = 22 } in 700 | 701 | let job = { 702 | Resume_t.company = "Acme Corp."; 703 | title = "Tester"; 704 | start_date = date1; 705 | end_date = Some date2; 706 | } 707 | in 708 | let valid_job = { job with Resume_t.start_date = valid } in 709 | let invalid_job = { job with Resume_t.end_date = Some invalid } in 710 | let valid_experience = [ job; valid_job ] in 711 | let invalid_experience = [ job; invalid_job ] in 712 | check_experience valid_experience; 713 | check_experience invalid_experience 714 | ``` 715 | 716 | Output: 717 | 718 | ``` 719 | VALID: 720 | [ 721 | { 722 | "company": "Acme Corp.", 723 | "title": "Tester", 724 | "start_date": { "year": 2005, "month": 8, "day": 1 }, 725 | "end_date": { "year": 2006, "month": 3, "day": 22 } 726 | }, 727 | { 728 | "company": "Acme Corp.", 729 | "title": "Tester", 730 | "start_date": { "year": 2000, "month": 2, "day": 29 }, 731 | "end_date": { "year": 2006, "month": 3, "day": 22 } 732 | } 733 | ] 734 | INVALID: 735 | [ 736 | { 737 | "company": "Acme Corp.", 738 | "title": "Tester", 739 | "start_date": { "year": 2005, "month": 8, "day": 1 }, 740 | "end_date": { "year": 2006, "month": 3, "day": 22 } 741 | }, 742 | { 743 | "company": "Acme Corp.", 744 | "title": "Tester", 745 | "start_date": { "year": 2005, "month": 8, "day": 1 }, 746 | "end_date": { "year": 1900, "month": 0, "day": 0 } 747 | } 748 | ] 749 | ``` 750 | 751 | [Source code for this section](https://github.com/mjambon/atdgen-doc/src/tutorial-data/validate) 752 | 753 | 754 | 755 | Modularity: referring to type definitions from another ATD file 756 | ======== 757 | 758 | It is possible to define types that depend on types 759 | defined in other `.atd` files. 760 | The example below is self-explanatory. 761 | 762 | `part1.atd`: 763 | 764 | ```ocaml 765 | type t = { x : int; y : int } 766 | ``` 767 | 768 | `part2.atd`: 769 | 770 | ```ocaml 771 | type t1 = abstract 772 | (* 773 | Imports type t defined in file part1.atd. 774 | The local name is t1. Because the local name (t1) is different from the 775 | original name (t), we must specify the original name using t=. 776 | *) 777 | 778 | type t2 = t1 list 779 | ``` 780 | 781 | `part3.atd`: 782 | 783 | ```ocaml 784 | type t2 = abstract 785 | 786 | type t3 = { 787 | name : string; 788 | ?data : t2 option; 789 | } 790 | ``` 791 | 792 | `main.ml`: 793 | 794 | ```ocaml 795 | let v = { 796 | Part3_t.name = "foo"; 797 | data = Some [ 798 | { Part1_t.x = 1; y = 2 }; 799 | { Part1_t.x = 3; y = 4 }; 800 | ] 801 | } 802 | 803 | let () = 804 | Ag_util.Json.to_channel Part3_j.write_t3 stdout v; 805 | print_newline () 806 | ``` 807 | 808 | Output: 809 | 810 | ``` 811 | {"name":"foo","data":[{"x":1,"y":2},{"x":3,"y":4}]} 812 | ``` 813 | 814 | [Source code for this section](https://github.com/mjambon/atdgen-doc/src/tutorial-data/modularity) 815 | 816 | 817 | Managing JSON configuration files 818 | ======== 819 | 820 | JSON makes a good format for configuration files because it is 821 | human-readable, easy to modify programmatically and widespread. 822 | Here is an example of how to use atdgen to manage config files. 823 | 824 | 825 | * **Specifying defaults** is done in the .atd file. See 826 | section [Optional fields and default values] for details on how to do that. 827 | * **Auto-generating a template config file with default values**: 828 | a sample value in the OCaml world needs to be created but only 829 | fields without default need to be specified. 830 | * **Describing the format** is achieved by embedding the .atd 831 | type definitions in the OCaml program and printing it out on request. 832 | * **Loading a config file and reporting illegal fields** is 833 | achieved using the JSON deserializers produced by `atdgen 834 | -j`. Option `-j-strict-fields` ensures the misspelled field 835 | names are not ignored but reported as errors. 836 | * **Reindenting a config file** is achieved by the 837 | pretty-printing function `Yojson.Safe.prettify` that takes a 838 | JSON string and returns an equivalent JSON string. 839 | * **Showing implicit (default) settings** is achieved by 840 | passing the `-j-defaults` option to `atdgen`. 841 | The OCaml config data is then serialized into JSON containing all 842 | fields, including those whose value is the default. 843 | 844 | 845 | The example uses the following type definitions: 846 | 847 | ```ocaml 848 | type config = { 849 | title : string; 850 | ?description : string option; 851 | ~timeout : int; 852 | ~credentials : param list 853 | ; 855 | } 856 | 857 | type param = { 858 | name : string 859 | ; 860 | key : string 861 | ; 862 | } 863 | ``` 864 | 865 | Our program will perform the following actions: 866 | 867 | ``` 868 | $ ./config -template 869 | { 870 | "title": "", 871 | "timeout": 10, 872 | "credentials": [ { "name": "foo", "key": "0123456789abcdef" } ] 873 | } 874 | 875 | $ ./config -format 876 | type config = { 877 | title : string; 878 | ?description : string option; 879 | ~timeout : int; 880 | ~credentials : param list 881 | ; 883 | } 884 | 885 | type param = { 886 | name : string 887 | ; 888 | key : string 889 | ; 890 | } 891 | 892 | $ cat sample-config.json 893 | { 894 | "title": "Example", 895 | "credentials": [ 896 | { 897 | "name": "joeuser", 898 | "key": "db7c0877bdef3016" 899 | }, 900 | { 901 | "name": "tester", 902 | "key": "09871ff387ac2b10" 903 | } 904 | ] 905 | } 906 | 907 | $ ./config -validate sample-config.json 908 | { 909 | "title": "Example", 910 | "timeout": 10, 911 | "credentials": [ 912 | { "name": "joeuser", "key": "db7c0877bdef3016" }, 913 | { "name": "tester", "key": "09871ff387ac2b10" } 914 | ] 915 | } 916 | ``` 917 | 918 | This is our `demo.sh` script that builds and runs our example 919 | program called `config`: 920 | 921 | ``` 922 | #! /bin/sh -e 923 | 924 | set -x 925 | 926 | # Embed the contents of the .atd file into our OCaml program 927 | echo 'let contents = "\' > config_atd.ml 928 | sed -e 's/\([\\"]\)/\\\1/g' config.atd >> config_atd.ml 929 | echo '"' >> config_atd.ml 930 | 931 | # Derive OCaml type definitions from .atd file 932 | atdgen -t config.atd 933 | 934 | # Derive JSON-related functions from .atd file 935 | atdgen -j -j-defaults -j-strict-fields config.atd 936 | 937 | # Derive validator from .atd file 938 | atdgen -v config.atd 939 | 940 | # Compile the OCaml program 941 | ocamlfind ocamlopt -o config \ 942 | config_t.mli config_t.ml config_j.mli config_j.ml config_v.mli config_v.ml \ 943 | config_atd.ml config.ml -package atdgen -linkpkg 944 | 945 | # Output a sample config 946 | ./config -template 947 | 948 | # Print the original type definitions 949 | ./config -format 950 | 951 | # Fail to validate an invalid config file 952 | ./config -validate bad-config1.json || : 953 | 954 | # Fail to validate another invalid config file (using custom validators) 955 | ./config -validate bad-config3.json || : 956 | 957 | # Validate, inject missing defaults and pretty-print 958 | ./config -validate sample-config.json 959 | ``` 960 | 961 | This is the hand-written OCaml program. It can be used as a start 962 | point for a real-world program using a JSON config file: 963 | 964 | ```ocaml 965 | open Printf 966 | 967 | let param_template = 968 | (* Sample item used to populate the template config file *) 969 | { 970 | Config_v.name = "foo"; 971 | key = "0123456789abcdef" 972 | } 973 | 974 | let config_template = 975 | (* 976 | Records can be conveniently created using functions generated by 977 | "atdgen -v". 978 | Here we use Config_v.create_config to create a record of type 979 | Config_t.config. The big advantage over creating the record 980 | directly using the record notation {...} is that we don't have to 981 | specify default values (such as timeout in this example). 982 | *) 983 | Config_v.create_config ~title:"" ~credentials: [param_template] () 984 | 985 | let make_json_template () = 986 | (* Thanks to the -j-defaults flag passed to atdgen, even default 987 | fields will be printed out *) 988 | let compact_json = Config_j.string_of_config config_template in 989 | Yojson.Safe.prettify compact_json 990 | 991 | let print_template () = 992 | print_endline (make_json_template ()) 993 | 994 | let print_format () = 995 | print_string Config_atd.contents 996 | 997 | let validate fname = 998 | let x = 999 | try 1000 | (* Read config data structure from JSON file *) 1001 | let x = Ag_util.Json.from_file Config_j.read_config fname in 1002 | (* Call the validators specified by *) 1003 | if not (Config_v.validate_config x) then 1004 | failwith "Some fields are invalid" 1005 | else 1006 | x 1007 | with e -> 1008 | (* Print decent error message and exit *) 1009 | let msg = 1010 | match e with 1011 | Failure s 1012 | | Yojson.Json_error s -> s 1013 | | e -> Printexc.to_string e 1014 | in 1015 | eprintf "Error: %s\n%!" msg; 1016 | exit 1 1017 | in 1018 | (* Convert config to compact JSON and pretty-print it. 1019 | ~std:true means that the output will not use extended syntax for 1020 | variants and tuples but only standard JSON. *) 1021 | let json = Yojson.Safe.prettify ~std:true (Config_j.string_of_config x) in 1022 | print_endline json 1023 | 1024 | type action = Template | Format | Validate of string 1025 | 1026 | let main () = 1027 | let action = ref Template in 1028 | let options = [ 1029 | "-template", Arg.Unit (fun () -> action := Template), 1030 | " 1031 | prints a sample configuration file"; 1032 | 1033 | "-format", Arg.Unit (fun () -> action := Format), 1034 | " 1035 | prints the format specification of the config files (atd format)"; 1036 | 1037 | "-validate", Arg.String (fun s -> action := Validate s), 1038 | " 1039 | reads a config file, validates it, adds default values 1040 | and prints the config nicely to stdout"; 1041 | ] 1042 | in 1043 | let usage_msg = sprintf "\ 1044 | Usage: %s [-template|-format|-validate ...] 1045 | Demonstration of how to manage JSON configuration files with atdgen. 1046 | " 1047 | Sys.argv.(0) 1048 | in 1049 | let anon_fun s = eprintf "Invalid command parameter %S\n%!" s; exit 1 in 1050 | Arg.parse options anon_fun usage_msg; 1051 | 1052 | match !action with 1053 | Template -> print_template () 1054 | | Format -> print_format () 1055 | | Validate s -> validate s 1056 | 1057 | let () = main () 1058 | ``` 1059 | 1060 | The full source code for this section with examples can be inspected 1061 | and [downloaded here](https://github.com/mjambon/atdgen-doc/src/tutorial-data/config-file). 1062 | 1063 | 1064 | Integration with ocamldoc 1065 | ======== 1066 | 1067 | Ocamldoc is a tool that comes with the core OCaml distribution. 1068 | It uses comments within `(**` and `*)` to produce 1069 | hyperlinked documentation (HTML) of module signatures. 1070 | 1071 | Atdgen can produce `.mli` files with comments in the syntax supported by 1072 | ocamldoc but regular ATD comments within `(*` and `*)` 1073 | are always discarded 1074 | by atdgen. Instead, `` must be used and placed after the 1075 | element they describe. The contents of the text field must be UTF8-encoded. 1076 | 1077 | ```ocaml 1078 | type point = { 1079 | x : float; 1080 | y : float; 1081 | ~z 1082 | 1083 | : float; 1084 | } 1085 | 1093 | ``` 1094 | 1095 | is converted into the following `.mli` file with 1096 | ocamldoc-compatible comments: 1097 | 1098 | ```ocaml 1099 | (** 1100 | Point with optional 3rd dimension. 1101 | 1102 | OCaml example: 1103 | 1104 | {v 1105 | let p = 1106 | \{ x = 0.5; y = 1.0; z = 0. \} 1107 | v} 1108 | *) 1109 | type point = { 1110 | x: float; 1111 | y: float; 1112 | z: float (** Optional depth, its default value is [0.0]. *) 1113 | } 1114 | ``` 1115 | 1116 | The only two forms of markup supported by `` 1117 | are `{{` ... `}}` 1118 | for inline code and `{{{` ... `}}}` for a block of 1119 | preformatted code. 1120 | 1121 | 1122 | Integration with build systems 1123 | ======== 1124 | 1125 | ### OMake ### 1126 | 1127 | We provide an 1128 | [Atdgen plugin](https://github.com/mjambon/atdgen-omake) 1129 | for [OMake](http://omake.metaprl.org). 1130 | It simplifies the compilation rules to a minimum. 1131 | 1132 | The plugin consists of a self-documented file to copy into a project's 1133 | root. The following is a sample `OMakefile` for a project using JSON and 1134 | five source files (`foo.atd`, `foo.ml`, 1135 | `bar.atd`, `bar.ml` and `main.ml`): 1136 | 1137 | ``` 1138 | include Atdgen 1139 | # requires file Atdgen.om 1140 | 1141 | OCAMLFILES = foo_t foo_j foo bar_t bar_j bar main 1142 | # correspond to the OCaml modules we want to build 1143 | 1144 | Atdgen(foo bar, -j-std) 1145 | OCamlProgram(foobar, $(OCAMLFILES)) 1146 | 1147 | .DEFAULT: foobar.opt 1148 | 1149 | .PHONY: clean 1150 | clean: 1151 | rm -f *.cm[ioxa] *.cmx[as] *.[oa] *.opt *.run *~ 1152 | rm -f $(ATDGEN_OUTFILES) 1153 | ``` 1154 | 1155 | Running `omake` builds the native code executable 1156 | `foobar.opt`. 1157 | 1158 | `omake clean` removes all the products 1159 | of compilation including the `.mli` and `.ml` produced 1160 | by `atdgen`. 1161 | 1162 | 1163 | ### GNU Make ### 1164 | 1165 | We provide 1166 | [`Atdgen.mk`](https://github.com/mjambon/atdgen-make), 1167 | a generic makefile that defines the 1168 | dependencies and rules for generating OCaml `.mli` and 1169 | `.ml` files from `.atd` files containing type 1170 | definitions. The `Atdgen.mk` file contains its own documentation. 1171 | 1172 | Here is a sample `Makefile` that takes advantage of 1173 | [`OCamlMakefile`](http://mmottl.github.io/ocaml-makefile/): 1174 | 1175 | ``` 1176 | .PHONY: default 1177 | default: opt 1178 | 1179 | ATDGEN_SOURCES = foo.atd bar.atd 1180 | ATDGEN_FLAGS = -j-std 1181 | include Atdgen.mk 1182 | 1183 | SOURCES = \ 1184 | foo_t.mli foo_t.ml foo_j.mli foo_j.ml \ 1185 | bar_t.mli bar_t.ml bar_j.mli bar_j.ml \ 1186 | hello.ml 1187 | RESULT = hello 1188 | PACKS = atdgen 1189 | # "include OCamlMakefile" must come after defs for SOURCES, RESULT, PACKS, etc. 1190 | include OCamlMakefile 1191 | 1192 | .PHONY: sources opt all 1193 | sources: $(SOURCES) 1194 | opt: sources 1195 | $(MAKE) native-code 1196 | all: sources 1197 | $(MAKE) byte-code 1198 | ``` 1199 | 1200 | `make` alone builds a native code executable from source files 1201 | `foo.atd`, `bar.atd` and `hello.ml`. 1202 | `make clean` removes generated files. `make all` builds 1203 | a bytecode executable. 1204 | In addition to `native-code`, `byte-code` and 1205 | `clean`, `OCamlMakefile` provides a number of other targets 1206 | and options which are documented in `OCamlMakefile`'s README. 1207 | 1208 | ### Ocamlbuild ### 1209 | 1210 | There is an [atdgen plugin for ocamlbuild](https://github.com/hcarty/ocamlbuild-plugins/blob/master/myatdgen.ml). 1211 | 1212 | 1213 | Dealing with untypable JSON 1214 | =========================== 1215 | 1216 | Sometimes we have to deal with JSON data that cannot be described 1217 | using type definitions. In such case, we can represent the data as its 1218 | JSON abstract syntax tree (AST), which lets the user inspect it at runtime. 1219 | 1220 | Let's consider a list of JSON objects for which we don't know the type 1221 | definitions, but somehow some other system knows how to deal with such 1222 | data. Here is such data: 1223 | 1224 | ``` 1225 | [ 1226 | { 1227 | "label": "flower", 1228 | "value": { 1229 | "petals": [12, 45, 83.5555], 1230 | "water": "a340bcf02e" 1231 | } 1232 | }, 1233 | { 1234 | "label": "flower", 1235 | "value": { 1236 | "petals": "undefined", 1237 | "fold": null, 1238 | "water": 0 1239 | } 1240 | }, 1241 | { "labels": ["fork", "scissors"], 1242 | "value": [ 8, 8 ] 1243 | } 1244 | ] 1245 | ``` 1246 | 1247 | Hopefully this means something for someone. We are going to assume 1248 | that each object has a `value` field of an unknown type, and may have 1249 | a field `label` or a field `labels` of type `string`: 1250 | 1251 | ```ocaml 1252 | (* File untypable.atd *) 1253 | 1254 | type json = abstract 1255 | (* uses type Yojson.Safe.json, 1256 | with the functions Yojson.Safe.write_json 1257 | and Yojson.Safe.read_json *) 1258 | 1259 | type obj_list = obj list 1260 | 1261 | type obj = { 1262 | ?label: string option; 1263 | ?labels: string list option; 1264 | value: json 1265 | } 1266 | ``` 1267 | 1268 | It is possible to give a different name than `json` to the type 1269 | of the JSON AST, but then the name of the type used in the original module 1270 | must be provided in the annotation, i.e.: 1271 | 1272 | ```ocaml 1273 | type raw_json = abstract 1274 | (* uses type Yojson.Safe.json, 1275 | with the functions Yojson.Safe.write_json 1276 | and Yojson.Safe.read_json *) 1277 | 1278 | type obj_list = obj list 1279 | 1280 | type obj = { 1281 | ?label: string option; 1282 | ?labels: string list option; 1283 | value: raw_json 1284 | } 1285 | ``` 1286 | 1287 | Compile the example with: 1288 | 1289 | ```bash 1290 | $ atdgen -t untypable.atd 1291 | $ atdgen -j -j-std untypable.atd 1292 | $ ocamlfind ocamlc -a -o untypable.cma -package atdgen \ 1293 | untypable_t.mli untypable_t.ml untypable_j.mli untypable_j.ml 1294 | ``` 1295 | 1296 | Test the example with your favorite OCaml toplevel (`ocaml` or `utop`): 1297 | 1298 | ```ocaml 1299 | $ utop 1300 | # #use "topfind";; 1301 | # #require "atdgen";; 1302 | # #load "untypable.cma";; 1303 | # Ag_util.Json.from_channel Untypable_j.read_obj_list stdin;; 1304 | [ 1305 | { 1306 | "label": "flower", 1307 | "value": { 1308 | "petals": [12, 45, 83.5555], 1309 | "water": "a340bcf02e" 1310 | } 1311 | }, 1312 | { 1313 | "label": "flower", 1314 | "value": { 1315 | "petals": "undefined", 1316 | "fold": null, 1317 | "water": 0 1318 | } 1319 | }, 1320 | { "labels": ["fork", "scissors"], 1321 | "value": [ 8, 8 ] 1322 | } 1323 | ] 1324 | - : Untypable_t.obj_list = 1325 | [{Untypable_t.label = Some "flower"; labels = None; 1326 | value = 1327 | `Assoc 1328 | [("petals", `List [`Int 12; `Int 45; `Float 83.5555]); 1329 | ("water", `String "a340bcf02e")]}; 1330 | {Untypable_t.label = Some "flower"; labels = None; 1331 | value = 1332 | `Assoc [("petals", `String "undefined"); 1333 | ("fold", `Null); 1334 | ("water", `Int 0)]}; 1335 | {Untypable_t.label = None; labels = Some ["fork"; "scissors"]; 1336 | value = `List [`Int 8; `Int 8]}] 1337 | ``` 1338 | -------------------------------------------------------------------------------- /src/atdgen.md: -------------------------------------------------------------------------------- 1 | % atdgen(1) user manual 2 | % May 28, 2018 3 | 4 | [Home](https://mjambon.github.io/atdgen-doc/) 5 | 6 | Name 7 | ==== 8 | 9 | atdgen - derive code from type definitions 10 | 11 | Synopsis 12 | ======== 13 | 14 | atdgen **-t** [_infile_**.atd**] [_options_...] 15 | 16 | atdgen **-j** **-j-std** [_infile_**.atd**] [_options_...] 17 | 18 | atdgen **-b** [_infile_**.atd**] [_options_...] 19 | 20 | atdgen **-v** [_infile_**.atd**] [_options_...] 21 | 22 | atdgen [_mode_] [_options_...] 23 | 24 | atdgen **-help** 25 | 26 | Description 27 | =========== 28 | 29 | Atdgen is a command-line program that takes as input type definitions 30 | in the [ATD syntax](http://mjambon.com/atd) and produces OCaml 31 | code suitable for data serialization and deserialization. 32 | 33 | Two data formats are currently supported, these are 34 | [JSON](http://json.org/) and [biniou](http://mjambon.com/biniou.html), 35 | a binary format with extensibility properties similar to JSON. 36 | Atdgen-json and Atdgen-biniou will refer to Atdgen used in one context 37 | or the other. 38 | 39 | Atdgen was designed with efficiency and durability in mind. Software 40 | authors are encouraged to use Atdgen directly and to write 41 | tools that may reuse part of Atdgen's source code. 42 | 43 | Atdgen uses the following packages that were developed in conjunction 44 | with Atdgen: 45 | 46 | * `atd`: parser for the syntax of type definitions 47 | * `biniou`: parser and printer for biniou, a binary 48 | extensible data format 49 | * [`yojson`](http://mjambon.com/yojson.html): 50 | parser and printer for JSON, a widespread text-based data format 51 | 52 | 53 | Command-line usage 54 | ================== 55 | 56 | Command-line help 57 | ----------------- 58 | 59 | Call `atdgen -help` for the full list of available options. 60 | 61 | Atdgen-json example 62 | ------------------- 63 | 64 | ``` 65 | $ atdgen -t example.atd 66 | $ atdgen -j -j-std example.atd 67 | ``` 68 | 69 | 70 | Input file `example.atd`: 71 | 72 | ```ocaml 73 | type profile = { 74 | id : string; 75 | email : string; 76 | ~email_validated : bool; 77 | name : string; 78 | ?real_name : string option; 79 | ~about_me : string list; 80 | ?gender : gender option; 81 | ?date_of_birth : date option; 82 | } 83 | 84 | type gender = [ Female | Male ] 85 | 86 | type date = { 87 | year : int; 88 | month : int; 89 | day : int; 90 | } 91 | ``` 92 | 93 | is used to produce 94 | files `example_t.mli`, 95 | `example_t.ml`, 96 | `example_j.mli` and 97 | `example_j.ml`. 98 | This is `example_j.mli`: 99 | 100 | ```ocaml 101 | (* Auto-generated from "example.atd" *) 102 | 103 | 104 | type gender = Example_t.gender 105 | 106 | type date = Example_t.date = { year: int; month: int; day: int } 107 | 108 | type profile = Example_t.profile = { 109 | id: string; 110 | email: string; 111 | email_validated: bool; 112 | name: string; 113 | real_name: string option; 114 | about_me: string list; 115 | gender: gender option; 116 | date_of_birth: date option 117 | } 118 | 119 | val write_gender : 120 | Bi_outbuf.t -> gender -> unit 121 | (** Output a JSON value of type {!gender}. *) 122 | 123 | val string_of_gender : 124 | ?len:int -> gender -> string 125 | (** Serialize a value of type {!gender} 126 | into a JSON string. 127 | @param len specifies the initial length 128 | of the buffer used internally. 129 | Default: 1024. *) 130 | 131 | val read_gender : 132 | Yojson.Safe.lexer_state -> Lexing.lexbuf -> gender 133 | (** Input JSON data of type {!gender}. *) 134 | 135 | val gender_of_string : 136 | string -> gender 137 | (** Deserialize JSON data of type {!gender}. *) 138 | 139 | val write_date : 140 | Bi_outbuf.t -> date -> unit 141 | (** Output a JSON value of type {!date}. *) 142 | 143 | val string_of_date : 144 | ?len:int -> date -> string 145 | (** Serialize a value of type {!date} 146 | into a JSON string. 147 | @param len specifies the initial length 148 | of the buffer used internally. 149 | Default: 1024. *) 150 | 151 | val read_date : 152 | Yojson.Safe.lexer_state -> Lexing.lexbuf -> date 153 | (** Input JSON data of type {!date}. *) 154 | 155 | val date_of_string : 156 | string -> date 157 | (** Deserialize JSON data of type {!date}. *) 158 | 159 | val write_profile : 160 | Bi_outbuf.t -> profile -> unit 161 | (** Output a JSON value of type {!profile}. *) 162 | 163 | val string_of_profile : 164 | ?len:int -> profile -> string 165 | (** Serialize a value of type {!profile} 166 | into a JSON string. 167 | @param len specifies the initial length 168 | of the buffer used internally. 169 | Default: 1024. *) 170 | 171 | val read_profile : 172 | Yojson.Safe.lexer_state -> Lexing.lexbuf -> profile 173 | (** Input JSON data of type {!profile}. *) 174 | 175 | val profile_of_string : 176 | string -> profile 177 | (** Deserialize JSON data of type {!profile}. *) 178 | ``` 179 | 180 | 181 | Module `Example_t` (files `example_t.mli` and 182 | `example_t.ml`) contains all OCaml type definitions that 183 | can be used independently from Biniou or JSON. 184 | 185 | For convenience, these definitions are also made available from the 186 | `Example_j` module whose interface is shown above. 187 | Any type name, record field name or variant constructor can be 188 | referred to using either module. For example, the OCaml 189 | expressions `((x : Example_t.date) : Example_j.date)` 190 | and `x.Example_t.year = x.Example_j.year` are both valid. 191 | 192 | Atdgen-biniou example 193 | --------------------- 194 | 195 | ``` 196 | $ atdgen -t example.atd 197 | $ atdgen -b example.atd 198 | ``` 199 | 200 | Input file `example.atd`: 201 | 202 | ```ocaml 203 | type profile = { 204 | id : string; 205 | email : string; 206 | ~email_validated : bool; 207 | name : string; 208 | ?real_name : string option; 209 | ~about_me : string list; 210 | ?gender : gender option; 211 | ?date_of_birth : date option; 212 | } 213 | 214 | type gender = [ Female | Male ] 215 | 216 | type date = { 217 | year : int; 218 | month : int; 219 | day : int; 220 | } 221 | ``` 222 | 223 | is used to produce files `example_t.mli`, 224 | `example_t.ml`, 225 | `example_b.mli` and 226 | `example_b.ml`. 227 | 228 | This is `example_b.mli`: 229 | 230 | ```ocaml 231 | (* Auto-generated from "example.atd" *) 232 | 233 | 234 | type gender = Example_t.gender 235 | 236 | type date = Example_t.date = { year: int; month: int; day: int } 237 | 238 | type profile = Example_t.profile = { 239 | id: string; 240 | email: string; 241 | email_validated: bool; 242 | name: string; 243 | real_name: string option; 244 | about_me: string list; 245 | gender: gender option; 246 | date_of_birth: date option 247 | } 248 | 249 | (* Writers for type gender *) 250 | 251 | val gender_tag : Bi_io.node_tag 252 | (** Tag used by the writers for type {!gender}. 253 | Readers may support more than just this tag. *) 254 | 255 | val write_untagged_gender : 256 | Bi_outbuf.t -> gender -> unit 257 | (** Output an untagged biniou value of type {!gender}. *) 258 | 259 | val write_gender : 260 | Bi_outbuf.t -> gender -> unit 261 | (** Output a biniou value of type {!gender}. *) 262 | 263 | val string_of_gender : 264 | ?len:int -> gender -> string 265 | (** Serialize a value of type {!gender} into 266 | a biniou string. *) 267 | 268 | (* Readers for type gender *) 269 | 270 | val get_gender_reader : 271 | Bi_io.node_tag -> (Bi_inbuf.t -> gender) 272 | (** Return a function that reads an untagged 273 | biniou value of type {!gender}. *) 274 | 275 | val read_gender : 276 | Bi_inbuf.t -> gender 277 | (** Input a tagged biniou value of type {!gender}. *) 278 | 279 | val gender_of_string : 280 | ?pos:int -> string -> gender 281 | (** Deserialize a biniou value of type {!gender}. 282 | @param pos specifies the position where 283 | reading starts. Default: 0. *) 284 | 285 | (* Writers for type date *) 286 | 287 | val date_tag : Bi_io.node_tag 288 | (** Tag used by the writers for type {!date}. 289 | Readers may support more than just this tag. *) 290 | 291 | val write_untagged_date : 292 | Bi_outbuf.t -> date -> unit 293 | (** Output an untagged biniou value of type {!date}. *) 294 | 295 | val write_date : 296 | Bi_outbuf.t -> date -> unit 297 | (** Output a biniou value of type {!date}. *) 298 | 299 | val string_of_date : 300 | ?len:int -> date -> string 301 | (** Serialize a value of type {!date} into 302 | a biniou string. *) 303 | 304 | (* Readers for type date *) 305 | 306 | val get_date_reader : 307 | Bi_io.node_tag -> (Bi_inbuf.t -> date) 308 | (** Return a function that reads an untagged 309 | biniou value of type {!date}. *) 310 | 311 | val read_date : 312 | Bi_inbuf.t -> date 313 | (** Input a tagged biniou value of type {!date}. *) 314 | 315 | val date_of_string : 316 | ?pos:int -> string -> date 317 | (** Deserialize a biniou value of type {!date}. 318 | @param pos specifies the position where 319 | reading starts. Default: 0. *) 320 | 321 | (* Writers for type profile *) 322 | 323 | val profile_tag : Bi_io.node_tag 324 | (** Tag used by the writers for type {!profile}. 325 | Readers may support more than just this tag. *) 326 | 327 | val write_untagged_profile : 328 | Bi_outbuf.t -> profile -> unit 329 | (** Output an untagged biniou value of type {!profile}. *) 330 | 331 | val write_profile : 332 | Bi_outbuf.t -> profile -> unit 333 | (** Output a biniou value of type {!profile}. *) 334 | 335 | val string_of_profile : 336 | ?len:int -> profile -> string 337 | (** Serialize a value of type {!profile} into 338 | a biniou string. *) 339 | 340 | (* Readers for type profile *) 341 | 342 | val get_profile_reader : 343 | Bi_io.node_tag -> (Bi_inbuf.t -> profile) 344 | (** Return a function that reads an untagged 345 | biniou value of type {!profile}. *) 346 | 347 | val read_profile : 348 | Bi_inbuf.t -> profile 349 | (** Input a tagged biniou value of type {!profile}. *) 350 | 351 | val profile_of_string : 352 | ?pos:int -> string -> profile 353 | (** Deserialize a biniou value of type {!profile}. 354 | @param pos specifies the position where 355 | reading starts. Default: 0. *) 356 | ``` 357 | 358 | Module `Example_t` (files `example_t.mli` and 359 | `example_t.ml`) contains all OCaml type definitions that 360 | can be used independently from Biniou or JSON. 361 | 362 | For convenience, these definitions are also made available from the 363 | `Example_b` module whose interface is shown above. 364 | Any type name, record field name or variant constructor can be 365 | referred to using either module. For example, the OCaml 366 | expressions `((x : Example_t.date) : Example_b.date)` 367 | and `x.Example_t.year = x.Example_b.year` are both valid. 368 | 369 | 370 | Validator example 371 | ----------------- 372 | 373 | ``` 374 | $ atdgen -t example.atd 375 | $ atdgen -v example.atd 376 | ``` 377 | 378 | Input file `example.atd`: 379 | 380 | ```ocaml 381 | type month = int 382 | type day = int 383 | 384 | type date = { 385 | year : int; 386 | month : month; 387 | day : day; 388 | } 389 | 390 | ``` 391 | 392 | is used to produce 393 | files `example_t.mli`, 394 | `example_t.ml`, 395 | `example_v.mli` and 396 | `example_v.ml`. 397 | This is `example_v.ml`, showing how the user-specified 398 | validators are used: 399 | 400 | ```ocaml 401 | (* Auto-generated from "example.atd" *) 402 | 403 | 404 | type gender = Example_t.gender 405 | 406 | type date = Example_t.date = { year: int; month: int; day: int } 407 | 408 | type profile = Example_t.profile = { 409 | id: string; 410 | email: string; 411 | email_validated: bool; 412 | name: string; 413 | real_name: string option; 414 | about_me: string list; 415 | gender: gender option; 416 | date_of_birth: date option 417 | } 418 | 419 | val validate_gender : 420 | Atdgen_runtime.Util.Validation.path -> gender -> Atdgen_runtime.Util.Validation.error option 421 | (** Validate a value of type {!gender}. *) 422 | 423 | val create_date : 424 | year: int -> 425 | month: int -> 426 | day: int -> 427 | unit -> date 428 | (** Create a record of type {!date}. *) 429 | 430 | val validate_date : 431 | Atdgen_runtime.Util.Validation.path -> date -> Atdgen_runtime.Util.Validation.error option 432 | (** Validate a value of type {!date}. *) 433 | 434 | val create_profile : 435 | id: string -> 436 | email: string -> 437 | ?email_validated: bool -> 438 | name: string -> 439 | ?real_name: string -> 440 | ?about_me: string list -> 441 | ?gender: gender -> 442 | ?date_of_birth: date -> 443 | unit -> profile 444 | (** Create a record of type {!profile}. *) 445 | 446 | val validate_profile : 447 | Atdgen_runtime.Util.Validation.path -> profile -> Atdgen_runtime.Util.Validation.error option 448 | (** Validate a value of type {!profile}. *) 449 | ``` 450 | 451 | 452 | 453 | Default type mapping 454 | ==================== 455 | 456 | The following table summarizes the default mapping between ATD types and 457 | OCaml, biniou and JSON data types. For each language more 458 | representations are available and are detailed in the next section of this 459 | manual. 460 | 461 | ------------------------------------------------------------------------- 462 | ATD OCaml JSON Biniou 463 | --------------- ------------------- ----------------- ------------------- 464 | `unit` `unit` null unit 465 | 466 | `bool` `bool` boolean bool 467 | 468 | `int` `int` -?(0|[1-9][0-9]*) svint 469 | 470 | `float` `float` number float64 471 | 472 | `string` `string` string string 473 | 474 | `'a option` `'a option` `"None"` or numeric variants 475 | `["Some", ...]` (tag 0) 476 | 477 | `'a nullable` `'a option` `null` or numeric variants 478 | representation (tag 0) 479 | of `'a` 480 | 481 | `'a list` `'a list` array array 482 | 483 | `'a shared` no wrapping not implemented no longer 484 | supported 485 | 486 | `'a wrap` defined representation representation 487 | by annotation, of `'a` of `'a` 488 | converted from 489 | `'a` 490 | 491 | variants polymorphic variants regular 492 | variants variants 493 | 494 | record record object record 495 | 496 | `('a * 'b)` `('a * 'b)` array tuple 497 | 498 | `('a)` `'a` array tuple 499 | ------------------------------------------------------------------------- 500 | 501 | Notes: 502 | 503 | * Null JSON fields by default are treated as if the field was missing. 504 | They can be made meaningful with the `keep_nulls` flag. 505 | * JSON nulls are used to represent the unit value and is 506 | useful for instanciating parametrized types with "nothing". 507 | * OCaml floats are written to JSON numbers with either a decimal 508 | point or an exponent such that they are distinguishable from 509 | ints, even though the JSON standard does not require a distinction 510 | between the two. 511 | * The optional values of record fields denoted in ATD by a 512 | question mark are unwrapped or omitted in both biniou and JSON. 513 | * JSON option values and JSON variants are represented in standard 514 | JSON (`atdgen -j -j-std`) by a single string e.g. `"None"` 515 | or a pair in which the 516 | first element is the name (constructor) e.g. `["Some", 1234]`. 517 | Yojson also provides a specific syntax for variants using edgy 518 | brackets: `<"None">`, `<"Some": 1234>`. 519 | * Biniou field names and variant names other than the option types 520 | use the hash of the ATD field or variant name and cannot currently 521 | be overridden by annotations. 522 | * JSON tuples in standard JSON (`atdgen -j -j-std`) use the 523 | array notation e.g. 524 | `["ABC", 123]`. 525 | Yojson also provides a specific syntax for tuples using parentheses, 526 | e.g. `("ABC", 123)`. 527 | * Types defined as abstractare defined in 528 | another module. 529 | 530 | 531 | 532 | 533 | ATD Annotations 534 | =============== 535 | 536 | Section '`json`' 537 | ---------------- 538 | 539 | ### Field '`keep_nulls`' ### 540 | 541 | Position: after record 542 | 543 | Values: none, `true` or `false` 544 | 545 | Semantics: this flag, if present or set to true, indicates that fields 546 | whose JSON value is `null` should not be treated as if they were 547 | missing. In this case, `null` is parsed as a normal value, possibly of 548 | a `nullable` type. 549 | 550 | Example: patch semantics 551 | 552 | ```ocaml 553 | (* Type of the objects stored in our database *) 554 | type t = { 555 | ?x : int option; 556 | ?y : int option; 557 | ?z : int option; 558 | } 559 | ``` 560 | 561 | ```ocaml 562 | (* Type of the requests to modify some of the fields of an object. *) 563 | type t_patch = { 564 | ?x : int nullable option; (* OCaml type: int option option *) 565 | ?y : int nullable option; 566 | ?z : int nullable option; 567 | } 568 | ``` 569 | 570 | Let's consider the following json patch that means "set `x` to 1, 571 | clear `y` and keep `z` as it is": 572 | ``` 573 | { 574 | "x": 1, 575 | "y": null 576 | } 577 | ``` 578 | 579 | It will be parsed by the generated function `t_patch_of_string` into 580 | the following OCaml value: 581 | 582 | ```ocaml 583 | { 584 | patch_x = Some (Some 1); 585 | patch_y = Some None; 586 | patch_z = None; 587 | } 588 | ``` 589 | 590 | Then presumably some code would be written to apply the patch to an 591 | object of type `t`. Such code is not generated by atdgen at this 592 | time. 593 | 594 | Available: from atd 1.12 595 | 596 | ### Field '`name`' ### 597 | 598 | Position: after field name or variant name 599 | 600 | Values: any string making a valid JSON string value 601 | 602 | Semantics: specifies an alternate object field name or variant 603 | name to be used by the JSON representation. 604 | 605 | Example: 606 | 607 | ```ocaml 608 | type color = [ 609 | Black 610 | | White 611 | | Grey 612 | ] 613 | 614 | type profile = { 615 | id : int; 616 | username : string; 617 | background_color : color; 618 | } 619 | ``` 620 | 621 | A valid JSON object of the `profile` type above is: 622 | ``` 623 | { 624 | "ID": 12345678, 625 | "username": "kimforever", 626 | "background_color": "black" 627 | } 628 | ``` 629 | 630 | 631 | ### Field '`repr`' ### 632 | 633 | #### Association lists #### 634 | 635 | Position: after `(string * _) list` type 636 | 637 | Values: `object` 638 | 639 | Semantics: uses JSON's object notation to represent association 640 | lists. 641 | 642 | Example: 643 | 644 | ```ocaml 645 | type counts = (string * int) list 646 | ``` 647 | 648 | A valid JSON object of the `counts` type above is: 649 | ``` 650 | { 651 | "bob": 3, 652 | "john": 1408, 653 | "mary": 450987, 654 | "peter": 93087 655 | } 656 | ``` 657 | Without the annotation ``, the data above 658 | would be represented as: 659 | ``` 660 | [ 661 | [ "bob", 3 ], 662 | [ "john", 1408 ], 663 | [ "mary", 450987 ], 664 | [ "peter", 93087 ] 665 | ] 666 | ``` 667 | 668 | #### Floats #### 669 | 670 | Position: after `float` type 671 | 672 | Values: `int` 673 | 674 | Semantics: specifies a float value that must be rounded to the 675 | nearest integer and represented in JSON without 676 | a decimal point nor an exponent. 677 | 678 | Example: 679 | 680 | ```ocaml 681 | type unixtime = float 682 | ``` 683 | 684 | ### Field '`tag_field`' ### 685 | 686 | Superseded by ``. 687 | Available since atdgen 1.5.0 and yojson 1.2.0 until atdgen 1.13. 688 | 689 | This feature makes it possible to read JSON objects representing 690 | variants that use one field for the tag and another field for the 691 | untagged value of the specific type associated with that tag. 692 | 693 | Position: on a record field name, for a field holding a variant type. 694 | 695 | Value: name of another JSON field which holds the 696 | string representing the constructor for the variant. 697 | 698 | Semantics: The type definition 699 | 700 | ```ocaml 701 | type t = { 702 | value : [ A | B of int ]; 703 | } 704 | ``` 705 | 706 | covers JSON objects that have an extra field `kind` which holds either 707 | `"A"` or `"b"`. Valid JSON values of type `t` include 708 | `{ "kind": "A" }` and `{ "kind": "b", "value": 123 }`. 709 | 710 | ### Field '`untyped`' ### 711 | 712 | Superseded by `` and ``. 713 | Available since atdgen 1.10.0 and atd 1.2.0 until atdgen 1.13. 714 | 715 | This flag enables parsing of arbitrary variants without prior knowledge 716 | of their type. It is useful for constructing flexible parsers for 717 | extensible serializations. `json untyped` is compatible with regular 718 | variants, `json tag_field` variants, default values, and implicit 719 | `tag_field` constructors. 720 | 721 | Position: on a variant constructor with argument type `string * json 722 | option` (at most one per variant type) 723 | 724 | Value: none, `true` or `false` 725 | 726 | Semantics: The type definition 727 | 728 | ```ocaml 729 | type v = [ 730 | | A 731 | | B of int 732 | | Unknown of (string * json option) 733 | ] 734 | ``` 735 | 736 | will parse and print `"A"`, `["b", 0]`, `"foo"`, and `["bar", [null]]` 737 | in a regular variant context. In the `tag_field` type `t` context in the 738 | previous section, `v` will parse and print `{ "kind": "foo" }` and `{ 739 | "kind": "bar", "value": [null] }` as well as the examples previously 740 | given. 741 | 742 | ### Field '`open_enum`' ### 743 | 744 | Where an enum (finite set of strings) is expected, this flag allows 745 | unexpected strings to be kept under a catch-all constructor rather 746 | than producing an error. 747 | 748 | Position: on a variant type comprising exactly one constructor with an 749 | argument. The type of that argument must be `string`. All other 750 | constructors must have no arguments. 751 | 752 | Value: none 753 | 754 | For example: 755 | 756 | ```ocaml 757 | type language = [ 758 | | English 759 | | Chinese 760 | | Other of string 761 | ] 762 | ``` 763 | 764 | maps the json string `"Chinese"` to the OCaml value `` `Chinese`` and 765 | maps `"French"` to `` `Other "French"``. 766 | 767 | Available since atdgen 2.0. 768 | 769 | ### Field '`adapter.ocaml`' ### 770 | 771 | Json adapters are a mechanism for rearranging json data on-the-fly, so 772 | as to make them compatible with ATD. The programmer must provide 773 | an OCaml module that provides converters between the original json 774 | representation and the ATD-compatible representation. The signature 775 | of the user-provided module must be equal to 776 | `Atdgen_runtime.Json_adapter.S`, which is: 777 | 778 | ```ocaml 779 | sig 780 | (** Convert from original json to ATD-compatible json *) 781 | val normalize : Yojson.Safe.json -> Yojson.Safe.json 782 | 783 | (** Convert from ATD-compatible json to original json *) 784 | val restore : Yojson.Safe.json -> Yojson.Safe.json 785 | end 786 | ``` 787 | 788 | The type `Yojson.Safe.json` is the type of parsed JSON as provided 789 | by the yojson library. 790 | 791 | Position: on a variant type or on a record type. 792 | 793 | Value: an OCaml module identifier. Note that 794 | `Atdgen_runtime.Json_adapter` provides a few modules and functors 795 | that are ready to use. Users are however encouraged to write their own 796 | to suit their needs. 797 | 798 | Sample ATD definitions: 799 | 800 | ```ocaml 801 | type document = [ 802 | | Image of image 803 | | Text of text 804 | ] 805 | 806 | type image = { 807 | url: string; 808 | } 809 | 810 | type text = { 811 | title: string; 812 | body: string; 813 | } 814 | ``` 815 | 816 | ATD-compliant json values: 817 | 818 | * `["Image", {"url": "https://example.com/ocean123.jpg"}]` 819 | * `["Text", {"title": "Cheeses Around the World", "body": "..."}]` 820 | 821 | Corresponding json values given by some API: 822 | 823 | * `{"type": "Image", "url": "https://example.com/ocean123.jpg"}` 824 | * `{"type": "Text", "title": "Cheeses Around the World", "body": "..."}` 825 | 826 | The json adapter `Type_field` that ships with the atdgen runtime 827 | takes care of converting between these two forms. For information on 828 | how to write your own adapter, please consult the documentation for 829 | the yojson library. 830 | 831 | Section '`biniou`' 832 | ------------------ 833 | 834 | ### Field '`repr`' ### 835 | 836 | #### Integers #### 837 | 838 | Position: after `int` type 839 | 840 | Values: `svint` (default), `uvint`, `int8`, 841 | `int16`, `int32`, `int64` 842 | 843 | Semantics: specifies an alternate type for representing integers. 844 | The default type is `svint`. 845 | The other integers types provided by biniou are 846 | supported by Atdgen-biniou. 847 | They have to map to the corresponding OCaml types 848 | in accordance with the following table: 849 | 850 | ------------------------------------------------------------------------ 851 | Biniou type Supported OCaml type OCaml value range 852 | ------------ --------------------- ------------------------------------- 853 | `svint` `int` `min_int` ... `max_int` 854 | 855 | `uvint` `int` 0 ... `max_int`, `min_int` ... -1 856 | 857 | `int8` `char` `'\000'` ... `'\255'` 858 | 859 | `int16` `int` 0 ... 65535 860 | 861 | `int32` `int32` `Int32.min_int` ... `Int32.max_int` 862 | 863 | `int64` `int64` `Int64.min_int` ... `Int64.max_int` 864 | ------------------------------------------------------------------------ 865 | 866 | In addition to the mapping above, if the OCaml type is `int`, 867 | any biniou integer type can be read into OCaml data regardless of the 868 | declared biniou type. 869 | 870 | Example: 871 | 872 | ```ocaml 873 | type t = { 874 | id : int 875 | 876 | ; 877 | data : string list; 878 | } 879 | ``` 880 | 881 | #### Floating-point numbers #### 882 | 883 | Position: after `float` type 884 | 885 | Values: `float64` (default), `float32` 886 | 887 | Semantics: `float32` allows for a shorter serialized representation 888 | of floats, using 4 bytes instead of 8, with reduced precision. 889 | OCaml floats always use 8 bytes, though. 890 | 891 | Example: 892 | 893 | ```ocaml 894 | type t = { 895 | lat : float ; 896 | lon : float ; 897 | } 898 | ``` 899 | 900 | 901 | #### Arrays and tables #### 902 | 903 | Position: applies to lists of records 904 | 905 | Values: `array` (default), `table` 906 | 907 | Semantics: `table` uses biniou's table format instead of a 908 | regular array for serializing OCaml data into biniou. 909 | Both formats are supported for reading into OCaml data 910 | regardless of the annotation. The table format allows 911 | 912 | Example: 913 | 914 | ```ocaml 915 | type item = { 916 | id : int; 917 | data : string list; 918 | } 919 | 920 | type items = item list 921 | ``` 922 | 923 | 924 | Section '`ocaml`' 925 | ----------------- 926 | 927 | ### Field '`predef`' ### 928 | 929 | Position: left-hand side of a type definition, after the type name 930 | 931 | Values: none, `true` or `false` 932 | 933 | Semantics: this flag indicates that the corresponding OCaml 934 | type definition must be omitted. 935 | 936 | Example: 937 | 938 | ```ocaml 939 | (* Some third-party OCaml code *) 940 | type message = { 941 | from : string; 942 | subject : string; 943 | body : string; 944 | } 945 | ``` 946 | 947 | ```ocaml 948 | (* 949 | Our own ATD file used for making message_of_string and 950 | string_of_message functions. 951 | *) 952 | type message = { 953 | from : string; 954 | subject : string; 955 | body : string; 956 | } 957 | ``` 958 | 959 | 960 | ### Field '`mutable`' ### 961 | 962 | Position: after a record field name 963 | 964 | Values: none, `true` or `false` 965 | 966 | Semantics: this flag indicates that the corresponding OCaml 967 | record field is mutable. 968 | 969 | Example: 970 | 971 | ```ocaml 972 | type counter = { 973 | total : int; 974 | errors : int; 975 | } 976 | ``` 977 | 978 | translates to the following OCaml definition: 979 | 980 | ```ocaml 981 | type counter = { 982 | mutable total : int; 983 | mutable errors : int; 984 | } 985 | ``` 986 | 987 | 988 | ### Field '`default`' ### 989 | 990 | 991 | Position: after a record field name marked with a `\~{`} 992 | symbol or at the beginning of a tuple field. 993 | 994 | 995 | Values: any valid OCaml expression 996 | 997 | Semantics: specifies an explicit default value for a field of an 998 | OCaml record or tuple, allowing that field to be omitted. Default strings must be escaped. 999 | 1000 | Example: 1001 | 1002 | ```ocaml 1003 | type color = [ Black | White | Rgb of (int * int * int) ] 1004 | 1005 | type ford_t = { 1006 | year : int; 1007 | ~color : color; 1008 | ~name : string; 1009 | } 1010 | 1011 | type point = (int * int * : int) 1012 | ``` 1013 | 1014 | 1015 | ### Field '`from`' ### 1016 | 1017 | Position: left-hand side of a type definition, after the type name 1018 | 1019 | Values: OCaml module name without the `_t`, `_b`, 1020 | `_j` or `_v` 1021 | suffix. This can be also seen as the name of the original ATD file, 1022 | without the `.atd` extension and capitalized like an OCaml 1023 | module name. 1024 | 1025 | Semantics: specifies the base name of the OCaml modules 1026 | where the type and values coming with that type are defined. 1027 | 1028 | It is useful for ATD types defined as 1029 | `abstract` and for types annotated as predefined using 1030 | the annotation ``. 1031 | In both cases, the missing definitions must be provided by 1032 | modules composed of the base name and the standard suffix assumed by 1033 | Atdgen which is 1034 | `_t`, `_b`, 1035 | `_j` or `_v`. 1036 | 1037 | Example: 1038 | First input file `part1.atd`: 1039 | 1040 | ```ocaml 1041 | type point = { x : int; y : int } 1042 | ``` 1043 | 1044 | Second input file `part2.atd` depending on the first one: 1045 | 1046 | ```ocaml 1047 | type point = abstract 1048 | type points = point list 1049 | ``` 1050 | 1051 | 1052 | ### Field '`module`' ### 1053 | 1054 | #### Using a custom wrapper #### 1055 | 1056 | Using the built-in `wrap` constructor, it is possible to add a layer 1057 | of abstraction on top of the concrete structure used for 1058 | serialization. 1059 | 1060 | Position: after a `wrap` type constructor 1061 | 1062 | Values: OCaml module name 1063 | 1064 | A common use case is to parse strings used 1065 | as unique identifiers and wrap the result into an abstract type. 1066 | Our OCaml module `Uid` needs to provide a type `t`, and two functions `wrap` 1067 | and `unwrap` as follows: 1068 | 1069 | ```ocaml 1070 | type t 1071 | val wrap : string -> t 1072 | val unwrap : t -> string 1073 | ``` 1074 | 1075 | Given that `Uid` OCaml module, we can write the following ATD 1076 | definition: 1077 | 1078 | ```ocaml 1079 | type uid = string wrap 1080 | ``` 1081 | 1082 | Other languages than OCaml using the same ATD type definitions may or 1083 | may not add their own abstract layer. Without an annotation, the 1084 | `wrap` construct has no effect on the value being wrapped, i.e. `wrap` 1085 | and `unwrap` default to the identity function. 1086 | 1087 | It is also possible to define `t`, `wrap`, and `unwrap` inline: 1088 | 1089 | ```ocaml 1090 | type uid = string wrap 1093 | ``` 1094 | 1095 | This can be useful for very simple validation: 1096 | 1097 | ```ocaml 1098 | type uid = string wrap 1099 | 1104 | ``` 1105 | 1106 | 1107 | #### Importing an external type definition #### 1108 | 1109 | In most cases since Atdgen 1.2.0 1110 | `module` annotations are deprecated in favor of `from` 1111 | annotations previously described. 1112 | 1113 | Position: left-hand side of a type definition, after the type name 1114 | 1115 | Values: OCaml module name 1116 | 1117 | Semantics: specifies the OCaml module where the type and values 1118 | coming with that type are defined. It is useful for ATD types defined as 1119 | `abstract` and for types annotated as predefined using 1120 | the annotation ``. 1121 | In both cases, the missing definitions can be provided either by 1122 | globally opening an OCaml module with an OCaml directive or by specifying 1123 | locally the name of the module to use. 1124 | 1125 | The latter approach is recommended because it allows to create 1126 | type and value aliases in the OCaml module being generated. It results 1127 | in a complete module signature regardless of the external 1128 | nature of some items. 1129 | 1130 | Example: 1131 | Input file `example.atd`: 1132 | 1133 | ```ocaml 1134 | type document = abstract 1135 | 1136 | type color = 1137 | [ Black | White ] 1138 | 1139 | type point = { 1140 | x : float; 1141 | y : float; 1142 | } 1143 | ``` 1144 | 1145 | gives the following OCaml type definitions 1146 | (file `example.mli`): 1147 | 1148 | ```ocaml 1149 | type document = Doc.document 1150 | 1151 | type color = Color.color = Black | White 1152 | 1153 | type point = Point.point = { x: float; y: float } 1154 | ``` 1155 | 1156 | Now for instance `Example.Black` and `Color.Black` 1157 | can be used interchangeably in other modules. 1158 | 1159 | 1160 | ### Field '`t`' ### 1161 | 1162 | 1163 | #### Using a custom wrapper #### 1164 | 1165 | Specifies the OCaml type of an abstract `wrap` construct, possibly 1166 | overriding the default _M_`.t` if _M_ is the module where the `wrap` 1167 | and `unwrap` functions are found. 1168 | 1169 | Position: after a `wrap` type constructor 1170 | 1171 | Values: OCaml type name 1172 | 1173 | Example: 1174 | 1175 | ```ocaml 1176 | type uid = string wrap 1177 | ``` 1178 | 1179 | is equivalent to: 1180 | 1181 | ```ocaml 1182 | type uid = string wrap 1183 | ``` 1184 | 1185 | 1186 | #### Importing an external type definition #### 1187 | 1188 | Position: left-hand side of a type definition, after the type 1189 | name. Must be used in conjunction with a `module` field. 1190 | 1191 | Values: OCaml type name as found in an external module. 1192 | 1193 | Semantics: This option allows to specify the name of an 1194 | OCaml type defined in an external module. 1195 | 1196 | It is useful when the type needs to be renamed because its original 1197 | name is already in use or not enough informative. 1198 | Typically we may want to give the name `foo` to a type 1199 | originally defined in OCaml as `Foo.t`. 1200 | 1201 | Example: 1202 | 1203 | ```ocaml 1204 | type foo = abstract 1205 | type bar = abstract 1206 | type t = abstract 1207 | ``` 1208 | 1209 | allows local type names to be unique 1210 | and gives the following OCaml type definitions: 1211 | 1212 | ```ocaml 1213 | type foo = Foo.t 1214 | type bar = Bar.t 1215 | type t = Baz.t 1216 | ``` 1217 | 1218 | ### Fields '`wrap`' and '`unwrap`' ### 1219 | 1220 | See "Using a custom wrapper" under section '`ocaml`', fields 1221 | '`module`' and '`t`'. 1222 | 1223 | 1224 | ### Field '`field_prefix`' ### 1225 | 1226 | Position: record type expression 1227 | 1228 | Values: any string making a valid prefix for OCaml record field names 1229 | 1230 | Semantics: specifies a prefix to be prepended to each field of 1231 | the OCaml definition of the record. Overridden by alternate field 1232 | names defined on a per-field basis. 1233 | 1234 | Example: 1235 | 1236 | ```ocaml 1237 | type point2 = { 1238 | x : int; 1239 | y : int; 1240 | } 1241 | ``` 1242 | 1243 | gives the following OCaml type definition: 1244 | 1245 | ```ocaml 1246 | type point2 = { 1247 | p2_x : int; 1248 | p2_y : int; 1249 | } 1250 | ``` 1251 | 1252 | ### Field '`name`' ### 1253 | 1254 | Position: after record field name or variant name 1255 | 1256 | Values: any string making a valid OCaml record field name or 1257 | variant name 1258 | 1259 | Semantics: specifies an alternate record field name or variant 1260 | names to be used in OCaml. 1261 | 1262 | Example: 1263 | 1264 | ```ocaml 1265 | type color = [ 1266 | Black 1267 | | White 1268 | | Grey 1269 | ] 1270 | 1271 | type profile = { 1272 | id : int; 1273 | username : string; 1274 | } 1275 | ``` 1276 | 1277 | gives the following OCaml type definitions: 1278 | 1279 | ```ocaml 1280 | type color = [ 1281 | `Grey0 1282 | | `Grey100 1283 | | `Grey50 1284 | ] 1285 | 1286 | type profile = { 1287 | profile_id : int; 1288 | username : string; 1289 | } 1290 | ``` 1291 | 1292 | 1293 | ### Field '`repr`' ### 1294 | 1295 | #### Integers #### 1296 | 1297 | Position: after `int` type 1298 | 1299 | Values: `char`, `int32`, `int64`, `float` 1300 | 1301 | Semantics: specifies an alternate type for representing integers. 1302 | The default type is `int`, but `char`, `int32`, 1303 | `int64` or `float` can be used instead. 1304 | 1305 | The three types `char`, `int32` and `int64` are 1306 | supported by both Atdgen-biniou and Atdgen-json but Atdgen-biniou 1307 | currently requires that they map to the corresponding fixed-width 1308 | types provided by the biniou format. 1309 | 1310 | The type `float` is only supported in conjunction with JSON and 1311 | is useful when an OCaml float is used to represent an integral value, 1312 | such as a time in seconds returned by `Unix.time()`. When 1313 | converted into JSON, floats are rounded to the nearest integer. 1314 | 1315 | Example: 1316 | 1317 | ```ocaml 1318 | type t = { 1319 | id : int 1320 | 1321 | ; 1322 | data : string list; 1323 | } 1324 | ``` 1325 | 1326 | 1327 | #### Lists and arrays #### 1328 | 1329 | Position: after a `list` type 1330 | 1331 | Values: `array` 1332 | 1333 | Semantics: maps to OCaml's `array` type instead of `list`. 1334 | 1335 | Example: 1336 | 1337 | ```ocaml 1338 | type t = { 1339 | id : int; 1340 | data : string list 1341 | ; 1342 | } 1343 | ``` 1344 | 1345 | #### Sum types #### 1346 | 1347 | Position: after a sum type (denoted by square brackets) 1348 | 1349 | Values: `classic` 1350 | 1351 | Semantics: maps to OCaml's classic variants instead of 1352 | polymorphic variants. 1353 | 1354 | Example: 1355 | 1356 | ```ocaml 1357 | type fruit = [ Apple | Orange ] 1358 | ``` 1359 | 1360 | translates to the following OCaml type definition: 1361 | 1362 | ```ocaml 1363 | type fruit = Apple | Orange 1364 | ``` 1365 | 1366 | 1367 | #### Shared values (obsolete) #### 1368 | 1369 | Position: after a `shared` type 1370 | 1371 | This feature is obsolete and was last supported by atdgen 1.3.1. 1372 | 1373 | 1374 | ### Field '`valid`' ### 1375 | 1376 | Since atdgen 1.6.0. 1377 | 1378 | Position: after any type expression except type variables 1379 | 1380 | Values: OCaml function that takes one argument of the given type 1381 | and returns a bool 1382 | 1383 | Semantics: `atdgen -v` produces for each type named 1384 | _t_ a function `validate_`_t_: 1385 | 1386 | ```ocaml 1387 | val validate_t : Atdgen_runtime.Util.Validation.path -> t -> Atdgen_runtime.Util.Validation.error option 1388 | ``` 1389 | 1390 | Such a function returns `None` if and only if the value and all of its 1391 | subnodes pass all the validators specified by annotations of the form 1392 | `` or `` (at most one per node). 1393 | 1394 | Example: 1395 | 1396 | ```ocaml 1397 | type positive = int 1398 | 1399 | type point = { 1400 | x : positive; 1401 | y : positive; 1402 | z : int; 1403 | } 1404 | 1405 | (* Some validating function from a user-defined module Point *) 1406 | ``` 1407 | 1408 | The generated `validate_point` function calls the validator 1409 | for the containing object first (`Point.validate`) and continues on 1410 | its fields `x` then `y` until an error is returned. 1411 | 1412 | ```ocaml 1413 | match validate_point [] { x = 1; y = 0; z = 1 } with 1414 | | None -> () 1415 | | Some e -> 1416 | Printf.eprintf "Error: %s\n%!" 1417 | (Atdgen_runtime.Util.Validation.string_of_error e) 1418 | ``` 1419 | 1420 | The above code prints the following error message: 1421 | 1422 | ``` 1423 | Error: Validation error; path = .y 1424 | ``` 1425 | 1426 | In order to customize the error message and print the faulty value, 1427 | use `validator` instead of `valid`, as described next. 1428 | 1429 | ### Field '`validator`' ### 1430 | 1431 | This is a variant of the `valid` annotation that allows 1432 | full control over the error message that gets generated in case of 1433 | an error. 1434 | 1435 | Position: after any type expression except type variables 1436 | 1437 | Values: OCaml function that takes the path in current JSON structure 1438 | and the object to validate, and returns an optional error. 1439 | 1440 | Semantics: `atdgen -v` produces for each type named 1441 | _t_ a function `validate_`_t_: 1442 | 1443 | ```ocaml 1444 | val validate_t : Atdgen_runtime.Util.Validation.path -> t -> Atdgen_runtime.Util.Validation.error option 1445 | ``` 1446 | 1447 | Such a function returns `None` if and only if the value and all of its 1448 | subnodes pass all the validators specified by annotations of the form 1449 | `` or `` (at most one per node). 1450 | 1451 | Example: 1452 | 1453 | ```ocaml 1454 | type positive = int 1464 | 1465 | type point = { 1466 | x : positive; 1467 | y : positive; 1468 | z : int; 1469 | } 1470 | 1471 | (* Some validating function from a user-defined module Point *) 1472 | ``` 1473 | 1474 | The following user code 1475 | 1476 | ```ocaml 1477 | match Toto_v.validate_point [] { x = 1; y = 0; z = 1 } with 1478 | | None -> () 1479 | | Some e -> 1480 | Printf.eprintf "Error: %s\n%!" 1481 | (Atdgen_runtime.Util.Validation.string_of_error e) 1482 | ``` 1483 | 1484 | results in printing: 1485 | 1486 | ``` 1487 | Error: Validation error: Not a positive integer: 0; path = .y 1488 | ``` 1489 | 1490 | Section '`ocaml_biniou`' 1491 | ------------------------ 1492 | 1493 | Section `ocaml_biniou` takes precedence over section `ocaml` 1494 | in Biniou mode (`-b`) for the following fields: 1495 | 1496 | * `predef` (see section `ocaml`, field `predef`) 1497 | * `module` (see section `ocaml`, field `module`) 1498 | * `t` (see section `ocaml.t`) 1499 | 1500 | 1501 | Section '`ocaml_json`' (obsolete) 1502 | --------------------------------- 1503 | 1504 | Section `ocaml_json` takes precedence over section `ocaml` 1505 | in JSON mode (`-json` or `-j`) for the following fields: 1506 | 1507 | * `predef` (see section `ocaml`, field `predef`) 1508 | * `module` (see section `ocaml`, field `module`) 1509 | * `t` (see section `ocaml`, field `t`) 1510 | 1511 | Please note that `atdgen -json` is now deprecated in favor of `atdgen -j` 1512 | (json) and `atdgen -t` (types). The latter is in charge of producing 1513 | type definitions independently from JSON and will ignore 1514 | `` annotations, making them almost useless. 1515 | The equivalent `` annotations are almost always preferable. 1516 | 1517 | 1518 | Example: 1519 | 1520 | This example shows how to parse a field into a generic tree 1521 | of type `Yojson.Safe.json` rather than a value of a specialized 1522 | OCaml type. 1523 | 1524 | ```ocaml 1525 | type dyn = abstract 1526 | 1527 | type t = { foo: int; bar: dyn } 1528 | ``` 1529 | 1530 | translates to the following OCaml type definitions: 1531 | 1532 | ```ocaml 1533 | type dyn = Yojson.Safe.json 1534 | 1535 | type t = { foo : int; bar : dyn } 1536 | ``` 1537 | 1538 | Sample OCaml value of type `t`: 1539 | 1540 | ```ocaml 1541 | { 1542 | foo = 12345; 1543 | bar = 1544 | `List [ 1545 | `Int 12; 1546 | `String "abc"; 1547 | `Assoc [ 1548 | "x", `Float 3.14; 1549 | "y", `Float 0.0; 1550 | "color", `List [ `Float 0.3; `Float 0.0; `Float 1.0 ] 1551 | ] 1552 | ] 1553 | } 1554 | ``` 1555 | 1556 | Corresponding JSON data as obtained with `string_of_t`: 1557 | ``` 1558 | {"foo":12345,"bar":[12,"abc",{"x":3.14,"y":0.0,"color":[0.3,0.0,1.0]}]} 1559 | ``` 1560 | 1561 | 1562 | Section '`doc`' 1563 | --------------- 1564 | 1565 | Unlike comments, `doc` annotations are meant to be 1566 | propagated into the generated source code. This is useful for 1567 | making generated interface files readable without having to consult 1568 | the original ATD file. 1569 | 1570 | Generated source code comments can comply to a standard format and 1571 | take advantage of documentation generators such as javadoc or 1572 | ocamldoc. 1573 | 1574 | 1575 | ### Field '`text`' ### 1576 | 1577 | 1578 | Position: 1579 | 1580 | * after the type name on the left-hand side of a type definition 1581 | * after the type expression on the right hand of a type definition 1582 | (but not after any type expression) 1583 | * after record field names 1584 | * after variant names 1585 | 1586 | 1587 | 1588 | Values: UTF-8-encoded text using a minimalistic markup language 1589 | 1590 | Semantics: The markup language is defined as follows: 1591 | 1592 | * Blank lines separate paragraphs. 1593 | * `{{ }}` can be used to enclose inline verbatim text. 1594 | * `{{{ }}}` can be used to enclose verbatim text where 1595 | whitespace is preserved. 1596 | * The backslash character is used to escape special character sequences. 1597 | In regular paragraph mode the special sequences are `\`, `{{` and `{{{`. 1598 | In inline verbatim text, special sequences are `\` and `}}`. 1599 | In verbatim text, special sequences are `\` and `}}}`. 1600 | 1601 | 1602 | Example: The following is an example demonstrating the use of 1603 | `doc` annotations generated using: 1604 | 1605 | ``` 1606 | $ atdgen -t ocamldoc_example.atd 1607 | ``` 1608 | 1609 | Input file `ocamldoc_example.atd`: 1610 | 1611 | ```ocaml 1612 | 1613 | 1614 | type point = { 1615 | x : float; 1616 | y : float; 1617 | } 1618 | 1624 | 1625 | type color = [ 1626 | | Black 1627 | | White 1628 | | RGB 1629 | 1630 | of (int * int * int) 1631 | ] 1632 | ``` 1633 | 1634 | translates using `atdgen -t ocamldoc_example.atd` 1635 | into the following OCaml interface 1636 | file `ocamldoc_example_t.mli` with ocamldoc-compliant comments: 1637 | 1638 | ```ocaml 1639 | (* Auto-generated from "ocamldoc_example.atd" *) 1640 | 1641 | 1642 | (** This is the title *) 1643 | 1644 | (** 1645 | The type of a point. A value [p] can be created as follows: 1646 | 1647 | {v 1648 | let p = \{ x = 1.2; y = 5.0 \} 1649 | v} 1650 | *) 1651 | type point = { 1652 | x: float (** The first coordinate *); 1653 | y: float (** The second coordinate *) 1654 | } 1655 | 1656 | type color = [ 1657 | `Black (** Same as [RGB (0,0,0)] *) 1658 | | `White (** Same as [RGB (255, 255, 255)] *) 1659 | | `RGB of (int * int * int) (** Red, green, blue components *) 1660 | ] 1661 | ``` 1662 | 1663 | 1664 | 1665 | 1666 | Library 1667 | ======= 1668 | 1669 | A library named `atdgen` is installed by the standard 1670 | installation process. Only a fraction of it is officially supported 1671 | and documented. It is intended for tool developers. Please refer to 1672 | the comments in the [source code of atdgen](https://github.com/mjambon/atdgen). 1673 | 1674 | 1675 | See also 1676 | ======== 1677 | 1678 | [atd](atd-syntax)(1) 1679 | --------------------------------------------------------------------------------