diff --git a/.circleci/setup-system b/.circleci/setup-system index 1eaab0b77..27ef2a5f6 100755 --- a/.circleci/setup-system +++ b/.circleci/setup-system @@ -25,6 +25,10 @@ sudo apt-get install -y \ # For JSON Schema and atdpy testing pip install jsonschema pytest mypy flake8 +# For atdcpp testing +sudo apt install -y \ + rapidjson-dev + # To get latest ldc version source $(curl https://dlang.org/install.sh | bash -s ldc -a) echo "source $(~/dlang/install.sh ldc -a)" >> "$BASH_ENV" diff --git a/CHANGES.md b/CHANGES.md index 1327a3ccc..f57191790 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ Unreleased * atdd: Workaround d compiler bug regarding declaration order when using aliases (#393) Algebraic data types (SumType) now uses `alias this` syntax. * atdgen: Add support for `` in Melange (#401) +* atdcpp: Initial Release 2.15.0 (2023-10-26) ------------------- diff --git a/Makefile b/Makefile index 7afe41dfd..2dfb68b5c 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ DUNE ?= dune all: $(MAKE) -C atdpy clean-for-dune $(MAKE) -C atdd clean-for-dune + $(MAKE) -C atdcpp clean-for-dune $(MAKE) -C atdts clean-for-dune $(DUNE) build @@ -50,6 +51,7 @@ test: $(MAKE) test-python $(MAKE) test-ts $(MAKE) test-d + $(MAKE) test-cpp # Test the OCaml code used by all the backends @@ -94,6 +96,11 @@ test-d: $(MAKE) test-common $(MAKE) -C atdd test +.PHONY: test-cpp +test-cpp: + $(MAKE) test-common + $(MAKE) -C atdcpp test + ############################################################################ .PHONY: js diff --git a/README.md b/README.md index 8b23267c6..63748396d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ cross-language data types. It is used as input to generate efficient and type-safe serializers, deserializers and validators. Target programming languages currently supported: +* C++: [atdcpp](atdcpp) * DLang: [atdd](atdd) * Java: [atdj](atdj) * OCaml, Melange: [atdgen](atdgen) diff --git a/atdcpp.opam b/atdcpp.opam new file mode 100644 index 000000000..aee196f88 --- /dev/null +++ b/atdcpp.opam @@ -0,0 +1,84 @@ +# This file is generated by dune, edit dune-project instead +opam-version: "2.0" +synopsis: "C++ code generation for ATD APIs" +description: "C++ code generation for ATD APIs" +maintainer: [ + "Louis Roché " + "Martin Jambon " + "Rudi Grinberg " +] +authors: [ + "Martin Jambon " + "Rudi Grinberg " + "Martin Jambon " + "Martin Jambon " + "Ivan Jager " + "oleksiy " + "David Sheets " + "Rudi Grinberg " + "Martin Jambon " + "Jeff Meister " + "Caio Wakamatsu " + "Carmelo Piccione " + "Daniel Weil " + "Egor Chemokhonenko " + "Gabriel Scherer " + "Raman Varabets " + "tzm " + "Mathieu Baudet " + "Oleksiy Golovko " + "Rauan Mayemir " + "Carmelo Piccione " + "John Billings " + "Louis Roché " + "Brendan Long " + "Chris Yocum " + "Louis Roché (Ahrefs) " + "Louis Roché " + "Pavel Antoshkin " + "Pierre Boutillier " + "Shon Feder " + "Anurag Soni " + "Arjun Ravi Narayan " + "Asya-kawai " + "Christophe Troestler " + "Damien Doligez " + "Daniel M " + "Ding Xiang Fei " + "François Pottier " + "Javier Chavarri " + "Kate " + "Louis " + "Louis Roché " + "Raman Varabets " + "Stephane Legrand " + "Vincent Bernardoff " + "haoyang " + "pmundkur " + "ygrek " +] +license: "MIT" +homepage: "https://github.com/ahrefs/atd" +bug-reports: "https://github.com/ahrefs/atd/issues" +depends: [ + "dune" {>= "2.8"} + "ocaml" {>= "4.08"} + "atd" {>= "2.11.0"} + "cmdliner" {>= "1.1.0"} + "re" + "odoc" {with-doc} +] +dev-repo: "git+https://github.com/ahrefs/atd.git" +build: [ + ["dune" "subst"] {dev} + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@doc" {with-doc} + ] +] diff --git a/atdcpp/.gitignore b/atdcpp/.gitignore new file mode 100644 index 000000000..5e56e040e --- /dev/null +++ b/atdcpp/.gitignore @@ -0,0 +1 @@ +/bin diff --git a/atdcpp/Makefile b/atdcpp/Makefile new file mode 100644 index 000000000..f7c099bcb --- /dev/null +++ b/atdcpp/Makefile @@ -0,0 +1,36 @@ +# +# Dlang/JSON backend +# + +DUNE ?= dune + +.PHONY: build +build: + rm -f bin/atdcpp + $(MAKE) clean-for-dune + $(DUNE) build @all + mkdir -p bin + ln -s ../../_build/install/default/bin/atdcpp bin/atdcpp + +# The symlink facilitates the development of test code that depends on the +# generated code. +.PHONY: test +test: + $(MAKE) clean-for-dune + $(DUNE) runtest -f; status=$$?; \ + ln -s ../../../_build/default/atdcpp/test/cpp-tests/everything_atd.hpp \ + test/cpp-tests/everything_atd.hpp && \ + ln -s ../../../_build/default/atdcpp/test/cpp-tests/everything_atd.cpp \ + test/cpp-tests/everything_atd.cpp && \ + exit "$$status" + +.PHONY: clean-for-dune +clean-for-dune: + rm -f test/cpp-tests/everything_atd.hpp + rm -f test/cpp-tests/everything_atd.cpp + +.PHONY: clean +clean: + $(MAKE) clean-for-dune + $(DUNE) clean + rm -rf bin diff --git a/atdcpp/README.md b/atdcpp/README.md new file mode 100644 index 000000000..f917558a2 --- /dev/null +++ b/atdcpp/README.md @@ -0,0 +1,50 @@ +atdcpp +== + +atdcpp takes type definitions in the ATD format and derives `C++` +classes that can read and write JSON data. This saves the developer the +labor writing boilerplate that converts between dicts and classes. + +This allows safe interoperability with other languages supported by +ATD such as OCaml, Java, Python or Scala. + +See the sample input type definitions +[everything.atd](test/atd-input/everything.atd) and +the C++ output [everything.hpp](test/cpp-expected/everything.hpp). + +This implementation makes use of the RapidJson C++ library. + +Requirements +-- + +Requirements for building and testing `atdcpp`: +* Opam and dependencies installed from the [`atd` project root](..) + with `make setup`. +* gcc / clang +* librapidjson + +Requirements for generating C++ code: +* the `atdcpp` executable + +Requirements for compiling the generated C++ code: +* A working C++ compiler (gcc / clang) +* The rapidjson library + +Documentation +-- + +* TODO + +Development notes +-- + +Build or rebuild with `make`. Test with `make test`. + +Running the tests is done from the `atdcpp/` main folder with `make +test`. + +We have two kinds of tests for atdcpp: +* code generation and C++ tests: + * they generate C++ code from ATD files and compare the C++ output + against the [expectations](cpp-expected). + * the generated code is executed by some tests. diff --git a/atdcpp/src/bin/Atdcpp_main.ml b/atdcpp/src/bin/Atdcpp_main.ml new file mode 100644 index 000000000..2457fb320 --- /dev/null +++ b/atdcpp/src/bin/Atdcpp_main.ml @@ -0,0 +1,134 @@ +(* + Entry point to the Atdcpp command. +*) + +open Printf +open Cmdliner + +type conf = { + input_files: string list; + version: bool; +} + +let run conf = + if conf.version then ( + print_endline Atdcpp.Version.version; + exit 0 + ) + else + conf.input_files + |> List.iter (fun atd_file -> + Atdcpp.Codegen.run_file atd_file + ) + +(***************************************************************************) +(* Command-line processing *) +(***************************************************************************) + +let error msg = + eprintf "Error: %s\n%!" msg; + exit 1 + +let input_files_term = + let info = + Arg.info [] (* list must be empty for anonymous arguments *) + ~docv:"PATH" + ~doc:"Input file in the ATD format with the '.atd' extension" + in + let default = [] in + Arg.value (Arg.pos_all Arg.file default info) + +let version_term = + let info = + Arg.info ["version"] + ~doc:"Prints the version of Atdcpp and exits" + in + Arg.value (Arg.flag info) + +let doc = + "Type-safe JSON serializers for D" + +(* + The structure of the help page. +*) +let man = [ + (* 'NAME' and 'SYNOPSIS' sections are inserted here by cmdliner. *) + + `S Manpage.s_description; (* standard 'DESCRIPTION' section *) + `P "Atdcpp turns a file containing type definitions into D classes \ + that read, write, and validate JSON data. The generated code \ + can be type-checked statically upon compilation to ensure user code agrees \ + with the ATD interface."; + + (* 'ARGUMENTS' and 'OPTIONS' sections are inserted here by cmdliner. *) + + `S Manpage.s_examples; (* standard 'EXAMPLES' section *) + `P "The following is a sample ATD file. 'sample.atd' becomes 'sample.d' \ + with the command 'Atdcpp sample.atd'."; + `Pre "\ +(* Sample ATD file sample.atd *) + +type foo = { + name: string; (* required field *) + ?description: string option; (* optional field *) + ~tags: string list; (* optional with implicit default *) + ~price : float; (* explicit default *) + items: bar list; +} + +(* sum type *) +type bar = [ + | Thing of int + | Nothing +] +"; + + `S Manpage.s_authors; + `P "Martin Jambon "; + + `S Manpage.s_bugs; + `P "Report issues at https://github.com/ahrefs/atd"; + + `S Manpage.s_see_also; + `P "atdgen, atdj, atds, atdts" +] + +let cmdline_term run = + let combine input_files version = + run { + input_files; + version; + } + in + Term.(const combine + $ input_files_term + $ version_term + ) + +let parse_command_line_and_run run = + let info = + Cmd.info + ~doc + ~man + "Atdcpp" + in + Cmd.v info (cmdline_term run) |> Cmd.eval |> exit + +let safe_run conf = + try run conf + with + (* for other exceptions, we show a backtrace *) + | Failure msg -> error msg + | Atd.Ast.Atd_error msg -> error msg + | e -> + let trace = Printexc.get_backtrace () in + eprintf "Error: exception %s\n%s%!" + (Printexc.to_string e) + trace + +let main () = + Printexc.record_backtrace true; + let conf = parse_command_line_and_run safe_run in + safe_run conf + +let () = main () diff --git a/atdcpp/src/bin/dune b/atdcpp/src/bin/dune new file mode 100644 index 000000000..f854622b6 --- /dev/null +++ b/atdcpp/src/bin/dune @@ -0,0 +1,10 @@ +(executable + (name atdcpp_main) + (public_name atdcpp) + (package atdcpp) + (libraries + cmdliner + atdcpp + atd + ) +) diff --git a/atdcpp/src/lib/Codegen.ml b/atdcpp/src/lib/Codegen.ml new file mode 100644 index 000000000..07c4fed50 --- /dev/null +++ b/atdcpp/src/lib/Codegen.ml @@ -0,0 +1,1615 @@ +(* + cpp code generation for JSON support (no biniou support) + + Takes the contents of a .atd file and translates it to a .d file. + Look into the tests to see what generated code looks like. +*) + +open Printf +open Atd.Ast +open Indent +module A = Atd.Ast +module B = Indent + +(* Mutable environment holding hash tables and such to avoid + naming conflicts. *) +type env = { + (* Global *) + create_variable: string -> string; + translate_variable: string -> string; + (* Local to a struct: instance variables, including method names *) + translate_inst_variable: unit -> (string -> string); +} + + +let annot_schema_cpp : Atd.Annot.schema_section = + { + section = "cpp"; + fields = [ + Type_expr, "t"; + Type_expr, "repr"; + Type_expr, "unwrap"; + Type_expr, "wrap"; + Type_expr, "templatize"; + Field, "default"; + Module_head, "include"; + Module_head, "namespace"; + ] + } + +let annot_schema : Atd.Annot.schema = + annot_schema_cpp :: Atd.Json.annot_schema_json + +(* Translate a preferred variable name into an available cpp identifier. *) +let trans env id = + env.translate_variable id + +(* + Convert an ascii string to CamelCase. + Note that this gets rid of leading and trailing underscores. +*) +let to_camel_case s = + let buf = Buffer.create (String.length s) in + let start_word = ref true in + for i = 0 to String.length s - 1 do + match s.[i] with + | '_' -> + start_word := true + | 'a'..'z' as c when !start_word -> + Buffer.add_char buf (Char.uppercase_ascii c); + start_word := false + | c -> + Buffer.add_char buf c; + start_word := false + done; + let name = Buffer.contents buf in + if name = "" then "X" + else + (* Make sure we don't start with a digit. This happens with + generated identifiers like '_42'. *) + match name.[0] with + | 'A'..'Z' | 'a'..'z' | '_' -> name + | _ -> "X" ^ name + +(* Use CamelCase *) +let struct_name env id = + trans env (to_camel_case id) + +(* + Create a struct identifier that hasn't been seen yet. + This is for internal disambiguation and still must translated using + the 'trans' function ('struct_name' will not work due to trailing + underscores being added for disambiguation). +*) +let create_struct_name env name = + let preferred_id = to_camel_case name in + env.create_variable preferred_id + +let init_env () : env = + let keywords = [ + (* Keywords + https://cpp.org/spec/lex.html#keywords + *) + "abstract";"alias";"align";"asm";"assert";"auto";"body";"bool"; + "break";"byte";"case";"cast";"catch";"cdouble";"cent";"cfloat"; + "char";"class";"const";"continue";"creal";"dchar";"debug";"default"; + "delegate";"delete";"deprecated";"do";"double";"else";"enum";"export"; + "extern";"false";"final";"finally";"float";"for";"foreach";"foreach_reverse"; + "function";"goto";"idouble";"if";"ifloat";"immutable";"import";"in"; + "inout";"int";"interface";"invariant";"ireal";"is";"lazy";"long";"macro"; + "mixin";"module";"new";"nothrow";"null";"out";"override";"package";"pragma"; + "private";"protected";"public";"pure";"real";"ref";"return";"scope";"shared"; + "short";"static";"struct";"super";"switch";"synchronized";"template";"this"; + "throw";"true";"try";"typeid";"typeof";"ubyte";"ucent";"uint";"ulong";"union"; + "unittest";"ushort";"version";"void";"wchar";"while";"with";"__FILE__";"__FILE_FULL_PATH__"; + "__MODULE__";"__LINE__";"__FUNCTION__";"__PRETTY_FUNCTION__";"__gshared"; + "__traits";"__vector";"__parameters"; + ] + in + (* Various variables used in the generated code. + Lowercase variables in this list are superfluous as long as all generated + variables either start with '_', 'atd_', or an uppercase letter. + *) + let reserved_variables = [ + (* from typing *) + "Any"; "Callable"; "Dict"; "List"; "Optional"; "Tuple"; + + (* for use in json.dumps, json.loads etc. *) + "json"; + + (* exceptions *) + "ValueError"; + + (* used to check JSON node type *) + "isinstance"; + "bool"; "int"; "float"; "str"; "dict"; "list"; "tuple"; + + (* other built-in variables *) + "self"; "cls"; "repr"; + ] in + let variables = + Atd.Unique_name.init + ~reserved_identifiers:(reserved_variables @ keywords) + ~reserved_prefixes:["atd_"; "_atd_"] + ~safe_prefix:"x_" + in + let method_names () = + Atd.Unique_name.init + ~reserved_identifiers:( + ["fromJson"; "toJson"; + "fromJsonString"; "toJsonString"] + @ keywords + ) + ~reserved_prefixes:["__"] + ~safe_prefix:"x_" + in + let create_variable name = + Atd.Unique_name.create variables name + in + let translate_variable id = + Atd.Unique_name.translate variables id + in + let translate_inst_variable () = + let u = method_names () in + fun id -> Atd.Unique_name.translate u id + in + { + create_variable; + translate_variable; + translate_inst_variable; + } + +type quote_kind = Single | Double + +(* Escape a string fragment to be placed in single quotes or double quotes. + https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals +*) +let escape_string_content quote_kind s = + let buf = Buffer.create (String.length s + 2) in + for i = 0 to String.length s - 1 do + match s.[i], quote_kind with + | '\n', _ -> Buffer.add_string buf "\\n" + | '\\', _ -> Buffer.add_string buf "\\\\" + | '\'', Single -> Buffer.add_string buf "\\'" + | '"', Double -> Buffer.add_string buf "\\\"" + | c, (Single | Double) -> Buffer.add_char buf c + done; + Buffer.contents buf + +let single_esc s = + escape_string_content Single s + +let _double_esc s = + escape_string_content Double s + +let fixed_size_preamble_header atd_filename = + sprintf {| +// Generated by atdcpp from type definitions in %s. +// This implements classes for the types defined in '%s', providing +// methods and functions to convert data from/to JSON. + +// ############################################################################ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +|} atd_filename atd_filename + +let fixed_size_preamble atd_filename namespace_name = + sprintf {| +// Generated by atdcpp from type definitions in %s. +// This implements classes for the types defined in '%s', providing +// methods and functions to convert data from/to JSON. + +// ############################################################################ +// # Private functions +// ############################################################################ + +// filename %s + +#include "%s_atd.hpp" + +namespace %s +{ + +namespace // anonymous +{ + +std::string _rapid_json_type_to_string(rapidjson::Type type) +{ + switch (type) + { + case rapidjson::kNullType: + return "null"; + case rapidjson::kFalseType: + return "false"; + case rapidjson::kTrueType: + return "true"; + case rapidjson::kObjectType: + return "object"; + case rapidjson::kArrayType: + return "array"; + case rapidjson::kStringType: + return "string"; + case rapidjson::kNumberType: + return "number"; + default: + return "unknown"; + } +} + +class AtdException : public std::exception +{ +public: + AtdException(const std::string &message) : msg_(message) {} + + const char *what() const throw() override + { + return msg_.c_str(); + } + +private: + std::string msg_; +}; + +template +[[maybe_unused]] T _atd_missing_json_field(const std::string &type, const std::string &field) +{ + throw AtdException("Missing JSON field '" + field + "' in " + type); +} + +[[maybe_unused]] auto _atd_bad_json(const std::string &type, const rapidjson::Value &x) +{ + auto x_type = x.GetType(); + return AtdException("Bad JSON for " + type + " got type " + _rapid_json_type_to_string(x_type)); +} + +// Reading an integer from JSON +[[maybe_unused]] int _atd_read_int(const rapidjson::Value &val) +{ + if (!val.IsInt()) + { + throw AtdException("Expected an integer but got" + _rapid_json_type_to_string(val.GetType())); + } + return val.GetInt(); +} + +[[maybe_unused]] bool _atd_read_bool(const rapidjson::Value &val) +{ + if (!val.IsBool()) + { + throw AtdException("Expected a boolean but got" + _rapid_json_type_to_string(val.GetType())); + } + return val.GetBool(); +} + +// Reading a float from JSON +[[maybe_unused]] float _atd_read_float(const rapidjson::Value &val) +{ + if (val.IsInt()) + { + return static_cast(val.GetInt()); + } + else if (val.IsUint()) + { + return static_cast(val.GetUint()); + } + if (!val.IsFloat()) + { + throw AtdException("Expected a float but got" + _rapid_json_type_to_string(val.GetType())); + } + + return val.GetFloat(); +} + +[[maybe_unused]] std::string _atd_read_string(const rapidjson::Value &val) +{ + if (!val.IsString()) + { + throw AtdException("Expected a string but got" + _rapid_json_type_to_string(val.GetType())); + } + return val.GetString(); +} + +[[maybe_unused]] std::string _atd_read_abstract(const rapidjson::Value &val) +{ + // will convert val to string + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + val.Accept(writer); + return buffer.GetString(); +} + +template +[[maybe_unused]] auto _atd_read_array(F read_func, const rapidjson::Value &val) +{ + using ResultType = typename std::invoke_result::type; + + if (!val.IsArray()) + { + throw AtdException("Expected an array but got" + _rapid_json_type_to_string(val.GetType())); + } + + std::vector result; + for (rapidjson::SizeType i = 0; i < val.Size(); i++) + { + result.push_back(read_func(val[i])); + } + + return result; +} + +template +[[maybe_unused]] auto _atd_read_object_to_tuple_list(F read_func, const rapidjson::Value &val) +{ + using ResultType = typename std::invoke_result::type; + + if (!val.IsObject()) + { + throw AtdException("Expected an object but got" + _rapid_json_type_to_string(val.GetType())); + } + + std::vector> result; + for (auto &m : val.GetObject()) + { + result.push_back(std::make_tuple(m.name.GetString(), read_func(m.value))); + } + + return result; +} + +template +[[maybe_unused]] auto _atd_read_array_to_assoc_dict(RK read_key_func, RV read_value_func, const rapidjson::Value &val) +{ + using KeyType = typename std::invoke_result::type; + using ValueType = typename std::invoke_result::type; + + if (!val.IsArray()) + { + throw AtdException("Expected an array but got" + _rapid_json_type_to_string(val.GetType())); + } + + std::map result; + for (rapidjson::SizeType i = 0; i < val.Size(); i++) + { + auto &pair = val[i]; + if (!pair.IsArray() || pair.Size() != 2) + { + throw AtdException("Expected an array of pairs"); + } + result[read_key_func(pair[0])] = read_value_func(pair[1]); + } + + return result; +} + +template +[[maybe_unused]] auto _atd_read_object_to_assoc_array(F read_func, const rapidjson::Value &val) +{ + using ResultType = typename std::invoke_result::type; + + if (!val.IsObject()) + { + throw AtdException("Expected an object but got" + _rapid_json_type_to_string(val.GetType())); + } + + std::map result; + for (auto &m : val.GetObject()) + { + result[m.name.GetString()] = read_func(m.value); + } + + return result; +} + +template +[[maybe_unused]] auto _atd_read_nullable(F read_func, const rapidjson::Value &val) +{ + if (val.IsNull()) + { + return std::optional::type>(); + } + return std::optional::type>(read_func(val)); +} + +template +[[maybe_unused]] auto _atd_read_option(F read_func, const rapidjson::Value &val) +{ + using ResultType = typename std::invoke_result::type; + if (val.IsString() && std::string_view(val.GetString()) == "None") + { + return std::optional(); + } + else if (val.IsArray() && val.Size() == 2 && val[0].IsString() && std::string_view(val[0].GetString()) == "Some") + { + return std::make_optional(read_func(val[1])); + } + else + { + throw AtdException("Expected an option"); + } +} + +template +[[maybe_unused]] auto _atd_read_wrap(F read_func, W wrap_func, const rapidjson::Value &val) +{ + return wrap_func(read_func(val)); +} + +template +[[maybe_unused]] std::shared_ptr _atd_wrap_shared_ptr(T val) +{ + return std::make_shared(val); +} + +template +[[maybe_unused]] T _atd_unwrap_shared_ptr(const std::shared_ptr val) +{ + if (!val) + { + throw AtdException("Expected a shared_ptr but got nullptr"); + } + return *val; +} + +[[maybe_unused]] void _atd_write_int(int value, rapidjson::Writer& writer) +{ + writer.Int(value); +} + +[[maybe_unused]] void _atd_write_bool(bool value, rapidjson::Writer& writer) +{ + writer.Bool(value); +} + +[[maybe_unused]] void _atd_write_float(float value, rapidjson::Writer& writer) +{ + writer.Double(value); +} + +[[maybe_unused]] void _atd_write_string(const std::string &value, rapidjson::Writer& writer) +{ + writer.String(value.c_str()); +} + +[[maybe_unused]] void _atd_write_abstract(const std::string &value, rapidjson::Writer& writer) +{ + // writes string value as raw json + writer.RawValue(value.c_str(), value.size(), rapidjson::kStringType); +} + +template +[[maybe_unused]] void _atd_write_array(F write_func, const V& values, rapidjson::Writer& writer) +{ + writer.StartArray(); + for (const auto& value : values) + { + write_func(value, writer); + } + writer.EndArray(); +} + +template +[[maybe_unused]] void _atd_write_tuple_list_to_object(F write_func, const V &values, rapidjson::Writer& writer) +{ + writer.StartObject(); + for (const auto& value : values) + { + writer.Key(std::get<0>(value).c_str()); + write_func(std::get<1>(value), writer); + } + writer.EndObject(); +} + +template +[[maybe_unused]] void _atd_write_assoc_dict_to_array(const Wk write_key_func, const Wv write_value_func, const Map &value_map, rapidjson::Writer& writer) +{ + writer.StartArray(); + for (const auto& pair : value_map) + { + writer.StartArray(); + write_key_func(pair.first, writer); + write_value_func(pair.second, writer); + writer.EndArray(); + } + writer.EndArray(); +} + +template +[[maybe_unused]] void _atd_write_assoc_array_to_object(F write_func, const Map &value_map, rapidjson::Writer& writer) +{ + writer.StartObject(); + for (const auto& pair : value_map) + { + writer.Key(pair.first.c_str()); + write_func(pair.second, writer); + } + writer.EndObject(); +} + + +template +[[maybe_unused]] void _atd_write_option(F write_func, const O &val, rapidjson::Writer& writer) +{ + if (val) + { + writer.StartArray(); + writer.String("Some"); + write_func(*val, writer); + writer.EndArray(); + } + else + { + writer.String("None"); + } +} + +template +[[maybe_unused]] void _atd_write_nullable(F write_func, const O &val, rapidjson::Writer& writer) +{ + if (val) + { + write_func(*val, writer); + } + else + { + writer.Null(); + } +} + +template +[[maybe_unused]] void _atd_write_wrap(F write_func, W wrap_func, const T &val, rapidjson::Writer& writer) +{ + write_func(wrap_func(val), writer); +} +} // anonymous namespace + |} + atd_filename + atd_filename + atd_filename + (Filename.chop_extension atd_filename) + namespace_name + + +let not_implemented loc msg = + A.error_at loc ("not implemented in atdcpp: " ^ msg) + +let spaced ?(spacer = [Line ""]) (blocks : B.node list) : B.node list = + let rec spaced xs = + match List.filter (fun x -> not (B.is_empty_node x)) xs with + | [] + | [_] as xs -> xs + | a :: rest -> a :: spacer @ spaced rest + in + spaced blocks + +let double_spaced blocks = + spaced ~spacer:[Line ""; Line ""] blocks + +(* + Representations of ATD type '(string * value) list' in JSON and cpp. + Key type or value type are provided when it's useful. +*) +type assoc_kind = + | Array_list (* default representation; possibly not even a list of pairs *) + | Array_dict of type_expr * type_expr (* key type, value type *) + (* Keys in JSON objects are always of type string. *) + | Object_dict of type_expr (* value type *) + | Object_list of type_expr (* value type *) + +let assoc_kind loc (e : type_expr) an : assoc_kind = + let json_repr = Atd.Json.get_json_list an in + let cpp_repr = Cpp_annot.get_cpp_assoc_repr an in + match e, json_repr, cpp_repr with + | Tuple (loc, [(_, key, _); (_, value, _)], an2), Array, Dict -> + Array_dict (key, value) + | Tuple (loc, + [(_, Name (_, (_, "string", _), _), _); (_, value, _)], an2), + Object, Dict -> + Object_dict value + | Tuple (loc, + [(_, Name (_, (_, "string", _), _), _); (_, value, _)], an2), + Object, List -> Object_list value + | _, Array, List -> Array_list + | _, Object, _ -> error_at loc "not a (string * _) list" + | _, Array, _ -> error_at loc "not a (_ * _) list" + +(* Map ATD built-in types to built-in cpp types *) +let cpp_type_name env (name : string) = + match name with + | "unit" -> "void" + | "bool" -> "bool" + | "int" -> "int" + | "float" -> "float" + | "string" -> "std::string" + | "abstract" -> "std::string" + | user_defined -> + let typename = (struct_name env user_defined) in + typename + +let cpp_type_name_namespaced env (name : string) = + match name with + | "unit" -> "void" + | "bool" -> "bool" + | "int" -> "int" + | "float" -> "float" + | "string" -> "std::string" + | "abstract" -> "std::string" + | user_defined -> + let typename = (struct_name env user_defined) in + sprintf "%s::%s" "typedefs" typename + +let rec type_name_of_expr env (e : type_expr) : string = + match e with + | Sum (loc, _, _) -> not_implemented loc "inline sum types" + | Record (loc, _, _) -> not_implemented loc "inline records" + | Tuple (loc, xs, an) -> + let type_names = + xs + |> List.map (fun (loc, x, an) -> type_name_of_expr env x) + in + sprintf "std::tuple<%s>" (String.concat ", " type_names) + | List (loc, e, an) -> + (match assoc_kind loc e an with + | Array_list + | Object_list _ -> + sprintf "std::vector<%s>" + (type_name_of_expr env e) + | Array_dict (key, value) -> + sprintf "std::map<%s, %s>" + (type_name_of_expr env key) + (type_name_of_expr env value) + | Object_dict value -> + sprintf "std::map" + (type_name_of_expr env value) + ) + | Option (loc, e, an) -> sprintf "std::optional<%s>" (type_name_of_expr env e) + | Nullable (loc, e, an) -> sprintf "std::optional<%s>" (type_name_of_expr env e) + | Shared (loc, e, an) -> not_implemented loc "shared" (* TODO *) + | Wrap (loc, e, an) -> + (match Cpp_annot.get_cpp_wrap loc an with + | None -> error_at loc "wrap type declared, but no cpp annotation found" + | Some { cpp_wrap_t ; cpp_templatize=false; _ } -> cpp_wrap_t + | Some { cpp_wrap_t ; cpp_templatize=true; _ } -> sprintf "%s<%s>" cpp_wrap_t (type_name_of_expr env e) + ) + | Name (loc, (loc2, name, []), an) -> cpp_type_name_namespaced env name + | Name (loc, (_, name, _::_), _) -> assert false + | Tvar (loc, _) -> not_implemented loc "type variables" + +let rec get_default_default (e : type_expr) : string option = + match e with + | Sum _ + | Record _ + | Tuple _ (* a default tuple could be possible but we're lazy *) -> None + | List _ -> Some "{}" + | Option _ + | Nullable _ -> Some "std::nullopt" + | Shared (loc, e, an) -> get_default_default e + | Wrap (loc, e, an) -> get_default_default e + | Name (loc, (loc2, name, []), an) -> + (match name with + | "unit" -> None + | "bool" -> Some "false" + | "int" -> Some "0" + | "float" -> Some "0.0f" + | "string" -> Some {|""|} + | "abstract" -> None + | _ -> None + ) + | Name _ -> None + | Tvar _ -> None + + +type cpp_default = | User_default of string | Default_default of string | No_default + +let get_cpp_default (e : type_expr) (an : annot) : cpp_default = + let user_default = Cpp_annot.get_cpp_default an in + match user_default with + | Some s -> User_default s + | None -> (match get_default_default e with + | Some s -> Default_default s + | None -> No_default) + +(* If the field is '?foo: bar option', its cpp or json value has type + 'bar' rather than 'bar option'. *) +let unwrap_field_type loc field_name kind e = (* todo : dubious for cpp*) + match kind with + | Required + | With_default -> e + | Optional -> + match e with + | Option (loc, e, an) -> e + | _ -> + A.error_at loc + (sprintf "the type of optional field '%s' should be of \ + the form 'xxx option'" field_name) + +(* + Instance variable that's really the name of the getter method created + by @dataclass. It can't start with '__' as those are reserved for + internal magic. The 'trans_meth' translator must take care of this. +*) +let inst_var_name trans_meth field_name = + trans_meth field_name + +let rec json_writer ?(nested=false) env e = + match e with + | Sum (loc, _, _) -> not_implemented loc "inline sum types" + | Record (loc, _, _) -> not_implemented loc "inline records" + | Tuple (loc, cells, an) -> tuple_writer env (loc, cells, an) + | List (loc, e, an) -> + (match assoc_kind loc e an with + | Array_list -> + sprintf "_atd_write_array([](auto v, auto &w){%sv, w);}, " (json_writer ~nested:true env e) + | Array_dict (key, value) -> + sprintf "_atd_write_assoc_dict_to_array([](auto v, auto &w){%sv, w);}, [](auto v, auto &w){%sv, w);}, " + (json_writer ~nested:true env key) (json_writer ~nested:true env value) + | Object_dict value -> + sprintf "_atd_write_assoc_array_to_object([](auto v, auto &w){%sv, w);}, " + (json_writer ~nested:true env value) + | Object_list value -> + sprintf "_atd_write_tuple_list_to_object([](auto v, auto &w){%sv, w);}, " + (json_writer ~nested:true env value) + ) + | Option (loc, e, an) -> + sprintf "_atd_write_option([](auto v, auto &w){%sv, w);}, "(json_writer ~nested:true env e) + | Nullable (loc, e, an) -> + sprintf "_atd_write_nullable([](auto v, auto &w){%sv, w);}, " (json_writer ~nested:true env e) + | Shared (loc, e, an) -> not_implemented loc "shared" + | Wrap (loc, e, an) -> + (match Cpp_annot.get_cpp_wrap loc an with + | None -> error_at loc "wrap type declared, but no cpp annotation found" + | Some { cpp_wrap_t; cpp_unwrap ; _ } -> + sprintf "_atd_write_wrap([](const auto &v, auto &w){%sv, w);}, [](const auto &e){return %s(e);}, " (json_writer ~nested:true env e) cpp_unwrap + ) + | Name (loc, (loc2, name, []), an) -> + (match name with + | "bool" | "int" | "float" | "string" | "abstract" -> sprintf "_atd_write_%s(" name + | _ -> let dtype_name = (cpp_type_name env name) in + sprintf "%s::to_json(" dtype_name) + | Name (loc, _, _) -> not_implemented loc "parametrized types" + | Tvar (loc, _) -> not_implemented loc "type variables" + +and tuple_writer env (loc, cells, an) = + let tuple_body = + List.mapi (fun i (loc, e, an) -> + sprintf "%sstd::get<%i>(t), writer)" (json_writer env e) i + ) cells + |> String.concat "; " + in + sprintf "[](const auto &t, auto &writer){ + writer.StartArray(); + %s; + writer.EndArray(); + }(" + tuple_body + +let get_is_set_to_default env an e field = + let cpp_default = get_cpp_default e an in + let cpp_default_str = match cpp_default with | User_default x | Default_default x -> x | No_default -> "" in + let default_format = sprintf "%s != %s(%s)" field (type_name_of_expr env e) cpp_default_str in + match e with + | List _ -> (match cpp_default with + | User_default _ -> default_format + | Default_default _ | No_default -> sprintf "!%s.empty()" field + ) + | _ -> default_format + +let construct_json_field env trans_meth + ((loc, (name, kind, an), e) : simple_field) = + let unwrapped_type = unwrap_field_type loc name kind e in + let writer_function n = json_writer ~nested:n env unwrapped_type in + let cpp_var_name = inst_var_name trans_meth name in + let assignement = + [ + Line (sprintf "writer.Key(\"%s\");" + (Atd.Json.get_json_fname name an |> single_esc)); + Line (sprintf "%st.%s, writer);" (writer_function false) (inst_var_name trans_meth name)); + ] + in + match kind with + | Required -> assignement + | With_default -> + [ + Line (sprintf "if (%s) {" (get_is_set_to_default env an e (sprintf "t.%s" cpp_var_name))); + Block assignement; + Line "}" + ] + | Optional -> + [ + Line (sprintf "if (t.%s != std::nullopt) {" cpp_var_name); + Block [ + Line (sprintf "writer.Key(\"%s\");" + (Atd.Json.get_json_fname name an |> single_esc)); + Line (sprintf "%st.%s.value(), writer);" (writer_function true) cpp_var_name) + ]; + Line "}"; + ] + +(* + Function value that can be applied to a JSON node, converting it + to the desired value. +*) +let rec json_reader ?(nested=false) env (e : type_expr) = + match e with + | Sum (loc, _, _) -> not_implemented loc "inline sum types" + | Record (loc, _, _) -> not_implemented loc "inline records" + | Tuple (loc, cells, an) -> tuple_reader env cells + | List (loc, e, an) -> + (* ATD lists of pairs can be represented as objects in JSON or + as dicts in Python. All 4 combinations are supported. + The default is to use JSON arrays and Python lists. *) + (match assoc_kind loc e an with + | Array_list -> + sprintf "_atd_read_array([](const auto &v){return %sv);}, " + (json_reader ~nested:true env e) + | Array_dict (key, value) -> + sprintf "_atd_read_array_to_assoc_dict([](const auto &k){return %sk);}, [](const auto &v){return %sv);}, " + (json_reader ~nested:true env key) (json_reader ~nested:true env value) + | Object_dict value -> + sprintf "_atd_read_object_to_assoc_array([](const auto &v){return %sv);}," + (json_reader ~nested:true env value) + | Object_list value -> + sprintf "_atd_read_object_to_tuple_list([](const auto &v){return %sv);}," + (json_reader ~nested:true env value) + ) + | Option (loc, e, an) -> + sprintf "_atd_read_option([](const auto &v){return %sv);}, " (json_reader ~nested:true env e) + | Nullable (loc, e, an) -> + sprintf "_atd_read_nullable([](const auto &v){return %sv);}, " (json_reader ~nested:true env e) + | Shared (loc, e, an) -> not_implemented loc "shared" + | Wrap (loc, e, an) -> + (match Cpp_annot.get_cpp_wrap loc an with + | None -> error_at loc "wrap type declared, but no cpp annotation found" + | Some { cpp_wrap ; _ } -> + sprintf "_atd_read_wrap([](const auto& v){return %sv);}, [](const auto &e){return %s(e);}," (json_reader ~nested:true env e) cpp_wrap + ) + | Name (loc, (loc2, name, []), an) -> + (match name with + | "bool" | "int" | "float" | "string" | "abstract" -> sprintf "_atd_read_%s(" name + | _ -> sprintf "%s::from_json(" + (struct_name env name) + ) + | Name (loc, _, _) -> not_implemented loc "parametrized types" + | Tvar (loc, _) -> not_implemented loc "type variables" + +and tuple_reader env cells = + let tuple_body = + List.mapi (fun i (loc, e, an) -> + sprintf "%sv[%i])" (json_reader env e) i + ) cells + |> String.concat ", " + in + sprintf "[](auto &v){ + if (!v.IsArray() || v.Size() != %d) + throw AtdException(\"Tuple of size %d\"); + return std::make_tuple(%s); + }(" + (List.length cells) (List.length cells) tuple_body + +let from_json_class_argument + env trans_meth cpp_struct_name ((loc, (name, kind, an), e) : simple_field) = + let cpp_name = inst_var_name trans_meth name in + let json_name = Atd.Json.get_json_fname name an in + let else_body = + match kind with + | Required -> + sprintf "_atd_missing_json_field(\"%s\", \"%s\")" + cpp_name + (single_esc cpp_struct_name) + (single_esc json_name) + | Optional -> (sprintf "std::nullopt") + | With_default -> + match get_cpp_default e an with + | User_default x | Default_default x -> x + | No_default -> + A.error_at loc + (sprintf "missing default cpp value for field '%s'" + name) + in + let reader = + match kind with + | Optional -> (match e with + | Option(_, e, _) -> (json_reader env e) + | _ -> A.error_at loc "optional field must be of type 'xxx option'") + | _ -> (json_reader env e) + in + Inline [ + Line (sprintf "if (doc.HasMember(\"%s\"))" (single_esc json_name)); + Block [ + Line (sprintf "record.%s = %sdoc[\"%s\"]);" + cpp_name + reader + (single_esc json_name)); + ]; + Line (sprintf "else record.%s = %s;" cpp_name else_body);] + +let inst_var_declaration + env trans_meth ((loc, (name, kind, an), e) : simple_field) = + let var_name = inst_var_name trans_meth name in + let type_name = type_name_of_expr env e in + let unwrapped_e = unwrap_field_type loc name kind e in + let default = + match kind with + | Required + | Optional -> "" + | With_default -> + match get_cpp_default unwrapped_e an with + | No_default -> "" + | User_default x | Default_default x -> sprintf " = %s" x + in + [ + Line (sprintf "%s %s%s;" type_name var_name default) + ] + +let from_json_string_declaration cpp_name static = + let static_val = match static with | true -> "static " | false -> "" in + [ + Line (sprintf "%s%s from_json_string(const std::string &s);" static_val cpp_name); + ] + +let from_json_string_definition cpp_name p = + [ + Line (sprintf "%s %sfrom_json_string(const std::string &s) {" cpp_name (match p with | Some x -> sprintf "%s::" x | None -> "")); + Block [ + Line "rapidjson::Document doc;"; + Line "doc.Parse(s.c_str());"; + Line "if (doc.HasParseError()) {"; + Block [Line "throw AtdException(\"Failed to parse JSON\");"]; + Line "}"; + Line "return from_json(doc);"; + ]; + Line "}"; + ] + +type codegen_type = + | Declaration + | Definition + | Forward_decl + | Struct_typedef + | Alias_typedef + | Variant_typedef + +let record_definition env loc name fields an = + let cpp_struct_name = struct_name env name in + let trans_meth = env.translate_inst_variable () in + let from_json_class_arguments = + List.map (fun x -> + (from_json_class_argument env trans_meth cpp_struct_name x) + ) fields in + let json_object_body = + List.map (fun x -> + Inline (construct_json_field env trans_meth x)) fields in + let from_json = + [ + Line (sprintf "%s %s::from_json(const rapidjson::Value & doc) {" + (single_esc cpp_struct_name) (single_esc cpp_struct_name)); + Block [ + Line (sprintf "%s record;" cpp_struct_name); + Line "if (!doc.IsObject()) {"; + Block [ + Line (sprintf "throw AtdException(\"atdtype: %s, expected an object but got\" + _rapid_json_type_to_string(doc.GetType()));" name); + ]; + Line "}"; + Inline from_json_class_arguments; + Line "return record;"; + ]; + Line "}"; + ] + in + let to_json = + [ + Line (sprintf "void %s::to_json(const %s &t, rapidjson::Writer &writer) {" (single_esc cpp_struct_name) (single_esc cpp_struct_name)); + Block [ + Line ("writer.StartObject();"); + Inline json_object_body; + Line ("writer.EndObject();"); + ]; + Line "}"; + ] + in + let to_json_string_static = + [ + Line (sprintf "std::string %s::to_json_string(const %s &t) {" (single_esc cpp_struct_name) (single_esc cpp_struct_name)); + Block [ + Line ("rapidjson::StringBuffer buffer;"); + Line ("rapidjson::Writer writer(buffer);"); + Line ("to_json(t, writer);"); + Line "return buffer.GetString();" + ]; + Line "}"; + ] + in + let to_json_string = + [ + Line (sprintf "std::string %s::to_json_string() {" (single_esc cpp_struct_name)); + Block [ + Line ("return to_json_string(*this);"); + ]; + Line "}"; + ] + in + [ + Inline from_json; + Inline (from_json_string_definition cpp_struct_name (Some cpp_struct_name)); + Inline to_json; + Inline to_json_string_static; + Inline to_json_string; + ] + + +let record env loc name (fields : field list) an codegen_type = + let cpp_struct_name = struct_name env name in + let trans_meth = env.translate_inst_variable () in + let fields_l = + List.map (function + | `Field x -> x + | `Inherit _ -> (* expanded at loading time *) assert false) + fields + in + let inst_var_declarations = + List.map (fun x -> Inline (inst_var_declaration env trans_meth x)) fields_l + in + let from_json = + [ + Line (sprintf "static %s from_json(const rapidjson::Value & doc);" + (single_esc cpp_struct_name)); + ] + in + let to_json = + [ + Line (sprintf "static void to_json(const %s &t, rapidjson::Writer &writer);" (single_esc cpp_struct_name)); + ] + in + let to_json_string_static = + [ + Line (sprintf "static std::string to_json_string(const %s &t);" (single_esc cpp_struct_name)); + ] + in + let to_json_string = + [ + Line (sprintf "std::string to_json_string();" ); + ] + in + match codegen_type with + | Declaration -> + [ + Line (sprintf "struct %s {" cpp_struct_name); + Block ([ + Inline inst_var_declarations; + Line (""); + Inline from_json; + Inline (from_json_string_declaration cpp_struct_name true); + Inline to_json; + Inline to_json_string_static; + Inline to_json_string; + ]); + Line ("};"); + ] + | Definition -> record_definition env loc name fields_l an + | Forward_decl -> [Line (sprintf "struct %s;" cpp_struct_name)] + | Struct_typedef -> [Line (sprintf "typedef %s %s;" cpp_struct_name cpp_struct_name)] + | _ -> [] + +let alias_wrapper env name type_expr codegen_type = + let cpp_struct_name = struct_name env name in + match codegen_type with + | Declaration -> + [ + Line (sprintf "namespace %s {" cpp_struct_name); + Block [ + Line (sprintf "typedefs::%s from_json(const rapidjson::Value &doc);" cpp_struct_name); + Inline (from_json_string_declaration (sprintf "typedefs::%s" cpp_struct_name) false); + Line (sprintf "void to_json(const typedefs::%s &t, rapidjson::Writer &writer);" cpp_struct_name); + Line (sprintf "std::string to_json_string(const typedefs::%s &t);" cpp_struct_name); + ]; + Line "}"; + ] + | Definition -> + [ + Line (sprintf "namespace %s {" cpp_struct_name); + Block [ + Line (sprintf "typedefs::%s from_json(const rapidjson::Value &doc) {" cpp_struct_name); + Block [ + Line (sprintf "return %sdoc);" (json_reader env type_expr)); + ]; + Line "}"; + Inline (from_json_string_definition (sprintf "typedefs::%s" cpp_struct_name) None); + Line (sprintf "void to_json(const typedefs::%s &t, rapidjson::Writer &writer) {" cpp_struct_name); + Block [ + Line (sprintf "%st, writer);" (json_writer env type_expr)); + ]; + Line "}"; + Line (sprintf "std::string to_json_string(const typedefs::%s &t) {" cpp_struct_name); + Block [ + Line ("rapidjson::StringBuffer buffer;"); + Line ("rapidjson::Writer writer(buffer);"); + Line (sprintf "to_json(t, writer);"); + Line "return buffer.GetString();" + ]; + Line "}"; ]; + Line "}"; + ] + | Alias_typedef -> [Line (sprintf "typedef %s %s;" (type_name_of_expr env type_expr) cpp_struct_name)] + | _ -> [] + + +let case_class env type_name (loc, orig_name, unique_name, an, opt_e) case_classes = + let json_name = Atd.Json.get_json_cons orig_name an in + match case_classes with + | Declaration -> (match opt_e with + | None -> + [ + Line (sprintf {|// Original type: %s = [ ... | %s | ... ]|} + type_name + orig_name); + Line (sprintf "struct %s {" (trans env orig_name)); + Block [Line (sprintf "static void to_json(const %s &e, rapidjson::Writer &writer);" (trans env orig_name));]; + Line (sprintf "};"); + ] + | Some e -> + [ + Line (sprintf {|// Original type: %s = [ ... | %s of ... | ... ]|} + type_name + orig_name); + Line (sprintf "struct %s" (trans env orig_name)); + Line(sprintf "{"); + Block [ + Line (sprintf "%s value;" (type_name_of_expr env e)); + Line (sprintf "static void to_json(const %s &e, rapidjson::Writer &writer);" (trans env orig_name)); + ]; + Line(sprintf "};"); + ]) + | Definition -> (match opt_e with + | None -> + [ + Line (sprintf "void %s::to_json(const %s &e, rapidjson::Writer &writer){" (trans env orig_name) (trans env orig_name)); + Block [ + Line (sprintf "writer.String(\"%s\");" (single_esc json_name)); + ]; + Line (sprintf "};"); + ] + | Some e -> + [ + Line (sprintf "void %s::to_json(const %s &e, rapidjson::Writer &writer){" (trans env orig_name) (trans env orig_name)); + Block [ + Line (sprintf "writer.StartArray();"); + Line (sprintf "writer.String(\"%s\");" (single_esc json_name)); + Line (sprintf "%se.value, writer);" (json_writer env e)); + Line (sprintf "writer.EndArray();"); + ]; + Line("}"); + ]) + | _ -> [] + + + +let read_cases0 env loc name cases0 sum_repr = + let ifs = + cases0 + |> List.map (fun (loc, orig_name, unique_name, an, opt_e) -> + let json_name = Atd.Json.get_json_cons orig_name an in + Inline [ + Line (sprintf "if (std::string_view(x.GetString()) == \"%s\") " (single_esc json_name)); + Block [ + Line (sprintf "return Types::%s%s;" (trans env orig_name) (match sum_repr with | Cpp_annot.Variant -> "()" | Cpp_annot.Enum -> "")) + ]; + ] + ) + in + [ + Inline ifs; + Line (sprintf "throw _atd_bad_json(\"%s\", x);" + (struct_name env name |> single_esc)) + ] + +let read_cases1 env loc name cases1 = + let ifs = + cases1 + |> List.map (fun (loc, orig_name, unique_name, an, opt_e) -> + let e = + match opt_e with + | None -> assert false + | Some x -> x + in + let json_name = Atd.Json.get_json_cons orig_name an in + Inline [ + Line (sprintf "if (cons == \"%s\")" (single_esc json_name)); + Block [ + Line (sprintf "return Types::%s({%sx[1])});" + (trans env orig_name) + (json_reader env e)) + ] + ] + ) + in + [ + Inline ifs; + Line (sprintf "throw _atd_bad_json(\"%s\", x);" + (struct_name env name |> single_esc)) + ] + +let sum_container env loc name cases codegen_type = + let cpp_struct_name = struct_name env name in + let cases0, cases1 = + List.partition (fun (loc, orig_name, unique_name, an, opt_e) -> + opt_e = None + ) cases + in + let cases0_block = + if cases0 <> [] then + [ + Line "if (x.IsString()) {"; + Block (read_cases0 env loc name cases0 Cpp_annot.Variant); + Line "}"; + ] + else + [] + in + let cases1_block = + if cases1 <> [] then + [ + Line "if (x.IsArray() && x.Size() == 2 && x[0].IsString()) {"; + Block [ + Line "std::string cons = x[0].GetString();"; + Inline (read_cases1 env loc name cases1) + ]; + Line "}"; + ] + else + [] + in + match codegen_type with + | Declaration -> + [ + Line (sprintf "typedefs::%s from_json(const rapidjson::Value &x);" (cpp_struct_name)); + Inline (from_json_string_declaration (sprintf "typedefs::%s" cpp_struct_name) false); + Line (sprintf "void to_json(const typedefs::%s &x, rapidjson::Writer &writer);" (cpp_struct_name)); + Line (sprintf "std::string to_json_string(const typedefs::%s &x);" (cpp_struct_name)); + ] + | Definition -> + [ + Line (sprintf "namespace %s {" (cpp_struct_name)); + Block [ + Line (sprintf "typedefs::%s from_json(const rapidjson::Value &x) {" (cpp_struct_name)); + Block [ + Inline cases0_block; + Inline cases1_block; + Line (sprintf "throw _atd_bad_json(\"%s\", x);" + (single_esc (struct_name env name))) + ]; + Line "}"; + ]; + Block [Inline (from_json_string_definition (sprintf "typedefs::%s" cpp_struct_name) (None))]; + Block [ + Line (sprintf "void to_json(const typedefs::%s &x, rapidjson::Writer &writer) {" (cpp_struct_name)); + Block [ + Line "std::visit([&writer](auto &&arg) {"; + Block [ + Line "using T = std::decay_t;"; + Block ( + List.map (fun (loc, orig_name, unique_name, an, opt_e) -> + Line (sprintf "if constexpr (std::is_same_v) Types::%s::to_json(arg, writer);" (trans env orig_name) (trans env orig_name)) + ) cases + ); + ]; + Line ("}, x);"); + ]; + Line ("}"); + ]; + Block [Line (sprintf "std::string to_json_string(const typedefs::%s &x) {" (cpp_struct_name)); + Block [ + Line ("rapidjson::StringBuffer buffer;"); + Line ("rapidjson::Writer writer(buffer);"); + Line ("to_json(x, writer);"); + Line "return buffer.GetString();" + ]; + Line "}";]; + Line ("}"); + ] + | _ -> [] + +let sum env loc name cases codegen_type = + let cases = + List.map (fun (x : variant) -> + match x with + | Variant (loc, (orig_name, an), opt_e) -> + let unique_name = create_struct_name env orig_name in + (loc, orig_name, unique_name, an, opt_e) + | Inherit _ -> assert false + ) cases + in + let case_classes = + List.map (fun x -> Inline (case_class env name x codegen_type)) cases + in + let container_class = sum_container env loc name cases codegen_type in + match codegen_type with + | Declaration -> + [ + Line (sprintf "namespace %s {" (struct_name env name)); + Block [ + Line (sprintf "namespace Types {"); + Block case_classes; + Line (sprintf "}"); + Line (""); + Inline container_class; + ]; + Line (sprintf "}"); + ] + | Definition -> + [ + Line (sprintf "namespace %s::Types {" (struct_name env name)); + Block (case_classes |> double_spaced); + Line (sprintf "}"); + Inline container_class; + ] |> double_spaced + | Forward_decl -> + let cases_forward_declarations = + List.map (fun (loc, orig_name, unique_name, an, opt_e) -> Line (sprintf "struct %s;" orig_name)) cases + in + [ + Line (sprintf "namespace %s::Types {" (struct_name env name)); + Block cases_forward_declarations; + Line (sprintf "}"); + ] + | Variant_typedef -> + let type_list = + List.map (fun (loc, orig_name, unique_name, an, opt_e) -> + (sprintf "%s::Types::%s" (struct_name env name) (trans env orig_name)) + ) cases + |> String.concat ", " + in + [Line(sprintf "typedef %s %s;" (sprintf "std::variant<%s>" type_list) (struct_name env name))] + | _ -> [] + + +let enum_container env loc name cases codegen_type = + let cpp_struct_name = struct_name env name in + let cases0, cases1 = + List.partition (fun (loc, orig_name, unique_name, an, opt_e) -> + opt_e = None + ) cases + in + let cases0_block = + if cases0 <> [] then + [ + Line "if (x.IsString()) {"; + Block (read_cases0 env loc name cases0 Cpp_annot.Enum); + Line "}"; + ] + else + [] + in + let cases1_block = + if cases1 <> [] then + error_at loc "enums with parameters are not supported" + else + [] + in + match codegen_type with + | Declaration -> + [ + Line (sprintf "typedefs::%s from_json(const rapidjson::Value &x);" (cpp_struct_name)); + Inline (from_json_string_declaration (sprintf "typedefs::%s" cpp_struct_name) false); + Line (sprintf "void to_json(const typedefs::%s &x, rapidjson::Writer &writer);" (cpp_struct_name)); + Line (sprintf "std::string to_json_string(const typedefs::%s &x);" (cpp_struct_name)); + ] + | Definition -> + [ + Line (sprintf "namespace %s {" (cpp_struct_name)); + Block [ + Line (sprintf "typedefs::%s from_json(const rapidjson::Value &x) {" (cpp_struct_name)); + Block [ + Inline cases0_block; + Inline cases1_block; + Line (sprintf "throw _atd_bad_json(\"%s\", x);" + (single_esc (struct_name env name))) + ]; + Line "}"; + ]; + Block [Inline (from_json_string_definition (sprintf "typedefs::%s" cpp_struct_name) (None))]; + Block [ + Line (sprintf "void to_json(const typedefs::%s &x, rapidjson::Writer &writer) {" (cpp_struct_name)); + Block [ + Line "switch (x) {"; + Block ( + List.map (fun (loc, orig_name, unique_name, an, opt_e) -> + Line (sprintf "case Types::%s: _atd_write_string(\"%s\", writer); break;" (trans env orig_name) (trans env orig_name)) + ) cases + ); + Line ("}"); + ]; + Line ("}"); + ]; + Block [Line (sprintf "std::string to_json_string(const typedefs::%s &x) {" (cpp_struct_name)); + Block [ + Line ("rapidjson::StringBuffer buffer;"); + Line ("rapidjson::Writer writer(buffer);"); + Line ("to_json(x, writer);"); + Line "return buffer.GetString();" + ]; + Line "}";]; + Line ("}"); + ] + | _ -> [] + +let enum env loc name cases codegen_type = + let cases = + List.map (fun (x : variant) -> + match x with + | Variant (loc, (orig_name, an), opt_e) -> + let unique_name = create_struct_name env orig_name in + (loc, orig_name, unique_name, an, opt_e) + | Inherit _ -> assert false + ) cases + in + (* let case_classes = + List.map (fun x -> Inline (case_class env name x codegen_type)) cases + in *) + let container_class = enum_container env loc name cases codegen_type in + match codegen_type with + | Declaration -> + [ + Line (sprintf "namespace %s {" (struct_name env name)); + Block [ + Line (sprintf "typedef typedefs::%s Types;" (struct_name env name)); + Line (""); + Inline container_class; + ]; + Line (sprintf "}"); + ] + | Definition -> + [ + Inline container_class; + ] |> double_spaced + | Struct_typedef -> + let type_list = + List.map (fun (loc, orig_name, unique_name, an, opt_e) -> + Line (sprintf "%s," (trans env orig_name))) cases in + [ + Line (sprintf "enum class %s {" (struct_name env name)); + Block type_list; + Line (sprintf "};"); + ] + | _ -> [] + + +let type_def env ((loc, (name, param, an), e) : A.type_def) codegen_type : B.t = + if param <> [] then + not_implemented loc "parametrized type"; + let unwrap e = + match e with + | Sum (loc, cases, an) -> + (match (Cpp_annot.get_cpp_sumtype_repr an) with + | Variant -> sum env loc name cases codegen_type + | Enum -> enum env loc name cases codegen_type) + | Record (loc, fields, an) -> + record env loc name fields an codegen_type + | Tuple _ + | List _ + | Option _ + | Nullable _ + | Wrap _ + | Name _ -> alias_wrapper env name e codegen_type + | Shared _ -> not_implemented loc "cyclic references" + | Tvar _ -> not_implemented loc "parametrized type" + in + unwrap e + +let module_body env x codegen_type = + let gen = + List.fold_left (fun acc (Type x) -> Inline (type_def env x codegen_type) :: acc) [] x + |> List.rev + in + match codegen_type with + | Declaration | Definition -> gen |> spaced + | _ -> gen + +let definition_group ~atd_filename env codegen_type + (is_recursive, (items: A.module_body)) : B.t = + [ + Inline (module_body env items codegen_type); + ] + +(* + Make sure that the types as defined in the atd file get a good name. + For example, type 'foo' should become struct 'Foo'. + We do this because each case constructor of sum types will also + translate to a struct in the same namespace. For example, + there may be a type like 'type bar = [ Foo | Bleep ]'. + We want to ensure that the type 'foo' gets the name 'Foo' and that only + later the case 'Foo' gets a lesser name like 'Foo_' or 'Foo2'. +*) +let reserve_good_struct_names env (items: A.module_body) = + List.iter + (fun (Type (loc, (name, param, an), e)) -> ignore (struct_name env name)) + items + +let to_header_file ~atd_filename ~head (items : A.module_body) dst_path namespace_name = + let env = init_env () in + reserve_good_struct_names env items; + let head = List.map (fun s -> Line s) head in + let get_lines_for codegen_type = + Atd.Util.tsort items + |> List.map (fun x -> Inline (definition_group ~atd_filename env codegen_type x)) + in + let cpp_forward_declarations = get_lines_for Forward_decl in + let cpp_forward_declarations = [Line "// forward declarations"] @ cpp_forward_declarations @ [Line ""; Line ""] in + let cpp_structs_typedefs = get_lines_for Struct_typedef in + let cpp_variant_typedefs = get_lines_for Variant_typedef in + let cpp_alias_typedefs = get_lines_for Alias_typedef in + let cpp_typedefs = + [Line "namespace typedefs {"] + @ + [(Block ([ + Inline cpp_structs_typedefs; + Inline cpp_variant_typedefs; + Inline cpp_alias_typedefs; + ] |> spaced)) + ] @ [Line "} // namespace typedefs"; Line ""] in + let cpp_declarations = get_lines_for Declaration in + let namespace_start = [Line (sprintf "namespace %s {" namespace_name)] in + let namespace_end = [Line (sprintf "} // namespace %s" namespace_name)] in + Line (fixed_size_preamble_header atd_filename) + :: Inline head + :: (namespace_start |> double_spaced) + @ cpp_forward_declarations + @ cpp_typedefs + @ (cpp_declarations |> double_spaced) + @ namespace_end + |> Indent.to_file ~indent:4 dst_path + +let to_cpp_file ~atd_filename (items : A.module_body) dst_path namespace_name = + let env = init_env () in + reserve_good_struct_names env items; + let cpp_defs = + Atd.Util.tsort items + |> List.map (fun x -> Inline (definition_group ~atd_filename env Definition x)) + in + let namespace_end = [Line (sprintf "} // namespace %s" namespace_name)] in + Line (fixed_size_preamble atd_filename namespace_name) :: cpp_defs @ namespace_end + |> double_spaced + |> Indent.to_file ~indent:4 dst_path + +let run_file src_path = + let src_name = Filename.basename src_path in + let dst_name = + (if Filename.check_suffix src_name ".atd" then + Filename.chop_suffix src_name ".atd" + else + src_name) ^ "_atd" + |> String.lowercase_ascii + in + let hpp_dst_path = dst_name ^ ".hpp" in + let cpp_dst_path = dst_name ^ ".cpp" in + let full_module, _original_types = + Atd.Util.load_file + ~annot_schema + ~expand:true (* monomorphization = eliminate parametrized type defs *) + ~keep_builtins:true + ~inherit_fields:true + ~inherit_variants:true + src_path + in + let full_module = Atd.Ast.use_only_specific_variants full_module in + let (atd_head, atd_module) = full_module in + let head = + Cpp_annot.get_cpp_include (snd atd_head) + |> List.map (sprintf "#include %s") + in + let namespace_name = match Cpp_annot.get_cpp_namespace (snd atd_head) with | Some v -> sprintf "atd::%s" v | None -> "atd" in + to_header_file ~atd_filename:src_name ~head atd_module hpp_dst_path namespace_name; + to_cpp_file ~atd_filename:src_name atd_module cpp_dst_path namespace_name diff --git a/atdcpp/src/lib/Codegen.mli b/atdcpp/src/lib/Codegen.mli new file mode 100644 index 000000000..fc58e2554 --- /dev/null +++ b/atdcpp/src/lib/Codegen.mli @@ -0,0 +1,7 @@ +(* + C++ code generation for JSON support (no biniou support) +*) + +(** Take ATD type definitions and translate them to C++, writing + them out to a file which should have the '.d' extension. *) +val run_file : string -> unit diff --git a/atdcpp/src/lib/Cpp_annot.ml b/atdcpp/src/lib/Cpp_annot.ml new file mode 100644 index 000000000..7edf05050 --- /dev/null +++ b/atdcpp/src/lib/Cpp_annot.ml @@ -0,0 +1,121 @@ +(* + ATD annotations to be interpreted specifically by atdcpp. + + atdcpp also honors json-related annotations defined in Atd.Json. +*) + +type assoc_repr = + | List + | Dict + +type sumtype_repr = + | Variant + | Enum + +type atd_cpp_wrap = { + cpp_wrap_t : string; + cpp_wrap : string; + cpp_unwrap : string; + cpp_templatize : bool; +} + +let get_cpp_default an : string option = + Atd.Annot.get_opt_field + ~parse:(fun s -> Some s) + ~sections:["cpp"] + ~field:"default" + an + +let get_cpp_sumtype_repr an : sumtype_repr = + Atd.Annot.get_field + ~parse:(function + | "variant" -> Some Variant + | "enum" -> Some Enum + | _ -> None + ) + ~default:Variant + ~sections:["cpp"] + ~field:"repr" + an + +let get_cpp_assoc_repr an : assoc_repr = + Atd.Annot.get_field + ~parse:(function + | "list" -> Some List + | "dict" -> Some Dict + | _ -> None + ) + ~default:List + ~sections:["cpp"] + ~field:"repr" + an + +(* includes etc. *) +let get_cpp_include an : string list = + Atd.Annot.get_fields + ~parse:(fun s -> Some s) + ~sections:["cpp"] + ~field:"include" + an + +let get_cpp_namespace an : string option = + Atd.Annot.get_opt_field + ~parse:(fun s -> Some s) + ~sections:["cpp"] + ~field:"namespace" + an + +let get_cpp_wrap loc an = + let path = ["cpp"] in + let module_ = + Atd.Annot.get_opt_field + ~parse:(fun s -> Some s) + ~sections:path + ~field:"module" + an + in + let open Printf in + let default field = + Option.map (fun s -> + sprintf "%s.%s" s field) module_ + in + let default_t field = + Option.map (fun s -> + sprintf "%s.%s" s field) module_ + in + let t = + Atd.Annot.get_field + ~parse:(fun s -> Some (Some s)) + ~default:(default_t "t") + ~sections:path + ~field:"t" + an + in + let wrap = + Atd.Annot.get_field + ~parse:(fun s -> Some (Some s)) + ~default:(default "wrap") + ~sections:path + ~field:"wrap" + an + in + let unwrap = + Atd.Annot.get_field + ~parse:(fun s -> Some (Some s)) + ~default:(default "unwrap") + ~sections:path + ~field:"unwrap" + an + in + let templatize = + Atd.Annot.get_flag + ~sections:path + ~field:"templatize" + an + in + match t, wrap, unwrap with + None, None, None -> None + | Some t, Some wrap, Some unwrap -> + Some { cpp_wrap_t = t; cpp_wrap = wrap; cpp_unwrap = unwrap; cpp_templatize = templatize} + | _ -> + Atd.Ast.error_at loc "Incomplete annotation. Missing t, wrap or unwrap" diff --git a/atdcpp/src/lib/Cpp_annot.mli b/atdcpp/src/lib/Cpp_annot.mli new file mode 100644 index 000000000..152c1d90f --- /dev/null +++ b/atdcpp/src/lib/Cpp_annot.mli @@ -0,0 +1,61 @@ +(** + cpp-specific ATD annotations. + + This interface serves as a reference of which cpp-specific + ATD annotations are supported. atdcpp also honors JSON-related annotations + defined in [Atd.Json]. +*) + +(** Extract ["42"] from []. + The provided default must be a well-formed cpp immutable expression. +*) +val get_cpp_default : Atd.Annot.t -> string option + +(** Whether an association list of ATD type [(string * foo) list] + must be represented in cpp as a list of pairs or as a dictionary. + This is independent of the JSON representation. +*) +type assoc_repr = + | List + | Dict + +(** Whether a sum type must be represented in cpp as a variant or as an enum. + This is independent of the JSON representation. +*) +type sumtype_repr = + | Variant + | Enum + +(** Inspection of annotations placed on sum types such as + [type foo = A | B | C ]. + Permissible values for the [repr] field are ["enum"] and ["variant"]. + The default is ["variant"]. +*) +val get_cpp_sumtype_repr : Atd.Annot.t -> sumtype_repr + + +(** Inspect annotations placed on lists of pairs such as + [(string * foo) list ]. + Permissible values for the [repr] field are ["dict"] and ["list"]. + The default is ["list"]. +*) +val get_cpp_assoc_repr : Atd.Annot.t -> assoc_repr + +(** Returns text the user wants to be inserted at the beginning of the + cpp file such as include. *) +val get_cpp_include : Atd.Annot.t -> string list + +(** Return the namespace that needs to be used for the types defined in the + ATD file. *) +val get_cpp_namespace : Atd.Annot.t -> string option + + +type atd_cpp_wrap = { + cpp_wrap_t : string; + cpp_wrap : string; + cpp_unwrap : string; + cpp_templatize : bool; +} + +val get_cpp_wrap : Atd.Ast.loc -> + Atd.Annot.t -> atd_cpp_wrap option diff --git a/atdcpp/src/lib/Indent.ml b/atdcpp/src/lib/Indent.ml new file mode 100644 index 000000000..f07cce716 --- /dev/null +++ b/atdcpp/src/lib/Indent.ml @@ -0,0 +1,51 @@ +(* + Simple indentation utility for code generators + + Something similar is found in atdgen/src but this API is simpler. +*) + +type node = + | Line of string + | Block of node list + | Inline of node list + +type t = node list + +let rec is_empty_node = function + | Line "" -> true + | Line _ -> false + | Block xs -> List.for_all is_empty_node xs + | Inline xs -> List.for_all is_empty_node xs + +let to_buffer ?(offset = 0) ?(indent = 2) buf l = + let rec print n = function + | Block l -> List.iter (print (n + indent)) l + | Inline l -> List.iter (print n) l + | Line "" -> Buffer.add_char buf '\n' + | Line s -> + for _ = 1 to n do + Buffer.add_char buf ' ' + done; + Buffer.add_string buf s; + Buffer.add_char buf '\n'; + in + List.iter (print offset) l + +let to_string ?offset ?indent l = + let buf = Buffer.create 1000 in + to_buffer ?offset ?indent buf l; + Buffer.contents buf + +let to_channel ?offset ?indent oc l = + let buf = Buffer.create 1000 in + to_buffer ?offset ?indent buf l; + Buffer.output_buffer oc buf + +let to_stdout ?offset ?indent l = + to_channel ?offset ?indent stdout l + +let to_file ?indent path l = + let oc = open_out path in + Fun.protect + ~finally:(fun () -> close_out_noerr oc) + (fun () -> to_channel ?indent oc l) diff --git a/atdcpp/src/lib/Indent.mli b/atdcpp/src/lib/Indent.mli new file mode 100644 index 000000000..ff0ed76d7 --- /dev/null +++ b/atdcpp/src/lib/Indent.mli @@ -0,0 +1,33 @@ +(** Simple indentation utility for code generators *) + +type node = + | Line of string (** single line (not indented) **) + | Block of node list (** indented sequence **) + | Inline of node list (** in-line sequence (not indented) **) + +type t = node list + +val is_empty_node : node -> bool + +val to_buffer : ?offset:int -> ?indent:int -> Buffer.t -> t -> unit + (** Write to a buffer. + + @param offset defines the number of space characters + to use for the left margin. Default: 0. + + @param indent defines the number of space characters to use for + indenting blocks. Default: 2. + *) + +val to_string : ?offset:int -> ?indent:int -> t -> string + (** Write to a string. See [to_buffer] for the options. *) + +val to_channel : ?offset:int -> ?indent:int -> out_channel -> t -> unit + (** Write to a channel. See [to_buffer] for the options. *) + +val to_stdout : ?offset:int -> ?indent:int -> t -> unit + (** Write to [stdout]. See [to_buffer] for the options. *) + +val to_file : ?indent:int -> string -> t -> unit + (** Write to a file, overwriting it if it was already there. + See [to_buffer] for the options. *) diff --git a/atdcpp/src/lib/Version.ml b/atdcpp/src/lib/Version.ml new file mode 100644 index 000000000..4cfa85ac8 --- /dev/null +++ b/atdcpp/src/lib/Version.ml @@ -0,0 +1,3 @@ +(* The version string is replaced by the actual version at release time + by 'dune release'. *) +let version = "%%VERSION%%" diff --git a/atdcpp/src/lib/dune b/atdcpp/src/lib/dune new file mode 100644 index 000000000..75e003e2b --- /dev/null +++ b/atdcpp/src/lib/dune @@ -0,0 +1,7 @@ +(library + (name atdcpp) + (libraries + re + atd + ) +) diff --git a/atdcpp/test/.gitignore b/atdcpp/test/.gitignore new file mode 100644 index 000000000..dfb825d7d --- /dev/null +++ b/atdcpp/test/.gitignore @@ -0,0 +1,2 @@ +!cpp-expected/ +cpp-tests/*.d diff --git a/atdcpp/test/atd-input/everything.atd b/atdcpp/test/atd-input/everything.atd new file mode 100644 index 000000000..3717d8649 --- /dev/null +++ b/atdcpp/test/atd-input/everything.atd @@ -0,0 +1,135 @@ + + + +type kind = [ + | Root (* class name conflict *) + | Thing of int + | WOW + | Amaze of string list +] + +type frozen = [ + | A + | B of int +] + +type enum_sumtype = [ + | A + | B + | C +] + +type ('a, 'b) parametrized_record = { + field_a: 'a; + ~field_b: 'b list; +} + +type 'a parametrized_tuple = ('a * 'a * int) + +type root = { + id : string; + await: bool; + integer: int; + __init__ : float; + ~float_with_auto_default: float; + ~float_with_default : float; + items: int list list; + ?maybe: int option; + ~extras: int list; + ~answer : int; + aliased: alias; + point: (float * float); + kind: kind; + kinds: kind list; + assoc1: (float * int) list; + assoc2: (string * int) list ; + assoc3: (float * int) list ; + assoc4: (string * int) list ; + nullables: int nullable list; + options: int option list; + untyped_things: abstract list; + parametrized_record: (int, float) parametrized_record; + parametrized_tuple: kind parametrized_tuple; + wrapped: st wrap ; + aaa: alias_of_alias_of_alias; + item: string wrap ; + ee : enum_sumtype; +} + + +type st = int +type alias = int list +type alias2 = int list +type alias3 = int wrap +type alias_of_alias = alias3 wrap +type alias_of_alias_not_Wrapped = alias3 +type alias_of_alias_of_alias = alias_of_alias_not_Wrapped + +type password = int wrap + + +type credential = { + name: string; + password: int; +} + +type credentials = { + credentials: credential list; +} + +type credentials2 = credential list + +type three_level_nested_list_record = { + field_a: int list list list +} + + +type pair = (string * int) + +type require_field = { + req: string; +} + +type recursive_class = { + id: int; + flag: bool; + children: recursive_class list; +} + +type default_list = { + ~items: int list; +} + +type record_with_wrapped_type = { + item: string wrap ; +} + +type 'a recursive = 'a list + +(* This only works if the recursive member is always present. If it can optionally not be present, we need extra wrapping in an optional as below *) +type recursive_variant = [ + | Integer of int + | Rec of recursive_variant wrap +] + +type struct_with_recursive_variant = { + variant: recursive_variant; +} + +type recursive_record2 = { + id: int; + flag: bool; + children: recursive_record2 nullable wrap ; +} + +type null_opt = { + a : int; + b : int option; + c : int nullable; + ?f : int option; + ~h :int option; + ~i : int nullable; +} + +type empty_record = { +} \ No newline at end of file diff --git a/atdcpp/test/cpp-expected/everything_atd.cpp b/atdcpp/test/cpp-expected/everything_atd.cpp new file mode 100644 index 000000000..72722a931 --- /dev/null +++ b/atdcpp/test/cpp-expected/everything_atd.cpp @@ -0,0 +1,1552 @@ + +// Generated by atdcpp from type definitions in everything.atd. +// This implements classes for the types defined in 'everything.atd', providing +// methods and functions to convert data from/to JSON. + +// ############################################################################ +// # Private functions +// ############################################################################ + +// filename everything.atd + +#include "everything_atd.hpp" + +namespace atd::my::custom::ns +{ + +namespace // anonymous +{ + +std::string _rapid_json_type_to_string(rapidjson::Type type) +{ + switch (type) + { + case rapidjson::kNullType: + return "null"; + case rapidjson::kFalseType: + return "false"; + case rapidjson::kTrueType: + return "true"; + case rapidjson::kObjectType: + return "object"; + case rapidjson::kArrayType: + return "array"; + case rapidjson::kStringType: + return "string"; + case rapidjson::kNumberType: + return "number"; + default: + return "unknown"; + } +} + +class AtdException : public std::exception +{ +public: + AtdException(const std::string &message) : msg_(message) {} + + const char *what() const throw() override + { + return msg_.c_str(); + } + +private: + std::string msg_; +}; + +template +[[maybe_unused]] T _atd_missing_json_field(const std::string &type, const std::string &field) +{ + throw AtdException("Missing JSON field '" + field + "' in " + type); +} + +[[maybe_unused]] auto _atd_bad_json(const std::string &type, const rapidjson::Value &x) +{ + auto x_type = x.GetType(); + return AtdException("Bad JSON for " + type + " got type " + _rapid_json_type_to_string(x_type)); +} + +// Reading an integer from JSON +[[maybe_unused]] int _atd_read_int(const rapidjson::Value &val) +{ + if (!val.IsInt()) + { + throw AtdException("Expected an integer but got" + _rapid_json_type_to_string(val.GetType())); + } + return val.GetInt(); +} + +[[maybe_unused]] bool _atd_read_bool(const rapidjson::Value &val) +{ + if (!val.IsBool()) + { + throw AtdException("Expected a boolean but got" + _rapid_json_type_to_string(val.GetType())); + } + return val.GetBool(); +} + +// Reading a float from JSON +[[maybe_unused]] float _atd_read_float(const rapidjson::Value &val) +{ + if (val.IsInt()) + { + return static_cast(val.GetInt()); + } + else if (val.IsUint()) + { + return static_cast(val.GetUint()); + } + if (!val.IsFloat()) + { + throw AtdException("Expected a float but got" + _rapid_json_type_to_string(val.GetType())); + } + + return val.GetFloat(); +} + +[[maybe_unused]] std::string _atd_read_string(const rapidjson::Value &val) +{ + if (!val.IsString()) + { + throw AtdException("Expected a string but got" + _rapid_json_type_to_string(val.GetType())); + } + return val.GetString(); +} + +[[maybe_unused]] std::string _atd_read_abstract(const rapidjson::Value &val) +{ + // will convert val to string + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + val.Accept(writer); + return buffer.GetString(); +} + +template +[[maybe_unused]] auto _atd_read_array(F read_func, const rapidjson::Value &val) +{ + using ResultType = typename std::invoke_result::type; + + if (!val.IsArray()) + { + throw AtdException("Expected an array but got" + _rapid_json_type_to_string(val.GetType())); + } + + std::vector result; + for (rapidjson::SizeType i = 0; i < val.Size(); i++) + { + result.push_back(read_func(val[i])); + } + + return result; +} + +template +[[maybe_unused]] auto _atd_read_object_to_tuple_list(F read_func, const rapidjson::Value &val) +{ + using ResultType = typename std::invoke_result::type; + + if (!val.IsObject()) + { + throw AtdException("Expected an object but got" + _rapid_json_type_to_string(val.GetType())); + } + + std::vector> result; + for (auto &m : val.GetObject()) + { + result.push_back(std::make_tuple(m.name.GetString(), read_func(m.value))); + } + + return result; +} + +template +[[maybe_unused]] auto _atd_read_array_to_assoc_dict(RK read_key_func, RV read_value_func, const rapidjson::Value &val) +{ + using KeyType = typename std::invoke_result::type; + using ValueType = typename std::invoke_result::type; + + if (!val.IsArray()) + { + throw AtdException("Expected an array but got" + _rapid_json_type_to_string(val.GetType())); + } + + std::map result; + for (rapidjson::SizeType i = 0; i < val.Size(); i++) + { + auto &pair = val[i]; + if (!pair.IsArray() || pair.Size() != 2) + { + throw AtdException("Expected an array of pairs"); + } + result[read_key_func(pair[0])] = read_value_func(pair[1]); + } + + return result; +} + +template +[[maybe_unused]] auto _atd_read_object_to_assoc_array(F read_func, const rapidjson::Value &val) +{ + using ResultType = typename std::invoke_result::type; + + if (!val.IsObject()) + { + throw AtdException("Expected an object but got" + _rapid_json_type_to_string(val.GetType())); + } + + std::map result; + for (auto &m : val.GetObject()) + { + result[m.name.GetString()] = read_func(m.value); + } + + return result; +} + +template +[[maybe_unused]] auto _atd_read_nullable(F read_func, const rapidjson::Value &val) +{ + if (val.IsNull()) + { + return std::optional::type>(); + } + return std::optional::type>(read_func(val)); +} + +template +[[maybe_unused]] auto _atd_read_option(F read_func, const rapidjson::Value &val) +{ + using ResultType = typename std::invoke_result::type; + if (val.IsString() && std::string_view(val.GetString()) == "None") + { + return std::optional(); + } + else if (val.IsArray() && val.Size() == 2 && val[0].IsString() && std::string_view(val[0].GetString()) == "Some") + { + return std::make_optional(read_func(val[1])); + } + else + { + throw AtdException("Expected an option"); + } +} + +template +[[maybe_unused]] auto _atd_read_wrap(F read_func, W wrap_func, const rapidjson::Value &val) +{ + return wrap_func(read_func(val)); +} + +template +[[maybe_unused]] std::shared_ptr _atd_wrap_shared_ptr(T val) +{ + return std::make_shared(val); +} + +template +[[maybe_unused]] T _atd_unwrap_shared_ptr(const std::shared_ptr val) +{ + if (!val) + { + throw AtdException("Expected a shared_ptr but got nullptr"); + } + return *val; +} + +[[maybe_unused]] void _atd_write_int(int value, rapidjson::Writer& writer) +{ + writer.Int(value); +} + +[[maybe_unused]] void _atd_write_bool(bool value, rapidjson::Writer& writer) +{ + writer.Bool(value); +} + +[[maybe_unused]] void _atd_write_float(float value, rapidjson::Writer& writer) +{ + writer.Double(value); +} + +[[maybe_unused]] void _atd_write_string(const std::string &value, rapidjson::Writer& writer) +{ + writer.String(value.c_str()); +} + +[[maybe_unused]] void _atd_write_abstract(const std::string &value, rapidjson::Writer& writer) +{ + // writes string value as raw json + writer.RawValue(value.c_str(), value.size(), rapidjson::kStringType); +} + +template +[[maybe_unused]] void _atd_write_array(F write_func, const V& values, rapidjson::Writer& writer) +{ + writer.StartArray(); + for (const auto& value : values) + { + write_func(value, writer); + } + writer.EndArray(); +} + +template +[[maybe_unused]] void _atd_write_tuple_list_to_object(F write_func, const V &values, rapidjson::Writer& writer) +{ + writer.StartObject(); + for (const auto& value : values) + { + writer.Key(std::get<0>(value).c_str()); + write_func(std::get<1>(value), writer); + } + writer.EndObject(); +} + +template +[[maybe_unused]] void _atd_write_assoc_dict_to_array(const Wk write_key_func, const Wv write_value_func, const Map &value_map, rapidjson::Writer& writer) +{ + writer.StartArray(); + for (const auto& pair : value_map) + { + writer.StartArray(); + write_key_func(pair.first, writer); + write_value_func(pair.second, writer); + writer.EndArray(); + } + writer.EndArray(); +} + +template +[[maybe_unused]] void _atd_write_assoc_array_to_object(F write_func, const Map &value_map, rapidjson::Writer& writer) +{ + writer.StartObject(); + for (const auto& pair : value_map) + { + writer.Key(pair.first.c_str()); + write_func(pair.second, writer); + } + writer.EndObject(); +} + + +template +[[maybe_unused]] void _atd_write_option(F write_func, const O &val, rapidjson::Writer& writer) +{ + if (val) + { + writer.StartArray(); + writer.String("Some"); + write_func(*val, writer); + writer.EndArray(); + } + else + { + writer.String("None"); + } +} + +template +[[maybe_unused]] void _atd_write_nullable(F write_func, const O &val, rapidjson::Writer& writer) +{ + if (val) + { + write_func(*val, writer); + } + else + { + writer.Null(); + } +} + +template +[[maybe_unused]] void _atd_write_wrap(F write_func, W wrap_func, const T &val, rapidjson::Writer& writer) +{ + write_func(wrap_func(val), writer); +} +} // anonymous namespace + + + +namespace RecursiveVariant::Types { + + + void Integer::to_json(const Integer &e, rapidjson::Writer &writer){ + writer.StartArray(); + writer.String("Integer"); + _atd_write_int(e.value, writer); + writer.EndArray(); + } + + + void Rec::to_json(const Rec &e, rapidjson::Writer &writer){ + writer.StartArray(); + writer.String("Rec"); + _atd_write_wrap([](const auto &v, auto &w){RecursiveVariant::to_json(v, w);}, [](const auto &e){return _atd_unwrap_shared_ptr(e);}, e.value, writer); + writer.EndArray(); + } + + +} + + +namespace RecursiveVariant { + typedefs::RecursiveVariant from_json(const rapidjson::Value &x) { + if (x.IsArray() && x.Size() == 2 && x[0].IsString()) { + std::string cons = x[0].GetString(); + if (cons == "Integer") + return Types::Integer({_atd_read_int(x[1])}); + if (cons == "Rec") + return Types::Rec({_atd_read_wrap([](const auto& v){return RecursiveVariant::from_json(v);}, [](const auto &e){return _atd_wrap_shared_ptr(e);},x[1])}); + throw _atd_bad_json("RecursiveVariant", x); + } + throw _atd_bad_json("RecursiveVariant", x); + } + typedefs::RecursiveVariant from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); + } + void to_json(const typedefs::RecursiveVariant &x, rapidjson::Writer &writer) { + std::visit([&writer](auto &&arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) Types::Integer::to_json(arg, writer); + if constexpr (std::is_same_v) Types::Rec::to_json(arg, writer); + }, x); + } + std::string to_json_string(const typedefs::RecursiveVariant &x) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(x, writer); + return buffer.GetString(); + } +} + + +RecursiveRecord2 RecursiveRecord2::from_json(const rapidjson::Value & doc) { + RecursiveRecord2 record; + if (!doc.IsObject()) { + throw AtdException("atdtype: recursive_record2, expected an object but got" + _rapid_json_type_to_string(doc.GetType())); + } + if (doc.HasMember("id")) + record.id = _atd_read_int(doc["id"]); + else record.id = _atd_missing_json_field("RecursiveRecord2", "id"); + if (doc.HasMember("flag")) + record.flag = _atd_read_bool(doc["flag"]); + else record.flag = _atd_missing_json_field("RecursiveRecord2", "flag"); + if (doc.HasMember("children")) + record.children = _atd_read_wrap([](const auto& v){return _atd_read_nullable([](const auto &v){return RecursiveRecord2::from_json(v);}, v);}, [](const auto &e){return _atd_wrap_shared_ptr(e);},doc["children"]); + else record.children = _atd_missing_json_field("RecursiveRecord2", "children"); + return record; +} +RecursiveRecord2 RecursiveRecord2::from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); +} +void RecursiveRecord2::to_json(const RecursiveRecord2 &t, rapidjson::Writer &writer) { + writer.StartObject(); + writer.Key("id"); + _atd_write_int(t.id, writer); + writer.Key("flag"); + _atd_write_bool(t.flag, writer); + writer.Key("children"); + _atd_write_wrap([](const auto &v, auto &w){_atd_write_nullable([](auto v, auto &w){RecursiveRecord2::to_json(v, w);}, v, w);}, [](const auto &e){return _atd_unwrap_shared_ptr(e);}, t.children, writer); + writer.EndObject(); +} +std::string RecursiveRecord2::to_json_string(const RecursiveRecord2 &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); +} +std::string RecursiveRecord2::to_json_string() { + return to_json_string(*this); +} + + +RecursiveClass RecursiveClass::from_json(const rapidjson::Value & doc) { + RecursiveClass record; + if (!doc.IsObject()) { + throw AtdException("atdtype: recursive_class, expected an object but got" + _rapid_json_type_to_string(doc.GetType())); + } + if (doc.HasMember("id")) + record.id = _atd_read_int(doc["id"]); + else record.id = _atd_missing_json_field("RecursiveClass", "id"); + if (doc.HasMember("flag")) + record.flag = _atd_read_bool(doc["flag"]); + else record.flag = _atd_missing_json_field("RecursiveClass", "flag"); + if (doc.HasMember("children")) + record.children = _atd_read_array([](const auto &v){return RecursiveClass::from_json(v);}, doc["children"]); + else record.children = _atd_missing_json_field("RecursiveClass", "children"); + return record; +} +RecursiveClass RecursiveClass::from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); +} +void RecursiveClass::to_json(const RecursiveClass &t, rapidjson::Writer &writer) { + writer.StartObject(); + writer.Key("id"); + _atd_write_int(t.id, writer); + writer.Key("flag"); + _atd_write_bool(t.flag, writer); + writer.Key("children"); + _atd_write_array([](auto v, auto &w){RecursiveClass::to_json(v, w);}, t.children, writer); + writer.EndObject(); +} +std::string RecursiveClass::to_json_string(const RecursiveClass &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); +} +std::string RecursiveClass::to_json_string() { + return to_json_string(*this); +} + + +ThreeLevelNestedListRecord ThreeLevelNestedListRecord::from_json(const rapidjson::Value & doc) { + ThreeLevelNestedListRecord record; + if (!doc.IsObject()) { + throw AtdException("atdtype: three_level_nested_list_record, expected an object but got" + _rapid_json_type_to_string(doc.GetType())); + } + if (doc.HasMember("field_a")) + record.field_a = _atd_read_array([](const auto &v){return _atd_read_array([](const auto &v){return _atd_read_array([](const auto &v){return _atd_read_int(v);}, v);}, v);}, doc["field_a"]); + else record.field_a = _atd_missing_json_field("ThreeLevelNestedListRecord", "field_a"); + return record; +} +ThreeLevelNestedListRecord ThreeLevelNestedListRecord::from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); +} +void ThreeLevelNestedListRecord::to_json(const ThreeLevelNestedListRecord &t, rapidjson::Writer &writer) { + writer.StartObject(); + writer.Key("field_a"); + _atd_write_array([](auto v, auto &w){_atd_write_array([](auto v, auto &w){_atd_write_array([](auto v, auto &w){_atd_write_int(v, w);}, v, w);}, v, w);}, t.field_a, writer); + writer.EndObject(); +} +std::string ThreeLevelNestedListRecord::to_json_string(const ThreeLevelNestedListRecord &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); +} +std::string ThreeLevelNestedListRecord::to_json_string() { + return to_json_string(*this); +} + + +StructWithRecursiveVariant StructWithRecursiveVariant::from_json(const rapidjson::Value & doc) { + StructWithRecursiveVariant record; + if (!doc.IsObject()) { + throw AtdException("atdtype: struct_with_recursive_variant, expected an object but got" + _rapid_json_type_to_string(doc.GetType())); + } + if (doc.HasMember("variant")) + record.variant = RecursiveVariant::from_json(doc["variant"]); + else record.variant = _atd_missing_json_field("StructWithRecursiveVariant", "variant"); + return record; +} +StructWithRecursiveVariant StructWithRecursiveVariant::from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); +} +void StructWithRecursiveVariant::to_json(const StructWithRecursiveVariant &t, rapidjson::Writer &writer) { + writer.StartObject(); + writer.Key("variant"); + RecursiveVariant::to_json(t.variant, writer); + writer.EndObject(); +} +std::string StructWithRecursiveVariant::to_json_string(const StructWithRecursiveVariant &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); +} +std::string StructWithRecursiveVariant::to_json_string() { + return to_json_string(*this); +} + + +namespace St { + typedefs::St from_json(const rapidjson::Value &doc) { + return _atd_read_int(doc); + } + typedefs::St from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); + } + void to_json(const typedefs::St &t, rapidjson::Writer &writer) { + _atd_write_int(t, writer); + } + std::string to_json_string(const typedefs::St &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); + } +} + + +namespace Kind::Types { + + + void Root::to_json(const Root &e, rapidjson::Writer &writer){ + writer.String("Root"); + }; + + + void Thing::to_json(const Thing &e, rapidjson::Writer &writer){ + writer.StartArray(); + writer.String("Thing"); + _atd_write_int(e.value, writer); + writer.EndArray(); + } + + + void WOW::to_json(const WOW &e, rapidjson::Writer &writer){ + writer.String("wow"); + }; + + + void Amaze::to_json(const Amaze &e, rapidjson::Writer &writer){ + writer.StartArray(); + writer.String("!!!"); + _atd_write_array([](auto v, auto &w){_atd_write_string(v, w);}, e.value, writer); + writer.EndArray(); + } + + +} + + +namespace Kind { + typedefs::Kind from_json(const rapidjson::Value &x) { + if (x.IsString()) { + if (std::string_view(x.GetString()) == "Root") + return Types::Root(); + if (std::string_view(x.GetString()) == "wow") + return Types::WOW(); + throw _atd_bad_json("Kind", x); + } + if (x.IsArray() && x.Size() == 2 && x[0].IsString()) { + std::string cons = x[0].GetString(); + if (cons == "Thing") + return Types::Thing({_atd_read_int(x[1])}); + if (cons == "!!!") + return Types::Amaze({_atd_read_array([](const auto &v){return _atd_read_string(v);}, x[1])}); + throw _atd_bad_json("Kind", x); + } + throw _atd_bad_json("Kind", x); + } + typedefs::Kind from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); + } + void to_json(const typedefs::Kind &x, rapidjson::Writer &writer) { + std::visit([&writer](auto &&arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) Types::Root::to_json(arg, writer); + if constexpr (std::is_same_v) Types::Thing::to_json(arg, writer); + if constexpr (std::is_same_v) Types::WOW::to_json(arg, writer); + if constexpr (std::is_same_v) Types::Amaze::to_json(arg, writer); + }, x); + } + std::string to_json_string(const typedefs::Kind &x) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(x, writer); + return buffer.GetString(); + } +} + + +namespace EnumSumtype { + typedefs::EnumSumtype from_json(const rapidjson::Value &x) { + if (x.IsString()) { + if (std::string_view(x.GetString()) == "A") + return Types::A; + if (std::string_view(x.GetString()) == "B") + return Types::B; + if (std::string_view(x.GetString()) == "C") + return Types::C; + throw _atd_bad_json("EnumSumtype", x); + } + throw _atd_bad_json("EnumSumtype", x); + } + typedefs::EnumSumtype from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); + } + void to_json(const typedefs::EnumSumtype &x, rapidjson::Writer &writer) { + switch (x) { + case Types::A: _atd_write_string("A", writer); break; + case Types::B: _atd_write_string("B", writer); break; + case Types::C: _atd_write_string("C", writer); break; + } + } + std::string to_json_string(const typedefs::EnumSumtype &x) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(x, writer); + return buffer.GetString(); + } +} + + +namespace Alias3 { + typedefs::Alias3 from_json(const rapidjson::Value &doc) { + return _atd_read_wrap([](const auto& v){return _atd_read_int(v);}, [](const auto &e){return static_cast(e);},doc); + } + typedefs::Alias3 from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); + } + void to_json(const typedefs::Alias3 &t, rapidjson::Writer &writer) { + _atd_write_wrap([](const auto &v, auto &w){_atd_write_int(v, w);}, [](const auto &e){return static_cast(e);}, t, writer); + } + std::string to_json_string(const typedefs::Alias3 &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); + } +} + + +namespace AliasOfAliasNotWrapped { + typedefs::AliasOfAliasNotWrapped from_json(const rapidjson::Value &doc) { + return Alias3::from_json(doc); + } + typedefs::AliasOfAliasNotWrapped from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); + } + void to_json(const typedefs::AliasOfAliasNotWrapped &t, rapidjson::Writer &writer) { + Alias3::to_json(t, writer); + } + std::string to_json_string(const typedefs::AliasOfAliasNotWrapped &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); + } +} + + +namespace AliasOfAliasOfAlias { + typedefs::AliasOfAliasOfAlias from_json(const rapidjson::Value &doc) { + return AliasOfAliasNotWrapped::from_json(doc); + } + typedefs::AliasOfAliasOfAlias from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); + } + void to_json(const typedefs::AliasOfAliasOfAlias &t, rapidjson::Writer &writer) { + AliasOfAliasNotWrapped::to_json(t, writer); + } + std::string to_json_string(const typedefs::AliasOfAliasOfAlias &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); + } +} + + +namespace Alias { + typedefs::Alias from_json(const rapidjson::Value &doc) { + return _atd_read_array([](const auto &v){return _atd_read_int(v);}, doc); + } + typedefs::Alias from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); + } + void to_json(const typedefs::Alias &t, rapidjson::Writer &writer) { + _atd_write_array([](auto v, auto &w){_atd_write_int(v, w);}, t, writer); + } + std::string to_json_string(const typedefs::Alias &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); + } +} + + +namespace KindParametrizedTuple { + typedefs::KindParametrizedTuple from_json(const rapidjson::Value &doc) { + return [](auto &v){ + if (!v.IsArray() || v.Size() != 3) + throw AtdException("Tuple of size 3"); + return std::make_tuple(Kind::from_json(v[0]), Kind::from_json(v[1]), _atd_read_int(v[2])); + }(doc); + } + typedefs::KindParametrizedTuple from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); + } + void to_json(const typedefs::KindParametrizedTuple &t, rapidjson::Writer &writer) { + [](const auto &t, auto &writer){ + writer.StartArray(); + Kind::to_json(std::get<0>(t), writer); Kind::to_json(std::get<1>(t), writer); _atd_write_int(std::get<2>(t), writer); + writer.EndArray(); + }(t, writer); + } + std::string to_json_string(const typedefs::KindParametrizedTuple &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); + } +} + + +IntFloatParametrizedRecord IntFloatParametrizedRecord::from_json(const rapidjson::Value & doc) { + IntFloatParametrizedRecord record; + if (!doc.IsObject()) { + throw AtdException("atdtype: _int_float_parametrized_record, expected an object but got" + _rapid_json_type_to_string(doc.GetType())); + } + if (doc.HasMember("field_a")) + record.field_a = _atd_read_int(doc["field_a"]); + else record.field_a = _atd_missing_json_field("IntFloatParametrizedRecord", "field_a"); + if (doc.HasMember("field_b")) + record.field_b = _atd_read_array([](const auto &v){return _atd_read_float(v);}, doc["field_b"]); + else record.field_b = {}; + return record; +} +IntFloatParametrizedRecord IntFloatParametrizedRecord::from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); +} +void IntFloatParametrizedRecord::to_json(const IntFloatParametrizedRecord &t, rapidjson::Writer &writer) { + writer.StartObject(); + writer.Key("field_a"); + _atd_write_int(t.field_a, writer); + if (!t.field_b.empty()) { + writer.Key("field_b"); + _atd_write_array([](auto v, auto &w){_atd_write_float(v, w);}, t.field_b, writer); + } + writer.EndObject(); +} +std::string IntFloatParametrizedRecord::to_json_string(const IntFloatParametrizedRecord &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); +} +std::string IntFloatParametrizedRecord::to_json_string() { + return to_json_string(*this); +} + + +Root Root::from_json(const rapidjson::Value & doc) { + Root record; + if (!doc.IsObject()) { + throw AtdException("atdtype: root, expected an object but got" + _rapid_json_type_to_string(doc.GetType())); + } + if (doc.HasMember("ID")) + record.id = _atd_read_string(doc["ID"]); + else record.id = _atd_missing_json_field("Root", "ID"); + if (doc.HasMember("await")) + record.await = _atd_read_bool(doc["await"]); + else record.await = _atd_missing_json_field("Root", "await"); + if (doc.HasMember("integer")) + record.integer = _atd_read_int(doc["integer"]); + else record.integer = _atd_missing_json_field("Root", "integer"); + if (doc.HasMember("__init__")) + record.x___init__ = _atd_read_float(doc["__init__"]); + else record.x___init__ = _atd_missing_json_field("Root", "__init__"); + if (doc.HasMember("float_with_auto_default")) + record.float_with_auto_default = _atd_read_float(doc["float_with_auto_default"]); + else record.float_with_auto_default = 0.0f; + if (doc.HasMember("float_with_default")) + record.float_with_default = _atd_read_float(doc["float_with_default"]); + else record.float_with_default = 0.1f; + if (doc.HasMember("items")) + record.items = _atd_read_array([](const auto &v){return _atd_read_array([](const auto &v){return _atd_read_int(v);}, v);}, doc["items"]); + else record.items = _atd_missing_json_field("Root", "items"); + if (doc.HasMember("maybe")) + record.maybe = _atd_read_int(doc["maybe"]); + else record.maybe = std::nullopt; + if (doc.HasMember("extras")) + record.extras = _atd_read_array([](const auto &v){return _atd_read_int(v);}, doc["extras"]); + else record.extras = {}; + if (doc.HasMember("answer")) + record.answer = _atd_read_int(doc["answer"]); + else record.answer = 42; + if (doc.HasMember("aliased")) + record.aliased = Alias::from_json(doc["aliased"]); + else record.aliased = _atd_missing_json_field("Root", "aliased"); + if (doc.HasMember("point")) + record.point = [](auto &v){ + if (!v.IsArray() || v.Size() != 2) + throw AtdException("Tuple of size 2"); + return std::make_tuple(_atd_read_float(v[0]), _atd_read_float(v[1])); + }(doc["point"]); + else record.point = _atd_missing_json_field("Root", "point"); + if (doc.HasMember("kind")) + record.kind = Kind::from_json(doc["kind"]); + else record.kind = _atd_missing_json_field("Root", "kind"); + if (doc.HasMember("kinds")) + record.kinds = _atd_read_array([](const auto &v){return Kind::from_json(v);}, doc["kinds"]); + else record.kinds = _atd_missing_json_field("Root", "kinds"); + if (doc.HasMember("assoc1")) + record.assoc1 = _atd_read_array([](const auto &v){return [](auto &v){ + if (!v.IsArray() || v.Size() != 2) + throw AtdException("Tuple of size 2"); + return std::make_tuple(_atd_read_float(v[0]), _atd_read_int(v[1])); + }(v);}, doc["assoc1"]); + else record.assoc1 = _atd_missing_json_field("Root", "assoc1"); + if (doc.HasMember("assoc2")) + record.assoc2 = _atd_read_object_to_tuple_list([](const auto &v){return _atd_read_int(v);},doc["assoc2"]); + else record.assoc2 = _atd_missing_json_field("Root", "assoc2"); + if (doc.HasMember("assoc3")) + record.assoc3 = _atd_read_array_to_assoc_dict([](const auto &k){return _atd_read_float(k);}, [](const auto &v){return _atd_read_int(v);}, doc["assoc3"]); + else record.assoc3 = _atd_missing_json_field("Root", "assoc3"); + if (doc.HasMember("assoc4")) + record.assoc4 = _atd_read_object_to_assoc_array([](const auto &v){return _atd_read_int(v);},doc["assoc4"]); + else record.assoc4 = _atd_missing_json_field("Root", "assoc4"); + if (doc.HasMember("nullables")) + record.nullables = _atd_read_array([](const auto &v){return _atd_read_nullable([](const auto &v){return _atd_read_int(v);}, v);}, doc["nullables"]); + else record.nullables = _atd_missing_json_field("Root", "nullables"); + if (doc.HasMember("options")) + record.options = _atd_read_array([](const auto &v){return _atd_read_option([](const auto &v){return _atd_read_int(v);}, v);}, doc["options"]); + else record.options = _atd_missing_json_field("Root", "options"); + if (doc.HasMember("untyped_things")) + record.untyped_things = _atd_read_array([](const auto &v){return _atd_read_abstract(v);}, doc["untyped_things"]); + else record.untyped_things = _atd_missing_json_field("Root", "untyped_things"); + if (doc.HasMember("parametrized_record")) + record.parametrized_record = IntFloatParametrizedRecord::from_json(doc["parametrized_record"]); + else record.parametrized_record = _atd_missing_json_field("Root", "parametrized_record"); + if (doc.HasMember("parametrized_tuple")) + record.parametrized_tuple = KindParametrizedTuple::from_json(doc["parametrized_tuple"]); + else record.parametrized_tuple = _atd_missing_json_field("Root", "parametrized_tuple"); + if (doc.HasMember("wrapped")) + record.wrapped = _atd_read_wrap([](const auto& v){return St::from_json(v);}, [](const auto &e){return [](typedefs::St st){return st - 1;}(e);},doc["wrapped"]); + else record.wrapped = _atd_missing_json_field("Root", "wrapped"); + if (doc.HasMember("aaa")) + record.aaa = AliasOfAliasOfAlias::from_json(doc["aaa"]); + else record.aaa = _atd_missing_json_field("Root", "aaa"); + if (doc.HasMember("item")) + record.item = _atd_read_wrap([](const auto& v){return _atd_read_string(v);}, [](const auto &e){return std::stoi(e);},doc["item"]); + else record.item = _atd_missing_json_field("Root", "item"); + if (doc.HasMember("ee")) + record.ee = EnumSumtype::from_json(doc["ee"]); + else record.ee = _atd_missing_json_field("Root", "ee"); + return record; +} +Root Root::from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); +} +void Root::to_json(const Root &t, rapidjson::Writer &writer) { + writer.StartObject(); + writer.Key("ID"); + _atd_write_string(t.id, writer); + writer.Key("await"); + _atd_write_bool(t.await, writer); + writer.Key("integer"); + _atd_write_int(t.integer, writer); + writer.Key("__init__"); + _atd_write_float(t.x___init__, writer); + if (t.float_with_auto_default != float(0.0f)) { + writer.Key("float_with_auto_default"); + _atd_write_float(t.float_with_auto_default, writer); + } + if (t.float_with_default != float(0.1f)) { + writer.Key("float_with_default"); + _atd_write_float(t.float_with_default, writer); + } + writer.Key("items"); + _atd_write_array([](auto v, auto &w){_atd_write_array([](auto v, auto &w){_atd_write_int(v, w);}, v, w);}, t.items, writer); + if (t.maybe != std::nullopt) { + writer.Key("maybe"); + _atd_write_int(t.maybe.value(), writer); + } + if (!t.extras.empty()) { + writer.Key("extras"); + _atd_write_array([](auto v, auto &w){_atd_write_int(v, w);}, t.extras, writer); + } + if (t.answer != int(42)) { + writer.Key("answer"); + _atd_write_int(t.answer, writer); + } + writer.Key("aliased"); + Alias::to_json(t.aliased, writer); + writer.Key("point"); + [](const auto &t, auto &writer){ + writer.StartArray(); + _atd_write_float(std::get<0>(t), writer); _atd_write_float(std::get<1>(t), writer); + writer.EndArray(); + }(t.point, writer); + writer.Key("kind"); + Kind::to_json(t.kind, writer); + writer.Key("kinds"); + _atd_write_array([](auto v, auto &w){Kind::to_json(v, w);}, t.kinds, writer); + writer.Key("assoc1"); + _atd_write_array([](auto v, auto &w){[](const auto &t, auto &writer){ + writer.StartArray(); + _atd_write_float(std::get<0>(t), writer); _atd_write_int(std::get<1>(t), writer); + writer.EndArray(); + }(v, w);}, t.assoc1, writer); + writer.Key("assoc2"); + _atd_write_tuple_list_to_object([](auto v, auto &w){_atd_write_int(v, w);}, t.assoc2, writer); + writer.Key("assoc3"); + _atd_write_assoc_dict_to_array([](auto v, auto &w){_atd_write_float(v, w);}, [](auto v, auto &w){_atd_write_int(v, w);}, t.assoc3, writer); + writer.Key("assoc4"); + _atd_write_assoc_array_to_object([](auto v, auto &w){_atd_write_int(v, w);}, t.assoc4, writer); + writer.Key("nullables"); + _atd_write_array([](auto v, auto &w){_atd_write_nullable([](auto v, auto &w){_atd_write_int(v, w);}, v, w);}, t.nullables, writer); + writer.Key("options"); + _atd_write_array([](auto v, auto &w){_atd_write_option([](auto v, auto &w){_atd_write_int(v, w);}, v, w);}, t.options, writer); + writer.Key("untyped_things"); + _atd_write_array([](auto v, auto &w){_atd_write_abstract(v, w);}, t.untyped_things, writer); + writer.Key("parametrized_record"); + IntFloatParametrizedRecord::to_json(t.parametrized_record, writer); + writer.Key("parametrized_tuple"); + KindParametrizedTuple::to_json(t.parametrized_tuple, writer); + writer.Key("wrapped"); + _atd_write_wrap([](const auto &v, auto &w){St::to_json(v, w);}, [](const auto &e){return [](uint16_t e){return e + 1;}(e);}, t.wrapped, writer); + writer.Key("aaa"); + AliasOfAliasOfAlias::to_json(t.aaa, writer); + writer.Key("item"); + _atd_write_wrap([](const auto &v, auto &w){_atd_write_string(v, w);}, [](const auto &e){return std::to_string(e);}, t.item, writer); + writer.Key("ee"); + EnumSumtype::to_json(t.ee, writer); + writer.EndObject(); +} +std::string Root::to_json_string(const Root &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); +} +std::string Root::to_json_string() { + return to_json_string(*this); +} + + +RequireField RequireField::from_json(const rapidjson::Value & doc) { + RequireField record; + if (!doc.IsObject()) { + throw AtdException("atdtype: require_field, expected an object but got" + _rapid_json_type_to_string(doc.GetType())); + } + if (doc.HasMember("req")) + record.req = _atd_read_string(doc["req"]); + else record.req = _atd_missing_json_field("RequireField", "req"); + return record; +} +RequireField RequireField::from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); +} +void RequireField::to_json(const RequireField &t, rapidjson::Writer &writer) { + writer.StartObject(); + writer.Key("req"); + _atd_write_string(t.req, writer); + writer.EndObject(); +} +std::string RequireField::to_json_string(const RequireField &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); +} +std::string RequireField::to_json_string() { + return to_json_string(*this); +} + + +RecordWithWrappedType RecordWithWrappedType::from_json(const rapidjson::Value & doc) { + RecordWithWrappedType record; + if (!doc.IsObject()) { + throw AtdException("atdtype: record_with_wrapped_type, expected an object but got" + _rapid_json_type_to_string(doc.GetType())); + } + if (doc.HasMember("item")) + record.item = _atd_read_wrap([](const auto& v){return _atd_read_string(v);}, [](const auto &e){return std::stoi(e);},doc["item"]); + else record.item = _atd_missing_json_field("RecordWithWrappedType", "item"); + return record; +} +RecordWithWrappedType RecordWithWrappedType::from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); +} +void RecordWithWrappedType::to_json(const RecordWithWrappedType &t, rapidjson::Writer &writer) { + writer.StartObject(); + writer.Key("item"); + _atd_write_wrap([](const auto &v, auto &w){_atd_write_string(v, w);}, [](const auto &e){return std::to_string(e);}, t.item, writer); + writer.EndObject(); +} +std::string RecordWithWrappedType::to_json_string(const RecordWithWrappedType &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); +} +std::string RecordWithWrappedType::to_json_string() { + return to_json_string(*this); +} + + +namespace Password { + typedefs::Password from_json(const rapidjson::Value &doc) { + return _atd_read_wrap([](const auto& v){return _atd_read_int(v);}, [](const auto &e){return static_cast(e);},doc); + } + typedefs::Password from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); + } + void to_json(const typedefs::Password &t, rapidjson::Writer &writer) { + _atd_write_wrap([](const auto &v, auto &w){_atd_write_int(v, w);}, [](const auto &e){return static_cast(e);}, t, writer); + } + std::string to_json_string(const typedefs::Password &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); + } +} + + +namespace Pair { + typedefs::Pair from_json(const rapidjson::Value &doc) { + return [](auto &v){ + if (!v.IsArray() || v.Size() != 2) + throw AtdException("Tuple of size 2"); + return std::make_tuple(_atd_read_string(v[0]), _atd_read_int(v[1])); + }(doc); + } + typedefs::Pair from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); + } + void to_json(const typedefs::Pair &t, rapidjson::Writer &writer) { + [](const auto &t, auto &writer){ + writer.StartArray(); + _atd_write_string(std::get<0>(t), writer); _atd_write_int(std::get<1>(t), writer); + writer.EndArray(); + }(t, writer); + } + std::string to_json_string(const typedefs::Pair &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); + } +} + + +NullOpt NullOpt::from_json(const rapidjson::Value & doc) { + NullOpt record; + if (!doc.IsObject()) { + throw AtdException("atdtype: null_opt, expected an object but got" + _rapid_json_type_to_string(doc.GetType())); + } + if (doc.HasMember("a")) + record.a = _atd_read_int(doc["a"]); + else record.a = _atd_missing_json_field("NullOpt", "a"); + if (doc.HasMember("b")) + record.b = _atd_read_option([](const auto &v){return _atd_read_int(v);}, doc["b"]); + else record.b = _atd_missing_json_field("NullOpt", "b"); + if (doc.HasMember("c")) + record.c = _atd_read_nullable([](const auto &v){return _atd_read_int(v);}, doc["c"]); + else record.c = _atd_missing_json_field("NullOpt", "c"); + if (doc.HasMember("f")) + record.f = _atd_read_int(doc["f"]); + else record.f = std::nullopt; + if (doc.HasMember("h")) + record.h = _atd_read_option([](const auto &v){return _atd_read_int(v);}, doc["h"]); + else record.h = 3; + if (doc.HasMember("i")) + record.i = _atd_read_nullable([](const auto &v){return _atd_read_int(v);}, doc["i"]); + else record.i = 3; + return record; +} +NullOpt NullOpt::from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); +} +void NullOpt::to_json(const NullOpt &t, rapidjson::Writer &writer) { + writer.StartObject(); + writer.Key("a"); + _atd_write_int(t.a, writer); + writer.Key("b"); + _atd_write_option([](auto v, auto &w){_atd_write_int(v, w);}, t.b, writer); + writer.Key("c"); + _atd_write_nullable([](auto v, auto &w){_atd_write_int(v, w);}, t.c, writer); + if (t.f != std::nullopt) { + writer.Key("f"); + _atd_write_int(t.f.value(), writer); + } + if (t.h != std::optional(3)) { + writer.Key("h"); + _atd_write_option([](auto v, auto &w){_atd_write_int(v, w);}, t.h, writer); + } + if (t.i != std::optional(3)) { + writer.Key("i"); + _atd_write_nullable([](auto v, auto &w){_atd_write_int(v, w);}, t.i, writer); + } + writer.EndObject(); +} +std::string NullOpt::to_json_string(const NullOpt &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); +} +std::string NullOpt::to_json_string() { + return to_json_string(*this); +} + + +namespace Frozen::Types { + + + void A::to_json(const A &e, rapidjson::Writer &writer){ + writer.String("A"); + }; + + + void B::to_json(const B &e, rapidjson::Writer &writer){ + writer.StartArray(); + writer.String("B"); + _atd_write_int(e.value, writer); + writer.EndArray(); + } + + +} + + +namespace Frozen { + typedefs::Frozen from_json(const rapidjson::Value &x) { + if (x.IsString()) { + if (std::string_view(x.GetString()) == "A") + return Types::A(); + throw _atd_bad_json("Frozen", x); + } + if (x.IsArray() && x.Size() == 2 && x[0].IsString()) { + std::string cons = x[0].GetString(); + if (cons == "B") + return Types::B({_atd_read_int(x[1])}); + throw _atd_bad_json("Frozen", x); + } + throw _atd_bad_json("Frozen", x); + } + typedefs::Frozen from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); + } + void to_json(const typedefs::Frozen &x, rapidjson::Writer &writer) { + std::visit([&writer](auto &&arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) Types::A::to_json(arg, writer); + if constexpr (std::is_same_v) Types::B::to_json(arg, writer); + }, x); + } + std::string to_json_string(const typedefs::Frozen &x) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(x, writer); + return buffer.GetString(); + } +} + + +EmptyRecord EmptyRecord::from_json(const rapidjson::Value & doc) { + EmptyRecord record; + if (!doc.IsObject()) { + throw AtdException("atdtype: empty_record, expected an object but got" + _rapid_json_type_to_string(doc.GetType())); + } + return record; +} +EmptyRecord EmptyRecord::from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); +} +void EmptyRecord::to_json(const EmptyRecord &t, rapidjson::Writer &writer) { + writer.StartObject(); + writer.EndObject(); +} +std::string EmptyRecord::to_json_string(const EmptyRecord &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); +} +std::string EmptyRecord::to_json_string() { + return to_json_string(*this); +} + + +DefaultList DefaultList::from_json(const rapidjson::Value & doc) { + DefaultList record; + if (!doc.IsObject()) { + throw AtdException("atdtype: default_list, expected an object but got" + _rapid_json_type_to_string(doc.GetType())); + } + if (doc.HasMember("items")) + record.items = _atd_read_array([](const auto &v){return _atd_read_int(v);}, doc["items"]); + else record.items = {}; + return record; +} +DefaultList DefaultList::from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); +} +void DefaultList::to_json(const DefaultList &t, rapidjson::Writer &writer) { + writer.StartObject(); + if (!t.items.empty()) { + writer.Key("items"); + _atd_write_array([](auto v, auto &w){_atd_write_int(v, w);}, t.items, writer); + } + writer.EndObject(); +} +std::string DefaultList::to_json_string(const DefaultList &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); +} +std::string DefaultList::to_json_string() { + return to_json_string(*this); +} + + +Credential Credential::from_json(const rapidjson::Value & doc) { + Credential record; + if (!doc.IsObject()) { + throw AtdException("atdtype: credential, expected an object but got" + _rapid_json_type_to_string(doc.GetType())); + } + if (doc.HasMember("name")) + record.name = _atd_read_string(doc["name"]); + else record.name = _atd_missing_json_field("Credential", "name"); + if (doc.HasMember("password")) + record.password = _atd_read_int(doc["password"]); + else record.password = _atd_missing_json_field("Credential", "password"); + return record; +} +Credential Credential::from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); +} +void Credential::to_json(const Credential &t, rapidjson::Writer &writer) { + writer.StartObject(); + writer.Key("name"); + _atd_write_string(t.name, writer); + writer.Key("password"); + _atd_write_int(t.password, writer); + writer.EndObject(); +} +std::string Credential::to_json_string(const Credential &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); +} +std::string Credential::to_json_string() { + return to_json_string(*this); +} + + +namespace Credentials2 { + typedefs::Credentials2 from_json(const rapidjson::Value &doc) { + return _atd_read_array([](const auto &v){return Credential::from_json(v);}, doc); + } + typedefs::Credentials2 from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); + } + void to_json(const typedefs::Credentials2 &t, rapidjson::Writer &writer) { + _atd_write_array([](auto v, auto &w){Credential::to_json(v, w);}, t, writer); + } + std::string to_json_string(const typedefs::Credentials2 &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); + } +} + + +Credentials Credentials::from_json(const rapidjson::Value & doc) { + Credentials record; + if (!doc.IsObject()) { + throw AtdException("atdtype: credentials, expected an object but got" + _rapid_json_type_to_string(doc.GetType())); + } + if (doc.HasMember("credentials")) + record.credentials = _atd_read_array([](const auto &v){return Credential::from_json(v);}, doc["credentials"]); + else record.credentials = _atd_missing_json_field("Credentials", "credentials"); + return record; +} +Credentials Credentials::from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); +} +void Credentials::to_json(const Credentials &t, rapidjson::Writer &writer) { + writer.StartObject(); + writer.Key("credentials"); + _atd_write_array([](auto v, auto &w){Credential::to_json(v, w);}, t.credentials, writer); + writer.EndObject(); +} +std::string Credentials::to_json_string(const Credentials &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); +} +std::string Credentials::to_json_string() { + return to_json_string(*this); +} + + +namespace AliasOfAlias { + typedefs::AliasOfAlias from_json(const rapidjson::Value &doc) { + return _atd_read_wrap([](const auto& v){return Alias3::from_json(v);}, [](const auto &e){return static_cast(e);},doc); + } + typedefs::AliasOfAlias from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); + } + void to_json(const typedefs::AliasOfAlias &t, rapidjson::Writer &writer) { + _atd_write_wrap([](const auto &v, auto &w){Alias3::to_json(v, w);}, [](const auto &e){return static_cast(e);}, t, writer); + } + std::string to_json_string(const typedefs::AliasOfAlias &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); + } +} + + +namespace Alias2 { + typedefs::Alias2 from_json(const rapidjson::Value &doc) { + return _atd_read_array([](const auto &v){return _atd_read_int(v);}, doc); + } + typedefs::Alias2 from_json_string(const std::string &s) { + rapidjson::Document doc; + doc.Parse(s.c_str()); + if (doc.HasParseError()) { + throw AtdException("Failed to parse JSON"); + } + return from_json(doc); + } + void to_json(const typedefs::Alias2 &t, rapidjson::Writer &writer) { + _atd_write_array([](auto v, auto &w){_atd_write_int(v, w);}, t, writer); + } + std::string to_json_string(const typedefs::Alias2 &t) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + to_json(t, writer); + return buffer.GetString(); + } +} + + +} // namespace atd::my::custom::ns diff --git a/atdcpp/test/cpp-expected/everything_atd.hpp b/atdcpp/test/cpp-expected/everything_atd.hpp new file mode 100644 index 000000000..3d0862a57 --- /dev/null +++ b/atdcpp/test/cpp-expected/everything_atd.hpp @@ -0,0 +1,444 @@ + +// Generated by atdcpp from type definitions in everything.atd. +// This implements classes for the types defined in 'everything.atd', providing +// methods and functions to convert data from/to JSON. + +// ############################################################################ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +namespace atd::my::custom::ns { +// forward declarations +namespace RecursiveVariant::Types { + struct Integer; + struct Rec; +} +struct RecursiveRecord2; +struct RecursiveClass; +struct ThreeLevelNestedListRecord; +struct StructWithRecursiveVariant; +namespace Kind::Types { + struct Root; + struct Thing; + struct WOW; + struct Amaze; +} +struct IntFloatParametrizedRecord; +struct Root; +struct RequireField; +struct RecordWithWrappedType; +struct NullOpt; +namespace Frozen::Types { + struct A; + struct B; +} +struct EmptyRecord; +struct DefaultList; +struct Credential; +struct Credentials; + + +namespace typedefs { + typedef RecursiveRecord2 RecursiveRecord2; + typedef RecursiveClass RecursiveClass; + typedef ThreeLevelNestedListRecord ThreeLevelNestedListRecord; + typedef StructWithRecursiveVariant StructWithRecursiveVariant; + enum class EnumSumtype { + A, + B, + C, + }; + typedef IntFloatParametrizedRecord IntFloatParametrizedRecord; + typedef Root Root; + typedef RequireField RequireField; + typedef RecordWithWrappedType RecordWithWrappedType; + typedef NullOpt NullOpt; + typedef EmptyRecord EmptyRecord; + typedef DefaultList DefaultList; + typedef Credential Credential; + typedef Credentials Credentials; + + typedef std::variant RecursiveVariant; + typedef std::variant Kind; + typedef std::variant Frozen; + + typedef int St; + typedef uint32_t Alias3; + typedef typedefs::Alias3 AliasOfAliasNotWrapped; + typedef typedefs::AliasOfAliasNotWrapped AliasOfAliasOfAlias; + typedef std::vector Alias; + typedef std::tuple KindParametrizedTuple; + typedef uint32_t Password; + typedef std::tuple Pair; + typedef std::vector Credentials2; + typedef uint16_t AliasOfAlias; + typedef std::vector Alias2; +} // namespace typedefs + +namespace RecursiveVariant { + namespace Types { + // Original type: recursive_variant = [ ... | Integer of ... | ... ] + struct Integer + { + int value; + static void to_json(const Integer &e, rapidjson::Writer &writer); + }; + // Original type: recursive_variant = [ ... | Rec of ... | ... ] + struct Rec + { + std::shared_ptr value; + static void to_json(const Rec &e, rapidjson::Writer &writer); + }; + } + + typedefs::RecursiveVariant from_json(const rapidjson::Value &x); + typedefs::RecursiveVariant from_json_string(const std::string &s); + void to_json(const typedefs::RecursiveVariant &x, rapidjson::Writer &writer); + std::string to_json_string(const typedefs::RecursiveVariant &x); +} + + +struct RecursiveRecord2 { + int id; + bool flag; + std::shared_ptr> children; + + static RecursiveRecord2 from_json(const rapidjson::Value & doc); + static RecursiveRecord2 from_json_string(const std::string &s); + static void to_json(const RecursiveRecord2 &t, rapidjson::Writer &writer); + static std::string to_json_string(const RecursiveRecord2 &t); + std::string to_json_string(); +}; + + +struct RecursiveClass { + int id; + bool flag; + std::vector children; + + static RecursiveClass from_json(const rapidjson::Value & doc); + static RecursiveClass from_json_string(const std::string &s); + static void to_json(const RecursiveClass &t, rapidjson::Writer &writer); + static std::string to_json_string(const RecursiveClass &t); + std::string to_json_string(); +}; + + +struct ThreeLevelNestedListRecord { + std::vector>> field_a; + + static ThreeLevelNestedListRecord from_json(const rapidjson::Value & doc); + static ThreeLevelNestedListRecord from_json_string(const std::string &s); + static void to_json(const ThreeLevelNestedListRecord &t, rapidjson::Writer &writer); + static std::string to_json_string(const ThreeLevelNestedListRecord &t); + std::string to_json_string(); +}; + + +struct StructWithRecursiveVariant { + typedefs::RecursiveVariant variant; + + static StructWithRecursiveVariant from_json(const rapidjson::Value & doc); + static StructWithRecursiveVariant from_json_string(const std::string &s); + static void to_json(const StructWithRecursiveVariant &t, rapidjson::Writer &writer); + static std::string to_json_string(const StructWithRecursiveVariant &t); + std::string to_json_string(); +}; + + +namespace St { + typedefs::St from_json(const rapidjson::Value &doc); + typedefs::St from_json_string(const std::string &s); + void to_json(const typedefs::St &t, rapidjson::Writer &writer); + std::string to_json_string(const typedefs::St &t); +} + + +namespace Kind { + namespace Types { + // Original type: kind = [ ... | Root | ... ] + struct Root { + static void to_json(const Root &e, rapidjson::Writer &writer); + }; + // Original type: kind = [ ... | Thing of ... | ... ] + struct Thing + { + int value; + static void to_json(const Thing &e, rapidjson::Writer &writer); + }; + // Original type: kind = [ ... | WOW | ... ] + struct WOW { + static void to_json(const WOW &e, rapidjson::Writer &writer); + }; + // Original type: kind = [ ... | Amaze of ... | ... ] + struct Amaze + { + std::vector value; + static void to_json(const Amaze &e, rapidjson::Writer &writer); + }; + } + + typedefs::Kind from_json(const rapidjson::Value &x); + typedefs::Kind from_json_string(const std::string &s); + void to_json(const typedefs::Kind &x, rapidjson::Writer &writer); + std::string to_json_string(const typedefs::Kind &x); +} + + +namespace EnumSumtype { + typedef typedefs::EnumSumtype Types; + + typedefs::EnumSumtype from_json(const rapidjson::Value &x); + typedefs::EnumSumtype from_json_string(const std::string &s); + void to_json(const typedefs::EnumSumtype &x, rapidjson::Writer &writer); + std::string to_json_string(const typedefs::EnumSumtype &x); +} + + +namespace Alias3 { + typedefs::Alias3 from_json(const rapidjson::Value &doc); + typedefs::Alias3 from_json_string(const std::string &s); + void to_json(const typedefs::Alias3 &t, rapidjson::Writer &writer); + std::string to_json_string(const typedefs::Alias3 &t); +} + + +namespace AliasOfAliasNotWrapped { + typedefs::AliasOfAliasNotWrapped from_json(const rapidjson::Value &doc); + typedefs::AliasOfAliasNotWrapped from_json_string(const std::string &s); + void to_json(const typedefs::AliasOfAliasNotWrapped &t, rapidjson::Writer &writer); + std::string to_json_string(const typedefs::AliasOfAliasNotWrapped &t); +} + + +namespace AliasOfAliasOfAlias { + typedefs::AliasOfAliasOfAlias from_json(const rapidjson::Value &doc); + typedefs::AliasOfAliasOfAlias from_json_string(const std::string &s); + void to_json(const typedefs::AliasOfAliasOfAlias &t, rapidjson::Writer &writer); + std::string to_json_string(const typedefs::AliasOfAliasOfAlias &t); +} + + +namespace Alias { + typedefs::Alias from_json(const rapidjson::Value &doc); + typedefs::Alias from_json_string(const std::string &s); + void to_json(const typedefs::Alias &t, rapidjson::Writer &writer); + std::string to_json_string(const typedefs::Alias &t); +} + + +namespace KindParametrizedTuple { + typedefs::KindParametrizedTuple from_json(const rapidjson::Value &doc); + typedefs::KindParametrizedTuple from_json_string(const std::string &s); + void to_json(const typedefs::KindParametrizedTuple &t, rapidjson::Writer &writer); + std::string to_json_string(const typedefs::KindParametrizedTuple &t); +} + + +struct IntFloatParametrizedRecord { + int field_a; + std::vector field_b = {}; + + static IntFloatParametrizedRecord from_json(const rapidjson::Value & doc); + static IntFloatParametrizedRecord from_json_string(const std::string &s); + static void to_json(const IntFloatParametrizedRecord &t, rapidjson::Writer &writer); + static std::string to_json_string(const IntFloatParametrizedRecord &t); + std::string to_json_string(); +}; + + +struct Root { + std::string id; + bool await; + int integer; + float x___init__; + float float_with_auto_default = 0.0f; + float float_with_default = 0.1f; + std::vector> items; + std::optional maybe; + std::vector extras = {}; + int answer = 42; + typedefs::Alias aliased; + std::tuple point; + typedefs::Kind kind; + std::vector kinds; + std::vector> assoc1; + std::vector> assoc2; + std::map assoc3; + std::map assoc4; + std::vector> nullables; + std::vector> options; + std::vector untyped_things; + typedefs::IntFloatParametrizedRecord parametrized_record; + typedefs::KindParametrizedTuple parametrized_tuple; + uint16_t wrapped; + typedefs::AliasOfAliasOfAlias aaa; + int item; + typedefs::EnumSumtype ee; + + static Root from_json(const rapidjson::Value & doc); + static Root from_json_string(const std::string &s); + static void to_json(const Root &t, rapidjson::Writer &writer); + static std::string to_json_string(const Root &t); + std::string to_json_string(); +}; + + +struct RequireField { + std::string req; + + static RequireField from_json(const rapidjson::Value & doc); + static RequireField from_json_string(const std::string &s); + static void to_json(const RequireField &t, rapidjson::Writer &writer); + static std::string to_json_string(const RequireField &t); + std::string to_json_string(); +}; + + +struct RecordWithWrappedType { + int item; + + static RecordWithWrappedType from_json(const rapidjson::Value & doc); + static RecordWithWrappedType from_json_string(const std::string &s); + static void to_json(const RecordWithWrappedType &t, rapidjson::Writer &writer); + static std::string to_json_string(const RecordWithWrappedType &t); + std::string to_json_string(); +}; + + +namespace Password { + typedefs::Password from_json(const rapidjson::Value &doc); + typedefs::Password from_json_string(const std::string &s); + void to_json(const typedefs::Password &t, rapidjson::Writer &writer); + std::string to_json_string(const typedefs::Password &t); +} + + +namespace Pair { + typedefs::Pair from_json(const rapidjson::Value &doc); + typedefs::Pair from_json_string(const std::string &s); + void to_json(const typedefs::Pair &t, rapidjson::Writer &writer); + std::string to_json_string(const typedefs::Pair &t); +} + + +struct NullOpt { + int a; + std::optional b; + std::optional c; + std::optional f; + std::optional h = 3; + std::optional i = 3; + + static NullOpt from_json(const rapidjson::Value & doc); + static NullOpt from_json_string(const std::string &s); + static void to_json(const NullOpt &t, rapidjson::Writer &writer); + static std::string to_json_string(const NullOpt &t); + std::string to_json_string(); +}; + + +namespace Frozen { + namespace Types { + // Original type: frozen = [ ... | A | ... ] + struct A { + static void to_json(const A &e, rapidjson::Writer &writer); + }; + // Original type: frozen = [ ... | B of ... | ... ] + struct B + { + int value; + static void to_json(const B &e, rapidjson::Writer &writer); + }; + } + + typedefs::Frozen from_json(const rapidjson::Value &x); + typedefs::Frozen from_json_string(const std::string &s); + void to_json(const typedefs::Frozen &x, rapidjson::Writer &writer); + std::string to_json_string(const typedefs::Frozen &x); +} + + +struct EmptyRecord { + + static EmptyRecord from_json(const rapidjson::Value & doc); + static EmptyRecord from_json_string(const std::string &s); + static void to_json(const EmptyRecord &t, rapidjson::Writer &writer); + static std::string to_json_string(const EmptyRecord &t); + std::string to_json_string(); +}; + + +struct DefaultList { + std::vector items = {}; + + static DefaultList from_json(const rapidjson::Value & doc); + static DefaultList from_json_string(const std::string &s); + static void to_json(const DefaultList &t, rapidjson::Writer &writer); + static std::string to_json_string(const DefaultList &t); + std::string to_json_string(); +}; + + +struct Credential { + std::string name; + int password; + + static Credential from_json(const rapidjson::Value & doc); + static Credential from_json_string(const std::string &s); + static void to_json(const Credential &t, rapidjson::Writer &writer); + static std::string to_json_string(const Credential &t); + std::string to_json_string(); +}; + + +namespace Credentials2 { + typedefs::Credentials2 from_json(const rapidjson::Value &doc); + typedefs::Credentials2 from_json_string(const std::string &s); + void to_json(const typedefs::Credentials2 &t, rapidjson::Writer &writer); + std::string to_json_string(const typedefs::Credentials2 &t); +} + + +struct Credentials { + std::vector credentials; + + static Credentials from_json(const rapidjson::Value & doc); + static Credentials from_json_string(const std::string &s); + static void to_json(const Credentials &t, rapidjson::Writer &writer); + static std::string to_json_string(const Credentials &t); + std::string to_json_string(); +}; + + +namespace AliasOfAlias { + typedefs::AliasOfAlias from_json(const rapidjson::Value &doc); + typedefs::AliasOfAlias from_json_string(const std::string &s); + void to_json(const typedefs::AliasOfAlias &t, rapidjson::Writer &writer); + std::string to_json_string(const typedefs::AliasOfAlias &t); +} + + +namespace Alias2 { + typedefs::Alias2 from_json(const rapidjson::Value &doc); + typedefs::Alias2 from_json_string(const std::string &s); + void to_json(const typedefs::Alias2 &t, rapidjson::Writer &writer); + std::string to_json_string(const typedefs::Alias2 &t); +} +} // namespace atd::my::custom::ns diff --git a/atdcpp/test/cpp-tests/dune b/atdcpp/test/cpp-tests/dune new file mode 100644 index 000000000..15786cbc1 --- /dev/null +++ b/atdcpp/test/cpp-tests/dune @@ -0,0 +1,28 @@ +; +; Convert ATD -> C++ +; +(rule + (targets + everything_atd.hpp + everything_atd.cpp + ) + (deps + ../atd-input/everything.atd + ) + (action + (run %{bin:atdcpp} %{deps}))) + +; +; Compile and run the tests on the generated C++ code. +; Linking with rapidjson library +; +(rule + (alias runtest) + (package atdcpp) + (deps + (glob_files *.cpp)) + (action + (progn + (bash "g++ -I../../lib/rapidjson/include -std=c++17 %{deps} -o test") + (bash ./test) + ))) diff --git a/atdcpp/test/cpp-tests/test_atdd.cpp b/atdcpp/test/cpp-tests/test_atdd.cpp new file mode 100644 index 000000000..5eb976d82 --- /dev/null +++ b/atdcpp/test/cpp-tests/test_atdd.cpp @@ -0,0 +1,173 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "everything_atd.hpp" + +int main() { + using namespace atd::my::custom::ns; + + std::map> tests; + + tests["simpleRecord"] = []() { + IntFloatParametrizedRecord record{32, {5.4, 3.3}}; + + std::string json = record.to_json_string(); + IntFloatParametrizedRecord recordFromJson = IntFloatParametrizedRecord::from_json_string(json); + + if (json == recordFromJson.to_json_string()) { + std::cout << "Test passed: simpleRecord" << std::endl; + } else { + std::cout << "Test failed: simpleRecord" << std::endl; + } + }; + + // Add the test for the Root object serialization and deserialization + tests["rootObjectSerialization"] = []() { + Root root; + + root.id = "id long"; + root.await = false; + root.integer = 43; + root.x___init__ = 3.14; + root.float_with_auto_default = 90.03; + root.float_with_default = 32.1; + root.items = {{1, 2}, {-1, -2}}; + root.maybe = 422; + root.extras = {34, 12}; + root.answer = 12; + root.aliased = {55, 44}; + root.point = {4.4, 1.1}; + root.kind = Kind::Types::Root(); + root.kinds = {Kind::Types::Amaze({{"one", "two"}}), Kind::Types::Root(), Kind::Types::Root(), Kind::Types::Thing({1})}; + root.assoc1 = {{4.12, 1},{2.2, 2}}; + root.assoc2 = {{"first", 1}, {"second", 2}}; + root.assoc3 = {{1.1, 1}, {2.2, 2}}; + root.assoc4 = {{"firstt", 1}, {"secondd", 2}}; + root.nullables = {1, std::nullopt, 3}; + root.options = {1, 2, std::nullopt}; + root.parametrized_record = {2, {1.0, 1.1}}; + root.parametrized_tuple = {Kind::Types::Root(), Kind::Types::WOW(), 9}; + root.wrapped = 1; + root.aaa = -90; + root.item = 45; + root.ee = EnumSumtype::Types::B; + root.untyped_things = {R"({"objec1t":"value"})", R"({"object":[1,2,3]})"}; + + std::string json = root.to_json_string(); + Root rootFromJson = Root::from_json_string(json); + + if (json == rootFromJson.to_json_string()) { + std::cout << "Test passed: rootObjectSerialization" << std::endl; + } else { + throw std::runtime_error("check is failed"); + } + }; + + tests["recursiveVariant"] = []() { + typedefs::RecursiveVariant recursiveVariant = RecursiveVariant::Types::Integer{42}; + + typedefs::RecursiveVariant recursiveVariant2 = RecursiveVariant::Types::Rec{std::make_shared(recursiveVariant)}; + typedefs::RecursiveVariant recursiveVariant3 = RecursiveVariant::Types::Rec{std::make_shared(recursiveVariant2)}; + typedefs::StructWithRecursiveVariant structWithRecursiveVariant = {recursiveVariant3}; + + std::string json = structWithRecursiveVariant.to_json_string(); + typedefs::StructWithRecursiveVariant structWithRecursiveVariantFromJson = StructWithRecursiveVariant::from_json_string(json); + + if (json == R"({"variant":["Rec",["Rec",["Integer",42]]]})" && json == structWithRecursiveVariantFromJson.to_json_string()) { + std::cout << "Test passed: recursiveVariant" << std::endl; + } else { + throw std::runtime_error("check is failed"); + } + }; + + tests["recursive record"] = []() { + using T = std::optional; + + auto optional = std::make_optional( + {2, + false, + std::make_shared(std::nullopt)} + ); + + typedefs::RecursiveRecord2 record{}; + record.id = 1; + record.flag = true; + record.children = std::make_shared(optional); + + std::string json = record.to_json_string(); + + auto target_json = R"({"id":1,"flag":true,"children":{"id":2,"flag":false,"children":null}})"; + RecursiveRecord2 recordFromJson = RecursiveRecord2::from_json_string(target_json); + + if (json == target_json && json == recordFromJson.to_json_string()) { + std::cout << "Test passed: recursive record" << std::endl; + } else { + throw std::runtime_error("check is failed"); + } + }; + + tests["optional nullable"] = []() { + std::optional x{3}; + std::optional xx{std::nullopt}; + + auto somes = typedefs::NullOpt{.a = 3, .b = x, .c = x, .f = x, .h = x, .i = x}; + auto nones = typedefs::NullOpt{.a = 0, .b = xx, .c = xx, .f = xx, .h = xx, .i = xx}; + + auto some_j = R"({"a":3,"b":["Some",3],"c":3,"f":3})"; + auto nones_j = R"({"a":0,"b":"None","c":null,"h":"None","i":null})"; + + if (NullOpt::from_json_string(some_j).to_json_string() == somes.to_json_string() && some_j == somes.to_json_string()) { + std::cout << "Test passed: optional nullable some" << std::endl; + } else { + throw std::runtime_error("check 1 is failed"); + } + + + if (NullOpt::from_json_string(nones_j).to_json_string() == nones.to_json_string() && nones_j == nones.to_json_string()) { + std::cout << "Test passed: optional nullable none" << std::endl; + } else { + throw std::runtime_error("check 2 is failed"); + } + }; + + tests["empty record"] = []() { + typedefs::EmptyRecord emptyRecord; + std::string json = "{}"; + typedefs::EmptyRecord emptyRecordFromJson = EmptyRecord::from_json_string(json); + + if (emptyRecord.to_json_string() == emptyRecordFromJson.to_json_string()) { + std::cout << "Test passed: empty record" << std::endl; + } else { + throw std::runtime_error("check is failed"); + } + }; + + std::cout << "Running tests..." << std::endl; + + int passed = 0; + for (const auto& test : tests) { + try { + test.second(); + passed++; + std::cout << "✅ Test " << test.first << std::endl; + } catch (const std::exception& e) { + std::cout << "❌ Test " << test.first << " with: " << e.what() << std::endl; + } + } + + if (passed == tests.size()) { + std::cout << "All tests passed" << std::endl; + } else { + std::cout << "Failure, " << passed << "/" << tests.size() << " tests passed" << std::endl; + } + + return 0; +} diff --git a/atdcpp/test/dune b/atdcpp/test/dune new file mode 100644 index 000000000..9d7df7d79 --- /dev/null +++ b/atdcpp/test/dune @@ -0,0 +1,18 @@ +; +; We test in two phases: +; +; 1. Check that the generated Dlang code is what we expect. +; + +(rule + (alias runtest) + (package atdcpp) + (action + (diff cpp-expected/everything_atd.hpp + cpp-tests/everything_atd.hpp) + )) + +; 2. Run the generated Dlang code and check that is reads or writes JSON +; data as expected. +; +; See cpp-tests/dune diff --git a/atdcpp/test_main.cpp b/atdcpp/test_main.cpp new file mode 100644 index 000000000..a054d9037 --- /dev/null +++ b/atdcpp/test_main.cpp @@ -0,0 +1,65 @@ +#include "everything_atd.hpp" + +#include +#include +#include +#include +#include +#include +#include + +const rapidjson::Document doc_from_json(const std::string &json) +{ + rapidjson::Document doc; + doc.Parse(json.c_str()); + + if (doc.HasParseError()) + { + } + + return doc; +} + +int main() +{ + using namespace atd; + Root root; + + root.id = "id long"; + root.await = false; + root.integer = 43; + root.x___init__ = 3.14; + root.float_with_auto_default = 90.03; + root.float_with_default = 32.1; + root.items = {{1, 2}, {-1, -2}}; + root.maybe = 422; + root.extras = {34, 12}; + root.answer = 12; + root.aliased = {55, 44}; + root.point = {4.4, 1.1}; + root.kind = Kind::Types::Root(); + root.kinds = {Kind::Types::Amaze({{"one", "two"}}), Kind::Types::Root(), Kind::Types::Root(), Kind::Types::Thing({1})}; + root.assoc1 = {{4.12, 1},{2.2, 2}}; + root.assoc2 = {{"first", 1}, {"second", 2}}; + root.assoc3 = {{1.1, 1}, {2.2, 2}}; + root.assoc4 = {{"firstt", 1}, {"secondd", 2}}; + root.nullables = {1, std::nullopt, 3}; + root.options = {1, 2, std::nullopt}; + root.parametrized_record = {2, {1.0, 1.1}}; + root.parametrized_tuple = {Kind::Types::Root(), Kind::Types::WOW(), 9}; + root.wrapped = 1; + root.aaa = -90; + root.item = 45; + + std::string json = root.to_json_string(); + + // now you turn json into pretty json string + rapidjson::Document doc; + doc.Parse(json.c_str()); + rapidjson::StringBuffer buffer; + rapidjson::PrettyWriter writer(buffer); + doc.Accept(writer); + std::cout << "Root: " << buffer.GetString() << std::endl; + + std::cout << "Root: " << json << std::endl; +} \ No newline at end of file diff --git a/dune b/dune index 49ce9dd08..1c9b39d17 100644 --- a/dune +++ b/dune @@ -12,3 +12,4 @@ (rule (copy atd.opam.template atds.opam.template)) (rule (copy atd.opam.template atdts.opam.template)) (rule (copy atd.opam.template atdd.opam.template)) +(rule (copy atd.opam.template atdcpp.opam.template)) diff --git a/dune-project b/dune-project index 665c5c0a3..6b6c5356d 100644 --- a/dune-project +++ b/dune-project @@ -210,3 +210,14 @@ Melange backend") (cmdliner (>= 1.1.0)) re (odoc :with-doc))) + +(package + (name atdcpp) + (synopsis "C++ code generation for ATD APIs") + (description "C++ code generation for ATD APIs") + (depends + (ocaml (>= 4.08)) + (atd (>= 2.11.0)) + (cmdliner (>= 1.1.0)) + re + (odoc :with-doc))) diff --git a/scripts/install-opam-dependencies b/scripts/install-opam-dependencies index fc6893944..6195d7d95 100755 --- a/scripts/install-opam-dependencies +++ b/scripts/install-opam-dependencies @@ -12,7 +12,7 @@ mkdir -p "$workspace" # Remove the dependencies on packages provided by this very project. for x in *.opam; do grep -v \ - '"atd"\|"atdgen"\|"atdgen-codec-runtime"\|"atdgen-runtime"\|"atdj"\|"atdpy"\|"atds"\|"atdts"\|"atdd"' "$x" \ + '"atd"\|"atdgen"\|"atdgen-codec-runtime"\|"atdgen-runtime"\|"atdj"\|"atdpy"\|"atds"\|"atdts"\|"atdd"\|"atdcpp"' "$x" \ > "$workspace"/"$x" done